Compare commits
354 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61f05d25d0 | ||
|
|
9794fe50b4 | ||
|
|
c1073ff3d2 | ||
|
|
06616535f4 | ||
|
|
1cd0178e0a | ||
|
|
a729269763 | ||
|
|
c452493555 | ||
|
|
d420e1efbb | ||
|
|
eb22fbddff | ||
|
|
1f5ee454c0 | ||
|
|
d80ac4a38f | ||
|
|
cfde6f3579 | ||
|
|
81f57ea3e7 | ||
|
|
ada9a09410 | ||
|
|
66717c7fe7 | ||
|
|
3f199a9500 | ||
|
|
798954d182 | ||
|
|
93bdd03f54 | ||
|
|
8d90ccb4a3 | ||
|
|
7446dc17c1 | ||
|
|
3dc4548077 | ||
|
|
96906ef89e | ||
|
|
1e1fb642f0 | ||
|
|
0a3ca2403e | ||
|
|
7daefe6f16 | ||
|
|
8a08152e65 | ||
|
|
f61f852a25 | ||
|
|
3c0621c5a0 | ||
|
|
f612112445 | ||
|
|
ab77e9d40c | ||
|
|
bb21a09244 | ||
|
|
f5f7eb7af9 | ||
|
|
12e05cb07e | ||
|
|
8f33e0af43 | ||
|
|
8896377f9c | ||
|
|
17003e47cd | ||
|
|
38f0055b74 | ||
|
|
a2c67f7af5 | ||
|
|
344027d757 | ||
|
|
addd3f7a45 | ||
|
|
a40e54f4e4 | ||
|
|
666b96f068 | ||
|
|
c6ccc68b2e | ||
|
|
c5976ccae1 | ||
|
|
1b8bcf4052 | ||
|
|
eeef7aff68 | ||
|
|
3605571f71 | ||
|
|
f3535e242c | ||
|
|
1703b5a103 | ||
|
|
775ae0ef91 | ||
|
|
ac6147e4a0 | ||
|
|
3cc7467d99 | ||
|
|
f97c00195c | ||
|
|
df8bfb1807 | ||
|
|
ccd0b9f7cc | ||
|
|
5505f20925 | ||
|
|
1adf76168d | ||
|
|
9250e27953 | ||
|
|
c3a9551683 | ||
|
|
fd324ac67e | ||
|
|
f6ea4df489 | ||
|
|
a86b8142eb | ||
|
|
e3b2b34940 | ||
|
|
b68ffa2725 | ||
|
|
a85d489907 | ||
|
|
928cfb4fde | ||
|
|
a9634988e9 | ||
|
|
87e0191a97 | ||
|
|
2cfa1e6ffd | ||
|
|
790378d2a5 | ||
|
|
a4607dffad | ||
|
|
ef66d27ffe | ||
|
|
84cb7cfeb7 | ||
|
|
53be3f0073 | ||
|
|
89aa818c0b | ||
|
|
3a5fbfde1e | ||
|
|
b9b457262f | ||
|
|
e161d92117 | ||
|
|
2e656509a8 | ||
|
|
b0b4acf392 | ||
|
|
e9673bf7f5 | ||
|
|
eeeb3d9db3 | ||
|
|
c882b0bb45 | ||
|
|
6c32873d4e | ||
|
|
bdd4dcfb9e | ||
|
|
ebe8ec455f | ||
|
|
d440ccbce2 | ||
|
|
1294ba6dec | ||
|
|
4c8e016dea | ||
|
|
232f7203e2 | ||
|
|
df0a4e62f5 | ||
|
|
7055480015 | ||
|
|
0e14983e88 | ||
|
|
9b05c51ae5 | ||
|
|
5e003bcf11 | ||
|
|
121f670c8b | ||
|
|
f05d4ef129 | ||
|
|
4167448279 | ||
|
|
413cc3ed4c | ||
|
|
4f2da55a8e | ||
|
|
f1808d9231 | ||
|
|
bcec409618 | ||
|
|
14c610e6c7 | ||
|
|
958335a01c | ||
|
|
b358882016 | ||
|
|
b8f1642d9d | ||
|
|
64818775cb | ||
|
|
5bfa15564a | ||
|
|
2ea97d3d5e | ||
|
|
bb2490eb13 | ||
|
|
57daa3ccb7 | ||
|
|
c1fdedf955 | ||
|
|
b70b6bf326 | ||
|
|
28acf02de0 | ||
|
|
1e76377b63 | ||
|
|
3dd9cfa741 | ||
|
|
26ac37ed46 | ||
|
|
5c136aab41 | ||
|
|
cf6104f630 | ||
|
|
7c8dd452cf | ||
|
|
cf3a50d6b5 | ||
|
|
ac329a70a4 | ||
|
|
e62450c763 | ||
|
|
44a9ce2417 | ||
|
|
23e0ff7aa6 | ||
|
|
de53b4514e | ||
|
|
994610869d | ||
|
|
b0e3faa2ad | ||
|
|
fe34dcb0bd | ||
|
|
8717706b1c | ||
|
|
b402e09b82 | ||
|
|
80832459b9 | ||
|
|
cf2c8b8e0e | ||
|
|
f802356b0a | ||
|
|
ecc4d8a6c1 | ||
|
|
d4b774642b | ||
|
|
103f28481d | ||
|
|
dd51bdae3f | ||
|
|
e5aa381e38 | ||
|
|
8190b3f312 | ||
|
|
e26cfa2e52 | ||
|
|
5b52d22b1f | ||
|
|
5a452709c3 | ||
|
|
8470e81dc7 | ||
|
|
947a481400 | ||
|
|
368b18d440 | ||
|
|
1fe68aa861 | ||
|
|
f38a7bd574 | ||
|
|
ce8b984542 | ||
|
|
d0969cc723 | ||
|
|
a66b9041ec | ||
|
|
83c198bc26 | ||
|
|
26b3827210 | ||
|
|
4b04131fe3 | ||
|
|
168ce83820 | ||
|
|
5cbb635e54 | ||
|
|
0452c1fc2d | ||
|
|
800dec3b84 | ||
|
|
580f26b526 | ||
|
|
f8ff97d2b7 | ||
|
|
9ee4728e2a | ||
|
|
22a74bf1fd | ||
|
|
845fcefad7 | ||
|
|
9b55bf4847 | ||
|
|
e74f083300 | ||
|
|
09bc2d0891 | ||
|
|
28b355911d | ||
|
|
e2c4282477 | ||
|
|
3192cc8b00 | ||
|
|
de73a18637 | ||
|
|
8e8b8eb8aa | ||
|
|
c676233dfd | ||
|
|
9b61cda6cf | ||
|
|
ebf2935fb4 | ||
|
|
6e556a02d4 | ||
|
|
94556fd6e7 | ||
|
|
d9d980122d | ||
|
|
394856fa04 | ||
|
|
a28333ff4e | ||
|
|
f4138d2398 | ||
|
|
91992194a9 | ||
|
|
a7e9dc8c9c | ||
|
|
da5b9389db | ||
|
|
ddd9dac2fb | ||
|
|
124b1e545d | ||
|
|
f52617deab | ||
|
|
c8657caefa | ||
|
|
c28a08a713 | ||
|
|
a0287b8561 | ||
|
|
6ac49a10f4 | ||
|
|
f70251fa3b | ||
|
|
13bfffa777 | ||
|
|
a50dce8fc2 | ||
|
|
a36bd70505 | ||
|
|
36471f2239 | ||
|
|
dff38f7609 | ||
|
|
5f38b4a43a | ||
|
|
07d838eee0 | ||
|
|
5747d896eb | ||
|
|
ad152ee747 | ||
|
|
471e256353 | ||
|
|
59584fefc1 | ||
|
|
948b7c48c5 | ||
|
|
5e33a908d2 | ||
|
|
9e2fc13460 | ||
|
|
6bbe5be5e6 | ||
|
|
9dde5cac7d | ||
|
|
6b9e48f96a | ||
|
|
30149b504d | ||
|
|
fb4648d759 | ||
|
|
d14c353115 | ||
|
|
f1bae4c1ca | ||
|
|
a61e705dbf | ||
|
|
64831bd580 | ||
|
|
dc1093eeed | ||
|
|
95376622fa | ||
|
|
511f98ec18 | ||
|
|
70e98467e8 | ||
|
|
e8bec96970 | ||
|
|
a505d48a4c | ||
|
|
bcc4357a16 | ||
|
|
668197ad6f | ||
|
|
0a91b89694 | ||
|
|
5b10661874 | ||
|
|
36a126774a | ||
|
|
705137e993 | ||
|
|
ce4cd1fefd | ||
|
|
224e32ccf1 | ||
|
|
f01ee61278 | ||
|
|
c637e34b48 | ||
|
|
67c195034d | ||
|
|
02b1f524b6 | ||
|
|
e76aaf5a34 | ||
|
|
422e8a6185 | ||
|
|
ba10d9d031 | ||
|
|
87dc9be10e | ||
|
|
d47b75c778 | ||
|
|
2c29969bfb | ||
|
|
c84d9660e2 | ||
|
|
dcd7df4860 | ||
|
|
b3d0fbeb5d | ||
|
|
fa340e91de | ||
|
|
771e73a798 | ||
|
|
e75208507d | ||
|
|
863ee23583 | ||
|
|
9cc20a03c0 | ||
|
|
cae3741953 | ||
|
|
80555ef933 | ||
|
|
cf251451ed | ||
|
|
4cf4b1604b | ||
|
|
fd037153ee | ||
|
|
02a7338ab8 | ||
|
|
70dd602bcf | ||
|
|
ea602f6d2f | ||
|
|
06348d3ba5 | ||
|
|
2eb593f461 | ||
|
|
244c0fbec3 | ||
|
|
7b529bce57 | ||
|
|
36bed09bde | ||
|
|
024b07d5f4 | ||
|
|
0cab7954f8 | ||
|
|
b325a01eea | ||
|
|
7e541cef3d | ||
|
|
bb8869268e | ||
|
|
cb171d350d | ||
|
|
5777b64a18 | ||
|
|
9b9529107c | ||
|
|
5421c7df4f | ||
|
|
4f02cb4967 | ||
|
|
6b8a0a7dca | ||
|
|
dfb31eef16 | ||
|
|
c6c0aadb00 | ||
|
|
3b65ea9008 | ||
|
|
708fe93efe | ||
|
|
13a9bc443e | ||
|
|
37e3a195dc | ||
|
|
a28a6f3320 | ||
|
|
67d3d5f587 | ||
|
|
f0f09c20fa | ||
|
|
f28749d455 | ||
|
|
ab2638b2cb | ||
|
|
22f587b7b9 | ||
|
|
92232d114b | ||
|
|
6619def365 | ||
|
|
c776a5424d | ||
|
|
2a1d7760e6 | ||
|
|
009957e3b6 | ||
|
|
297f6a38cb | ||
|
|
440cbed76a | ||
|
|
b36224f480 | ||
|
|
f9a467453a | ||
|
|
60ed549605 | ||
|
|
3d51abc56b | ||
|
|
295c365a8f | ||
|
|
adf9303a7e | ||
|
|
b626fd1e47 | ||
|
|
ed09908c3b | ||
|
|
0a9c673753 | ||
|
|
c2aa7dd948 | ||
|
|
25081466be | ||
|
|
8dedc54468 | ||
|
|
d0f5cb87e7 | ||
|
|
8dc58445f9 | ||
|
|
6faf1a0fcb | ||
|
|
ee0988588f | ||
|
|
6e66a3114a | ||
|
|
15e0a2bd10 | ||
|
|
09faa8403a | ||
|
|
7e0e671539 | ||
|
|
cc07db941f | ||
|
|
f1e1b9238d | ||
|
|
dda26df250 | ||
|
|
6e7b7c804c | ||
|
|
6a21255a38 | ||
|
|
6362a9c34f | ||
|
|
3ddaceb5ac | ||
|
|
055a508901 | ||
|
|
0bfceacaf3 | ||
|
|
0438d3c4ba | ||
|
|
3e81e27160 | ||
|
|
42acb32c69 | ||
|
|
d6968f4ea9 | ||
|
|
75da3a398d | ||
|
|
1dad2babb7 | ||
|
|
fe982b15b0 | ||
|
|
595470ccfb | ||
|
|
7d39c092c6 | ||
|
|
6a6be3caa4 | ||
|
|
1daf68dea1 | ||
|
|
4f8531332e | ||
|
|
d70885711b | ||
|
|
6499e7608c | ||
|
|
569e14f50f | ||
|
|
71c2e54afd | ||
|
|
bd5d04f5ab | ||
|
|
26b657a96f | ||
|
|
cce5bf26c8 | ||
|
|
4d97a427f9 | ||
|
|
aa7e825e70 | ||
|
|
2778df0fe7 | ||
|
|
c6713f37bb | ||
|
|
52d920d969 | ||
|
|
cb88d215fa | ||
|
|
7436ea3e8c | ||
|
|
d2b9ca4c1b | ||
|
|
4b2bac7cd8 | ||
|
|
1da82cdec2 | ||
|
|
76fc52e042 | ||
|
|
6880e5e9ee | ||
|
|
01cc1d331b | ||
|
|
9e957a75b0 | ||
|
|
a546615f69 | ||
|
|
9f1c3fa823 | ||
|
|
886eebdbb2 |
2
.github/DISCUSSION_TEMPLATE/bug-reports.yml
vendored
2
.github/DISCUSSION_TEMPLATE/bug-reports.yml
vendored
@@ -73,7 +73,7 @@ body:
|
|||||||
label: Version
|
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.
|
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:
|
options:
|
||||||
- v1.12.6.2 (Spring Update 2026)
|
- v1.13.3.1 (Summer Update 2026)
|
||||||
- Other
|
- Other
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -258,7 +258,9 @@ namespace Barotrauma
|
|||||||
//RelativeSpacing = 0.05f
|
//RelativeSpacing = 0.05f
|
||||||
};
|
};
|
||||||
|
|
||||||
InventorySlotContainer = new GUICustomComponent(new RectTransform(new Vector2(0.1f, 1.0f), characterIndicatorArea.RectTransform, Anchor.TopLeft, Pivot.TopRight),
|
GUIFrame left = new(new RectTransform(new Vector2(0.25f, 1f), characterIndicatorArea.RectTransform), style: null);
|
||||||
|
|
||||||
|
InventorySlotContainer = new GUICustomComponent(new RectTransform(Vector2.One, left.RectTransform),
|
||||||
(spriteBatch, component) =>
|
(spriteBatch, component) =>
|
||||||
{
|
{
|
||||||
for (int i = 0; i < character.Inventory.Capacity; i++)
|
for (int i = 0; i < character.Inventory.Capacity; i++)
|
||||||
@@ -266,6 +268,10 @@ namespace Barotrauma
|
|||||||
if (character.Inventory.SlotTypes[i] != InvSlotType.HealthInterface) { continue; }
|
if (character.Inventory.SlotTypes[i] != InvSlotType.HealthInterface) { continue; }
|
||||||
if (character.Inventory.HideSlot(i)) { continue; }
|
if (character.Inventory.HideSlot(i)) { continue; }
|
||||||
|
|
||||||
|
int width = Character.Inventory.visualSlots[i].Rect.Width;
|
||||||
|
left.RectTransform.MinSize = new Point(width, left.RectTransform.MinSize.Y);
|
||||||
|
if (afflictionIconList != null) { afflictionIconList.RectTransform.MinSize = new Point(width, afflictionIconList.RectTransform.MinSize.Y); }
|
||||||
|
|
||||||
//don't draw the item if it's being dragged out of the slot
|
//don't draw the item if it's being dragged out of the slot
|
||||||
bool drawItem = !Inventory.DraggingItems.Any() || !Character.Inventory.GetItemsAt(i).All(it => Inventory.DraggingItems.Contains(it)) || character.Inventory.visualSlots[i].MouseOn();
|
bool drawItem = !Inventory.DraggingItems.Any() || !Character.Inventory.GetItemsAt(i).All(it => Inventory.DraggingItems.Contains(it)) || character.Inventory.visualSlots[i].MouseOn();
|
||||||
|
|
||||||
@@ -292,8 +298,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cprButton = new GUIButton(new RectTransform(new Vector2(0.75f), left.RectTransform, Anchor.BottomLeft, scaleBasis: ScaleBasis.Smallest), text: "", style: "CPRButton")
|
||||||
cprButton = new GUIButton(new RectTransform(new Vector2(0.17f, 0.17f), characterIndicatorArea.RectTransform, Anchor.BottomLeft, scaleBasis: ScaleBasis.Smallest), text: "", style: "CPRButton")
|
|
||||||
{
|
{
|
||||||
UserData = UIHighlightAction.ElementId.CPRButton,
|
UserData = UIHighlightAction.ElementId.CPRButton,
|
||||||
OnClicked = (button, userData) =>
|
OnClicked = (button, userData) =>
|
||||||
@@ -316,12 +321,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
ToolTip = TextManager.Get("doctor.cprobjective"),
|
ToolTip = TextManager.Get("tutorial.roles.medic.objective.cpr"),
|
||||||
IgnoreLayoutGroups = true,
|
|
||||||
Visible = false
|
Visible = false
|
||||||
};
|
};
|
||||||
|
|
||||||
var limbSelection = new GUICustomComponent(new RectTransform(new Vector2(0.4f, 1.0f), characterIndicatorArea.RectTransform),
|
var limbSelection = new GUICustomComponent(new RectTransform(new Vector2(0.5f, 1.0f), characterIndicatorArea.RectTransform),
|
||||||
(spriteBatch, component) =>
|
(spriteBatch, component) =>
|
||||||
{
|
{
|
||||||
DrawHealthWindow(spriteBatch, component.RectTransform.Rect, true);
|
DrawHealthWindow(spriteBatch, component.RectTransform.Rect, true);
|
||||||
@@ -368,8 +372,6 @@ namespace Barotrauma
|
|||||||
CanBeFocused = false
|
CanBeFocused = false
|
||||||
};
|
};
|
||||||
|
|
||||||
characterIndicatorArea.Recalculate();
|
|
||||||
|
|
||||||
healthBarHolder = new GUIFrame(new RectTransform(Point.Zero, GUI.Canvas), style: null)
|
healthBarHolder = new GUIFrame(new RectTransform(Point.Zero, GUI.Canvas), style: null)
|
||||||
{
|
{
|
||||||
HoverCursor = CursorState.Hand
|
HoverCursor = CursorState.Hand
|
||||||
|
|||||||
@@ -866,7 +866,20 @@ namespace Barotrauma
|
|||||||
GameSettings.SaveCurrentConfig();
|
GameSettings.SaveCurrentConfig();
|
||||||
});
|
});
|
||||||
}, isCheat: false));
|
}, isCheat: false));
|
||||||
|
|
||||||
|
commands.Add(new Command("togglespoofeventmanagerid", "togglespoofeventmanagerid: Forces the client to report the last received event ID as always being 1, making the server believe the client is always behind.", (string[] args) =>
|
||||||
|
{
|
||||||
|
if (GameMain.Client != null)
|
||||||
|
{
|
||||||
|
GameMain.Client.SpoofEntityManagerReceivedId = !GameMain.Client.SpoofEntityManagerReceivedId;
|
||||||
|
DebugConsole.NewMessage(GameMain.Client.SpoofEntityManagerReceivedId ? "Spoofing enabled ": "Spoofing disabled", Color.Green);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DebugConsole.NewMessage("Not connected to server", Color.Red);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
commands.Add(new Command("togglegrid", "Toggle visual snap grid in sub editor.", (string[] args) =>
|
commands.Add(new Command("togglegrid", "Toggle visual snap grid in sub editor.", (string[] args) =>
|
||||||
{
|
{
|
||||||
SubEditorScreen.ShouldDrawGrid = !SubEditorScreen.ShouldDrawGrid;
|
SubEditorScreen.ShouldDrawGrid = !SubEditorScreen.ShouldDrawGrid;
|
||||||
@@ -4454,17 +4467,24 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static void StartLocalMPSession(int numClients = 2)
|
public static void StartLocalMPSession(int numClients = 2)
|
||||||
{
|
{
|
||||||
|
string extraArguments = "-multiclienttestmode";
|
||||||
|
if (NetConfig.UseLenientHandshake)
|
||||||
|
{
|
||||||
|
extraArguments += " -lenienthandshake";
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Process.GetProcessesByName("DedicatedServer").Length == 0)
|
if (Process.GetProcessesByName("DedicatedServer").Length == 0)
|
||||||
{
|
{
|
||||||
#if WINDOWS
|
#if WINDOWS
|
||||||
Process.Start("DedicatedServer.exe", arguments: "-multiclienttestmode");
|
Process.Start("DedicatedServer.exe", arguments: extraArguments);
|
||||||
#else
|
#else
|
||||||
Process.Start("./DedicatedServer", arguments: "-multiclienttestmode");
|
Process.Start("./DedicatedServer", arguments: extraArguments);
|
||||||
#endif
|
#endif
|
||||||
System.Threading.Thread.Sleep(1000);
|
System.Threading.Thread.Sleep(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
GameClient.MultiClientTestMode = true;
|
GameClient.MultiClientTestMode = true;
|
||||||
#endif
|
#endif
|
||||||
@@ -4478,10 +4498,13 @@ namespace Barotrauma
|
|||||||
for (int i = 2; i <= numClients; i++)
|
for (int i = 2; i <= numClients; i++)
|
||||||
{
|
{
|
||||||
System.Threading.Thread.Sleep(1000);
|
System.Threading.Thread.Sleep(1000);
|
||||||
|
|
||||||
|
string clientArguments = $"-connect server localhost -username client{i} -skipintro";
|
||||||
|
|
||||||
#if WINDOWS
|
#if WINDOWS
|
||||||
Process.Start("Barotrauma.exe", arguments: "-connect server localhost -username client" + i + " -multiclienttestmode");
|
Process.Start("Barotrauma.exe", arguments: $"{clientArguments} {extraArguments}");
|
||||||
#else
|
#else
|
||||||
Process.Start("./Barotrauma", arguments: "-connect server localhost -username client" + i + " -multiclienttestmode");
|
Process.Start("./Barotrauma", arguments: $"{clientArguments} {extraArguments}");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using Microsoft.Xna.Framework;
|
|||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
using Microsoft.Xna.Framework.Input;
|
using Microsoft.Xna.Framework.Input;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -649,12 +650,12 @@ namespace Barotrauma
|
|||||||
DrawMessages(spriteBatch, cam);
|
DrawMessages(spriteBatch, cam);
|
||||||
|
|
||||||
if (MouseOn != null)
|
if (MouseOn != null)
|
||||||
{
|
{
|
||||||
|
MouseOn.OnDrawToolTip?.Invoke(MouseOn);
|
||||||
if (!MouseOn.ToolTip.IsNullOrWhiteSpace())
|
if (!MouseOn.ToolTip.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
MouseOn.DrawToolTip(spriteBatch);
|
MouseOn.DrawToolTip(spriteBatch);
|
||||||
}
|
}
|
||||||
MouseOn.OnDrawToolTip?.Invoke(MouseOn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SubEditorScreen.IsSubEditor())
|
if (SubEditorScreen.IsSubEditor())
|
||||||
@@ -2323,10 +2324,10 @@ namespace Barotrauma
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a 7-segment display.
|
/// Creates a 7-segment display.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="leftLabel">Returns <see langword="null"/> if <paramref name="leftLabelText"/> is <see langword="null"/> or empty.</param>
|
/// <param name="leftLabel">Returns <see langword="null"/> if <paramref name="leftLabelText"/> is <see langword="null"/>.</param>
|
||||||
/// <param name="rightLabelText">Defaults to <c>TextManager.Get("kilowatt")</c>.</param>
|
/// <param name="rightLabelText">Defaults to <c>TextManager.Get("kilowatt")</c>.</param>
|
||||||
/// <param name="leftLabelFont">Defaults to <see cref="GUIStyle.LargeFont"/>.</param>
|
/// <param name="leftLabelFont">Defaults to <see cref="GUIStyle.LargeFont"/>.</param>
|
||||||
public static GUITextBlock CreateDigitalDisplay(RectTransform rect, out GUITextBlock? leftLabel, out GUITextBlock rightLabel, LocalizedString? leftLabelText = null, LocalizedString? rightLabelText = null, LocalizedString? tooltip = null, GUIFont? leftLabelFont = null)
|
public static GUITextBlock CreateDigitalDisplay(RectTransform rect, [NotNullIfNotNull(nameof(leftLabelText))] out GUITextBlock? leftLabel, out GUITextBlock rightLabel, LocalizedString? leftLabelText = null, LocalizedString? rightLabelText = null, LocalizedString? tooltip = null, GUIFont? leftLabelFont = null)
|
||||||
{
|
{
|
||||||
GUILayoutGroup textArea = new(rect, isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
GUILayoutGroup textArea = new(rect, isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
||||||
{
|
{
|
||||||
@@ -2337,7 +2338,7 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
|
|
||||||
leftLabel = null;
|
leftLabel = null;
|
||||||
if (!leftLabelText.IsNullOrEmpty())
|
if (leftLabelText != null)
|
||||||
{
|
{
|
||||||
leftLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1f), textArea.RectTransform), leftLabelText, textColor: GUIStyle.TextColorBright, font: leftLabelFont ?? GUIStyle.LargeFont, textAlignment: Alignment.CenterRight);
|
leftLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1f), textArea.RectTransform), leftLabelText, textColor: GUIStyle.TextColorBright, font: leftLabelFont ?? GUIStyle.LargeFont, textAlignment: Alignment.CenterRight);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -384,14 +384,14 @@ namespace Barotrauma
|
|||||||
CaretIndex = forcedCaretIndex == - 1 ? textBlock.GetCaretIndexFromScreenPos(PlayerInput.MousePosition) : forcedCaretIndex;
|
CaretIndex = forcedCaretIndex == - 1 ? textBlock.GetCaretIndexFromScreenPos(PlayerInput.MousePosition) : forcedCaretIndex;
|
||||||
CalculateCaretPos();
|
CalculateCaretPos();
|
||||||
ClearSelection();
|
ClearSelection();
|
||||||
bool wasSelected = selected;
|
|
||||||
selected = true;
|
|
||||||
GUI.KeyboardDispatcher.Subscriber = this;
|
|
||||||
OnSelected?.Invoke(this, Keys.None);
|
OnSelected?.Invoke(this, Keys.None);
|
||||||
if (!wasSelected && PlaySoundOnSelect && !ignoreSelectSound)
|
if (!selected && PlaySoundOnSelect && !ignoreSelectSound)
|
||||||
{
|
{
|
||||||
SoundPlayer.PlayUISound(GUISoundType.Select);
|
SoundPlayer.PlayUISound(GUISoundType.Select);
|
||||||
}
|
}
|
||||||
|
selected = true;
|
||||||
|
//set this after we've set selected to true -> otherwise the textbox taking keyboard focus will trigger Select again
|
||||||
|
GUI.KeyboardDispatcher.Subscriber = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Deselect()
|
public void Deselect()
|
||||||
|
|||||||
@@ -113,7 +113,10 @@ namespace Barotrauma
|
|||||||
|
|
||||||
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, samplerState: GUI.SamplerState);
|
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, samplerState: GUI.SamplerState);
|
||||||
|
|
||||||
GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea);
|
if (currentBackgroundTexture.Texture != null)
|
||||||
|
{
|
||||||
|
GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea);
|
||||||
|
}
|
||||||
overlay.Draw(spriteBatch, Vector2.Zero, scale: overlayScale);
|
overlay.Draw(spriteBatch, Vector2.Zero, scale: overlayScale);
|
||||||
|
|
||||||
double noiseT = Timing.TotalTime * 0.02f;
|
double noiseT = Timing.TotalTime * 0.02f;
|
||||||
|
|||||||
@@ -278,6 +278,12 @@ namespace Barotrauma
|
|||||||
GameClient.MultiClientTestMode = true;
|
GameClient.MultiClientTestMode = true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (ConsoleArguments.Contains("-lenienthandshake"))
|
||||||
|
{
|
||||||
|
NetConfig.UseLenientHandshake = true;
|
||||||
|
}
|
||||||
|
|
||||||
GUI.KeyboardDispatcher = new EventInput.KeyboardDispatcher(Window);
|
GUI.KeyboardDispatcher = new EventInput.KeyboardDispatcher(Window);
|
||||||
|
|
||||||
PerformanceCounter = new PerformanceCounter();
|
PerformanceCounter = new PerformanceCounter();
|
||||||
|
|||||||
@@ -127,12 +127,6 @@ namespace Barotrauma
|
|||||||
(int)SlotPositions[i].X,
|
(int)SlotPositions[i].X,
|
||||||
(int)SlotPositions[i].Y,
|
(int)SlotPositions[i].Y,
|
||||||
(int)(slotSprite.size.X * multiplier), (int)(slotSprite.size.Y * multiplier));
|
(int)(slotSprite.size.X * multiplier), (int)(slotSprite.size.Y * multiplier));
|
||||||
|
|
||||||
if (SlotTypes[i] == InvSlotType.HealthInterface &&
|
|
||||||
character.CharacterHealth?.InventorySlotContainer != null)
|
|
||||||
{
|
|
||||||
slotRect.Width = slotRect.Height = (int)(character.CharacterHealth.InventorySlotContainer.Rect.Width * 1.2f);
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemContainer itemContainer = slots[i].FirstOrDefault()?.GetComponent<ItemContainer>();
|
ItemContainer itemContainer = slots[i].FirstOrDefault()?.GetComponent<ItemContainer>();
|
||||||
if (itemContainer != null)
|
if (itemContainer != null)
|
||||||
@@ -622,6 +616,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
for (int i = 0; i < capacity; i++)
|
for (int i = 0; i < capacity; i++)
|
||||||
{
|
{
|
||||||
|
if (HideSlot(i)) { continue; }
|
||||||
var item = slots[i].FirstOrDefault();
|
var item = slots[i].FirstOrDefault();
|
||||||
if (item != null)
|
if (item != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DisplayMsg = TextManager.ParseInputTypes(TextManager.Get(Msg));
|
DisplayMsg = TextManager.ParseInputTypes(TextManager.Get(Msg)).Fallback(Msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
CharacterHUD.RecreateHudTextsIfControlling(Character.Controlled);
|
CharacterHUD.RecreateHudTextsIfControlling(Character.Controlled);
|
||||||
|
|||||||
@@ -12,9 +12,12 @@ namespace Barotrauma.Items.Components
|
|||||||
private partial class PowerGroup
|
private partial class PowerGroup
|
||||||
{
|
{
|
||||||
private GUIFrame? frame;
|
private GUIFrame? frame;
|
||||||
|
private GUIFrame? groupContent;
|
||||||
|
private GUILayoutGroup? nameGroup;
|
||||||
private GUITextBox? nameBox;
|
private GUITextBox? nameBox;
|
||||||
|
private GUITextBlock? loadDisplayNameLabel;
|
||||||
private GUIScrollBar? ratioSlider;
|
private GUIScrollBar? ratioSlider;
|
||||||
private readonly List<GUITextBlock> powerUnitLabels = new List<GUITextBlock>();
|
private readonly List<GUITextBlock> powerUnitLabels = [];
|
||||||
private GUIFrame? divider;
|
private GUIFrame? divider;
|
||||||
|
|
||||||
public bool IsVisible { get; private set; } = true;
|
public bool IsVisible { get; private set; } = true;
|
||||||
@@ -22,9 +25,9 @@ namespace Barotrauma.Items.Components
|
|||||||
public void CreateGUI()
|
public void CreateGUI()
|
||||||
{
|
{
|
||||||
frame = new GUIFrame(new RectTransform(new Vector2(1f, 0.25f), distributor.groupList!.Content.RectTransform, minSize: (0, 130)), style: null);
|
frame = new GUIFrame(new RectTransform(new Vector2(1f, 0.25f), distributor.groupList!.Content.RectTransform, minSize: (0, 130)), style: null);
|
||||||
GUIFrame groupContent = new(new RectTransform(frame.Rect.Size - new Point(10), frame.RectTransform, Anchor.Center), style: null);
|
groupContent = new GUIFrame(new RectTransform(frame.Rect.Size - new Point(10), frame.RectTransform, Anchor.Center), style: null);
|
||||||
|
|
||||||
GUILayoutGroup nameGroup = new(new RectTransform(new Vector2(0.65f, 0.33f), groupContent.RectTransform, Anchor.TopLeft), isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
nameGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.65f, 0.33f), groupContent.RectTransform, Anchor.TopLeft), isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
||||||
{
|
{
|
||||||
Stretch = true
|
Stretch = true
|
||||||
};
|
};
|
||||||
@@ -37,7 +40,7 @@ namespace Barotrauma.Items.Components
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
nameBox = new GUITextBox(new RectTransform(Vector2.One, nameGroup.RectTransform), Name, font: GUIStyle.SubHeadingFont, style: "GUITextBoxNoStyle")
|
nameBox = new GUITextBox(new RectTransform(Vector2.One, nameGroup.RectTransform), Screen.Selected == GameMain.SubEditorScreen ? Name : DisplayName.Value, font: GUIStyle.SubHeadingFont, style: "GUITextBoxNoStyle")
|
||||||
{
|
{
|
||||||
MaxTextLength = MaxNameLength,
|
MaxTextLength = MaxNameLength,
|
||||||
OverflowClip = true,
|
OverflowClip = true,
|
||||||
@@ -48,17 +51,40 @@ namespace Barotrauma.Items.Components
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
nameBox.OnSelected += (tb, _) =>
|
||||||
|
{
|
||||||
|
if (tb.Selected) { return; }
|
||||||
|
tb.Text = Name;
|
||||||
|
};
|
||||||
nameBox.OnDeselected += (tb, _) =>
|
nameBox.OnDeselected += (tb, _) =>
|
||||||
{
|
{
|
||||||
Name = tb.Text;
|
Name = tb.Text;
|
||||||
|
tb.CaretIndex = 0;
|
||||||
if (GameMain.Client == null) { return; }
|
if (GameMain.Client == null) { return; }
|
||||||
distributor.item.CreateClientEvent(distributor, new EventData(this, EventType.NameChange));
|
distributor.item.CreateClientEvent(distributor, new EventData(this, EventType.NameChange));
|
||||||
};
|
};
|
||||||
|
nameBox.GetChild<GUIFrame>().GetChild<GUICustomComponent>().OnDrawToolTip = comp =>
|
||||||
|
{
|
||||||
|
if (Screen.Selected != GameMain.SubEditorScreen && !nameBox.Selected)
|
||||||
|
{
|
||||||
|
comp.ToolTip = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalizedString localizedText = TextManager.Get(nameBox.Text);
|
||||||
|
comp.ToolTip = localizedText.IsNullOrEmpty()
|
||||||
|
? TextManager.GetWithVariable("StringPropertyCannotTranslate", "[tag]", nameBox.Text)
|
||||||
|
: TextManager.GetWithVariable("StringPropertyTranslate", "[translation]", localizedText);
|
||||||
|
};
|
||||||
|
|
||||||
GUITextBlock loadDisplay = GUI.CreateDigitalDisplay(new RectTransform(new Vector2(0.35f, 0.33f), groupContent.RectTransform, Anchor.TopRight) { AbsoluteOffset = (5, 0) },
|
GUITextBlock loadDisplay = GUI.CreateDigitalDisplay(new RectTransform(new Vector2(0.35f, 0.33f), groupContent.RectTransform, Anchor.TopRight) { AbsoluteOffset = (5, 0) },
|
||||||
out GUITextBlock? _, out GUITextBlock loadDisplayUnitLabel, TextManager.Get("PowerTransferLoadLabel"), tooltip: TextManager.Get("PowerTransferTipLoad"), leftLabelFont: GUIStyle.Font);
|
out loadDisplayNameLabel, out GUITextBlock loadDisplayUnitLabel, TextManager.Get("PowerTransferLoadLabel"), tooltip: TextManager.Get("PowerTransferTipLoad"), leftLabelFont: GUIStyle.Font);
|
||||||
loadDisplay.TextGetter = () => MathUtils.RoundToInt(Load).ToString();
|
loadDisplay.TextGetter = () => MathUtils.RoundToInt(Load).ToString();
|
||||||
|
|
||||||
|
float textAndPaddingWidth = loadDisplayNameLabel!.Font.MeasureString(loadDisplayNameLabel!.Text).X + loadDisplayNameLabel.Padding.X + loadDisplayNameLabel.Padding.Z;
|
||||||
|
float availableWidth = groupContent!.Rect.Width - loadDisplayNameLabel.Parent.Rect.Width + loadDisplayNameLabel.Rect.Width - textAndPaddingWidth;
|
||||||
|
nameGroup!.RectTransform.Resize(new Point((int)availableWidth, nameGroup.Rect.Height));
|
||||||
|
|
||||||
ratioSlider = new GUIScrollBar(new RectTransform(new Vector2(1f, 0.33f), groupContent.RectTransform, Anchor.Center), barSize: 0.15f, style: "DeviceSlider")
|
ratioSlider = new GUIScrollBar(new RectTransform(new Vector2(1f, 0.33f), groupContent.RectTransform, Anchor.Center), barSize: 0.15f, style: "DeviceSlider")
|
||||||
{
|
{
|
||||||
Step = SupplyRatioStep,
|
Step = SupplyRatioStep,
|
||||||
@@ -78,16 +104,17 @@ namespace Barotrauma.Items.Components
|
|||||||
ratioSlider.Bar.RectTransform.MaxSize = new Point(ratioSlider.Bar.Rect.Height);
|
ratioSlider.Bar.RectTransform.MaxSize = new Point(ratioSlider.Bar.Rect.Height);
|
||||||
|
|
||||||
GUITextBlock ratioDisplay = GUI.CreateDigitalDisplay(new RectTransform(new Vector2(0.2f, 0.33f), groupContent.RectTransform, Anchor.BottomLeft),
|
GUITextBlock ratioDisplay = GUI.CreateDigitalDisplay(new RectTransform(new Vector2(0.2f, 0.33f), groupContent.RectTransform, Anchor.BottomLeft),
|
||||||
out GUITextBlock? _, out GUITextBlock _,
|
out GUITextBlock? _, out GUITextBlock ratioDisplayUnitLabel,
|
||||||
rightLabelText: "%");
|
rightLabelText: "%");
|
||||||
ratioDisplay.TextGetter = () => DisplayRatio.ToString();
|
ratioDisplay.TextGetter = () => DisplayRatio.ToString();
|
||||||
|
|
||||||
GUITextBlock outputDisplay = GUI.CreateDigitalDisplay(new RectTransform(new Vector2(0.35f, 0.33f), groupContent.RectTransform, Anchor.BottomRight) { AbsoluteOffset = (5, 0) },
|
GUITextBlock outputDisplay = GUI.CreateDigitalDisplay(new RectTransform(new Vector2(0.35f, 0.33f), groupContent.RectTransform, Anchor.BottomRight) { AbsoluteOffset = (5, 0) },
|
||||||
out GUITextBlock? _, out GUITextBlock outputDisplayUnitLabel,
|
out GUITextBlock? outputDisplayNameLabel, out GUITextBlock outputDisplayUnitLabel,
|
||||||
TextManager.Get("powerdistributor.supplylabel"), tooltip: TextManager.Get("PowerTransferTipPower"), leftLabelFont: GUIStyle.Font);
|
TextManager.Get("powerdistributor.supplylabel"), tooltip: TextManager.Get("PowerTransferTipPower"), leftLabelFont: GUIStyle.Font);
|
||||||
outputDisplay.TextGetter = () => distributor.IsShortCircuited(PowerOut) ? "err" : MathUtils.RoundToInt(distributor.CalculatePowerOut(this)).ToString();
|
outputDisplay.TextGetter = () => distributor.IsShortCircuited(PowerOut) ? "err" : MathUtils.RoundToInt(distributor.CalculatePowerOut(this)).ToString();
|
||||||
|
|
||||||
powerUnitLabels.Add(loadDisplayUnitLabel);
|
powerUnitLabels.Add(loadDisplayUnitLabel);
|
||||||
|
powerUnitLabels.Add(ratioDisplayUnitLabel);
|
||||||
powerUnitLabels.Add(outputDisplayUnitLabel);
|
powerUnitLabels.Add(outputDisplayUnitLabel);
|
||||||
GUITextBlock.AutoScaleAndNormalize(powerUnitLabels);
|
GUITextBlock.AutoScaleAndNormalize(powerUnitLabels);
|
||||||
|
|
||||||
@@ -111,7 +138,14 @@ namespace Barotrauma.Items.Components
|
|||||||
IsVisible = PowerOut.Wires.Count >= 1;
|
IsVisible = PowerOut.Wires.Count >= 1;
|
||||||
frame!.Visible = IsVisible;
|
frame!.Visible = IsVisible;
|
||||||
divider!.Visible = IsVisible && distributor.powerGroups.Last(group => group.frame!.Visible) != this;
|
divider!.Visible = IsVisible && distributor.powerGroups.Last(group => group.frame!.Visible) != this;
|
||||||
if (distributor.prevLanguage != GameSettings.CurrentConfig.Language) { GUITextBlock.AutoScaleAndNormalize(powerUnitLabels); }
|
if (distributor.prevLanguage != GameSettings.CurrentConfig.Language)
|
||||||
|
{
|
||||||
|
GUITextBlock.AutoScaleAndNormalize(powerUnitLabels);
|
||||||
|
|
||||||
|
float textAndPaddingWidth = loadDisplayNameLabel!.Font.MeasureString(loadDisplayNameLabel!.Text).X + loadDisplayNameLabel.Padding.X + loadDisplayNameLabel.Padding.Z;
|
||||||
|
float availableWidth = groupContent!.Rect.Width - loadDisplayNameLabel.Parent.Rect.Width + loadDisplayNameLabel.Rect.Width - textAndPaddingWidth;
|
||||||
|
nameGroup!.RectTransform.Resize(new Point((int)availableWidth, nameGroup.Rect.Height));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -716,13 +716,19 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
GetAvailablePower(out float batteryCharge, out float batteryCapacity);
|
GetAvailablePower(out float batteryCharge, out float batteryCapacity);
|
||||||
|
|
||||||
List<Item> availableAmmo = new List<Item>();
|
List<Item> availableAmmo = [];
|
||||||
|
AddAmmoFromContainer(item.GetComponent<ItemContainer>());
|
||||||
foreach (MapEntity e in item.linkedTo)
|
foreach (MapEntity e in item.linkedTo)
|
||||||
{
|
{
|
||||||
if (!(e is Item linkedItem)) { continue; }
|
if (e is not Item linkedItem) { continue; }
|
||||||
var itemContainer = linkedItem.GetComponent<ItemContainer>();
|
AddAmmoFromContainer(linkedItem.GetComponent<ItemContainer>());
|
||||||
if (itemContainer == null) { continue; }
|
}
|
||||||
|
|
||||||
|
void AddAmmoFromContainer(ItemContainer itemContainer)
|
||||||
|
{
|
||||||
|
if (itemContainer == null) { return; }
|
||||||
availableAmmo.AddRange(itemContainer.Inventory.AllItems);
|
availableAmmo.AddRange(itemContainer.Inventory.AllItems);
|
||||||
|
//add empty slots too
|
||||||
for (int i = 0; i < itemContainer.Inventory.Capacity - itemContainer.Inventory.AllItems.Count(); i++)
|
for (int i = 0; i < itemContainer.Inventory.Capacity - itemContainer.Inventory.AllItems.Count(); i++)
|
||||||
{
|
{
|
||||||
availableAmmo.Add(null);
|
availableAmmo.Add(null);
|
||||||
|
|||||||
@@ -339,6 +339,19 @@ namespace Barotrauma
|
|||||||
toolTip += $"‖color:{conditionColorStr}‖ ({(int)item.ConditionPercentage} %)‖color:end‖";
|
toolTip += $"‖color:{conditionColorStr}‖ ({(int)item.ConditionPercentage} %)‖color:end‖";
|
||||||
}
|
}
|
||||||
if (!description.IsNullOrEmpty()) { toolTip += '\n' + description; }
|
if (!description.IsNullOrEmpty()) { toolTip += '\n' + description; }
|
||||||
|
|
||||||
|
if (item.Prefab.UnlockedRecipeInToolTip.Length > 0 && GameMain.GameSession is { } GameSession)
|
||||||
|
{
|
||||||
|
if (item.Prefab.UnlockedRecipeInToolTip.All(id => GameSession.HasUnlockedRecipe(Character.Controlled, id)))
|
||||||
|
{
|
||||||
|
toolTip += $"\n‖color:{XMLExtensions.ToStringHex(GUIStyle.Green)}‖{TextManager.Get("unlockedrecipe.true")}‖color:end‖";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
toolTip += $"\n‖color:{XMLExtensions.ToStringHex(GUIStyle.Yellow)}‖{TextManager.Get("unlockedrecipe.false")}‖color:end‖";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (item.Prefab.ContentPackage != GameMain.VanillaContent && item.Prefab.ContentPackage != null)
|
if (item.Prefab.ContentPackage != GameMain.VanillaContent && item.Prefab.ContentPackage != null)
|
||||||
{
|
{
|
||||||
colorStr = XMLExtensions.ToStringHex(Color.MediumPurple);
|
colorStr = XMLExtensions.ToStringHex(Color.MediumPurple);
|
||||||
@@ -356,19 +369,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
toolTip += $" ({item.Prefab.Identifier})";
|
toolTip += $" ({item.Prefab.Identifier})";
|
||||||
#endif
|
#endif
|
||||||
if (!item.Prefab.UnlockedRecipeInToolTip.IsEmpty && GameMain.GameSession is { } GameSession)
|
|
||||||
{
|
|
||||||
if (GameSession.HasUnlockedRecipe(Character.Controlled, item.Prefab.UnlockedRecipeInToolTip))
|
|
||||||
{
|
|
||||||
toolTip += TextManager.Get("unlockedrecipe.true");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
toolTip += $"\n‖color:{XMLExtensions.ToStringHex(GUIStyle.Yellow)}‖{TextManager.Get("unlockedrecipe.false")}‖color:end‖";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PlayerInput.KeyDown(InputType.ContextualCommand))
|
if (PlayerInput.KeyDown(InputType.ContextualCommand))
|
||||||
{
|
{
|
||||||
toolTip += $"\n‖color:gui.blue‖{TextManager.ParseInputTypes(TextManager.Get("itemmsgcontextualorders"))}‖color:end‖";
|
toolTip += $"\n‖color:gui.blue‖{TextManager.ParseInputTypes(TextManager.Get("itemmsgcontextualorders"))}‖color:end‖";
|
||||||
@@ -1300,8 +1301,7 @@ namespace Barotrauma
|
|||||||
SubEditorScreen.StoreCommand(new InventoryPlaceCommand(DraggingItems.First().ParentInventory, new List<Item>(DraggingItems), true));
|
SubEditorScreen.StoreCommand(new InventoryPlaceCommand(DraggingItems.First().ParentInventory, new List<Item>(DraggingItems), true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SoundPlayer.PlayUISound(GUISoundType.DropItem);
|
|
||||||
bool removed = false;
|
bool removed = false;
|
||||||
if (Screen.Selected is SubEditorScreen editor)
|
if (Screen.Selected is SubEditorScreen editor)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -75,11 +75,11 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
|||||||
|
|
||||||
|
|
||||||
// ReSharper restore FieldCanBeMadeReadOnly.Local
|
// ReSharper restore FieldCanBeMadeReadOnly.Local
|
||||||
private const string SettingsResetButtonText = "LuaCsForBarotrauma.SettingsMenu.ResetVisibleSettings";
|
private const string SettingsResetButtonText = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetVisibleSettings";
|
||||||
private const string SettingsResetPromptTitle = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Title";
|
private const string SettingsResetPromptTitle = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetPrompt.Title";
|
||||||
private const string SettingsResetPromptContents = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Message";
|
private const string SettingsResetPromptContents = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetPrompt.Message";
|
||||||
private const string SettingsResetPromptYesText = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Yes";
|
private const string SettingsResetPromptYesText = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetPrompt.Yes";
|
||||||
private const string SettingsResetPromptNoText = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.No";
|
private const string SettingsResetPromptNoText = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetPrompt.No";
|
||||||
|
|
||||||
|
|
||||||
private event Action OnApplyInstalledModsChanges;
|
private event Action OnApplyInstalledModsChanges;
|
||||||
@@ -100,7 +100,7 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
|||||||
var menuTitleLayoutGroup = new GUILayoutGroup(
|
var menuTitleLayoutGroup = new GUILayoutGroup(
|
||||||
new RectTransform(new Vector2(1f, MenuTitleHeight), mainLayoutGroup.RectTransform, Anchor.TopLeft), true, Anchor.TopLeft);
|
new RectTransform(new Vector2(1f, MenuTitleHeight), mainLayoutGroup.RectTransform, Anchor.TopLeft), true, Anchor.TopLeft);
|
||||||
GUIUtil.Label(menuTitleLayoutGroup,
|
GUIUtil.Label(menuTitleLayoutGroup,
|
||||||
GetLocalizedString("LuaCsForBarotrauma.SettingsMenu.ModGameplayButton", "Mod Gameplay Settings"),
|
GetLocalizedString($"{LuaCsSetup.PackageName}.SettingsMenu.ModGameplayButton", "Mod Gameplay Settings"),
|
||||||
GUIStyle.LargeFont, new Vector2(1f, 1f));
|
GUIStyle.LargeFont, new Vector2(1f, 1f));
|
||||||
|
|
||||||
// page contents
|
// page contents
|
||||||
|
|||||||
@@ -46,10 +46,10 @@ public class SettingsMenuSystem : ISettingsMenuSystem
|
|||||||
var tabControlsIndex = (SettingsMenu.Tab)tabCount+1;
|
var tabControlsIndex = (SettingsMenu.Tab)tabCount+1;
|
||||||
|
|
||||||
_gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance,
|
_gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance,
|
||||||
GUIStyle.ComponentStyles.ContainsKey("SettingsMenuTab.LuaCsSettings") ? "SettingsMenuTab.LuaCsSettings" : "SettingsMenuTab.Mods",
|
GUIStyle.ComponentStyles.ContainsKey("SettingsMenuTab.LuaCsSettings") ? "SettingsMenuTab.LuaCsSettings" : "SettingsMenuTab.Mods",
|
||||||
"LuaCsForBarotrauma.SettingsMenu.ModGameplayButton");
|
$"{LuaCsSetup.PackageName}.SettingsMenu.ModGameplayButton");
|
||||||
/*_controlsContentFrame = CreateNewContentTab(tabControlsIndex, __instance,
|
/*_controlsContentFrame = CreateNewContentTab(tabControlsIndex, __instance,
|
||||||
"SettingsMenuTab.Controls", "LuaCsForBarotrauma.SettingsMenu.ModControlsButton");
|
"SettingsMenuTab.Controls", $"{LuaCsSetup.PackageName}.SettingsMenu.ModControlsButton");
|
||||||
*/
|
*/
|
||||||
|
|
||||||
_gameplayMenuInstance = new ModsGameplaySettingsMenu(_gameplayContentFrame, _packageManagementService, _configService, _loggerService, __instance);
|
_gameplayMenuInstance = new ModsGameplaySettingsMenu(_gameplayContentFrame, _packageManagementService, _configService, _loggerService, __instance);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ using System.Xml.Linq;
|
|||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
|
|
||||||
class BackgroundCreature : ISteerable
|
class BackgroundCreature : ISteerable, ILevelRenderableObject
|
||||||
{
|
{
|
||||||
const float MaxDepth = 10000.0f;
|
const float MaxDepth = 10000.0f;
|
||||||
|
|
||||||
@@ -76,6 +76,8 @@ namespace Barotrauma
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Vector3 Position => new Vector3(position.X, position.Y, Depth);
|
||||||
|
|
||||||
public BackgroundCreature(BackgroundCreaturePrefab prefab, Vector2 position)
|
public BackgroundCreature(BackgroundCreaturePrefab prefab, Vector2 position)
|
||||||
{
|
{
|
||||||
this.Prefab = prefab;
|
this.Prefab = prefab;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework.Graphics;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -15,52 +14,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private float checkVisibleTimer;
|
private float checkVisibleTimer;
|
||||||
|
|
||||||
private readonly List<BackgroundCreature> creatures = new List<BackgroundCreature>();
|
private readonly List<BackgroundCreature> creatures = [];
|
||||||
|
|
||||||
private readonly List<BackgroundCreature> visibleCreatures = new List<BackgroundCreature>();
|
private readonly List<BackgroundCreature> visibleCreatures = [];
|
||||||
|
|
||||||
public BackgroundCreatureManager()
|
public IEnumerable<BackgroundCreature> VisibleCreatures => visibleCreatures;
|
||||||
{
|
|
||||||
/*foreach(var file in files)
|
|
||||||
{
|
|
||||||
LoadConfig(file.Path);
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
/*public BackgroundCreatureManager(string path)
|
|
||||||
{
|
|
||||||
DebugConsole.AddWarning($"Couldn't find any BackgroundCreaturePrefabs files, falling back to {path}");
|
|
||||||
LoadConfig(ContentPath.FromRaw(null, path));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadConfig(ContentPath configPath)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
XDocument doc = XMLExtensions.TryLoadXml(configPath);
|
|
||||||
if (doc == null) { return; }
|
|
||||||
var mainElement = doc.Root.FromPackage(configPath.ContentPackage);
|
|
||||||
if (mainElement.IsOverride())
|
|
||||||
{
|
|
||||||
mainElement = mainElement.FirstElement();
|
|
||||||
Prefabs.Clear();
|
|
||||||
DebugConsole.NewMessage($"Overriding all background creatures with '{configPath}'", Color.MediumPurple);
|
|
||||||
}
|
|
||||||
else if (Prefabs.Any())
|
|
||||||
{
|
|
||||||
DebugConsole.NewMessage($"Loading additional background creatures from file '{configPath}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var element in mainElement.Elements())
|
|
||||||
{
|
|
||||||
Prefabs.Add(new BackgroundCreaturePrefab(element));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
DebugConsole.ThrowError(String.Format("Failed to load BackgroundCreatures from {0}", configPath), e);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
public void SpawnCreatures(Level level, int count, Vector2? position = null)
|
public void SpawnCreatures(Level level, int count, Vector2? position = null)
|
||||||
{
|
{
|
||||||
@@ -161,14 +119,6 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Draw(SpriteBatch spriteBatch, Camera cam)
|
|
||||||
{
|
|
||||||
foreach (BackgroundCreature creature in visibleCreatures)
|
|
||||||
{
|
|
||||||
creature.Draw(spriteBatch, cam);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DrawLights(SpriteBatch spriteBatch, Camera cam)
|
public void DrawLights(SpriteBatch spriteBatch, Camera cam)
|
||||||
{
|
{
|
||||||
foreach (BackgroundCreature creature in visibleCreatures)
|
foreach (BackgroundCreature creature in visibleCreatures)
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void DrawFront(SpriteBatch spriteBatch, Camera cam)
|
public void DrawFront(SpriteBatch spriteBatch, Camera cam)
|
||||||
{
|
{
|
||||||
renderer?.DrawForeground(spriteBatch, cam, LevelObjectManager);
|
renderer?.DrawForeground(spriteBatch, cam, backgroundCreatureManager, LevelObjectManager);
|
||||||
}
|
}
|
||||||
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ using System.Xml.Linq;
|
|||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
partial class LevelObject
|
partial class LevelObject : ILevelRenderableObject
|
||||||
{
|
{
|
||||||
public float SwingTimer;
|
public float SwingTimer;
|
||||||
public float ScaleOscillateTimer;
|
public float ScaleOscillateTimer;
|
||||||
|
|||||||
@@ -8,13 +8,20 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
|
public interface ILevelRenderableObject
|
||||||
|
{
|
||||||
|
public Vector3 Position { get; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
partial class LevelObjectManager
|
partial class LevelObjectManager
|
||||||
{
|
{
|
||||||
|
|
||||||
// Pre-initialized to the max size, so that we don't have to resize the lists at runtime. TODO: Could the capacity (of some collections?) be lower?
|
// Pre-initialized to the max size, so that we don't have to resize the lists at runtime. TODO: Could the capacity (of some collections?) be lower?
|
||||||
private readonly List<LevelObject> visibleObjectsBack = new List<LevelObject>(MaxVisibleObjects);
|
private readonly List<ILevelRenderableObject> visibleObjectsBack = new List<ILevelRenderableObject>(MaxVisibleObjects);
|
||||||
private readonly List<LevelObject> visibleObjectsMid = new List<LevelObject>(MaxVisibleObjects);
|
private readonly List<ILevelRenderableObject> visibleObjectsMid = new List<ILevelRenderableObject>(MaxVisibleObjects);
|
||||||
private readonly List<LevelObject> visibleObjectsFront = new List<LevelObject>(MaxVisibleObjects);
|
private readonly List<ILevelRenderableObject> visibleObjectsFront = new List<ILevelRenderableObject>(MaxVisibleObjects);
|
||||||
private readonly HashSet<LevelObject> allVisibleObjects = new HashSet<LevelObject>(MaxVisibleObjects);
|
private readonly HashSet<ILevelRenderableObject> allVisibleObjects = new HashSet<ILevelRenderableObject>(MaxVisibleObjects);
|
||||||
|
|
||||||
private double NextRefreshTime;
|
private double NextRefreshTime;
|
||||||
|
|
||||||
@@ -35,35 +42,44 @@ namespace Barotrauma
|
|||||||
|
|
||||||
partial void UpdateProjSpecific(float deltaTime, Camera cam)
|
partial void UpdateProjSpecific(float deltaTime, Camera cam)
|
||||||
{
|
{
|
||||||
foreach (LevelObject obj in visibleObjectsBack)
|
foreach (ILevelRenderableObject obj in visibleObjectsBack)
|
||||||
{
|
{
|
||||||
obj.Update(deltaTime, cam);
|
if (obj is LevelObject levelObj)
|
||||||
|
{
|
||||||
|
levelObj.Update(deltaTime, cam);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
foreach (LevelObject obj in visibleObjectsMid)
|
foreach (ILevelRenderableObject obj in visibleObjectsMid)
|
||||||
{
|
{
|
||||||
obj.Update(deltaTime, cam);
|
if (obj is LevelObject levelObj)
|
||||||
|
{
|
||||||
|
levelObj.Update(deltaTime, cam);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
foreach (LevelObject obj in visibleObjectsFront)
|
foreach (ILevelRenderableObject obj in visibleObjectsFront)
|
||||||
{
|
{
|
||||||
obj.Update(deltaTime, cam);
|
if (obj is LevelObject levelObj)
|
||||||
|
{
|
||||||
|
levelObj.Update(deltaTime, cam);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all visible objects, but not in order, because internally uses a HashSet.
|
/// Returns all visible objects, but not in order, because internally uses a HashSet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<LevelObject> GetAllVisibleObjects()
|
public IEnumerable<ILevelRenderableObject> GetAllVisibleObjects()
|
||||||
{
|
{
|
||||||
allVisibleObjects.Clear();
|
allVisibleObjects.Clear();
|
||||||
foreach (LevelObject obj in visibleObjectsBack)
|
foreach (ILevelRenderableObject obj in visibleObjectsBack)
|
||||||
{
|
{
|
||||||
allVisibleObjects.Add(obj);
|
allVisibleObjects.Add(obj);
|
||||||
}
|
}
|
||||||
foreach (LevelObject obj in visibleObjectsMid)
|
foreach (ILevelRenderableObject obj in visibleObjectsMid)
|
||||||
{
|
{
|
||||||
allVisibleObjects.Add(obj);
|
allVisibleObjects.Add(obj);
|
||||||
}
|
}
|
||||||
foreach (LevelObject obj in visibleObjectsFront)
|
foreach (ILevelRenderableObject obj in visibleObjectsFront)
|
||||||
{
|
{
|
||||||
allVisibleObjects.Add(obj);
|
allVisibleObjects.Add(obj);
|
||||||
}
|
}
|
||||||
@@ -73,7 +89,7 @@ namespace Barotrauma
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks which level objects are in camera view and adds them to the visibleObjects lists
|
/// Checks which level objects are in camera view and adds them to the visibleObjects lists
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void RefreshVisibleObjects(Rectangle currentIndices, float zoom)
|
private void RefreshVisibleObjects(Rectangle currentIndices, BackgroundCreatureManager backgroundCreatureManager, float zoom)
|
||||||
{
|
{
|
||||||
visibleObjectsBack.Clear();
|
visibleObjectsBack.Clear();
|
||||||
visibleObjectsMid.Clear();
|
visibleObjectsMid.Clear();
|
||||||
@@ -152,6 +168,27 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var backgroundCreature in backgroundCreatureManager.VisibleCreatures)
|
||||||
|
{
|
||||||
|
int drawOrderIndex = 0;
|
||||||
|
for (int i = 0; i < visibleObjectsBack.Count; i++)
|
||||||
|
{
|
||||||
|
if (visibleObjectsBack[i].Position.Z > backgroundCreature.Position.Z)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
drawOrderIndex = i + 1;
|
||||||
|
if (drawOrderIndex >= MaxVisibleObjects) { break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (drawOrderIndex >= 0 && drawOrderIndex < MaxVisibleObjects)
|
||||||
|
{
|
||||||
|
visibleObjectsBack.Insert(drawOrderIndex, backgroundCreature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//object grid is sorted in an ascending order
|
//object grid is sorted in an ascending order
|
||||||
//(so we prefer the objects in the foreground instead of ones in the background if some need to be culled)
|
//(so we prefer the objects in the foreground instead of ones in the background if some need to be culled)
|
||||||
//rendering needs to be done in a descending order though to get the background objects to be drawn first -> reverse the lists
|
//rendering needs to be done in a descending order though to get the background objects to be drawn first -> reverse the lists
|
||||||
@@ -165,28 +202,28 @@ namespace Barotrauma
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Draw the objects behind the level walls
|
/// Draw the objects behind the level walls
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DrawObjectsBack(SpriteBatch spriteBatch, Camera cam)
|
public void DrawObjectsBack(SpriteBatch spriteBatch, BackgroundCreatureManager backgroundCreatureManager, Camera cam)
|
||||||
{
|
{
|
||||||
DrawObjects(spriteBatch, cam, visibleObjectsBack);
|
DrawObjects(spriteBatch, cam, backgroundCreatureManager, visibleObjectsBack);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Draw the objects in front of the level walls, but behind characters
|
/// Draw the objects in front of the level walls, but behind characters
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DrawObjectsMid(SpriteBatch spriteBatch, Camera cam)
|
public void DrawObjectsMid(SpriteBatch spriteBatch, BackgroundCreatureManager backgroundCreatureManager, Camera cam)
|
||||||
{
|
{
|
||||||
DrawObjects(spriteBatch, cam, visibleObjectsMid);
|
DrawObjects(spriteBatch, cam, backgroundCreatureManager, visibleObjectsMid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Draw the objects in front of the level walls and characters
|
/// Draw the objects in front of the level walls and characters
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DrawObjectsFront(SpriteBatch spriteBatch, Camera cam)
|
public void DrawObjectsFront(SpriteBatch spriteBatch, BackgroundCreatureManager backgroundCreatureManager, Camera cam)
|
||||||
{
|
{
|
||||||
DrawObjects(spriteBatch, cam, visibleObjectsFront);
|
DrawObjects(spriteBatch, cam, backgroundCreatureManager, visibleObjectsFront);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawObjects(SpriteBatch spriteBatch, Camera cam, List<LevelObject> objectList)
|
private void DrawObjects(SpriteBatch spriteBatch, Camera cam, BackgroundCreatureManager backgroundCreatureManager, List<ILevelRenderableObject> objectList)
|
||||||
{
|
{
|
||||||
Rectangle indices = Rectangle.Empty;
|
Rectangle indices = Rectangle.Empty;
|
||||||
indices.X = (int)Math.Floor(cam.WorldView.X / (float)GridSize);
|
indices.X = (int)Math.Floor(cam.WorldView.X / (float)GridSize);
|
||||||
@@ -207,7 +244,7 @@ namespace Barotrauma
|
|||||||
float z = 0.0f;
|
float z = 0.0f;
|
||||||
if (ForceRefreshVisibleObjects || (currentGridIndices != indices && Timing.TotalTime > NextRefreshTime))
|
if (ForceRefreshVisibleObjects || (currentGridIndices != indices && Timing.TotalTime > NextRefreshTime))
|
||||||
{
|
{
|
||||||
RefreshVisibleObjects(indices, cam.Zoom);
|
RefreshVisibleObjects(indices, backgroundCreatureManager, cam.Zoom);
|
||||||
ForceRefreshVisibleObjects = false;
|
ForceRefreshVisibleObjects = false;
|
||||||
if (cam.Zoom < 0.1f)
|
if (cam.Zoom < 0.1f)
|
||||||
{
|
{
|
||||||
@@ -216,61 +253,93 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (LevelObject obj in objectList)
|
bool prevObjectHasDeformableSprite = false;
|
||||||
|
foreach (ILevelRenderableObject obj2 in objectList)
|
||||||
{
|
{
|
||||||
Vector2 camDiff = new Vector2(obj.Position.X, obj.Position.Y) - cam.WorldViewCenter;
|
Vector2 camDiff = new Vector2(obj2.Position.X, obj2.Position.Y) - cam.WorldViewCenter;
|
||||||
camDiff.Y = -camDiff.Y;
|
camDiff.Y = -camDiff.Y;
|
||||||
|
|
||||||
Sprite activeSprite = obj.Sprite;
|
|
||||||
activeSprite?.Draw(
|
|
||||||
spriteBatch,
|
|
||||||
new Vector2(obj.Position.X, -obj.Position.Y) - camDiff * obj.Position.Z * ParallaxStrength,
|
|
||||||
Color.Lerp(obj.Prefab.SpriteColor, obj.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), obj.Position.Z / obj.Prefab.FadeOutDepth),
|
|
||||||
activeSprite.Origin,
|
|
||||||
obj.CurrentRotation,
|
|
||||||
obj.CurrentScale,
|
|
||||||
SpriteEffects.None,
|
|
||||||
z);
|
|
||||||
|
|
||||||
if (obj.ActivePrefab.DeformableSprite != null)
|
bool hasDeformableSprite = false;
|
||||||
|
if (obj2 is LevelObject levelObject)
|
||||||
{
|
{
|
||||||
if (obj.CurrentSpriteDeformation != null)
|
hasDeformableSprite = levelObject.ActivePrefab.DeformableSprite != null;
|
||||||
|
if (hasDeformableSprite != prevObjectHasDeformableSprite)
|
||||||
{
|
{
|
||||||
obj.ActivePrefab.DeformableSprite.Deform(obj.CurrentSpriteDeformation);
|
spriteBatch.End();
|
||||||
|
spriteBatch.Begin(SpriteSortMode.Deferred,
|
||||||
|
BlendState.NonPremultiplied,
|
||||||
|
SamplerState.LinearWrap, DepthStencilState.DepthRead,
|
||||||
|
transformMatrix: cam.Transform);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
Sprite activeSprite = levelObject.Sprite;
|
||||||
|
activeSprite?.Draw(
|
||||||
|
spriteBatch,
|
||||||
|
new Vector2(levelObject.Position.X, -levelObject.Position.Y) - camDiff * levelObject.Position.Z * ParallaxStrength,
|
||||||
|
Color.Lerp(levelObject.Prefab.SpriteColor, levelObject.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), levelObject.Position.Z / levelObject.Prefab.FadeOutDepth),
|
||||||
|
activeSprite.Origin,
|
||||||
|
levelObject.CurrentRotation,
|
||||||
|
levelObject.CurrentScale,
|
||||||
|
SpriteEffects.None,
|
||||||
|
z);
|
||||||
|
|
||||||
|
if (hasDeformableSprite)
|
||||||
{
|
{
|
||||||
obj.ActivePrefab.DeformableSprite.Reset();
|
if (levelObject.CurrentSpriteDeformation != null)
|
||||||
}
|
|
||||||
obj.ActivePrefab.DeformableSprite?.Draw(cam,
|
|
||||||
new Vector3(new Vector2(obj.Position.X, obj.Position.Y) - camDiff * obj.Position.Z * ParallaxStrength, z * 10.0f),
|
|
||||||
obj.ActivePrefab.DeformableSprite.Origin,
|
|
||||||
obj.CurrentRotation,
|
|
||||||
obj.CurrentScale,
|
|
||||||
Color.Lerp(obj.Prefab.SpriteColor, obj.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), obj.Position.Z / 5000.0f));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (GameMain.DebugDraw)
|
|
||||||
{
|
|
||||||
GUI.DrawRectangle(spriteBatch, new Vector2(obj.Position.X, -obj.Position.Y), new Vector2(10.0f, 10.0f), GUIStyle.Red, true);
|
|
||||||
|
|
||||||
if (obj.Triggers == null) { continue; }
|
|
||||||
foreach (LevelTrigger trigger in obj.Triggers)
|
|
||||||
{
|
|
||||||
if (trigger.PhysicsBody == null) continue;
|
|
||||||
GUI.DrawLine(spriteBatch, new Vector2(obj.Position.X, -obj.Position.Y), new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y), Color.Cyan, 0, 3);
|
|
||||||
|
|
||||||
Vector2 flowForce = trigger.GetWaterFlowVelocity();
|
|
||||||
if (flowForce.LengthSquared() > 1)
|
|
||||||
{
|
{
|
||||||
flowForce.Y = -flowForce.Y;
|
levelObject.ActivePrefab.DeformableSprite.Deform(levelObject.CurrentSpriteDeformation);
|
||||||
GUI.DrawLine(spriteBatch, new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y), new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y) + flowForce * 10, GUIStyle.Orange, 0, 5);
|
|
||||||
}
|
}
|
||||||
trigger.PhysicsBody.UpdateDrawPosition();
|
else
|
||||||
trigger.PhysicsBody.DebugDraw(spriteBatch, trigger.IsTriggered ? Color.Cyan : Color.DarkCyan);
|
{
|
||||||
|
levelObject.ActivePrefab.DeformableSprite.Reset();
|
||||||
|
}
|
||||||
|
levelObject.ActivePrefab.DeformableSprite?.Draw(cam,
|
||||||
|
new Vector3(new Vector2(levelObject.Position.X, levelObject.Position.Y) - camDiff * levelObject.Position.Z * ParallaxStrength, z * 10.0f),
|
||||||
|
levelObject.ActivePrefab.DeformableSprite.Origin,
|
||||||
|
levelObject.CurrentRotation,
|
||||||
|
levelObject.CurrentScale,
|
||||||
|
Color.Lerp(levelObject.Prefab.SpriteColor, levelObject.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), levelObject.Position.Z / 5000.0f));
|
||||||
}
|
}
|
||||||
|
prevObjectHasDeformableSprite = hasDeformableSprite;
|
||||||
|
|
||||||
|
if (GameMain.DebugDraw)
|
||||||
|
{
|
||||||
|
GUI.DrawRectangle(spriteBatch, new Vector2(levelObject.Position.X, -levelObject.Position.Y), new Vector2(10.0f, 10.0f), GUIStyle.Red, true);
|
||||||
|
|
||||||
|
if (levelObject.Triggers == null) { continue; }
|
||||||
|
foreach (LevelTrigger trigger in levelObject.Triggers)
|
||||||
|
{
|
||||||
|
if (trigger.PhysicsBody == null) continue;
|
||||||
|
GUI.DrawLine(spriteBatch, new Vector2(levelObject.Position.X, -levelObject.Position.Y), new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y), Color.Cyan, 0, 3);
|
||||||
|
|
||||||
|
Vector2 flowForce = trigger.GetWaterFlowVelocity();
|
||||||
|
if (flowForce.LengthSquared() > 1)
|
||||||
|
{
|
||||||
|
flowForce.Y = -flowForce.Y;
|
||||||
|
GUI.DrawLine(spriteBatch, new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y), new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y) + flowForce * 10, GUIStyle.Orange, 0, 5);
|
||||||
|
}
|
||||||
|
trigger.PhysicsBody.UpdateDrawPosition();
|
||||||
|
trigger.PhysicsBody.DebugDraw(spriteBatch, trigger.IsTriggered ? Color.Cyan : Color.DarkCyan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
else if (obj2 is BackgroundCreature backgroundCreature && cam.Zoom > 0.05f)
|
||||||
|
{
|
||||||
|
hasDeformableSprite = backgroundCreature.Prefab.DeformableSprite != null;
|
||||||
|
if (hasDeformableSprite != prevObjectHasDeformableSprite)
|
||||||
|
{
|
||||||
|
spriteBatch.End();
|
||||||
|
spriteBatch.Begin(SpriteSortMode.Deferred,
|
||||||
|
BlendState.NonPremultiplied,
|
||||||
|
SamplerState.LinearWrap, DepthStencilState.DepthRead,
|
||||||
|
transformMatrix: cam.Transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
backgroundCreature.Draw(spriteBatch, cam);
|
||||||
|
}
|
||||||
|
prevObjectHasDeformableSprite = hasDeformableSprite;
|
||||||
|
|
||||||
|
|
||||||
z += 0.0001f;
|
z += 0.0001f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,8 +214,9 @@ namespace Barotrauma
|
|||||||
//calculate the sum of the forces of nearby level triggers
|
//calculate the sum of the forces of nearby level triggers
|
||||||
//and use it to move the water texture and water distortion effect
|
//and use it to move the water texture and water distortion effect
|
||||||
Vector2 currentWaterParticleVel = level.GenerationParams.WaterParticleVelocity;
|
Vector2 currentWaterParticleVel = level.GenerationParams.WaterParticleVelocity;
|
||||||
foreach (LevelObject levelObject in level.LevelObjectManager.GetAllVisibleObjects())
|
foreach (ILevelRenderableObject obj in level.LevelObjectManager.GetAllVisibleObjects())
|
||||||
{
|
{
|
||||||
|
if (obj is not LevelObject levelObject) { continue; }
|
||||||
if (levelObject.Triggers == null) { continue; }
|
if (levelObject.Triggers == null) { continue; }
|
||||||
//use the largest water flow velocity of all the triggers
|
//use the largest water flow velocity of all the triggers
|
||||||
Vector2 objectMaxFlow = Vector2.Zero;
|
Vector2 objectMaxFlow = Vector2.Zero;
|
||||||
@@ -274,11 +275,7 @@ namespace Barotrauma
|
|||||||
SamplerState.LinearWrap, DepthStencilState.DepthRead, null, null,
|
SamplerState.LinearWrap, DepthStencilState.DepthRead, null, null,
|
||||||
cam.Transform);
|
cam.Transform);
|
||||||
|
|
||||||
backgroundSpriteManager?.DrawObjectsBack(spriteBatch, cam);
|
backgroundSpriteManager?.DrawObjectsBack(spriteBatch, backgroundCreatureManager, cam);
|
||||||
if (cam.Zoom > 0.05f)
|
|
||||||
{
|
|
||||||
backgroundCreatureManager?.Draw(spriteBatch, cam);
|
|
||||||
}
|
|
||||||
|
|
||||||
level.GenerationParams.DrawWaterParticles(spriteBatch, cam, waterParticleOffset);
|
level.GenerationParams.DrawWaterParticles(spriteBatch, cam, waterParticleOffset);
|
||||||
|
|
||||||
@@ -292,17 +289,18 @@ namespace Barotrauma
|
|||||||
BlendState.NonPremultiplied,
|
BlendState.NonPremultiplied,
|
||||||
SamplerState.LinearClamp, DepthStencilState.DepthRead, null, null,
|
SamplerState.LinearClamp, DepthStencilState.DepthRead, null, null,
|
||||||
cam.Transform);
|
cam.Transform);
|
||||||
backgroundSpriteManager?.DrawObjectsMid(spriteBatch, cam);
|
backgroundSpriteManager?.DrawObjectsMid(spriteBatch, backgroundCreatureManager, cam);
|
||||||
spriteBatch.End();
|
spriteBatch.End();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawForeground(SpriteBatch spriteBatch, Camera cam, LevelObjectManager backgroundSpriteManager = null)
|
public void DrawForeground(SpriteBatch spriteBatch, Camera cam,
|
||||||
|
BackgroundCreatureManager backgroundCreatureManager, LevelObjectManager backgroundSpriteManager = null)
|
||||||
{
|
{
|
||||||
spriteBatch.Begin(SpriteSortMode.Deferred,
|
spriteBatch.Begin(SpriteSortMode.Deferred,
|
||||||
BlendState.NonPremultiplied,
|
BlendState.NonPremultiplied,
|
||||||
SamplerState.LinearClamp, DepthStencilState.DepthRead, null, null,
|
SamplerState.LinearClamp, DepthStencilState.DepthRead, null, null,
|
||||||
cam.Transform);
|
cam.Transform);
|
||||||
backgroundSpriteManager?.DrawObjectsFront(spriteBatch, cam);
|
backgroundSpriteManager?.DrawObjectsFront(spriteBatch, backgroundCreatureManager, cam);
|
||||||
spriteBatch.End();
|
spriteBatch.End();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2547,7 +2547,7 @@ namespace Barotrauma.Networking
|
|||||||
segmentTable.StartNewSegment(ClientNetSegment.SyncIds);
|
segmentTable.StartNewSegment(ClientNetSegment.SyncIds);
|
||||||
//outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
|
//outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
|
||||||
outmsg.WriteUInt16(ChatMessage.LastID);
|
outmsg.WriteUInt16(ChatMessage.LastID);
|
||||||
outmsg.WriteUInt16(EntityEventManager.LastReceivedID);
|
outmsg.WriteUInt16(SpoofEntityManagerReceivedId ? (ushort)1 : EntityEventManager.LastReceivedID);
|
||||||
outmsg.WriteUInt16(LastClientListUpdateID);
|
outmsg.WriteUInt16(LastClientListUpdateID);
|
||||||
|
|
||||||
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
|
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
|
||||||
@@ -3370,6 +3370,12 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
get { return votingInterface; }
|
get { return votingInterface; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Forces the client to report the last received event ID as always being 1, making the server believe the client is always behind.
|
||||||
|
/// </summary>
|
||||||
|
public bool SpoofEntityManagerReceivedId { get; set; }
|
||||||
|
|
||||||
private VotingInterface votingInterface;
|
private VotingInterface votingInterface;
|
||||||
|
|
||||||
public bool TypingChatMessage(GUITextBox textBox, string text)
|
public bool TypingChatMessage(GUITextBox textBox, string text)
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
byte queueId = msg.ReadByte();
|
byte queueId = msg.ReadByte();
|
||||||
float distanceFactor = msg.ReadRangedSingle(0.0f, 1.0f, 8);
|
float distanceFactor = msg.ReadRangedSingle(0.0f, 1.0f, 8);
|
||||||
|
bool isRadio = msg.ReadBoolean();
|
||||||
VoipQueue queue = queues.Find(q => q.QueueID == queueId);
|
VoipQueue queue = queues.Find(q => q.QueueID == queueId);
|
||||||
|
|
||||||
if (queue == null)
|
if (queue == null)
|
||||||
@@ -117,19 +118,21 @@ namespace Barotrauma.Networking
|
|||||||
float rangeMultiplier = spectating ? 2.0f : 1.0f;
|
float rangeMultiplier = spectating ? 2.0f : 1.0f;
|
||||||
WifiComponent senderRadio = null;
|
WifiComponent senderRadio = null;
|
||||||
|
|
||||||
var messageType =
|
var messageType = isRadio ? ChatMessageType.Radio : ChatMessageType.Default;
|
||||||
!client.VoipQueue.ForceLocal &&
|
|
||||||
ChatMessage.CanUseRadio(client.Character, out senderRadio) &&
|
|
||||||
(spectating || (ChatMessage.CanUseRadio(Character.Controlled, out var recipientRadio) && senderRadio.CanReceive(recipientRadio)))
|
|
||||||
? ChatMessageType.Radio : ChatMessageType.Default;
|
|
||||||
client.Character.ShowTextlessSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
|
client.Character.ShowTextlessSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
|
||||||
|
|
||||||
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters;
|
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters;
|
||||||
client.RadioNoise = 0.0f;
|
client.RadioNoise = 0.0f;
|
||||||
if (messageType == ChatMessageType.Radio)
|
if (messageType == ChatMessageType.Radio)
|
||||||
{
|
{
|
||||||
|
//If the client cannot establish a radio, use a headsets default range as a fallback to calculate the radio noise.
|
||||||
|
//This cannot happen in an un-modded setting as CanUseRadio is part of the server side check for isRadio to be true.
|
||||||
|
ChatMessage.CanUseRadio(client.Character, out senderRadio);
|
||||||
|
float senderRadioRange = (senderRadio == null) ? 35000.0f : senderRadio.Range;
|
||||||
|
|
||||||
client.VoipSound.UsingRadio = true;
|
client.VoipSound.UsingRadio = true;
|
||||||
client.VoipSound.SetRange(senderRadio.Range * RangeNear * speechImpedimentMultiplier * rangeMultiplier, senderRadio.Range * speechImpedimentMultiplier * rangeMultiplier);
|
client.VoipSound.SetRange(senderRadioRange * RangeNear * speechImpedimentMultiplier * rangeMultiplier, senderRadioRange * speechImpedimentMultiplier * rangeMultiplier);
|
||||||
if (distanceFactor > RangeNear && !spectating)
|
if (distanceFactor > RangeNear && !spectating)
|
||||||
{
|
{
|
||||||
//noise starts increasing exponentially after 40% range
|
//noise starts increasing exponentially after 40% range
|
||||||
|
|||||||
@@ -533,6 +533,18 @@ namespace Barotrauma
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
new GUITickBox(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(40, 280) },
|
||||||
|
"Lenient server startup timeouts")
|
||||||
|
{
|
||||||
|
Selected = NetConfig.UseLenientHandshake,
|
||||||
|
ToolTip = "Start with more lenient Lidgren handshake timeouts. The server is more likely to start even when running multiple instances on the same machine under heavy load.",
|
||||||
|
OnSelected = (tickBox) =>
|
||||||
|
{
|
||||||
|
NetConfig.UseLenientHandshake = tickBox.Selected;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var minButtonSize = new Point(120, 20);
|
var minButtonSize = new Point(120, 20);
|
||||||
@@ -1126,6 +1138,11 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (NetConfig.UseLenientHandshake)
|
||||||
|
{
|
||||||
|
arguments.Add("-lenienthandshake");
|
||||||
|
}
|
||||||
|
|
||||||
var processInfo = new ProcessStartInfo
|
var processInfo = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
FileName = fileName,
|
FileName = fileName,
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
using Barotrauma.IO;
|
using Barotrauma.IO;
|
||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
|
using Barotrauma.Sounds;
|
||||||
using Barotrauma.Steam;
|
using Barotrauma.Steam;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
using Microsoft.Xna.Framework.Input;
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using Steamworks;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Xml;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Barotrauma.LuaCs.Events;
|
using Barotrauma.LuaCs.Events;
|
||||||
using Barotrauma.Sounds;
|
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -2044,6 +2047,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private bool SaveSubToFile(string name, ContentPackage packageToSaveTo)
|
private bool SaveSubToFile(string name, ContentPackage packageToSaveTo)
|
||||||
{
|
{
|
||||||
|
bool remoteStorageWasEnabled = Submarine.MainSub.Info.SaveToRemoteStorage;
|
||||||
|
|
||||||
Type subFileType = DetermineSubFileType(MainSub?.Info.Type ?? SubmarineType.Player);
|
Type subFileType = DetermineSubFileType(MainSub?.Info.Type ?? SubmarineType.Player);
|
||||||
|
|
||||||
static string getExistingFilePath(ContentPackage package, string fileName)
|
static string getExistingFilePath(ContentPackage package, string fileName)
|
||||||
@@ -2272,6 +2277,16 @@ namespace Barotrauma
|
|||||||
linkedSubBox.AddItem(sub.Name, sub);
|
linkedSubBox.AddItem(sub.Name, sub);
|
||||||
}
|
}
|
||||||
subNameLabel.Text = ToolBox.LimitString(MainSub.Info.Name, subNameLabel.Font, subNameLabel.Rect.Width);
|
subNameLabel.Text = ToolBox.LimitString(MainSub.Info.Name, subNameLabel.Font, subNameLabel.Rect.Width);
|
||||||
|
|
||||||
|
if (remoteStorageWasEnabled)
|
||||||
|
{
|
||||||
|
Submarine.MainSub.Info.SaveToRemoteStorage = true;
|
||||||
|
|
||||||
|
RemoteStorageHelper.TryWrite(
|
||||||
|
localPath: MainSub.Info.FilePath,
|
||||||
|
saveAs: MainSub.Info.FilePath.CleanUpPathCrossPlatform(correctFilenameCase: false),
|
||||||
|
allowOverwrite: true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3223,6 +3238,42 @@ namespace Barotrauma
|
|||||||
|
|
||||||
previewImageButtonHolder.RectTransform.MinSize = new Point(0, previewImageButtonHolder.RectTransform.Children.Max(c => c.MinSize.Y));
|
previewImageButtonHolder.RectTransform.MinSize = new Point(0, previewImageButtonHolder.RectTransform.Children.Max(c => c.MinSize.Y));
|
||||||
|
|
||||||
|
if (SteamManager.IsInitialized)
|
||||||
|
{
|
||||||
|
GUILayoutGroup remoteStorageArea = new(
|
||||||
|
new RectTransform(new Vector2(1f, 0.05f), rightColumn.RectTransform,
|
||||||
|
minSize: new Point(0, minHeight)),
|
||||||
|
isHorizontal: true,
|
||||||
|
childAnchor: Anchor.CenterLeft)
|
||||||
|
{
|
||||||
|
Stretch = true,
|
||||||
|
AbsoluteSpacing = 5
|
||||||
|
};
|
||||||
|
|
||||||
|
new GUITextBlock(new RectTransform(Vector2.One, remoteStorageArea.RectTransform),
|
||||||
|
TextManager.Get("RemoteStorageToggle.Title"), textAlignment: Alignment.CenterLeft, wrap: true);
|
||||||
|
|
||||||
|
new GUITickBox(new RectTransform(Vector2.One, remoteStorageArea.RectTransform), label: "")
|
||||||
|
{
|
||||||
|
OnAddedToGUIUpdateList = component =>
|
||||||
|
{
|
||||||
|
// Values may change outside of game.
|
||||||
|
component.Enabled = SteamRemoteStorage.IsCloudEnabledForAccount;
|
||||||
|
component.ToolTip = !SteamRemoteStorage.IsCloudEnabledForAccount ? TextManager.Get("RemoteStorageToggle.Disabled") : "";
|
||||||
|
((GUITickBox)component).SetSelected(SteamRemoteStorage.IsCloudEnabled && MainSub.Info.SaveToRemoteStorage, callOnSelected: false);
|
||||||
|
},
|
||||||
|
OnSelected = tickBox =>
|
||||||
|
{
|
||||||
|
if (tickBox.Selected && !SteamRemoteStorage.IsCloudEnabledForApp)
|
||||||
|
{
|
||||||
|
RemoteStorageHelper.AskToEnable(onAccepted: () => MainSub.Info.SaveToRemoteStorage = true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return MainSub.Info.SaveToRemoteStorage = tickBox.Selected;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var contentPackageTabber = new GUILayoutGroup(new RectTransform((1.0f, 0.075f), rightColumn.RectTransform), isHorizontal: true);
|
var contentPackageTabber = new GUILayoutGroup(new RectTransform((1.0f, 0.075f), rightColumn.RectTransform), isHorizontal: true);
|
||||||
|
|
||||||
GUIButton createTabberBtn(string labelTag)
|
GUIButton createTabberBtn(string labelTag)
|
||||||
@@ -3746,7 +3797,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
var package = GetLocalPackageThatOwnsSub(subInfo);
|
var package = GetLocalPackageThatOwnsSub(subInfo);
|
||||||
if (package != null)
|
if (package != null || subInfo.IsFromRemoteStorage)
|
||||||
{
|
{
|
||||||
deleteBtn.Enabled = true;
|
deleteBtn.Enabled = true;
|
||||||
}
|
}
|
||||||
@@ -3771,13 +3822,29 @@ namespace Barotrauma
|
|||||||
searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = sender.Text.IsNullOrEmpty(); };
|
searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = sender.Text.IsNullOrEmpty(); };
|
||||||
searchBox.OnTextChanged += (textBox, text) => { FilterSubs(subList, text); return true; };
|
searchBox.OnTextChanged += (textBox, text) => { FilterSubs(subList, text); return true; };
|
||||||
|
|
||||||
var sortedSubs = GetLoadableSubs()
|
List<SubmarineInfo> allSubs = [.. SubmarineInfo.SavedSubmarines];
|
||||||
.OrderBy(s => s.Type)
|
foreach (SteamRemoteStorage.RemoteFile remoteFile in SteamRemoteStorage.Files.Where(file => file.Filename.EndsWith(".sub")))
|
||||||
.ThenBy(s => s.Name)
|
{
|
||||||
.ToList();
|
if (!remoteFile.TryRead(out byte[] bytes)) { continue; }
|
||||||
|
|
||||||
|
using System.IO.MemoryStream stream = new(bytes);
|
||||||
|
using GZipStream zipStream = new(stream, CompressionMode.Decompress);
|
||||||
|
if (XMLExtensions.TryLoadXml(zipStream) is not XDocument doc)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"{RemoteStorageHelper.DebugPrefix} Failed to load submarine \"{remoteFile.Filename}\" from remote storage: file is not a valid XML document.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
SubmarineInfo subInfo = new(remoteFile.Filename, element: doc.Root, tryLoad: false) { IsFromRemoteStorage = true };
|
||||||
|
allSubs.Add(subInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
IOrderedEnumerable<SubmarineInfo> sortedSubs = allSubs
|
||||||
|
.OrderBy(kvp => kvp.Type)
|
||||||
|
.ThenBy(kvp => kvp.Name)
|
||||||
|
.ThenBy(kvp => kvp.IsFromRemoteStorage);
|
||||||
|
|
||||||
SubmarineInfo prevSub = null;
|
SubmarineInfo prevSub = null;
|
||||||
|
|
||||||
foreach (SubmarineInfo sub in sortedSubs)
|
foreach (SubmarineInfo sub in sortedSubs)
|
||||||
{
|
{
|
||||||
if (prevSub == null || prevSub.Type != sub.Type)
|
if (prevSub == null || prevSub.Type != sub.Type)
|
||||||
@@ -3795,35 +3862,44 @@ namespace Barotrauma
|
|||||||
prevSub = sub;
|
prevSub = sub;
|
||||||
}
|
}
|
||||||
|
|
||||||
string pathWithoutUserName = Path.GetFullPath(sub.FilePath);
|
string displayPath = sub.FilePath;
|
||||||
string saveFolder = Path.GetFullPath(SaveUtil.DefaultSaveFolder);
|
if (sub.IsFromRemoteStorage)
|
||||||
if (pathWithoutUserName.StartsWith(saveFolder))
|
|
||||||
{
|
{
|
||||||
pathWithoutUserName = "..." + pathWithoutUserName[saveFolder.Length..];
|
displayPath += $" {TextManager.Get("RemoteStorage")}";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pathWithoutUserName = sub.FilePath;
|
string saveFolder = Path.GetFullPath(SaveUtil.DefaultSaveFolder);
|
||||||
|
string fullPath = Path.GetFullPath(displayPath);
|
||||||
|
if (fullPath.StartsWith(saveFolder))
|
||||||
|
{
|
||||||
|
displayPath = $"...{fullPath[saveFolder.Length..]}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), subList.Content.RectTransform) { MinSize = new Point(0, 30) },
|
LocalizedString limitedName = ToolBox.LimitString(sub.Name, GUIStyle.Font, subList.Rect.Width - 80);
|
||||||
ToolBox.LimitString(sub.Name, GUIStyle.Font, subList.Rect.Width - 80))
|
GUITextBlock textBlock = new(new RectTransform(Vector2.UnitX, subList.Content.RectTransform) { MinSize = new Point(0, 30) }, limitedName)
|
||||||
{
|
{
|
||||||
UserData = sub,
|
UserData = sub,
|
||||||
ToolTip = pathWithoutUserName
|
ToolTip = displayPath
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!(ContentPackageManager.VanillaCorePackage?.Files.Any(f => f.Path == sub.FilePath) ?? false))
|
if (sub.IsFromRemoteStorage)
|
||||||
|
{
|
||||||
|
// remote storage
|
||||||
|
textBlock.OverrideTextColor(RemoteStorageHelper.SteamColor);
|
||||||
|
}
|
||||||
|
else if (ContentPackageManager.VanillaCorePackage == null || ContentPackageManager.VanillaCorePackage.Files.None(f => f.Path == sub.FilePath))
|
||||||
{
|
{
|
||||||
if (GetLocalPackageThatOwnsSub(sub) == null &&
|
if (GetLocalPackageThatOwnsSub(sub) == null &&
|
||||||
ContentPackageManager.AllPackages.FirstOrDefault(p => p.Files.Any(f => f.Path == sub.FilePath)) is ContentPackage subPackage)
|
ContentPackageManager.AllPackages.FirstOrDefault(p => p.Files.Any(f => f.Path == sub.FilePath)) is ContentPackage subPackage)
|
||||||
{
|
{
|
||||||
//workshop mod
|
// workshop mod
|
||||||
textBlock.OverrideTextColor(Color.MediumPurple);
|
textBlock.OverrideTextColor(Color.MediumPurple);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//local mod
|
// local mod
|
||||||
textBlock.OverrideTextColor(GUIStyle.TextColorBright);
|
textBlock.OverrideTextColor(GUIStyle.TextColorBright);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4018,10 +4094,9 @@ namespace Barotrauma
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(subList.SelectedComponent?.UserData is SubmarineInfo selectedSubInfo)) { return false; }
|
if (subList.SelectedComponent?.UserData is not SubmarineInfo selectedSubInfo) { return false; }
|
||||||
|
|
||||||
var ownerPackage = GetLocalPackageThatOwnsSub(selectedSubInfo);
|
if (!selectedSubInfo.IsFromRemoteStorage && GetLocalPackageThatOwnsSub(selectedSubInfo) is null)
|
||||||
if (ownerPackage is null)
|
|
||||||
{
|
{
|
||||||
if (IsVanillaSub(selectedSubInfo))
|
if (IsVanillaSub(selectedSubInfo))
|
||||||
{
|
{
|
||||||
@@ -4183,21 +4258,23 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (sub == null) { return; }
|
if (sub == null) { return; }
|
||||||
|
|
||||||
//If the sub is included in a content package that only defines that one sub,
|
// If the sub is included in a content package that only defines that one sub,
|
||||||
//check that it's a local content package and only allow deletion if it is.
|
// check that it's a local content package and only allow deletion if it is.
|
||||||
//(deleting from the Submarines folder is also currently allowed, but this is temporary)
|
// (deleting from the Submarines folder is also currently allowed, but this is temporary)
|
||||||
var subPackage = GetLocalPackageThatOwnsSub(sub);
|
ContentPackage subPackage = GetLocalPackageThatOwnsSub(sub);
|
||||||
if (!ContentPackageManager.LocalPackages.Regular.Contains(subPackage)) { return; }
|
if (!ContentPackageManager.LocalPackages.Regular.Contains(subPackage)) { subPackage = null; }
|
||||||
|
if (!sub.IsFromRemoteStorage && subPackage == null) { return; }
|
||||||
var msgBox = new GUIMessageBox(
|
|
||||||
TextManager.Get("DeleteDialogLabel"),
|
GUIMessageBox msgBox = new(TextManager.Get("DeleteDialogLabel"), TextManager.GetWithVariable("DeleteDialogQuestion", "[file]", sub.Name), [TextManager.Get("Yes"), TextManager.Get("Cancel")]);
|
||||||
TextManager.GetWithVariable("DeleteDialogQuestion", "[file]", sub.Name),
|
|
||||||
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("Cancel") });
|
|
||||||
msgBox.Buttons[0].OnClicked += (btn, userData) =>
|
msgBox.Buttons[0].OnClicked += (btn, userData) =>
|
||||||
{
|
{
|
||||||
try
|
if (sub.IsFromRemoteStorage)
|
||||||
{
|
{
|
||||||
if (subPackage != null)
|
RemoteStorageHelper.TryDelete(sub.FilePath);
|
||||||
|
}
|
||||||
|
else if (subPackage != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
File.Delete(sub.FilePath, catchUnauthorizedAccessExceptions: false);
|
File.Delete(sub.FilePath, catchUnauthorizedAccessExceptions: false);
|
||||||
ModProject modProject = new ModProject(subPackage);
|
ModProject modProject = new ModProject(subPackage);
|
||||||
@@ -4208,17 +4285,17 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
MainSub.Info.FilePath = null;
|
MainSub.Info.FilePath = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sub.Dispose();
|
catch (Exception e)
|
||||||
CreateLoadScreen();
|
{
|
||||||
|
DebugConsole.ThrowErrorLocalized(TextManager.GetWithVariable("DeleteFileError", "[file]", sub.FilePath), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
sub.Dispose();
|
||||||
DebugConsole.ThrowErrorLocalized(TextManager.GetWithVariable("DeleteFileError", "[file]", sub.FilePath), e);
|
CreateLoadScreen();
|
||||||
}
|
return msgBox.Close(btn, userData);
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
msgBox.Buttons[0].OnClicked += msgBox.Close;
|
|
||||||
msgBox.Buttons[1].OnClicked += msgBox.Close;
|
msgBox.Buttons[1].OnClicked += msgBox.Close;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
DebugConsole.NewMessage("Missing Localization for property: " + propertyTag);
|
DebugConsole.NewMessage("Missing Localization for property: " + propertyTag);
|
||||||
MissingLocalizations.Add($"sp.{propertyTag}.name|{displayName}");
|
MissingLocalizations.Add($"sp.{propertyTag}.name|{displayName}");
|
||||||
MissingLocalizations.Add($"sp.{propertyTag}.description|{property.GetAttribute<Serialize>().Description}");
|
MissingLocalizations.Add($"sp.{propertyTag}.description|{property.GetAttribute<Serialize>()?.Description}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -467,7 +467,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (toolTip.IsNullOrEmpty())
|
if (toolTip.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
toolTip = property.GetAttribute<Serialize>().Description;
|
toolTip = property.GetAttribute<Serialize>()?.Description;
|
||||||
}
|
}
|
||||||
|
|
||||||
GUIComponent propertyField = null;
|
GUIComponent propertyField = null;
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Steamworks;
|
||||||
|
using System;
|
||||||
|
namespace Barotrauma.Steam;
|
||||||
|
|
||||||
|
internal static partial class RemoteStorageHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Asks the user if they wish to enable remote storage. Accepting enables it automatically.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="onAccepted">Invoked when the user accepts enabling remote storage.</param>
|
||||||
|
/// <param name="onRejected">Invoked when the user rejects enabling remote storage.</param>
|
||||||
|
/// <remarks>Closes automatically if remote storage was enabled outside of the game, or if remote storage can not be enabled.</remarks>
|
||||||
|
public static void AskToEnable(Action? onAccepted = null, Action? onRejected = null)
|
||||||
|
{
|
||||||
|
GUIMessageBox confirmBox = new GUIMessageBox(
|
||||||
|
TextManager.Get("RemoteStorageEnablePopup.Header"),
|
||||||
|
TextManager.Get("RemoteStorageEnablePopup.Text"),
|
||||||
|
[TextManager.Get("Yes"), TextManager.Get("No")],
|
||||||
|
autoCloseCondition: () => !SteamRemoteStorage.IsCloudEnabledForAccount || SteamRemoteStorage.IsCloudEnabledForApp);
|
||||||
|
|
||||||
|
confirmBox.Buttons[0].OnClicked += (btn, data) =>
|
||||||
|
{
|
||||||
|
SteamRemoteStorage.IsCloudEnabledForApp = true;
|
||||||
|
onAccepted?.Invoke();
|
||||||
|
return confirmBox.Close(btn, data);
|
||||||
|
};
|
||||||
|
confirmBox.Buttons[1].OnClicked += (btn, data) =>
|
||||||
|
{
|
||||||
|
onRejected?.Invoke();
|
||||||
|
return confirmBox.Close(btn, data);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma</Product>
|
<Product>Barotrauma</Product>
|
||||||
<Version>1.12.7.0</Version>
|
<Version>1.13.3.1</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>Barotrauma</AssemblyName>
|
<AssemblyName>Barotrauma</AssemblyName>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma</Product>
|
<Product>Barotrauma</Product>
|
||||||
<Version>1.12.7.0</Version>
|
<Version>1.13.3.1</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>Barotrauma</AssemblyName>
|
<AssemblyName>Barotrauma</AssemblyName>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma</Product>
|
<Product>Barotrauma</Product>
|
||||||
<Version>1.12.7.0</Version>
|
<Version>1.13.3.1</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>Barotrauma</AssemblyName>
|
<AssemblyName>Barotrauma</AssemblyName>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma Dedicated Server</Product>
|
<Product>Barotrauma Dedicated Server</Product>
|
||||||
<Version>1.12.7.0</Version>
|
<Version>1.13.3.1</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>DedicatedServer</AssemblyName>
|
<AssemblyName>DedicatedServer</AssemblyName>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma Dedicated Server</Product>
|
<Product>Barotrauma Dedicated Server</Product>
|
||||||
<Version>1.12.7.0</Version>
|
<Version>1.13.3.1</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>DedicatedServer</AssemblyName>
|
<AssemblyName>DedicatedServer</AssemblyName>
|
||||||
|
|||||||
@@ -1799,22 +1799,35 @@ namespace Barotrauma
|
|||||||
(Client client, Vector2 cursorWorldPos, string[] args) =>
|
(Client client, Vector2 cursorWorldPos, string[] args) =>
|
||||||
{
|
{
|
||||||
if (Submarine.MainSub == null || Level.Loaded == null) { return; }
|
if (Submarine.MainSub == null || Level.Loaded == null) { return; }
|
||||||
|
Submarine submarineToTeleport = Submarine.MainSub;
|
||||||
|
if (args.Length > 1)
|
||||||
|
{
|
||||||
|
foreach (Submarine sub in Submarine.Loaded.Where(s => s.PhysicsBody.BodyType == FarseerPhysics.BodyType.Dynamic))
|
||||||
|
{
|
||||||
|
if ((sub.Info.Name + "_" + sub.TeamID) == args[1])
|
||||||
|
{
|
||||||
|
submarineToTeleport = sub;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (args.Length == 0 || args[0].Equals("cursor", StringComparison.OrdinalIgnoreCase))
|
if (args.Length == 0 || args[0].Equals("cursor", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Submarine.MainSub.SetPosition(cursorWorldPos);
|
submarineToTeleport.SetPosition(cursorWorldPos);
|
||||||
}
|
}
|
||||||
else if (args[0].Equals("start", StringComparison.OrdinalIgnoreCase))
|
else if (args[0].Equals("start", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Submarine.MainSub.SetPosition(Level.Loaded.StartPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height);
|
submarineToTeleport.SetPosition(Level.Loaded.StartPosition - Vector2.UnitY * submarineToTeleport.Borders.Height);
|
||||||
}
|
}
|
||||||
else if (args[0].Equals("end", StringComparison.OrdinalIgnoreCase))
|
else if (args[0].Equals("end", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Submarine.MainSub.SetPosition(Level.Loaded.EndPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height);
|
submarineToTeleport.SetPosition(Level.Loaded.EndPosition - Vector2.UnitY * submarineToTeleport.Borders.Height);
|
||||||
}
|
}
|
||||||
else if (args[0].Equals("endoutpost", StringComparison.OrdinalIgnoreCase))
|
else if (args[0].Equals("endoutpost", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Submarine.MainSub.SetPosition(Level.Loaded.EndExitPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height);
|
submarineToTeleport.SetPosition(Level.Loaded.EndExitPosition - Vector2.UnitY * submarineToTeleport.Borders.Height);
|
||||||
var submarineDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == Submarine.MainSub);
|
var submarineDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == submarineToTeleport);
|
||||||
if (Level.Loaded?.EndOutpost == null)
|
if (Level.Loaded?.EndOutpost == null)
|
||||||
{
|
{
|
||||||
NewMessage("Can't teleport the sub to the end outpost (no outpost at the end of the level).", Color.Red);
|
NewMessage("Can't teleport the sub to the end outpost (no outpost at the end of the level).", Color.Red);
|
||||||
|
|||||||
@@ -1179,6 +1179,7 @@ namespace Barotrauma
|
|||||||
NetWalletTransfer transfer = INetSerializableStruct.Read<NetWalletTransfer>(msg);
|
NetWalletTransfer transfer = INetSerializableStruct.Read<NetWalletTransfer>(msg);
|
||||||
|
|
||||||
if (GameMain.Server is null) { return; }
|
if (GameMain.Server is null) { return; }
|
||||||
|
if (transfer.Amount <= 0) { return; }
|
||||||
|
|
||||||
if (transfer.Sender.TryUnwrap(out var id))
|
if (transfer.Sender.TryUnwrap(out var id))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -193,13 +193,6 @@ namespace Barotrauma
|
|||||||
(item.PreviousParentInventory == null ||
|
(item.PreviousParentInventory == null ||
|
||||||
!sender.Character.CanAccessInventory(item.PreviousParentInventory));
|
!sender.Character.CanAccessInventory(item.PreviousParentInventory));
|
||||||
|
|
||||||
// Prevent modified clients from being able to steal items from characters by item swapping with an existing item
|
|
||||||
// due to drag and drop being enabled
|
|
||||||
if (!sender.Character.CanAccessInventory(this, CharacterInventory.AccessLevel.AllowBotsAndPets) && GetItemAt(slotIndex) != null)
|
|
||||||
{
|
|
||||||
itemAccessDenied = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//more restricted "adding" of handcuffs: we can't allow putting handcuffs on a player just because dragging and dropping is allowed
|
//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)
|
if (item.HasTag(Tags.HandLockerItem) && !itemAccessDenied)
|
||||||
{
|
{
|
||||||
@@ -219,7 +212,7 @@ namespace Barotrauma
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TryPutItem(item, slotIndex, true, true, sender.Character, false);
|
TryPutItem(item, slotIndex, allowSwapping: false, allowCombine: false, user: sender.Character, createNetworkEvent: false);
|
||||||
for (int j = 0; j < capacity; j++)
|
for (int j = 0; j < capacity; j++)
|
||||||
{
|
{
|
||||||
if (slots[j].Contains(item) && !receivedItemIdsFromClient[j].Contains(item.ID))
|
if (slots[j].Contains(item) && !receivedItemIdsFromClient[j].Contains(item.ID))
|
||||||
|
|||||||
@@ -8,6 +8,17 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
partial class ChatMessage
|
partial class ChatMessage
|
||||||
{
|
{
|
||||||
|
private static string SanitizeText(Client client, string text)
|
||||||
|
{
|
||||||
|
if (!client.HasPermission(ClientPermissions.SpamImmunity))
|
||||||
|
{
|
||||||
|
// Prevent clients without spam immunity from being able to use RichString features
|
||||||
|
text = text.Replace('‖', ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
public static void ServerRead(IReadMessage msg, Client c)
|
public static void ServerRead(IReadMessage msg, Client c)
|
||||||
{
|
{
|
||||||
c.KickAFKTimer = 0.0f;
|
c.KickAFKTimer = 0.0f;
|
||||||
@@ -69,8 +80,7 @@ namespace Barotrauma.Networking
|
|||||||
txt = msg.ReadString() ?? "";
|
txt = msg.ReadString() ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanitize incoming text message from client so they can't use RichString features
|
txt = SanitizeText(c, txt);
|
||||||
txt = txt.Replace('‖', ' ');
|
|
||||||
|
|
||||||
if (!NetIdUtils.IdMoreRecent(ID, c.LastSentChatMsgID)) { return; }
|
if (!NetIdUtils.IdMoreRecent(ID, c.LastSentChatMsgID)) { return; }
|
||||||
|
|
||||||
|
|||||||
@@ -904,7 +904,10 @@ namespace Barotrauma.Networking
|
|||||||
string subHash = inc.ReadString();
|
string subHash = inc.ReadString();
|
||||||
CampaignSettings settings = INetSerializableStruct.Read<CampaignSettings>(inc);
|
CampaignSettings settings = INetSerializableStruct.Read<CampaignSettings>(inc);
|
||||||
|
|
||||||
var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.StringRepresentation == subHash);
|
var matchingSub =
|
||||||
|
ServerSettings.AllowSubVoting ?
|
||||||
|
Voting.HighestVoted<SubmarineInfo>(VoteType.Sub, connectedClients) :
|
||||||
|
SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.StringRepresentation == subHash);
|
||||||
|
|
||||||
if (GameStarted)
|
if (GameStarted)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ namespace Barotrauma.Networking
|
|||||||
// in which case we'll wait until the timeout runs out before kicking the client
|
// in which case we'll wait until the timeout runs out before kicking the client
|
||||||
List<Client> toKick = inGameClients.FindAll(c =>
|
List<Client> toKick = inGameClients.FindAll(c =>
|
||||||
NetIdUtils.IdMoreRecent((UInt16)(lastSentToAll + 1), c.LastRecvEntityEventID) &&
|
NetIdUtils.IdMoreRecent((UInt16)(lastSentToAll + 1), c.LastRecvEntityEventID) &&
|
||||||
(firstEventToResend.CreateTime > c.MidRoundSyncTimeOut || lastSentToAnyoneTime > c.MidRoundSyncTimeOut || Timing.TotalTime > c.MidRoundSyncTimeOut + 10.0));
|
(!c.NeedsMidRoundSync || firstEventToResend.CreateTime > c.MidRoundSyncTimeOut || lastSentToAnyoneTime > c.MidRoundSyncTimeOut || Timing.TotalTime > c.MidRoundSyncTimeOut + 10.0));
|
||||||
toKick.ForEach(c =>
|
toKick.ForEach(c =>
|
||||||
{
|
{
|
||||||
DebugConsole.NewMessage(c.Name + " was kicked because they were expecting a very old network event (" + (c.LastRecvEntityEventID + 1).ToString() + ")", Color.Red);
|
DebugConsole.NewMessage(c.Name + " was kicked because they were expecting a very old network event (" + (c.LastRecvEntityEventID + 1).ToString() + ")", Color.Red);
|
||||||
|
|||||||
@@ -54,13 +54,14 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
if (recipient == sender) { continue; }
|
if (recipient == sender) { continue; }
|
||||||
|
|
||||||
if (!CanReceive(sender, recipient, out float distanceFactor)) { continue; }
|
if (!CanReceive(sender, recipient, out float distanceFactor, out bool isRadio)) { continue; }
|
||||||
|
|
||||||
IWriteMessage msg = new WriteOnlyMessage();
|
IWriteMessage msg = new WriteOnlyMessage();
|
||||||
|
|
||||||
msg.WriteByte((byte)ServerPacketHeader.VOICE);
|
msg.WriteByte((byte)ServerPacketHeader.VOICE);
|
||||||
msg.WriteByte((byte)queue.QueueID);
|
msg.WriteByte((byte)queue.QueueID);
|
||||||
msg.WriteRangedSingle(distanceFactor, 0.0f, 1.0f, 8);
|
msg.WriteRangedSingle(distanceFactor, 0.0f, 1.0f, 8);
|
||||||
|
msg.WriteBoolean(isRadio);
|
||||||
queue.Write(msg);
|
queue.Write(msg);
|
||||||
|
|
||||||
netServer.Send(msg, recipient.Connection, DeliveryMethod.Unreliable);
|
netServer.Send(msg, recipient.Connection, DeliveryMethod.Unreliable);
|
||||||
@@ -68,15 +69,17 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool CanReceive(Client sender, Client recipient, out float distanceFactor)
|
private static bool CanReceive(Client sender, Client recipient, out float distanceFactor, out bool isRadio)
|
||||||
{
|
{
|
||||||
if (Screen.Selected != GameMain.GameScreen)
|
if (Screen.Selected != GameMain.GameScreen)
|
||||||
{
|
{
|
||||||
distanceFactor = 0.0f;
|
distanceFactor = 0.0f;
|
||||||
return true;
|
isRadio = false;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
distanceFactor = 0.0f;
|
distanceFactor = 0.0f;
|
||||||
|
isRadio = false;
|
||||||
|
|
||||||
//no-one can hear muted players
|
//no-one can hear muted players
|
||||||
if (sender.Muted) { return false; }
|
if (sender.Muted) { return false; }
|
||||||
@@ -109,12 +112,14 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
if (recipientSpectating)
|
if (recipientSpectating)
|
||||||
{
|
{
|
||||||
|
isRadio = true;
|
||||||
if (recipient.SpectatePos == null) { return true; }
|
if (recipient.SpectatePos == null) { return true; }
|
||||||
distanceFactor = MathHelper.Clamp(Vector2.Distance(sender.Character.WorldPosition, recipient.SpectatePos.Value) / senderRadio.Range, 0.0f, 1.0f);
|
distanceFactor = MathHelper.Clamp(Vector2.Distance(sender.Character.WorldPosition, recipient.SpectatePos.Value) / senderRadio.Range, 0.0f, 1.0f);
|
||||||
return distanceFactor < 1.0f;
|
return distanceFactor < 1.0f;
|
||||||
}
|
}
|
||||||
else if (recipientRadio != null && recipientRadio.CanReceive(senderRadio))
|
else if (recipientRadio != null && recipientRadio.CanReceive(senderRadio))
|
||||||
{
|
{
|
||||||
|
isRadio = true;
|
||||||
distanceFactor = MathHelper.Clamp(Vector2.Distance(sender.Character.WorldPosition, recipient.Character.WorldPosition) / senderRadio.Range, 0.0f, 1.0f);
|
distanceFactor = MathHelper.Clamp(Vector2.Distance(sender.Character.WorldPosition, recipient.Character.WorldPosition) / senderRadio.Range, 0.0f, 1.0f);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma Dedicated Server</Product>
|
<Product>Barotrauma Dedicated Server</Product>
|
||||||
<Version>1.12.7.0</Version>
|
<Version>1.13.3.1</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>DedicatedServer</AssemblyName>
|
<AssemblyName>DedicatedServer</AssemblyName>
|
||||||
|
|||||||
Binary file not shown.
@@ -23,6 +23,70 @@
|
|||||||
</Holdable>
|
</Holdable>
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
|
<Item name="fliptestweapon" identifier="fliptestweapon" category="Weapon" cargocontaineridentifier="metalcrate" tags="mediumitem,weapon,gun,gunsmith,provocativetohumanai,mountableweapon" Scale="0.5" impactsoundtag="impact_metal_light">
|
||||||
|
<PreferredContainer primary="secarmcab" amount="1" spawnprobability="0.2" notcampaign="true" notpvp="true" />
|
||||||
|
<PreferredContainer secondary="wrecksecarmcab,abandonedsecarmcab,piratesecarmcab" spawnprobability="0.1" />
|
||||||
|
<PreferredContainer secondary="armcab" />
|
||||||
|
<Price baseprice="1000" sold="false" minleveldifficulty="30">
|
||||||
|
<Price storeidentifier="merchantoutpost" multiplier="1.5" />
|
||||||
|
<Price storeidentifier="merchantcity" multiplier="1.25" />
|
||||||
|
<Price storeidentifier="merchantresearch" multiplier="1.25" />
|
||||||
|
<Price storeidentifier="merchantmilitary" sold="true" multiplier="0.9" minavailable="1" />
|
||||||
|
<Price storeidentifier="merchantmine" multiplier="1.25" />
|
||||||
|
<Price storeidentifier="merchantarmory" sold="true" multiplier="0.9" minavailable="1" />
|
||||||
|
</Price>
|
||||||
|
<Deconstruct time="10">
|
||||||
|
<Item identifier="steel" />
|
||||||
|
<Item identifier="titaniumaluminiumalloy" />
|
||||||
|
</Deconstruct>
|
||||||
|
<InventoryIcon texture="Content/Items/InventoryIconAtlas.png" sourcerect="896,831,64,64" origin="0.5,0.5" />
|
||||||
|
<Sprite texture="Content/Items/Weapons/weapons_new.png" sourcerect="0,244,186,65" depth="0.55" origin="0.5,0.5" />
|
||||||
|
<Body width="170" height="40" density="25" />
|
||||||
|
<Holdable slots="RightHand+LeftHand" controlpose="true" holdpos="40,-20" aimpos="45,-10" handle1="-33,-15" handle2="26,5" holdangle="-25">
|
||||||
|
<StatusEffect type="OnActive" target="This" offset="80,20" OffsetCopiesEntityTransform="true">
|
||||||
|
<ParticleEmitter particle="electricshock" particlespersecond="60" copyentityangle="true" velocitymin="0" velocitymax="100" distancemin="0" distancemax="10" scalemin="0.03" scalemax="0.04" />
|
||||||
|
</StatusEffect>
|
||||||
|
</Holdable>
|
||||||
|
<Wearable slots="Bag" msg="ItemMsgEquipSelect" canbeselected="false" canbepicked="true" pickkey="Select">
|
||||||
|
<sprite name="Grenade Launcher Worn" texture="Content/Items/Weapons/weapons_new.png" canbehiddenbyotherwearables="false" sourcerect="0,244,186,65" rotation="90" depth="0.6" limb="Torso" depthlimb="LeftArm" scale="0.5" origin="0.5,0.8" />
|
||||||
|
</Wearable>
|
||||||
|
<RangedWeapon barrelpos="80,11" reload="0.8" spread="1" unskilledspread="10" combatPriority="75" drawhudwhenequipped="true" crosshairscale="0.2">
|
||||||
|
<Crosshair texture="Content/Items/Weapons/Crosshairs.png" sourcerect="0,256,256,256" />
|
||||||
|
<CrosshairPointer texture="Content/Items/Weapons/Crosshairs.png" sourcerect="256,256,256,256" />
|
||||||
|
<ParticleEmitter particle="muzzleflashchaingun" particleamount="1" velocitymin="0" velocitymax="0" scalemin="0.5" scalemax="0.6" />
|
||||||
|
<ParticleEmitter particle="explosionsmoke" particleamount="1" velocitymin="0" velocitymax="0" scalemin="0.5" scalemax="0.6" />
|
||||||
|
<Sound file="Content/Items/Weapons/GrenadeLauncherShot1.ogg" type="OnUse" range="1000" />
|
||||||
|
<Sound file="Content/Items/Weapons/GrenadeLauncherShot2.ogg" type="OnUse" range="1000" />
|
||||||
|
<Sound file="Content/Items/Weapons/GrenadeLauncherShot3.ogg" type="OnUse" range="1000" />
|
||||||
|
<StatusEffect type="OnUse" target="This">
|
||||||
|
<Explosion range="150.0" force="2" shockwave="false" smoke="false" flames="false" flash="true" sparks="false" underwaterbubble="false" applyfireeffects="false" camerashake="6.0" />
|
||||||
|
</StatusEffect>
|
||||||
|
<RequiredItems items="grenade" type="Contained" msg="ItemMsgAmmoRequired" />
|
||||||
|
<RequiredSkill identifier="weapons" level="60" />
|
||||||
|
</RangedWeapon>
|
||||||
|
<!--Holds six grenades at a time-->
|
||||||
|
<ItemContainer capacity="6" maxstacksize="1" hideitems="false" ShowTotalStackCapacityInContainedStateIndicator="true" containedstateindicatorstyle="bullet" containedspritedepth="0.56">
|
||||||
|
<Containable items="grenade" hide="true" />
|
||||||
|
<SlotIcon slotindex="0" texture="Content/UI/StatusMonitorUI.png" sourcerect="448,448,64,64" origin="0.5,0.5" />
|
||||||
|
<SlotIcon slotindex="1" texture="Content/UI/StatusMonitorUI.png" sourcerect="448,448,64,64" origin="0.5,0.5" />
|
||||||
|
<SlotIcon slotindex="2" texture="Content/UI/StatusMonitorUI.png" sourcerect="448,448,64,64" origin="0.5,0.5" />
|
||||||
|
<SlotIcon slotindex="3" texture="Content/UI/StatusMonitorUI.png" sourcerect="448,448,64,64" origin="0.5,0.5" />
|
||||||
|
<SlotIcon slotindex="4" texture="Content/UI/StatusMonitorUI.png" sourcerect="448,448,64,64" origin="0.5,0.5" />
|
||||||
|
<SlotIcon slotindex="5" texture="Content/UI/StatusMonitorUI.png" sourcerect="448,448,64,64" origin="0.5,0.5" />
|
||||||
|
<SlotIcon slotindex="6" texture="Content/UI/StatusMonitorUI.png" sourcerect="320,448,64,64" origin="0.5,0.5" />
|
||||||
|
<SubContainer capacity="1" maxstacksize="1">
|
||||||
|
<Containable items="flashlight" hide="false" itempos="22,-1" setactive="true" />
|
||||||
|
</SubContainer>
|
||||||
|
</ItemContainer>
|
||||||
|
<aitarget sightrange="500" soundrange="500" fadeouttime="3" />
|
||||||
|
<Quality>
|
||||||
|
<QualityStat stattype="ExplosionRadius" value="0.1" />
|
||||||
|
<QualityStat stattype="ExplosionDamage" value="0.1" />
|
||||||
|
</Quality>
|
||||||
|
<Upgrade gameversion="0.10.0.0" scale="0.5" />
|
||||||
|
<SkillRequirementHint identifier="weapons" level="60" />
|
||||||
|
</Item>
|
||||||
|
|
||||||
<Item name="fliptestlight" identifier="fliptestlighttower" width="176" height="352" texturescale="1.0,1.0" scale="0.5" category="Decorative" subcategory="mining" noninteractable="true">
|
<Item name="fliptestlight" identifier="fliptestlighttower" width="176" height="352" texturescale="1.0,1.0" scale="0.5" category="Decorative" subcategory="mining" noninteractable="true">
|
||||||
<sprite texture="Content/Map/Outposts/Art/TunnelWalls.png" sourcerect="849,1697,176,352" depth="0.97" premultiplyalpha="false" origin="0.5,0.5" />
|
<sprite texture="Content/Map/Outposts/Art/TunnelWalls.png" sourcerect="849,1697,176,352" depth="0.97" premultiplyalpha="false" origin="0.5,0.5" />
|
||||||
<LightComponent range="160.0" lightcolor="255,234,181,200" IsOn="true" castshadows="false" LightOffset="200,147" allowingameediting="false">
|
<LightComponent range="160.0" lightcolor="255,234,181,200" IsOn="true" castshadows="false" LightOffset="200,147" allowingameediting="false">
|
||||||
|
|||||||
@@ -524,8 +524,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
UnlockAchievement(causeOfDeath.Killer, "killpoison".ToIdentifier());
|
UnlockAchievement(causeOfDeath.Killer, "killpoison".ToIdentifier());
|
||||||
}
|
}
|
||||||
else if (item.Prefab.Identifier == "nuclearshell" ||
|
else if (item.Prefab.Tags.Contains("nuclearexplosive"))
|
||||||
item.Prefab.Identifier == "nucleardepthcharge")
|
|
||||||
{
|
{
|
||||||
UnlockAchievement(causeOfDeath.Killer, "killnuke".ToIdentifier());
|
UnlockAchievement(causeOfDeath.Killer, "killnuke".ToIdentifier());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,6 +197,18 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private readonly List<Body> myBodies;
|
private readonly List<Body> myBodies;
|
||||||
|
|
||||||
|
#if SERVER
|
||||||
|
/// <summary>
|
||||||
|
/// How often the server can send messages about a limb targeting some attack target.
|
||||||
|
/// Mainly relevant for attacks with no cooldown, e.g. fractal guardian's steam cannons which run continuously over time (we can't send events every frame)
|
||||||
|
/// </summary>
|
||||||
|
private const double MinSetAttackTargetEventInterval = 0.5;
|
||||||
|
private IDamageable lastDamageTarget;
|
||||||
|
private Limb lastTargetLimb;
|
||||||
|
private Limb lastAttackLimb;
|
||||||
|
private double lastSetAttackTargetEventTime;
|
||||||
|
#endif
|
||||||
|
|
||||||
public LatchOntoAI LatchOntoAI { get; private set; }
|
public LatchOntoAI LatchOntoAI { get; private set; }
|
||||||
public SwarmBehavior SwarmBehavior { get; private set; }
|
public SwarmBehavior SwarmBehavior { get; private set; }
|
||||||
public PetBehavior PetBehavior { get; private set; }
|
public PetBehavior PetBehavior { get; private set; }
|
||||||
@@ -2679,11 +2691,19 @@ namespace Barotrauma
|
|||||||
if (!ActiveAttack.IsRunning)
|
if (!ActiveAttack.IsRunning)
|
||||||
{
|
{
|
||||||
#if SERVER
|
#if SERVER
|
||||||
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.SetAttackTargetEventData(
|
if (Timing.TotalTime > lastSetAttackTargetEventTime + MinSetAttackTargetEventInterval ||
|
||||||
AttackLimb,
|
damageTarget != lastDamageTarget || AttackLimb != lastAttackLimb || targetLimb != lastTargetLimb)
|
||||||
damageTarget,
|
{
|
||||||
targetLimb,
|
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.SetAttackTargetEventData(
|
||||||
SimPosition));
|
AttackLimb,
|
||||||
|
damageTarget,
|
||||||
|
targetLimb,
|
||||||
|
SimPosition));
|
||||||
|
lastSetAttackTargetEventTime = Timing.TotalTime;
|
||||||
|
lastDamageTarget = damageTarget;
|
||||||
|
lastAttackLimb = AttackLimb;
|
||||||
|
lastTargetLimb = targetLimb;
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
|
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private float respondToAttackTimer;
|
private float respondToAttackTimer;
|
||||||
private const float RespondToAttackInterval = 1.0f;
|
private const float RespondToAttackInterval = 1.0f;
|
||||||
private bool wasConscious;
|
private bool wasDead;
|
||||||
|
|
||||||
private bool freezeAI;
|
private bool freezeAI;
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (isIncapacitated) { return; }
|
if (isIncapacitated) { return; }
|
||||||
|
|
||||||
wasConscious = true;
|
wasDead = false;
|
||||||
|
|
||||||
respondToAttackTimer -= deltaTime;
|
respondToAttackTimer -= deltaTime;
|
||||||
if (respondToAttackTimer <= 0.0f)
|
if (respondToAttackTimer <= 0.0f)
|
||||||
@@ -1256,14 +1256,15 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public override void OnAttacked(Character attacker, AttackResult attackResult)
|
public override void OnAttacked(Character attacker, AttackResult attackResult)
|
||||||
{
|
{
|
||||||
// The attack incapacitated/killed the character: respond immediately to trigger nearby characters because the update loop no longer runs
|
// If the character is incapacitated or dead, respond to the attack anyway to let other nearby characters react to it
|
||||||
if (wasConscious && (Character.IsIncapacitated || Character.Stun > 0.0f))
|
// (But if the character is already dead, and was dead before this attack, don't react)
|
||||||
|
if (Character.IsDead && wasDead) { return; }
|
||||||
|
if (Character.IsIncapacitated || Character.Stun > 0.0f)
|
||||||
{
|
{
|
||||||
RespondToAttack(attacker, attackResult);
|
RespondToAttack(attacker, attackResult);
|
||||||
wasConscious = false;
|
wasDead = Character.IsDead;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Character.IsDead) { return; }
|
|
||||||
if (attacker == null || Character.IsPlayer)
|
if (attacker == null || Character.IsPlayer)
|
||||||
{
|
{
|
||||||
// The player characters need to "respond" to the attack always, because the update loop doesn't run for them.
|
// The player characters need to "respond" to the attack always, because the update loop doesn't run for them.
|
||||||
@@ -1467,10 +1468,10 @@ namespace Barotrauma
|
|||||||
otherHumanAI.VisibleHulls.Contains(attacker.CurrentHull) ||
|
otherHumanAI.VisibleHulls.Contains(attacker.CurrentHull) ||
|
||||||
otherCharacter.CanSeeTarget(attacker, seeThroughWindows: true);
|
otherCharacter.CanSeeTarget(attacker, seeThroughWindows: true);
|
||||||
if (!isWitnessing)
|
if (!isWitnessing)
|
||||||
{
|
{
|
||||||
if (Character.IsDead || Character.IsUnconscious || otherCharacter.TeamID != Character.TeamID)
|
if (Character.IsKnockedDown || otherCharacter.TeamID != Character.TeamID)
|
||||||
{
|
{
|
||||||
// Dead or in different team -> cannot report.
|
// Knocked down or in different team -> cannot report.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (otherHumanAI.objectiveManager.HasOrders())
|
if (otherHumanAI.objectiveManager.HasOrders())
|
||||||
@@ -1494,6 +1495,14 @@ namespace Barotrauma
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (!otherCharacter.IsSecurity)
|
||||||
|
{
|
||||||
|
//witnessed the attack as non-security, trigger security
|
||||||
|
foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID))
|
||||||
|
{
|
||||||
|
TriggerSecurity(security.AIController as HumanAIController, attacker, DetermineCombatMode(security, cumulativeDamage, isWitnessing));
|
||||||
|
}
|
||||||
|
}
|
||||||
float delay = isWitnessing ? GetReactionTime() : Rand.Range(2.0f, 3.0f, Rand.RandSync.Unsynced);
|
float delay = isWitnessing ? GetReactionTime() : Rand.Range(2.0f, 3.0f, Rand.RandSync.Unsynced);
|
||||||
otherHumanAI.AddCombatObjective(combatMode, attacker, delay);
|
otherHumanAI.AddCombatObjective(combatMode, attacker, delay);
|
||||||
}
|
}
|
||||||
@@ -1926,12 +1935,12 @@ namespace Barotrauma
|
|||||||
character.IsCriminal = true;
|
character.IsCriminal = true;
|
||||||
character.IsActingOffensively = true;
|
character.IsActingOffensively = true;
|
||||||
}
|
}
|
||||||
if (!TriggerSecurity(otherHumanAI, combatMode))
|
if (!TriggerSecurity(otherHumanAI, character, combatMode))
|
||||||
{
|
{
|
||||||
// Else call the others
|
// Else call the others
|
||||||
foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID).OrderBy(c => Vector2.DistanceSquared(character.WorldPosition, c.WorldPosition)))
|
foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID).OrderBy(c => Vector2.DistanceSquared(character.WorldPosition, c.WorldPosition)))
|
||||||
{
|
{
|
||||||
if (!TriggerSecurity(security.AIController as HumanAIController, combatMode))
|
if (!TriggerSecurity(security.AIController as HumanAIController, character, combatMode))
|
||||||
{
|
{
|
||||||
// Only alert one guard at a time
|
// Only alert one guard at a time
|
||||||
return;
|
return;
|
||||||
@@ -1941,25 +1950,25 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TriggerSecurity(HumanAIController humanAI, AIObjectiveCombat.CombatMode combatMode)
|
}
|
||||||
{
|
private static bool TriggerSecurity(HumanAIController humanAI, Character targetCharacter, AIObjectiveCombat.CombatMode combatMode)
|
||||||
if (humanAI == null) { return false; }
|
{
|
||||||
if (!humanAI.Character.IsSecurity) { return false; }
|
if (humanAI == null) { return false; }
|
||||||
if (humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveCombat>()) { return false; }
|
if (!humanAI.Character.IsSecurity) { return false; }
|
||||||
humanAI.AddCombatObjective(combatMode, character, delay: GetReactionTime(),
|
if (humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveCombat>()) { return false; }
|
||||||
onCompleted: () =>
|
humanAI.AddCombatObjective(combatMode, targetCharacter, delay: GetReactionTime(),
|
||||||
{
|
onCompleted: () =>
|
||||||
//if the target is arrested successfully, reset the damage accumulator
|
{
|
||||||
foreach (Character anyCharacter in Character.CharacterList)
|
//if the target is arrested successfully, reset the damage accumulator
|
||||||
|
foreach (Character anyCharacter in Character.CharacterList)
|
||||||
|
{
|
||||||
|
if (anyCharacter.AIController is HumanAIController anyAI)
|
||||||
{
|
{
|
||||||
if (anyCharacter.AIController is HumanAIController anyAI)
|
anyAI.structureDamageAccumulator?.Remove(targetCharacter);
|
||||||
{
|
|
||||||
anyAI.structureDamageAccumulator?.Remove(character);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
return true;
|
});
|
||||||
}
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ItemTaken(Item item, Character thief)
|
public static void ItemTaken(Item item, Character thief)
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ namespace Barotrauma
|
|||||||
bool operateExtinguisher = !moveCloser || (dist < extinguisher.Range * 1.2f && character.CanSeeTarget(targetHull));
|
bool operateExtinguisher = !moveCloser || (dist < extinguisher.Range * 1.2f && character.CanSeeTarget(targetHull));
|
||||||
if (operateExtinguisher)
|
if (operateExtinguisher)
|
||||||
{
|
{
|
||||||
character.CursorPosition = fs.Position;
|
character.CursorPosition = FarseerPhysics.ConvertUnits.ToDisplayUnits(Submarine.GetRelativeSimPositionFromWorldPosition(fs.WorldPosition, character.Submarine, fs.Submarine));
|
||||||
Vector2 fromCharacterToFireSource = fs.WorldPosition - character.WorldPosition;
|
Vector2 fromCharacterToFireSource = fs.WorldPosition - character.WorldPosition;
|
||||||
character.CursorPosition += VectorExtensions.Forward(extinguisherItem.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, fromCharacterToFireSource.Length() / 2);
|
character.CursorPosition += VectorExtensions.Forward(extinguisherItem.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, fromCharacterToFireSource.Length() / 2);
|
||||||
if (extinguisherItem.RequireAimToUse)
|
if (extinguisherItem.RequireAimToUse)
|
||||||
|
|||||||
@@ -173,6 +173,16 @@ namespace Barotrauma
|
|||||||
if (character.CanInteractWith(Item, out _, checkLinked: false))
|
if (character.CanInteractWith(Item, out _, checkLinked: false))
|
||||||
{
|
{
|
||||||
waitTimer += deltaTime;
|
waitTimer += deltaTime;
|
||||||
|
|
||||||
|
//if we're climbing upwards to the item, ensure the character stays within arm's length of it
|
||||||
|
//without this, the character can get stuck in a loop where the GoTo objective takes them close enough to the item,
|
||||||
|
//then the character shifts a bit downwards on the ladder and goes outside interaction range, and the GoTo objective kicks in again
|
||||||
|
if (character.IsClimbing &&
|
||||||
|
Item.WorldPosition.Y > character.WorldPosition.Y + FarseerPhysics.ConvertUnits.ToDisplayUnits(character.AnimController.ArmLength))
|
||||||
|
{
|
||||||
|
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.UnitY);
|
||||||
|
}
|
||||||
|
|
||||||
if (waitTimer < WaitTimeBeforeRepair) { return; }
|
if (waitTimer < WaitTimeBeforeRepair) { return; }
|
||||||
|
|
||||||
HumanAIController.FaceTarget(Item);
|
HumanAIController.FaceTarget(Item);
|
||||||
|
|||||||
@@ -758,6 +758,11 @@ namespace Barotrauma
|
|||||||
public float CoolDownTimer { get; set; }
|
public float CoolDownTimer { get; set; }
|
||||||
public float CurrentRandomCoolDown { get; private set; }
|
public float CurrentRandomCoolDown { get; private set; }
|
||||||
public float SecondaryCoolDownTimer { get; set; }
|
public float SecondaryCoolDownTimer { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The attack is considered to be running from the moment it starts until the <see cref="AttackTimer"/> reaches the <see cref="Duration"/> of the attack, or until the attack lands successfully.
|
||||||
|
/// E.g. from the moment the monster decides to lunge itself towards the target until it hits a target or until it completes that lunge.
|
||||||
|
/// </summary>
|
||||||
public bool IsRunning { get; private set; }
|
public bool IsRunning { get; private set; }
|
||||||
|
|
||||||
public float AfterAttackTimer { get; set; }
|
public float AfterAttackTimer { get; set; }
|
||||||
|
|||||||
@@ -997,6 +997,9 @@ namespace Barotrauma
|
|||||||
public bool IsForceRagdolled;
|
public bool IsForceRagdolled;
|
||||||
public bool FollowCursor = true;
|
public bool FollowCursor = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is the character currently dead, unconscious or paralyzed?
|
||||||
|
/// </summary>
|
||||||
public bool IsIncapacitated
|
public bool IsIncapacitated
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -1006,6 +1009,9 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is the character dead or below 0 vitality and not able to stay conscious?
|
||||||
|
/// </summary>
|
||||||
public bool IsUnconscious
|
public bool IsUnconscious
|
||||||
{
|
{
|
||||||
get { return CharacterHealth.IsUnconscious; }
|
get { return CharacterHealth.IsUnconscious; }
|
||||||
@@ -1673,7 +1679,8 @@ namespace Barotrauma
|
|||||||
AnimController.FindHull(setInWater: true);
|
AnimController.FindHull(setInWater: true);
|
||||||
if (AnimController.CurrentHull != null) { Submarine = AnimController.CurrentHull.Submarine; }
|
if (AnimController.CurrentHull != null) { Submarine = AnimController.CurrentHull.Submarine; }
|
||||||
|
|
||||||
IsContainable = prefab.ConfigElement.GetAttributeBool(nameof(IsContainable), def: Mass <= 30.0f);
|
//mass < 35 = husk chimera is the largest vanilla monster that can be contained by default
|
||||||
|
IsContainable = prefab.ConfigElement.GetAttributeBool(nameof(IsContainable), def: Mass < 35.0f);
|
||||||
|
|
||||||
CharacterList.Add(this);
|
CharacterList.Add(this);
|
||||||
|
|
||||||
@@ -2262,7 +2269,10 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
Vector2 targetMovement = GetTargetMovement();
|
Vector2 targetMovement = GetTargetMovement();
|
||||||
AnimController.TargetMovement = targetMovement;
|
AnimController.TargetMovement = targetMovement;
|
||||||
AnimController.IgnorePlatforms = AnimController.TargetMovement.Y < -0.1f;
|
if (SelectedItem?.GetComponent<Controller>() is not { ControlCharacterPose: true })
|
||||||
|
{
|
||||||
|
AnimController.IgnorePlatforms = AnimController.TargetMovement.Y < -0.1f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AnimController is HumanoidAnimController humanAnimController)
|
if (AnimController is HumanoidAnimController humanAnimController)
|
||||||
@@ -3508,9 +3518,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
UpdateAttackers(deltaTime);
|
UpdateAttackers(deltaTime);
|
||||||
|
|
||||||
foreach (var characterTalent in characterTalents)
|
//use a for loop instead of foreach because talents can unlock other talents via StatusEffectAction (see #17328)
|
||||||
|
//this way we'll just add them to the end of the list without causing a collection was modified exception
|
||||||
|
for (int i = 0; i < characterTalents.Count; i++)
|
||||||
{
|
{
|
||||||
characterTalent.UpdateTalent(deltaTime);
|
characterTalents[i].UpdateTalent(deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsDead) { return; }
|
if (IsDead) { return; }
|
||||||
@@ -5801,6 +5813,12 @@ namespace Barotrauma
|
|||||||
return info.UnlockedTalents.Contains(identifier);
|
return info.UnlockedTalents.Contains(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsTalentLocked(Identifier talentIdentifier)
|
||||||
|
{
|
||||||
|
if (info == null) { return true; }
|
||||||
|
return Info.GetSavedStatValue(StatTypes.LockedTalents, talentIdentifier) >= 1;
|
||||||
|
}
|
||||||
|
|
||||||
public bool HasUnlockedAllTalents()
|
public bool HasUnlockedAllTalents()
|
||||||
{
|
{
|
||||||
if (TalentTree.JobTalentTrees.TryGet(Info.Job.Prefab.Identifier, out TalentTree talentTree))
|
if (TalentTree.JobTalentTrees.TryGet(Info.Job.Prefab.Identifier, out TalentTree talentTree))
|
||||||
|
|||||||
@@ -1022,6 +1022,15 @@ namespace Barotrauma
|
|||||||
|
|
||||||
partial void UpdateProjSpecific(float deltaTime);
|
partial void UpdateProjSpecific(float deltaTime);
|
||||||
|
|
||||||
|
#if SERVER
|
||||||
|
/// <summary>
|
||||||
|
/// How often the server can send messages about attacks being executed. Note that the timer is per-limb: if one limb executes an attack immediately after another, an network event can still be created.
|
||||||
|
/// Mainly relevant for attacks with no cooldown, e.g. fractal guardian's steam cannons which run continuously over time (we can't send events every frame)
|
||||||
|
/// </summary>
|
||||||
|
private const double MinExecuteAttackEventInterval = 0.5f;
|
||||||
|
private double lastExecuteAttackEventTime;
|
||||||
|
#endif
|
||||||
|
|
||||||
private readonly List<Body> contactBodies = new List<Body>();
|
private readonly List<Body> contactBodies = new List<Body>();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated.
|
/// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated.
|
||||||
@@ -1142,9 +1151,13 @@ namespace Barotrauma
|
|||||||
ExecuteAttack(damageTarget, targetLimb, out attackResult);
|
ExecuteAttack(damageTarget, targetLimb, out attackResult);
|
||||||
}
|
}
|
||||||
#if SERVER
|
#if SERVER
|
||||||
GameMain.NetworkMember.CreateEntityEvent(character, new Character.ExecuteAttackEventData(
|
if (Timing.TotalTime > lastExecuteAttackEventTime + MinExecuteAttackEventInterval)
|
||||||
attackLimb: this, targetEntity: damageTarget, targetLimb: targetLimb,
|
{
|
||||||
targetSimPos: attackSimPos));
|
GameMain.NetworkMember.CreateEntityEvent(character, new Character.ExecuteAttackEventData(
|
||||||
|
attackLimb: this, targetEntity: damageTarget, targetLimb: targetLimb,
|
||||||
|
targetSimPos: attackSimPos));
|
||||||
|
lastExecuteAttackEventTime = Timing.TotalTime;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,14 +43,14 @@ namespace Barotrauma.Abilities
|
|||||||
{
|
{
|
||||||
foreach (Identifier identifier in option.TalentIdentifiers)
|
foreach (Identifier identifier in option.TalentIdentifiers)
|
||||||
{
|
{
|
||||||
if (IsShowCaseTalent(identifier, option) || TalentTree.IsTalentLocked(identifier, characters)) { continue; }
|
if (IsShowCaseTalent(identifier, option) || Character.IsTalentLocked(identifier)) { continue; }
|
||||||
|
|
||||||
identifiers.Add(identifier);
|
identifiers.Add(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var (_, value) in option.ShowCaseTalents)
|
foreach (var (_, value) in option.ShowCaseTalents)
|
||||||
{
|
{
|
||||||
var ids = value.Where(i => !TalentTree.IsTalentLocked(i, characters)).ToImmutableHashSet();
|
var ids = value.Where(i => !Character.IsTalentLocked(i)).ToImmutableHashSet();
|
||||||
if (ids.Count is 0) { continue; }
|
if (ids.Count is 0) { continue; }
|
||||||
|
|
||||||
identifiers.Add(value.GetRandomUnsynced());
|
identifiers.Add(value.GetRandomUnsynced());
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ namespace Barotrauma
|
|||||||
if (character.Info.GetTotalTalentPoints() - selectedTalents.Count <= 0) { return false; }
|
if (character.Info.GetTotalTalentPoints() - selectedTalents.Count <= 0) { return false; }
|
||||||
if (!JobTalentTrees.TryGet(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return false; }
|
if (!JobTalentTrees.TryGet(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return false; }
|
||||||
|
|
||||||
if (IsTalentLocked(talentIdentifier, Character.GetFriendlyCrew(character))) { return false; }
|
if (character.IsTalentLocked(talentIdentifier)) { return false; }
|
||||||
|
|
||||||
if (character.Info.GetUnlockedTalentsInTree().Contains(talentIdentifier))
|
if (character.Info.GetUnlockedTalentsInTree().Contains(talentIdentifier))
|
||||||
{
|
{
|
||||||
@@ -163,16 +163,6 @@ namespace Barotrauma
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsTalentLocked(Identifier talentIdentifier, IEnumerable<Character> characterList)
|
|
||||||
{
|
|
||||||
foreach (Character c in characterList)
|
|
||||||
{
|
|
||||||
if (c.Info.GetSavedStatValue(StatTypes.LockedTalents, talentIdentifier) >= 1) { return true; }
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<Identifier> CheckTalentSelection(Character controlledCharacter, IEnumerable<Identifier> selectedTalents)
|
public static List<Identifier> CheckTalentSelection(Character controlledCharacter, IEnumerable<Identifier> selectedTalents)
|
||||||
{
|
{
|
||||||
List<Identifier> viableTalents = new List<Identifier>();
|
List<Identifier> viableTalents = new List<Identifier>();
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ namespace Barotrauma
|
|||||||
GameMain.NetworkMember.ShowNetStats = !GameMain.NetworkMember.ShowNetStats;
|
GameMain.NetworkMember.ShowNetStats = !GameMain.NetworkMember.ShowNetStats;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
commands.Add(new Command("spawn|spawncharacter", "spawn [creaturename/jobname] [near/inside/outside/cursor] [team] [add to crew (true/false)]: Spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine). You can also enter the name of a job (e.g. \"Mechanic\") to spawn a character with a specific job and the appropriate equipment.", null,
|
commands.Add(new Command("spawn|spawncharacter", "spawn [creaturename/jobname] [near/inside/outside/cursor] [team] [add to crew (true/false)] [name]: Spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine). You can also enter the name of a job (e.g. \"Mechanic\") to spawn a character with a specific job and the appropriate equipment.", null,
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
string[] creatureAndJobNames =
|
string[] creatureAndJobNames =
|
||||||
@@ -271,7 +271,7 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
}, isCheat: true));
|
}, isCheat: true));
|
||||||
|
|
||||||
commands.Add(new Command("give|giveitem", "give|giveitem [itemname/itemidentifier] [amount] [condition]: Spawn an item in the inventory of the controlled character",
|
commands.Add(new Command("give|giveitem", "give|giveitem [itemname/itemidentifier] [amount] [condition] [quality]: Spawn an item in the inventory of the controlled character",
|
||||||
(string[] args) =>
|
(string[] args) =>
|
||||||
{
|
{
|
||||||
if (Character.Controlled == null)
|
if (Character.Controlled == null)
|
||||||
@@ -292,9 +292,12 @@ namespace Barotrauma
|
|||||||
},
|
},
|
||||||
getValidArgs: () =>
|
getValidArgs: () =>
|
||||||
{
|
{
|
||||||
return new string[][]
|
return new string[][]
|
||||||
{
|
{
|
||||||
GetItemNameOrIdParams().ToArray()
|
GetItemNameOrIdParams().ToArray(),
|
||||||
|
new string[] { "1" },
|
||||||
|
new string[] { "100" },
|
||||||
|
ItemQualityNames.ToArray()
|
||||||
};
|
};
|
||||||
}, isCheat: true));
|
}, isCheat: true));
|
||||||
|
|
||||||
@@ -311,7 +314,7 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
}, isCheat: true));
|
}, isCheat: true));
|
||||||
|
|
||||||
commands.Add(new Command("spawnitem", "spawnitem [itemname/itemidentifier] [cursor/inventory/cargo/random/[name]] [amount] [condition]: Spawn an item at the position of the cursor, in the inventory of the controlled character, in the inventory of the client with the given name, or at a random spawnpoint if the location parameter is omitted or \"random\".",
|
commands.Add(new Command("spawnitem", "spawnitem [itemname/itemidentifier] [cursor/inventory/cargo/random/[name]] [amount] [condition] [quality]: Spawn an item at the position of the cursor, in the inventory of the controlled character, in the inventory of the client with the given name, or at a random spawnpoint if the location parameter is omitted or \"random\".",
|
||||||
(string[] args) =>
|
(string[] args) =>
|
||||||
{
|
{
|
||||||
TrySpawnItem(args);
|
TrySpawnItem(args);
|
||||||
@@ -321,7 +324,10 @@ namespace Barotrauma
|
|||||||
return new string[][]
|
return new string[][]
|
||||||
{
|
{
|
||||||
GetItemNameOrIdParams().ToArray(),
|
GetItemNameOrIdParams().ToArray(),
|
||||||
GetSpawnPosParams().ToArray()
|
GetSpawnPosParams().ToArray(),
|
||||||
|
new string[] { "1" },
|
||||||
|
new string[] { "100" },
|
||||||
|
ItemQualityNames.ToArray()
|
||||||
};
|
};
|
||||||
}, isCheat: true));
|
}, isCheat: true));
|
||||||
|
|
||||||
@@ -1324,6 +1330,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (GameMain.GameSession?.Map is Map map) { NewMessage("Map seed: " + map.Seed); }
|
||||||
NewMessage("Level seed: " + Level.Loaded.Seed);
|
NewMessage("Level seed: " + Level.Loaded.Seed);
|
||||||
NewMessage("Level generation params: " + Level.Loaded.GenerationParams.Identifier);
|
NewMessage("Level generation params: " + Level.Loaded.GenerationParams.Identifier);
|
||||||
NewMessage("Adjacent locations: " + (Level.Loaded.StartLocation?.Type.Identifier ?? "none".ToIdentifier()) + ", " + (Level.Loaded.StartLocation?.Type.Identifier ?? "none".ToIdentifier()));
|
NewMessage("Adjacent locations: " + (Level.Loaded.StartLocation?.Type.Identifier ?? "none".ToIdentifier()) + ", " + (Level.Loaded.StartLocation?.Type.Identifier ?? "none".ToIdentifier()));
|
||||||
@@ -1333,17 +1340,29 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
},null));
|
},null));
|
||||||
|
|
||||||
commands.Add(new Command("teleportsub", "teleportsub [start/end/endoutpost/cursor]: Teleport the submarine to the position of the cursor, or the start or end of the level. The 'endoutpost' argument also automatically docks the sub with the outpost at the end of the level. WARNING: does not take outposts into account, so often leads to physics glitches. Only use for debugging.",
|
commands.Add(new Command("teleportsub", "teleportsub [start/end/endoutpost/cursor] [submarine_team]: Teleport the submarine to the position of the cursor, or the start or end of the level. The 'endoutpost' argument also automatically docks the sub with the outpost at the end of the level. WARNING: does not take outposts into account, so often leads to physics glitches. Only use for debugging.",
|
||||||
onExecute:(string[] args) =>
|
onExecute:(string[] args) =>
|
||||||
{
|
{
|
||||||
if (Submarine.MainSub == null) { return; }
|
if (Submarine.MainSub == null) { return; }
|
||||||
|
Submarine submarineToTeleport = Submarine.MainSub;
|
||||||
|
if (args.Length > 1)
|
||||||
|
{
|
||||||
|
foreach (Submarine sub in Submarine.Loaded.Where(s => s.PhysicsBody.BodyType == FarseerPhysics.BodyType.Dynamic))
|
||||||
|
{
|
||||||
|
if ((sub.Info.Name + "_" + sub.TeamID) == args[1])
|
||||||
|
{
|
||||||
|
submarineToTeleport = sub;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (args.Length == 0 || args[0].Equals("cursor", StringComparison.OrdinalIgnoreCase))
|
if (args.Length == 0 || args[0].Equals("cursor", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
#if SERVER
|
#if SERVER
|
||||||
ThrowError("Cannot teleport the sub to the position of the cursor. Use \"start\" or \"end\", or execute the command as a client.");
|
ThrowError("Cannot teleport the sub to the position of the cursor. Use \"start\" or \"end\", or execute the command as a client.");
|
||||||
#else
|
#else
|
||||||
Submarine.MainSub.SetPosition(Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition));
|
submarineToTeleport.SetPosition(Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else if (args[0].Equals("start", StringComparison.OrdinalIgnoreCase))
|
else if (args[0].Equals("start", StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -1356,9 +1375,9 @@ namespace Barotrauma
|
|||||||
Vector2 pos = Level.Loaded.StartPosition;
|
Vector2 pos = Level.Loaded.StartPosition;
|
||||||
if (Level.Loaded.StartOutpost != null)
|
if (Level.Loaded.StartOutpost != null)
|
||||||
{
|
{
|
||||||
pos -= Vector2.UnitY * (Submarine.MainSub.Borders.Height + Level.Loaded.StartOutpost.Borders.Height) / 2;
|
pos -= Vector2.UnitY * (submarineToTeleport.Borders.Height + Level.Loaded.StartOutpost.Borders.Height) / 2;
|
||||||
}
|
}
|
||||||
Submarine.MainSub.SetPosition(pos);
|
submarineToTeleport.SetPosition(pos);
|
||||||
}
|
}
|
||||||
else if (args[0].Equals("end", StringComparison.OrdinalIgnoreCase))
|
else if (args[0].Equals("end", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@@ -1370,9 +1389,9 @@ namespace Barotrauma
|
|||||||
Vector2 pos = Level.Loaded.EndPosition;
|
Vector2 pos = Level.Loaded.EndPosition;
|
||||||
if (Level.Loaded.EndOutpost != null)
|
if (Level.Loaded.EndOutpost != null)
|
||||||
{
|
{
|
||||||
pos -= Vector2.UnitY * (Submarine.MainSub.Borders.Height + Level.Loaded.EndOutpost.Borders.Height) / 2;
|
pos -= Vector2.UnitY * (submarineToTeleport.Borders.Height + Level.Loaded.EndOutpost.Borders.Height) / 2;
|
||||||
}
|
}
|
||||||
Submarine.MainSub.SetPosition(pos);
|
submarineToTeleport.SetPosition(pos);
|
||||||
}
|
}
|
||||||
else if (args[0].Equals("endoutpost", StringComparison.OrdinalIgnoreCase))
|
else if (args[0].Equals("endoutpost", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@@ -1381,8 +1400,8 @@ namespace Barotrauma
|
|||||||
NewMessage("Can't teleport the sub to the end outpost (no outpost at the end of the level).", Color.Red);
|
NewMessage("Can't teleport the sub to the end outpost (no outpost at the end of the level).", Color.Red);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Submarine.MainSub.SetPosition(Level.Loaded.EndExitPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height);
|
submarineToTeleport.SetPosition(Level.Loaded.EndExitPosition - Vector2.UnitY * submarineToTeleport.Borders.Height);
|
||||||
var submarineDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == Submarine.MainSub);
|
var submarineDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == submarineToTeleport);
|
||||||
var outpostDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == Level.Loaded.EndOutpost);
|
var outpostDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == Level.Loaded.EndOutpost);
|
||||||
if (submarineDockingPort != null && outpostDockingPort != null)
|
if (submarineDockingPort != null && outpostDockingPort != null)
|
||||||
{
|
{
|
||||||
@@ -1394,7 +1413,8 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
return new string[][]
|
return new string[][]
|
||||||
{
|
{
|
||||||
new string[] { "start", "end", "endoutpost", "cursor" }
|
new string[] { "start", "end", "endoutpost", "cursor" },
|
||||||
|
ListAvailableSubmarines()
|
||||||
};
|
};
|
||||||
}, isCheat: true));
|
}, isCheat: true));
|
||||||
|
|
||||||
@@ -2569,7 +2589,17 @@ namespace Barotrauma
|
|||||||
|
|
||||||
return locationNames.ToArray();
|
return locationNames.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string[] ListAvailableSubmarines()
|
||||||
|
{
|
||||||
|
List<string> submarineNames = new();
|
||||||
|
foreach (var submarine in Submarine.Loaded.Where(s => s.PhysicsBody.BodyType == FarseerPhysics.BodyType.Dynamic))
|
||||||
|
{
|
||||||
|
submarineNames.Add(submarine.Info.Name + "_" + submarine.TeamID);
|
||||||
|
}
|
||||||
|
return submarineNames.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryFindTeleportPosition(string locationName, out Vector2 teleportPosition)
|
private static bool TryFindTeleportPosition(string locationName, out Vector2 teleportPosition)
|
||||||
{
|
{
|
||||||
if (Submarine.MainSub is Submarine mainSub && string.Equals(locationName, "mainsub", StringComparison.InvariantCultureIgnoreCase))
|
if (Submarine.MainSub is Submarine mainSub && string.Equals(locationName, "mainsub", StringComparison.InvariantCultureIgnoreCase))
|
||||||
@@ -2952,7 +2982,7 @@ namespace Barotrauma
|
|||||||
isHuman = job != null || characterLowerCase == CharacterPrefab.HumanSpeciesName;
|
isHuman = job != null || characterLowerCase == CharacterPrefab.HumanSpeciesName;
|
||||||
}
|
}
|
||||||
|
|
||||||
ParseOptionalArgs(out Vector2 spawnPosition, out WayPoint spawnPoint, out CharacterTeamType? teamType, out bool addToCrew);
|
ParseOptionalArgs(out Vector2 spawnPosition, out WayPoint spawnPoint, out CharacterTeamType? teamType, out bool addToCrew, out string renameCharacter);
|
||||||
|
|
||||||
if (usePreConfiguredNPC)
|
if (usePreConfiguredNPC)
|
||||||
{
|
{
|
||||||
@@ -2983,6 +3013,14 @@ namespace Barotrauma
|
|||||||
CharacterInfo characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: job, variant: variant);
|
CharacterInfo characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: job, variant: variant);
|
||||||
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, spawnPosition, characterInfo, onSpawn: newCharacter =>
|
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, spawnPosition, characterInfo, onSpawn: newCharacter =>
|
||||||
{
|
{
|
||||||
|
if (renameCharacter != null)
|
||||||
|
{
|
||||||
|
if (renameCharacter.Length > 31)
|
||||||
|
{
|
||||||
|
renameCharacter = renameCharacter.Substring(0, 32);
|
||||||
|
}
|
||||||
|
newCharacter.Info.Name = renameCharacter;
|
||||||
|
}
|
||||||
SetTeamAndCrew(newCharacter);
|
SetTeamAndCrew(newCharacter);
|
||||||
newCharacter.GiveJobItems(isPvPMode: GameMain.GameSession?.GameMode is PvPMode, spawnPoint);
|
newCharacter.GiveJobItems(isPvPMode: GameMain.GameSession?.GameMode is PvPMode, spawnPoint);
|
||||||
newCharacter.GiveIdCardTags(spawnPoint);
|
newCharacter.GiveIdCardTags(spawnPoint);
|
||||||
@@ -3010,7 +3048,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ParseOptionalArgs(out Vector2 spawnPosition, out WayPoint spawnPoint, out CharacterTeamType? teamType, out bool addToCrew)
|
void ParseOptionalArgs(out Vector2 spawnPosition, out WayPoint spawnPoint, out CharacterTeamType? teamType, out bool addToCrew, out string renameCharacter)
|
||||||
{
|
{
|
||||||
spawnPosition = Vector2.Zero;
|
spawnPosition = Vector2.Zero;
|
||||||
spawnPoint = null;
|
spawnPoint = null;
|
||||||
@@ -3096,6 +3134,12 @@ namespace Barotrauma
|
|||||||
ThrowError($"Could not parse the \"add to crew\" argument ({args[argIndex]}). Defaulting to {addToCrew}.");
|
ThrowError($"Could not parse the \"add to crew\" argument ({args[argIndex]}). Defaulting to {addToCrew}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
argIndex++;
|
||||||
|
renameCharacter = null;
|
||||||
|
if (args.Length > argIndex)
|
||||||
|
{
|
||||||
|
renameCharacter = args[argIndex];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3103,6 +3147,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
yield return "cursor";
|
yield return "cursor";
|
||||||
yield return "inventory";
|
yield return "inventory";
|
||||||
|
yield return "cargo";
|
||||||
|
|
||||||
#if SERVER
|
#if SERVER
|
||||||
if (GameMain.Server != null)
|
if (GameMain.Server != null)
|
||||||
@@ -3143,6 +3188,8 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ImmutableArray<string> ItemQualityNames = ["normal", "good", "excellent", "masterwork"];
|
||||||
|
|
||||||
private static void TrySpawnItem(string[] args)
|
private static void TrySpawnItem(string[] args)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -3203,7 +3250,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
int amount = 1;
|
int amount = 1;
|
||||||
int conditionPrc = 100;
|
int conditionPrc = 100;
|
||||||
|
int itemQuality = 0;
|
||||||
|
|
||||||
if (TryGetSpawnPosParam(out string spawnLocation, out int spawnLocationIndex))
|
if (TryGetSpawnPosParam(out string spawnLocation, out int spawnLocationIndex))
|
||||||
{
|
{
|
||||||
switch (spawnLocation)
|
switch (spawnLocation)
|
||||||
@@ -3223,7 +3271,7 @@ namespace Barotrauma
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
var matchingCharacter = FindMatchingCharacter(args.Skip(1).Take(1).ToArray());
|
var matchingCharacter = FindMatchingCharacter(args.Skip(1).Take(1).ToArray());
|
||||||
if (matchingCharacter != null){ spawnInventory = matchingCharacter.Inventory; }
|
if (matchingCharacter != null) { spawnInventory = matchingCharacter.Inventory; }
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3232,10 +3280,21 @@ namespace Barotrauma
|
|||||||
if (!int.TryParse(args[spawnLocationIndex + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out amount)) { amount = 1; }
|
if (!int.TryParse(args[spawnLocationIndex + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out amount)) { amount = 1; }
|
||||||
amount = Math.Min(amount, 100);
|
amount = Math.Min(amount, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.Length > spawnLocationIndex + 2)
|
if (args.Length > spawnLocationIndex + 2)
|
||||||
{
|
{
|
||||||
if (!int.TryParse(args[^1], NumberStyles.Any, CultureInfo.InvariantCulture, out conditionPrc)) { conditionPrc = 100; }
|
if (!int.TryParse(args[spawnLocationIndex + 2], NumberStyles.Any, CultureInfo.InvariantCulture, out conditionPrc)) { conditionPrc = 100; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Length > spawnLocationIndex + 3)
|
||||||
|
{
|
||||||
|
for (int i = 0; i <= Quality.MaxQuality; i++)
|
||||||
|
{
|
||||||
|
if (args[spawnLocationIndex + 3].ToLowerInvariant() == ItemQualityNames[i])
|
||||||
|
{
|
||||||
|
itemQuality = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3257,7 +3316,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Entity.Spawner?.AddItemToSpawnQueue(itemPrefab, spawnPos.Value, condition: itemCondition);
|
Entity.Spawner?.AddItemToSpawnQueue(itemPrefab, spawnPos.Value, condition: itemCondition, quality: itemQuality);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (spawnInventory != null)
|
else if (spawnInventory != null)
|
||||||
@@ -3284,6 +3343,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
item.Condition = item.Health * Math.Clamp(conditionPrc / 100f, 0f, 1f);
|
item.Condition = item.Health * Math.Clamp(conditionPrc / 100f, 0f, 1f);
|
||||||
|
item.Quality = itemQuality;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,12 +31,17 @@ namespace Barotrauma
|
|||||||
Actions = new List<EventAction>();
|
Actions = new List<EventAction>();
|
||||||
foreach (var e in element.Elements())
|
foreach (var e in element.Elements())
|
||||||
{
|
{
|
||||||
if (e.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase))
|
if (e.NameAsIdentifier().Equals("statuseffect"))
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError($"Error in event prefab \"{scriptedEvent.Prefab.Identifier}\". Status effect configured as a sub action. Please configure status effects as child elements of a StatusEffectAction.",
|
DebugConsole.ThrowError($"Error in event prefab \"{scriptedEvent.Prefab.Identifier}\". Status effect configured as a sub action. Please configure status effects as child elements of a StatusEffectAction.",
|
||||||
contentPackage: element.ContentPackage);
|
contentPackage: element.ContentPackage);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
else if (e.NameAsIdentifier().Equals(nameof(OnRoundEndAction)))
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"Error in event prefab \"{scriptedEvent.Prefab.Identifier}\". {nameof(OnRoundEndAction)} configured as a sub action. Please configure it as an action at the end of the event.",
|
||||||
|
contentPackage: element.ContentPackage);
|
||||||
|
}
|
||||||
var action = Instantiate(scriptedEvent, e);
|
var action = Instantiate(scriptedEvent, e);
|
||||||
if (action != null) { Actions.Add(action); }
|
if (action != null) { Actions.Add(action); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ namespace Barotrauma
|
|||||||
AddTargetPredicate(
|
AddTargetPredicate(
|
||||||
Tags.Traitor,
|
Tags.Traitor,
|
||||||
ScriptedEvent.TargetPredicate.EntityType.Character,
|
ScriptedEvent.TargetPredicate.EntityType.Character,
|
||||||
e => e is Character c && (c.IsPlayer || c.IsBot) && c.IsTraitor && !c.IsIncapacitated);
|
e => e is Character c && (c.IsPlayer || c.IsBot) && c.IsTraitor && !c.IsIncapacitated && CharacterTeamMatches(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TagNonTraitors()
|
private void TagNonTraitors()
|
||||||
@@ -131,7 +131,7 @@ namespace Barotrauma
|
|||||||
AddTargetPredicate(
|
AddTargetPredicate(
|
||||||
Tags.NonTraitor,
|
Tags.NonTraitor,
|
||||||
ScriptedEvent.TargetPredicate.EntityType.Character,
|
ScriptedEvent.TargetPredicate.EntityType.Character,
|
||||||
e => e is Character c && (c.IsPlayer || c.IsBot) && !c.IsTraitor && c.IsOnPlayerTeam && !c.IsIncapacitated);
|
e => e is Character c && (c.IsPlayer || c.IsBot) && !c.IsTraitor && c.IsOnPlayerTeam && !c.IsIncapacitated && CharacterTeamMatches(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TagNonTraitorPlayers()
|
private void TagNonTraitorPlayers()
|
||||||
@@ -139,7 +139,7 @@ namespace Barotrauma
|
|||||||
AddTargetPredicate(
|
AddTargetPredicate(
|
||||||
Tags.NonTraitorPlayer,
|
Tags.NonTraitorPlayer,
|
||||||
ScriptedEvent.TargetPredicate.EntityType.Character,
|
ScriptedEvent.TargetPredicate.EntityType.Character,
|
||||||
e => e is Character c && c.IsPlayer && !c.IsTraitor && c.IsOnPlayerTeam && !c.IsIncapacitated);
|
e => e is Character c && c.IsPlayer && !c.IsTraitor && c.IsOnPlayerTeam && !c.IsIncapacitated && CharacterTeamMatches(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TagBots(bool playerCrewOnly)
|
private void TagBots(bool playerCrewOnly)
|
||||||
@@ -151,7 +151,8 @@ namespace Barotrauma
|
|||||||
e is Character c &&
|
e is Character c &&
|
||||||
c.IsBot &&
|
c.IsBot &&
|
||||||
(!c.IsIncapacitated || !IgnoreIncapacitatedCharacters) &&
|
(!c.IsIncapacitated || !IgnoreIncapacitatedCharacters) &&
|
||||||
(!playerCrewOnly || c.TeamID == CharacterTeamType.Team1));
|
(!playerCrewOnly || c.TeamID == CharacterTeamType.Team1) &&
|
||||||
|
CharacterTeamMatches(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TagCrew()
|
private void TagCrew()
|
||||||
@@ -171,7 +172,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void TagHumansByTag(Identifier tag)
|
private void TagHumansByTag(Identifier tag)
|
||||||
{
|
{
|
||||||
AddTarget(Tag, Character.CharacterList.Where(c => c.HumanPrefab != null && c.HumanPrefab.GetTags().Contains(tag)));
|
AddTarget(Tag, Character.CharacterList.Where(c => c.HumanPrefab != null && c.HumanPrefab.GetTags().Contains(tag) && CharacterTeamMatches(c)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TagHumansByJobIdentifier(Identifier jobIdentifier)
|
private void TagHumansByJobIdentifier(Identifier jobIdentifier)
|
||||||
|
|||||||
@@ -168,6 +168,9 @@ namespace Barotrauma.Items.Components
|
|||||||
set { attachedByDefault = value; }
|
set { attachedByDefault = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
[Editable]
|
||||||
|
#endif
|
||||||
[Serialize("0.0,0.0", IsPropertySaveable.No, description: "The position the character holds the item at (in pixels, as an offset from the character's shoulder)."+
|
[Serialize("0.0,0.0", IsPropertySaveable.No, description: "The position the character holds the item at (in pixels, as an offset from the character's shoulder)."+
|
||||||
" For example, a value of 10,-100 would make the character hold the item 100 pixels below the shoulder and 10 pixels forwards.")]
|
" For example, a value of 10,-100 would make the character hold the item 100 pixels below the shoulder and 10 pixels forwards.")]
|
||||||
public Vector2 HoldPos
|
public Vector2 HoldPos
|
||||||
@@ -177,8 +180,10 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
//the distance from the holding characters elbow to center of the physics body of the item
|
//the distance from the holding characters elbow to center of the physics body of the item
|
||||||
protected Vector2 holdPos;
|
protected Vector2 holdPos;
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
[Editable]
|
||||||
|
#endif
|
||||||
[Serialize("0.0,0.0", IsPropertySaveable.No, description: "The position the character holds the item at when aiming (in pixels, as an offset from the character's shoulder)."+
|
[Serialize("0.0,0.0", IsPropertySaveable.No, description: "The position the character holds the item at when aiming (in pixels, as an offset from the character's shoulder)."+
|
||||||
" Works similarly as HoldPos, except that the position is rotated according to the direction the player is aiming at. For example, a value of 10,-100 would make the character hold the item 100 pixels below the shoulder and 10 pixels forwards when aiming directly to the right.")]
|
" Works similarly as HoldPos, except that the position is rotated according to the direction the player is aiming at. For example, a value of 10,-100 would make the character hold the item 100 pixels below the shoulder and 10 pixels forwards when aiming directly to the right.")]
|
||||||
public Vector2 AimPos
|
public Vector2 AimPos
|
||||||
@@ -279,6 +284,9 @@ namespace Barotrauma.Items.Components
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// For setting the handle positions using status effects
|
/// For setting the handle positions using status effects
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
#if DEBUG
|
||||||
|
[Editable]
|
||||||
|
#endif
|
||||||
public Vector2 Handle1
|
public Vector2 Handle1
|
||||||
{
|
{
|
||||||
get { return ConvertUnits.ToDisplayUnits(handlePos[0]); }
|
get { return ConvertUnits.ToDisplayUnits(handlePos[0]); }
|
||||||
@@ -299,6 +307,9 @@ namespace Barotrauma.Items.Components
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// For setting the handle positions using status effects
|
/// For setting the handle positions using status effects
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
#if DEBUG
|
||||||
|
[Editable]
|
||||||
|
#endif
|
||||||
public Vector2 Handle2
|
public Vector2 Handle2
|
||||||
{
|
{
|
||||||
get { return ConvertUnits.ToDisplayUnits(handlePos[1]); }
|
get { return ConvertUnits.ToDisplayUnits(handlePos[1]); }
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ namespace Barotrauma.Items.Components
|
|||||||
return OnPicked(picker, pickDroppedStack: true);
|
return OnPicked(picker, pickDroppedStack: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool OnPicked(Character picker, bool pickDroppedStack)
|
public bool OnPicked(Character picker, bool pickDroppedStack, bool playSound = true)
|
||||||
{
|
{
|
||||||
//if the item has multiple Pickable components (e.g. Holdable and Wearable, check that we don't equip it in hands when the item is worn or vice versa)
|
//if the item has multiple Pickable components (e.g. Holdable and Wearable, check that we don't equip it in hands when the item is worn or vice versa)
|
||||||
if (item.GetComponents<Pickable>().Any())
|
if (item.GetComponents<Pickable>().Any())
|
||||||
@@ -156,7 +156,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
ApplyStatusEffects(ActionType.OnPicked, 1.0f, picker);
|
ApplyStatusEffects(ActionType.OnPicked, 1.0f, picker);
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
if (!GameMain.Instance.LoadingScreenOpen && picker == Character.Controlled) { SoundPlayer.PlayUISound(GUISoundType.PickItem); }
|
if (!GameMain.Instance.LoadingScreenOpen && playSound && picker == Character.Controlled) { SoundPlayer.PlayUISound(GUISoundType.PickItem); }
|
||||||
PlaySound(ActionType.OnPicked, picker);
|
PlaySound(ActionType.OnPicked, picker);
|
||||||
#endif
|
#endif
|
||||||
if (pickDroppedStack)
|
if (pickDroppedStack)
|
||||||
@@ -164,7 +164,7 @@ namespace Barotrauma.Items.Components
|
|||||||
foreach (var droppedItem in droppedStack)
|
foreach (var droppedItem in droppedStack)
|
||||||
{
|
{
|
||||||
if (droppedItem == item) { continue; }
|
if (droppedItem == item) { continue; }
|
||||||
droppedItem.GetComponent<Pickable>().OnPicked(picker, pickDroppedStack: false);
|
droppedItem.GetComponent<Pickable>().OnPicked(picker, pickDroppedStack: false, playSound: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ namespace Barotrauma.Items.Components
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serialize(true, IsPropertySaveable.No, description: $"Should this item be removed if the linked character is null?")]
|
||||||
|
public bool RemoveItemIfCharacterNull { get; set; }
|
||||||
|
|
||||||
public Character? Character { get; private set; }
|
public Character? Character { get; private set; }
|
||||||
|
|
||||||
public bool DoesBleed => Character?.DoesBleed == true;
|
public bool DoesBleed => Character?.DoesBleed == true;
|
||||||
@@ -50,6 +53,8 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
public LinkedControllerCharacterComponent(Item item, ContentXElement element) : base(item, element)
|
public LinkedControllerCharacterComponent(Item item, ContentXElement element) : base(item, element)
|
||||||
{
|
{
|
||||||
|
IsActive = true;
|
||||||
|
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
spriteOverrides = element.Elements()
|
spriteOverrides = element.Elements()
|
||||||
.Where(static e => e.Name.LocalName.ToLowerInvariant() == "spriteoverride")
|
.Where(static e => e.Name.LocalName.ToLowerInvariant() == "spriteoverride")
|
||||||
@@ -58,6 +63,16 @@ namespace Barotrauma.Items.Components
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Update(float deltaTime, Camera cam)
|
||||||
|
{
|
||||||
|
base.Update(deltaTime, cam);
|
||||||
|
|
||||||
|
if (RemoveItemIfCharacterNull && GameMain.NetworkMember is not { IsClient: true } && (Character == null || Character.Removed))
|
||||||
|
{
|
||||||
|
Entity.Spawner?.AddEntityToRemoveQueue(Item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateLinkedCharacter(Character? character)
|
public void UpdateLinkedCharacter(Character? character)
|
||||||
{
|
{
|
||||||
Character = character;
|
Character = character;
|
||||||
|
|||||||
@@ -525,7 +525,7 @@ namespace Barotrauma.Items.Components
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (containerToSpawnOnSelectedItem.Inventory.AllItems.Any(x => x.Prefab == spawnItemOnSelectedPrefab))
|
if (containerToSpawnOnSelectedItem.Inventory.AllItems.Any(item => item == spawnedItemOnSelected))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -345,6 +345,8 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
if (targetItem.AllowDeconstruct && allowRemove)
|
if (targetItem.AllowDeconstruct && allowRemove)
|
||||||
{
|
{
|
||||||
|
ApplyDeconstructionStatusEffects(targetItem, ActionType.OnDeconstructed, 1f);
|
||||||
|
|
||||||
//drop all items that are inside the deconstructed item
|
//drop all items that are inside the deconstructed item
|
||||||
foreach (ItemContainer ic in targetItem.GetComponents<ItemContainer>())
|
foreach (ItemContainer ic in targetItem.GetComponents<ItemContainer>())
|
||||||
{
|
{
|
||||||
@@ -480,6 +482,7 @@ namespace Barotrauma.Items.Components
|
|||||||
// Move items again since the status effect could have spawned additional items in the character inventory
|
// Move items again since the status effect could have spawned additional items in the character inventory
|
||||||
MoveItemsFromCharacterToOutput();
|
MoveItemsFromCharacterToOutput();
|
||||||
|
|
||||||
|
character.Kill(CauseOfDeathType.Unknown, causeOfDeathAffliction: null);
|
||||||
Entity.Spawner?.AddEntityToRemoveQueue(character);
|
Entity.Spawner?.AddEntityToRemoveQueue(character);
|
||||||
}, 0.1f);
|
}, 0.1f);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -732,6 +732,7 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readonly HashSet<Item> usedIngredients = new HashSet<Item>();
|
private readonly HashSet<Item> usedIngredients = new HashSet<Item>();
|
||||||
|
private readonly Dictionary<ItemPrefab, int> ingredientFlexibilityCache = new Dictionary<ItemPrefab, int>();
|
||||||
|
|
||||||
public bool MissingRequiredRecipe(FabricationRecipe fabricableItem, Character character)
|
public bool MissingRequiredRecipe(FabricationRecipe fabricableItem, Character character)
|
||||||
{
|
{
|
||||||
@@ -786,10 +787,24 @@ namespace Barotrauma.Items.Components
|
|||||||
//maintain a list of used ingredients so we don't end up considering the same item a suitable for multiple required ingredients
|
//maintain a list of used ingredients so we don't end up considering the same item a suitable for multiple required ingredients
|
||||||
usedIngredients.Clear();
|
usedIngredients.Clear();
|
||||||
|
|
||||||
return fabricableItem.RequiredItems.All(requiredItem =>
|
// Items are considered more flexible if they can be used in many different requirements
|
||||||
|
ingredientFlexibilityCache.Clear();
|
||||||
|
foreach (var prefab in fabricableItem.RequiredItems.SelectMany(static r => r.ItemPrefabs))
|
||||||
|
{
|
||||||
|
ingredientFlexibilityCache[prefab] = fabricableItem.RequiredItems.Count(r => r.ItemPrefabs.Contains(prefab));
|
||||||
|
}
|
||||||
|
|
||||||
|
return fabricableItem.RequiredItems
|
||||||
|
// Match the most restrictive requirements to least restrictive first, while we still have items that we can use
|
||||||
|
.OrderBy(static r => r.ItemPrefabs.Count())
|
||||||
|
.ThenByDescending(static requiredItem => requiredItem.Amount)
|
||||||
|
.All(requiredItem =>
|
||||||
{
|
{
|
||||||
int availableItemsAmount = 0;
|
int availableItemsAmount = 0;
|
||||||
foreach (ItemPrefab requiredPrefab in requiredItem.ItemPrefabs)
|
foreach (ItemPrefab requiredPrefab in requiredItem.ItemPrefabs
|
||||||
|
// Fill in the least flexible and more abundant items first, so we don't end up using unique items first
|
||||||
|
.OrderBy(GetItemFlexibility)
|
||||||
|
.ThenByDescending(GetAvailableItemsCount))
|
||||||
{
|
{
|
||||||
if (!availableIngredients.TryGetValue(requiredPrefab.Identifier, out var availableItems)) { continue; }
|
if (!availableIngredients.TryGetValue(requiredPrefab.Identifier, out var availableItems)) { continue; }
|
||||||
|
|
||||||
@@ -811,6 +826,16 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
int GetAvailableItemsCount(ItemPrefab itemPrefab)
|
||||||
|
{
|
||||||
|
return availableIngredients.TryGetValue(itemPrefab.Identifier, out var list) ? list.Count : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetItemFlexibility(ItemPrefab itemPrefab)
|
||||||
|
{
|
||||||
|
return ingredientFlexibilityCache[itemPrefab];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private float GetRequiredTime(FabricationRecipe fabricableItem, Character user)
|
private float GetRequiredTime(FabricationRecipe fabricableItem, Character user)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocalizedString? DisplayName { get; private set; }
|
public LocalizedString DisplayName { get; private set; }
|
||||||
|
|
||||||
private float supplyRatio = 1f;
|
private float supplyRatio = 1f;
|
||||||
public float SupplyRatio
|
public float SupplyRatio
|
||||||
@@ -80,6 +80,7 @@ namespace Barotrauma.Items.Components
|
|||||||
SupplyRatio = element.GetAttributeFloat("ratio", SupplyRatio);
|
SupplyRatio = element.GetAttributeFloat("ratio", SupplyRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DisplayName = TextManager.Get(name).Fallback(name);
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
CreateGUI();
|
CreateGUI();
|
||||||
if (Screen.Selected is not { IsEditor: true })
|
if (Screen.Selected is not { IsEditor: true })
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
PhysicsBody = new PhysicsBody(currentWidth, currentHeight, radius: 0.0f, density: 1.5f, BodyType.Static, Physics.CollisionWall, LevelTrigger.GetCollisionCategories(triggeredBy))
|
PhysicsBody = new PhysicsBody(currentWidth, currentHeight, radius: 0.0f, density: 1.5f, BodyType.Static, Physics.CollisionWall, LevelTrigger.GetCollisionCategories(triggeredBy))
|
||||||
{
|
{
|
||||||
UserData = item
|
UserData = this
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -260,7 +260,7 @@ namespace Barotrauma.Items.Components
|
|||||||
currentRadius = Math.Max(ConvertUnits.ToSimUnits(Radius * item.Scale), 0.01f);
|
currentRadius = Math.Max(ConvertUnits.ToSimUnits(Radius * item.Scale), 0.01f);
|
||||||
PhysicsBody = new PhysicsBody(width: 0.0f, height: 0.0f, radius: currentRadius, density: 1.5f, BodyType.Static, Physics.CollisionWall, LevelTrigger.GetCollisionCategories(triggeredBy))
|
PhysicsBody = new PhysicsBody(width: 0.0f, height: 0.0f, radius: currentRadius, density: 1.5f, BodyType.Static, Physics.CollisionWall, LevelTrigger.GetCollisionCategories(triggeredBy))
|
||||||
{
|
{
|
||||||
UserData = item
|
UserData = this
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -734,8 +734,8 @@ namespace Barotrauma
|
|||||||
[Serialize(false, IsPropertySaveable.No, description: "Hides the condition displayed in the item's tooltip.")]
|
[Serialize(false, IsPropertySaveable.No, description: "Hides the condition displayed in the item's tooltip.")]
|
||||||
public bool HideConditionInTooltip { get; set; }
|
public bool HideConditionInTooltip { get; set; }
|
||||||
|
|
||||||
[Serialize("", IsPropertySaveable.No, description: "If set, displays if the given fabrication recipe has been unlocked or not in the tooltip. The actual unlocking of the recipe should be handled in a status effect.")]
|
[Serialize("", IsPropertySaveable.No, description: "If set, the item's tooltip displays if the given fabrication recipe has been unlocked or not. The actual unlocking of the recipe should be handled in a status effect.")]
|
||||||
public Identifier UnlockedRecipeInToolTip { get; set; }
|
public Identifier[] UnlockedRecipeInToolTip { get; set; }
|
||||||
|
|
||||||
//if true and the item has trigger areas defined, characters need to be within the trigger to interact with the item
|
//if true and the item has trigger areas defined, characters need to be within the trigger to interact with the item
|
||||||
//if false, trigger areas define areas that can be used to highlight the item
|
//if false, trigger areas define areas that can be used to highlight the item
|
||||||
|
|||||||
@@ -332,17 +332,9 @@ namespace Barotrauma
|
|||||||
void RunStateUnloaded_OnEnter(State<RunState> currentState)
|
void RunStateUnloaded_OnEnter(State<RunState> currentState)
|
||||||
{
|
{
|
||||||
Logger.LogMessage("LuaCs unloaded state entered");
|
Logger.LogMessage("LuaCs unloaded state entered");
|
||||||
|
Logger.LogResults(PackageManagementService.StopRunningPackages());
|
||||||
if (PackageManagementService.IsAnyPackageRunning())
|
DisposeLuaCsConfig();
|
||||||
{
|
Logger.LogResults(PackageManagementService.UnloadAllPackages());
|
||||||
Logger.LogResults(PackageManagementService.StopRunningPackages());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PackageManagementService.IsAnyPackageLoaded())
|
|
||||||
{
|
|
||||||
DisposeLuaCsConfig();
|
|
||||||
Logger.LogResults(PackageManagementService.UnloadAllPackages());
|
|
||||||
}
|
|
||||||
|
|
||||||
EventService.Reset();
|
EventService.Reset();
|
||||||
ConfigService.Reset();
|
ConfigService.Reset();
|
||||||
@@ -362,11 +354,7 @@ namespace Barotrauma
|
|||||||
void RunStateLoadedNoExec_OnEnter(State<RunState> currentState)
|
void RunStateLoadedNoExec_OnEnter(State<RunState> currentState)
|
||||||
{
|
{
|
||||||
Logger.LogMessage("LuaCs no execution state entered");
|
Logger.LogMessage("LuaCs no execution state entered");
|
||||||
|
Logger.LogResults(PackageManagementService.StopRunningPackages());
|
||||||
if (PackageManagementService.IsAnyPackageRunning())
|
|
||||||
{
|
|
||||||
Logger.LogResults(PackageManagementService.StopRunningPackages());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PackageManagementService.IsAnyPackageLoaded())
|
if (!PackageManagementService.IsAnyPackageLoaded())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -256,6 +256,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public Result<Assembly> CompileScriptAssembly([NotNull] string assemblyName,
|
public Result<Assembly> CompileScriptAssembly([NotNull] string assemblyName,
|
||||||
bool compileWithInternalAccess,
|
bool compileWithInternalAccess,
|
||||||
ImmutableArray<SyntaxTree> syntaxTrees,
|
ImmutableArray<SyntaxTree> syntaxTrees,
|
||||||
@@ -348,6 +349,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public FluentResults.Result<Assembly> LoadAssemblyFromFile(string assemblyFilePath,
|
public FluentResults.Result<Assembly> LoadAssemblyFromFile(string assemblyFilePath,
|
||||||
ImmutableArray<string> additionalDependencyPaths)
|
ImmutableArray<string> additionalDependencyPaths)
|
||||||
{
|
{
|
||||||
@@ -434,6 +436,8 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public FluentResults.Result<Assembly> GetAssemblyByName(string assemblyName)
|
public FluentResults.Result<Assembly> GetAssemblyByName(string assemblyName)
|
||||||
{
|
{
|
||||||
if (IsDisposed)
|
if (IsDisposed)
|
||||||
@@ -481,6 +485,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public FluentResults.Result<ImmutableArray<Type>> GetTypesInAssemblies()
|
public FluentResults.Result<ImmutableArray<Type>> GetTypesInAssemblies()
|
||||||
{
|
{
|
||||||
if (IsDisposed)
|
if (IsDisposed)
|
||||||
@@ -501,6 +506,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public IEnumerable<Type> UnsafeGetTypesInAssemblies()
|
public IEnumerable<Type> UnsafeGetTypesInAssemblies()
|
||||||
{
|
{
|
||||||
if (IsDisposed)
|
if (IsDisposed)
|
||||||
@@ -529,6 +535,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public Result<Type> GetTypeInAssemblies(string typeName)
|
public Result<Type> GetTypeInAssemblies(string typeName)
|
||||||
{
|
{
|
||||||
if (IsDisposed)
|
if (IsDisposed)
|
||||||
@@ -557,13 +564,12 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
|
|||||||
return; // we don't want to invoke events twice nor cause strong GC handles.
|
return; // we don't want to invoke events twice nor cause strong GC handles.
|
||||||
IsDisposed = true;
|
IsDisposed = true;
|
||||||
this.Unload();
|
this.Unload();
|
||||||
this.DisposeInternal();
|
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
~AssemblyLoader()
|
~AssemblyLoader()
|
||||||
{
|
{
|
||||||
this.DisposeInternal();
|
this.Unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUnload(AssemblyLoadContext context)
|
private void OnUnload(AssemblyLoadContext context)
|
||||||
@@ -578,8 +584,8 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
|
|||||||
Thread.Sleep(1000/Timing.FixedUpdateRate-1);
|
Thread.Sleep(1000/Timing.FixedUpdateRate-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
var wf = new WeakReference<IAssemblyLoaderService>(this);
|
|
||||||
_onUnload?.Invoke(this);
|
_onUnload?.Invoke(this);
|
||||||
|
this.DisposeInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeInternal()
|
private void DisposeInternal()
|
||||||
@@ -590,6 +596,9 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
|
|||||||
base.Unloading -= OnUnload;
|
base.Unloading -= OnUnload;
|
||||||
this._dependencyResolvers.Clear();
|
this._dependencyResolvers.Clear();
|
||||||
this._loadedAssemblyData.Clear();
|
this._loadedAssemblyData.Clear();
|
||||||
|
|
||||||
|
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true);
|
||||||
|
GC.WaitForFullGCComplete(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Assembly Load(AssemblyName assemblyName)
|
protected override Assembly Load(AssemblyName assemblyName)
|
||||||
@@ -658,6 +667,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
|
|||||||
public readonly ImmutableArray<Type> Types;
|
public readonly ImmutableArray<Type> Types;
|
||||||
public readonly ImmutableDictionary<string, Type> TypesByName;
|
public readonly ImmutableDictionary<string, Type> TypesByName;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoOptimization)]
|
||||||
public AssemblyData(Assembly assembly, byte[] assemblyImage)
|
public AssemblyData(Assembly assembly, byte[] assemblyImage)
|
||||||
{
|
{
|
||||||
Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
|
Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
|
||||||
@@ -667,6 +677,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
|
|||||||
TypesByName = Types.ToImmutableDictionary(type => type.FullName, type => type);
|
TypesByName = Types.ToImmutableDictionary(type => type.FullName, type => type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoOptimization)]
|
||||||
public AssemblyData(Assembly assembly, string path)
|
public AssemblyData(Assembly assembly, string path)
|
||||||
{
|
{
|
||||||
Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
|
Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
|
||||||
@@ -694,6 +705,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
|
|||||||
HashCode = AssemblyName.GetHashCode();
|
HashCode = AssemblyName.GetHashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoOptimization)]
|
||||||
public AssemblyOrStringKey(string assemblyName)
|
public AssemblyOrStringKey(string assemblyName)
|
||||||
{
|
{
|
||||||
if (assemblyName.IsNullOrWhiteSpace())
|
if (assemblyName.IsNullOrWhiteSpace())
|
||||||
|
|||||||
@@ -396,7 +396,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService,
|
|||||||
typeof(ISettingList<ulong>),
|
typeof(ISettingList<ulong>),
|
||||||
typeof(ISettingList<long>),
|
typeof(ISettingList<long>),
|
||||||
typeof(ISettingList<float>),
|
typeof(ISettingList<float>),
|
||||||
typeof(ISettingList<double>),
|
typeof(ISettingList<double>)
|
||||||
];
|
];
|
||||||
|
|
||||||
Dictionary<string, Dictionary<string, object>> settingsTable = [];
|
Dictionary<string, Dictionary<string, object>> settingsTable = [];
|
||||||
@@ -420,9 +420,9 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService,
|
|||||||
_script.Globals[keyPair.Key] = keyPair.Value;
|
_script.Globals[keyPair.Key] = keyPair.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserData.RegisterType(typeof(ISettingRangeBase<int>));
|
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
UserData.RegisterType(typeof(ISettingControl));
|
UserData.RegisterType(typeof(ISettingControl));
|
||||||
|
_script.Globals["SettingControl"] = UserData.CreateStatic(typeof(ISettingControl));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
new LuaConverters(this).RegisterLuaConverters();
|
new LuaConverters(this).RegisterLuaConverters();
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ internal class MainMenuPatch : ISystem, IEventScreenSelected
|
|||||||
{
|
{
|
||||||
if (mainMenuUIAdded) { return; }
|
if (mainMenuUIAdded) { return; }
|
||||||
|
|
||||||
var textBlock = new GUITextBlock(new RectTransform(new Point(300, 30), screen.Frame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(10, 10) }, "", Color.Red)
|
var textBlock = new GUITextBlock(
|
||||||
|
new RectTransform(new Point(300, 30), screen.Frame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(10, 10) },
|
||||||
|
"", Color.Red, textAlignment: Alignment.TopLeft)
|
||||||
{
|
{
|
||||||
IgnoreLayoutGroups = false
|
IgnoreLayoutGroups = false
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public sealed partial class ModConfigFileParserService :
|
|||||||
public ModConfigFileParserService(IStorageService storageService)
|
public ModConfigFileParserService(IStorageService storageService)
|
||||||
{
|
{
|
||||||
_storageService = storageService;
|
_storageService = storageService;
|
||||||
|
_storageService.UseCaching = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Dispose
|
#region Dispose
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ public sealed class ModConfigService : IModConfigService
|
|||||||
#if CLIENT
|
#if CLIENT
|
||||||
_stylesParserService = stylesParserService;
|
_stylesParserService = stylesParserService;
|
||||||
#endif
|
#endif
|
||||||
|
_storageService.UseCaching = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Dispose
|
#region Dispose
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ public sealed class PackageManagementService : IPackageManagementService
|
|||||||
|
|
||||||
IService.CheckDisposed(this);
|
IService.CheckDisposed(this);
|
||||||
var result = new FluentResults.Result();
|
var result = new FluentResults.Result();
|
||||||
var packages2 = packages.OrderBy(pkg => pkg.Name == "LuaCsForBarotrauma" ? 0 : 1) // always run lua cs first.
|
var packages2 = packages.OrderBy(pkg => pkg.Name == LuaCsSetup.PackageName ? 0 : 1) // always run lua cs first.
|
||||||
.ThenBy(packages.IndexOf)
|
.ThenBy(packages.IndexOf)
|
||||||
.ToImmutableArray();
|
.ToImmutableArray();
|
||||||
|
|
||||||
@@ -318,7 +318,7 @@ public sealed class PackageManagementService : IPackageManagementService
|
|||||||
|
|
||||||
// get loading order. Note: packages not in the execution order list will load first.
|
// get loading order. Note: packages not in the execution order list will load first.
|
||||||
var loadingOrderedPackages = _loadedPackages
|
var loadingOrderedPackages = _loadedPackages
|
||||||
.OrderBy(pkg => pkg.Key.Name == "LuaCsForBarotrauma" ? 0 : 1) // always run lua cs first.
|
.OrderBy(pkg => pkg.Key.Name == LuaCsSetup.PackageName ? 0 : 1) // always run lua cs first.
|
||||||
.ThenBy(pkg => executionOrder.IndexOf(pkg.Key))
|
.ThenBy(pkg => executionOrder.IndexOf(pkg.Key))
|
||||||
.ToImmutableArray();
|
.ToImmutableArray();
|
||||||
var loadOrderByPackage = loadingOrderedPackages.Select(p => p.Key).ToImmutableArray();
|
var loadOrderByPackage = loadingOrderedPackages.Select(p => p.Key).ToImmutableArray();
|
||||||
@@ -415,7 +415,9 @@ public sealed class PackageManagementService : IPackageManagementService
|
|||||||
|
|
||||||
if (_loadedPackages.IsEmpty || _runningPackages.IsEmpty)
|
if (_loadedPackages.IsEmpty || _runningPackages.IsEmpty)
|
||||||
{
|
{
|
||||||
|
#if DEGUG
|
||||||
_logger.LogWarning($"{nameof(StopRunningPackages)}: No packages are currently executing.");
|
_logger.LogWarning($"{nameof(StopRunningPackages)}: No packages are currently executing.");
|
||||||
|
#endif
|
||||||
return FluentResults.Result.Ok();
|
return FluentResults.Result.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using System.Runtime.InteropServices;
|
|||||||
using System.Runtime.Loader;
|
using System.Runtime.Loader;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
using Barotrauma.IO;
|
using Barotrauma.IO;
|
||||||
@@ -88,7 +89,15 @@ public class PluginManagementService : IAssemblyManagementService
|
|||||||
|
|
||||||
private ImmutableArray<MetadataReference> _baseMetadataReferences = ImmutableArray<MetadataReference>.Empty;
|
private ImmutableArray<MetadataReference> _baseMetadataReferences = ImmutableArray<MetadataReference>.Empty;
|
||||||
private ImmutableArray<MetadataReference> _baseMetadataReferencesNonPublicized = ImmutableArray<MetadataReference>.Empty;
|
private ImmutableArray<MetadataReference> _baseMetadataReferencesNonPublicized = ImmutableArray<MetadataReference>.Empty;
|
||||||
|
|
||||||
|
private Thread _backgroundGCCleanupThread = null;
|
||||||
|
private long _backgroundGCWatchdogTicks = 0;
|
||||||
|
|
||||||
|
private static readonly int
|
||||||
|
GC_TASK_COMPLETION_TIMEOUT = 5000,
|
||||||
|
GC_BACKGND_MAXITERATIONS = 2,
|
||||||
|
GC_BACKGND_INTERVAL_MILLIS = 200,
|
||||||
|
GC_BACKGND_GENERATION_WAIT_MILLIS = 100;
|
||||||
|
|
||||||
private IEnumerable<MetadataReference> BaseMetadataReferences
|
private IEnumerable<MetadataReference> BaseMetadataReferences
|
||||||
{
|
{
|
||||||
@@ -174,6 +183,7 @@ public class PluginManagementService : IAssemblyManagementService
|
|||||||
_pluginInjectorContainer?.Dispose();
|
_pluginInjectorContainer?.Dispose();
|
||||||
_pluginInjectorContainer = null;
|
_pluginInjectorContainer = null;
|
||||||
|
|
||||||
|
ReflectionUtils.ResetCache();
|
||||||
foreach (var loader in _assemblyLoaders)
|
foreach (var loader in _assemblyLoaders)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -184,14 +194,6 @@ public class PluginManagementService : IAssemblyManagementService
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger?.LogError($"Failed to dispose of {nameof(IAssemblyLoaderService)} for ContentPackage {loader.Key.Name}: \n{e.Message}");
|
_logger?.LogError($"Failed to dispose of {nameof(IAssemblyLoaderService)} for ContentPackage {loader.Key.Name}: \n{e.Message}");
|
||||||
if (loader.Value.Assemblies.Any())
|
|
||||||
{
|
|
||||||
foreach (var ass in loader.Value.Assemblies)
|
|
||||||
{
|
|
||||||
_logger?.LogWarning($"{nameof(PluginManagementService)}: Fallback manual unsubscription of assemblies: {ass.GetName()}");
|
|
||||||
ReflectionUtils.RemoveAssemblyFromCache(ass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_assemblyLoaders.Clear();
|
_assemblyLoaders.Clear();
|
||||||
@@ -222,6 +224,7 @@ public class PluginManagementService : IAssemblyManagementService
|
|||||||
private IEventService _pluginEventService;
|
private IEventService _pluginEventService;
|
||||||
private Lazy<ILuaPatcher> _pluginLuaPatcherService;
|
private Lazy<ILuaPatcher> _pluginLuaPatcherService;
|
||||||
private Func<IConsoleCommandsService> _consoleCommandServiceFactory;
|
private Func<IConsoleCommandsService> _consoleCommandServiceFactory;
|
||||||
|
private readonly IConsoleCommandsService _internalConsoleCommandsService;
|
||||||
private ILuaCsInfoProvider _luaCsInfoProvider;
|
private ILuaCsInfoProvider _luaCsInfoProvider;
|
||||||
private readonly ConcurrentDictionary<ContentPackage, IAssemblyLoaderService> _assemblyLoaders = new();
|
private readonly ConcurrentDictionary<ContentPackage, IAssemblyLoaderService> _assemblyLoaders = new();
|
||||||
private readonly ConcurrentDictionary<Type, ContentPackage> _pluginPackageLookup = new();
|
private readonly ConcurrentDictionary<Type, ContentPackage> _pluginPackageLookup = new();
|
||||||
@@ -251,6 +254,21 @@ public class PluginManagementService : IAssemblyManagementService
|
|||||||
_pluginLuaPatcherService = pluginLuaPatcherService;
|
_pluginLuaPatcherService = pluginLuaPatcherService;
|
||||||
_consoleCommandServiceFactory = consoleCommandServiceFactory;
|
_consoleCommandServiceFactory = consoleCommandServiceFactory;
|
||||||
_luaCsInfoProvider = luaCsInfoProvider;
|
_luaCsInfoProvider = luaCsInfoProvider;
|
||||||
|
_internalConsoleCommandsService = consoleCommandServiceFactory.Invoke();
|
||||||
|
|
||||||
|
RegisterCommands(_internalConsoleCommandsService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterCommands(IConsoleCommandsService cmdService)
|
||||||
|
{
|
||||||
|
cmdService.RegisterCommand("plugin_forcerungc", "Forces the GC to run", cmds =>
|
||||||
|
{
|
||||||
|
_logger.LogMessage("Forcing GC run.");
|
||||||
|
Task.Factory.StartNew(async () =>
|
||||||
|
{
|
||||||
|
await RunGC(true, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private ServiceContainer CreatePluginServiceContainer()
|
private ServiceContainer CreatePluginServiceContainer()
|
||||||
@@ -317,11 +335,13 @@ public class PluginManagementService : IAssemblyManagementService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public bool TryGetPackageForPlugin<TPlugin>(out ContentPackage ownerPackage)
|
public bool TryGetPackageForPlugin<TPlugin>(out ContentPackage ownerPackage)
|
||||||
{
|
{
|
||||||
return _pluginPackageLookup.TryGetValue(typeof(TPlugin), out ownerPackage);
|
return _pluginPackageLookup.TryGetValue(typeof(TPlugin), out ownerPackage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public Type GetType(string typeName, bool isByRefType = false, bool includeInterfaces = false,
|
public Type GetType(string typeName, bool isByRefType = false, bool includeInterfaces = false,
|
||||||
bool includeDefaultContext = true)
|
bool includeDefaultContext = true)
|
||||||
{
|
{
|
||||||
@@ -370,6 +390,7 @@ public class PluginManagementService : IAssemblyManagementService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoOptimization)]
|
||||||
public FluentResults.Result ActivatePluginInstances(ImmutableArray<ContentPackage> executionOrder, bool excludeAlreadyRunningPackages = true)
|
public FluentResults.Result ActivatePluginInstances(ImmutableArray<ContentPackage> executionOrder, bool excludeAlreadyRunningPackages = true)
|
||||||
{
|
{
|
||||||
if (executionOrder.IsDefaultOrEmpty)
|
if (executionOrder.IsDefaultOrEmpty)
|
||||||
@@ -488,6 +509,7 @@ public class PluginManagementService : IAssemblyManagementService
|
|||||||
return results;
|
return results;
|
||||||
|
|
||||||
// helper
|
// helper
|
||||||
|
[MethodImpl(MethodImplOptions.NoOptimization)]
|
||||||
FluentResults.Result PluginInitRunner(IAssemblyPlugin plugin, Action<IAssemblyPlugin> action)
|
FluentResults.Result PluginInitRunner(IAssemblyPlugin plugin, Action<IAssemblyPlugin> action)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -502,7 +524,7 @@ public class PluginManagementService : IAssemblyManagementService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public FluentResults.Result LoadAssemblyResources(ImmutableArray<IAssemblyResourceInfo> resources)
|
public FluentResults.Result LoadAssemblyResources(ImmutableArray<IAssemblyResourceInfo> resources)
|
||||||
{
|
{
|
||||||
if (resources.IsDefaultOrEmpty)
|
if (resources.IsDefaultOrEmpty)
|
||||||
@@ -705,7 +727,7 @@ public class PluginManagementService : IAssemblyManagementService
|
|||||||
{
|
{
|
||||||
builder.AddRange(BaseMetadataReferencesWithBarotrauma);
|
builder.AddRange(BaseMetadataReferencesWithBarotrauma);
|
||||||
foreach (var loaderService in _assemblyLoaders
|
foreach (var loaderService in _assemblyLoaders
|
||||||
.Where(asl => !asl.Key.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase))
|
.Where(asl => !asl.Key.Name.Equals(LuaCsSetup.PackageName, StringComparison.InvariantCultureIgnoreCase))
|
||||||
.ToImmutableArray())
|
.ToImmutableArray())
|
||||||
{
|
{
|
||||||
builder.AddRange(loaderService.Value.AssemblyReferences.Where(ar => ar is not null));
|
builder.AddRange(loaderService.Value.AssemblyReferences.Where(ar => ar is not null));
|
||||||
@@ -732,7 +754,7 @@ public class PluginManagementService : IAssemblyManagementService
|
|||||||
.Replace(" Barotrauma.Networking.Client.ClientList", " ModUtils.Client.ClientList")
|
.Replace(" Barotrauma.Networking.Client.ClientList", " ModUtils.Client.ClientList")
|
||||||
.Replace("ItemPrefab.GetItemPrefab", "ModUtils.ItemPrefab.GetItemPrefab");
|
.Replace("ItemPrefab.GetItemPrefab", "ModUtils.ItemPrefab.GetItemPrefab");
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntPtr OnAssemblyLoaderResolvingUnmanaged(Assembly callerAssembly, string targetAssemblyName)
|
private IntPtr OnAssemblyLoaderResolvingUnmanaged(Assembly callerAssembly, string targetAssemblyName)
|
||||||
{
|
{
|
||||||
Guard.IsNull(callerAssembly, nameof(callerAssembly));
|
Guard.IsNull(callerAssembly, nameof(callerAssembly));
|
||||||
@@ -805,21 +827,58 @@ public class PluginManagementService : IAssemblyManagementService
|
|||||||
{
|
{
|
||||||
_eventService?.Value?.PublishEvent<IEventAssemblyUnloading>(sub => sub.OnAssemblyUnloading(assembly));
|
_eventService?.Value?.PublishEvent<IEventAssemblyUnloading>(sub => sub.OnAssemblyUnloading(assembly));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_unloadingAssemblyLoaders.Add(loader, loader.OwnerPackage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoOptimization)]
|
||||||
public FluentResults.Result UnloadManagedAssemblies()
|
public FluentResults.Result UnloadManagedAssemblies()
|
||||||
{
|
{
|
||||||
using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
IService.CheckDisposed(this);
|
IService.CheckDisposed(this);
|
||||||
|
|
||||||
if (_assemblyLoaders.Count == 0)
|
|
||||||
{
|
|
||||||
return FluentResults.Result.Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
var results = new FluentResults.Result();
|
var results = new FluentResults.Result();
|
||||||
|
|
||||||
results.WithReasons(UnsafeDisposeManagedTypeInstances().Reasons);
|
if (!_pluginInstances.IsEmpty)
|
||||||
|
{
|
||||||
|
foreach (var instance in _pluginInstances.SelectMany(kvp => kvp.Value))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
instance.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
results.WithError(new ExceptionalError(e));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_pluginInstances.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_pluginEventService is not null)
|
||||||
|
{
|
||||||
|
_eventService.Value.RemoveDispatcherEventService(_pluginEventService);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_pluginEventService.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
results.WithError(new ExceptionalError(e));
|
||||||
|
}
|
||||||
|
_pluginEventService = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_pluginInjectorContainer?.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
results.WithError(new ExceptionalError(e));
|
||||||
|
}
|
||||||
|
_pluginInjectorContainer = null;
|
||||||
|
|
||||||
ReflectionUtils.ResetCache();
|
ReflectionUtils.ResetCache();
|
||||||
foreach (var loaderService in _assemblyLoaders)
|
foreach (var loaderService in _assemblyLoaders)
|
||||||
@@ -827,7 +886,6 @@ public class PluginManagementService : IAssemblyManagementService
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
loaderService.Value.Dispose();
|
loaderService.Value.Dispose();
|
||||||
_unloadingAssemblyLoaders.Add(loaderService.Value, loaderService.Key);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -837,38 +895,7 @@ public class PluginManagementService : IAssemblyManagementService
|
|||||||
|
|
||||||
_assemblyLoaders.Clear();
|
_assemblyLoaders.Clear();
|
||||||
_storageService.PurgeCache();
|
_storageService.PurgeCache();
|
||||||
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true);
|
_pluginPackageLookup.Clear();
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
// Print still loaded assembly load ctx after giving some time
|
|
||||||
CoroutineManager.Invoke(() =>
|
|
||||||
{
|
|
||||||
if (!_unloadingAssemblyLoaders.Any())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
sb.AppendLine("The following ContentPackages have not unloaded their assemblies:");
|
|
||||||
|
|
||||||
foreach (var kvp in _unloadingAssemblyLoaders.ToImmutableArray())
|
|
||||||
{
|
|
||||||
sb.AppendLine($"- '{kvp.Value.Name}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Use DebugConsole in case logger is null by the time this executes.
|
|
||||||
if (_logger is null)
|
|
||||||
{
|
|
||||||
DebugConsole.LogError(sb.ToString());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogWarning(sb.ToString());
|
|
||||||
}
|
|
||||||
}, 3.0f);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// clear native libraries
|
// clear native libraries
|
||||||
if (_loadedNativeLibraries.Any())
|
if (_loadedNativeLibraries.Any())
|
||||||
@@ -888,41 +915,109 @@ public class PluginManagementService : IAssemblyManagementService
|
|||||||
|
|
||||||
_loadedNativeLibraries.Clear();
|
_loadedNativeLibraries.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Task.Factory.StartNew(async () =>
|
||||||
|
{
|
||||||
|
await RunGC(true, false);
|
||||||
|
});
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FluentResults.Result UnsafeDisposeManagedTypeInstances()
|
private void SafeLogUnloadingPackages()
|
||||||
{
|
{
|
||||||
var results = new FluentResults.Result();
|
if (!_unloadingAssemblyLoaders.Any())
|
||||||
|
|
||||||
if (!_pluginInstances.IsEmpty)
|
|
||||||
{
|
{
|
||||||
foreach (var instance in _pluginInstances.SelectMany(kvp => kvp.Value))
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.AppendLine("The following ContentPackages have not unloaded their assemblies:");
|
||||||
|
|
||||||
|
foreach (var kvp in _unloadingAssemblyLoaders.ToImmutableArray())
|
||||||
|
{
|
||||||
|
sb.AppendLine($"- '{kvp.Value.Name}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use DebugConsole in case logger is null by the time this executes.
|
||||||
|
if (_logger is null)
|
||||||
|
{
|
||||||
|
DebugConsole.Log(sb.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning(sb.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GCCleanupTask(TaskCompletionSource<bool> completionSuccess)
|
||||||
|
{
|
||||||
|
GC.RegisterForFullGCNotification(1, 1);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (int iter = 0; iter < GC_BACKGND_MAXITERATIONS; iter++)
|
||||||
{
|
{
|
||||||
try
|
int maxGen = GC.MaxGeneration;
|
||||||
|
for (int currGen = 0; currGen < maxGen; currGen++)
|
||||||
{
|
{
|
||||||
instance.Dispose();
|
GC.Collect(currGen, GCCollectionMode.Forced, false, false); // marking pass
|
||||||
}
|
GC.WaitForFullGCComplete(GC_BACKGND_GENERATION_WAIT_MILLIS);
|
||||||
catch (Exception e)
|
GC.Collect(currGen); // generation cleanup
|
||||||
{
|
|
||||||
results.WithError(new ExceptionalError(e));
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
Thread.Sleep(GC_BACKGND_INTERVAL_MILLIS);
|
||||||
}
|
}
|
||||||
|
completionSuccess.SetResult(true);
|
||||||
}
|
}
|
||||||
|
catch (ThreadInterruptedException tie)
|
||||||
if (_pluginEventService is not null)
|
|
||||||
{
|
{
|
||||||
_eventService.Value.RemoveDispatcherEventService(_pluginEventService);
|
completionSuccess.SetResult(false);
|
||||||
_pluginEventService = null;
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
completionSuccess.SetException(e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
GC.CancelFullGCNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RunGC(bool logResults, bool runOnMainThread)
|
||||||
|
{
|
||||||
|
var gcCompletionSuccess = new TaskCompletionSource<bool>();
|
||||||
|
if (runOnMainThread)
|
||||||
|
{
|
||||||
|
GCCleanupTask(gcCompletionSuccess);
|
||||||
|
if (logResults)
|
||||||
|
{
|
||||||
|
SafeLogUnloadingPackages();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
_pluginInjectorContainer = null;
|
|
||||||
|
|
||||||
_pluginInstances.Clear();
|
var gcThread = new Thread(() =>
|
||||||
_pluginPackageLookup.Clear();
|
{
|
||||||
|
GCCleanupTask(gcCompletionSuccess);
|
||||||
return results;
|
}) { IsBackground = true };
|
||||||
|
gcThread.Start();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await gcCompletionSuccess.Task.WaitAsync(TimeSpan.FromMilliseconds(GC_TASK_COMPLETION_TIMEOUT));
|
||||||
|
}
|
||||||
|
catch (TimeoutException te)
|
||||||
|
{
|
||||||
|
_logger.LogError($"{nameof(RunGC)}: The GC task thread has timed out.");
|
||||||
|
gcThread.Interrupt();
|
||||||
|
gcThread.Join();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logResults)
|
||||||
|
{
|
||||||
|
SafeLogUnloadingPackages();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<Assembly> GetLoadedAssembly(OneOf<AssemblyName, string> assemblyName, in Guid[] excludedContexts)
|
public Result<Assembly> GetLoadedAssembly(OneOf<AssemblyName, string> assemblyName, in Guid[] excludedContexts)
|
||||||
|
|||||||
@@ -2971,11 +2971,39 @@ namespace Barotrauma
|
|||||||
string percentage = string.Format(CultureInfo.InvariantCulture, "{0:P2}", (float)spawnPointsContainingResources / PathPoints.Count);
|
string percentage = string.Format(CultureInfo.InvariantCulture, "{0:P2}", (float)spawnPointsContainingResources / PathPoints.Count);
|
||||||
DebugConsole.NewMessage($"Level resources spawned: {itemCount}\n" +
|
DebugConsole.NewMessage($"Level resources spawned: {itemCount}\n" +
|
||||||
$" Spawn points containing resources: {spawnPointsContainingResources} ({percentage})\n" +
|
$" Spawn points containing resources: {spawnPointsContainingResources} ({percentage})\n" +
|
||||||
$" Total value: {PathPoints.Sum(p => p.ClusterLocations.Sum(c => c.Resources.Sum(r => r.Prefab.DefaultPrice?.Price ?? 0)))} mk");
|
$" Total value: {GetTotalLevelResourceValue()} mk");
|
||||||
if (AbyssResources.Count > 0)
|
if (AbyssResources.Count > 0)
|
||||||
{
|
{
|
||||||
DebugConsole.NewMessage($"Abyss resources spawned: {AbyssResources.Sum(a => a.Resources.Count)}\n" +
|
DebugConsole.NewMessage($"Abyss resources spawned: {AbyssResources.Sum(a => a.Resources.Count)}\n" +
|
||||||
$" Total value: {AbyssResources.Sum(c => c.Resources.Sum(r => r.Prefab.DefaultPrice?.Price ?? 0))} mk");
|
$" Total value: {GetTotalAbyssResourceValue()} mk");
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetTotalLevelResourceValue()
|
||||||
|
{
|
||||||
|
int value = 0;
|
||||||
|
foreach (var pathPoint in PathPoints)
|
||||||
|
{
|
||||||
|
foreach (var clusterLocation in pathPoint.ClusterLocations)
|
||||||
|
{
|
||||||
|
foreach (var resource in clusterLocation.Resources)
|
||||||
|
{
|
||||||
|
value += resource.Prefab.DefaultPrice?.Price ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
int GetTotalAbyssResourceValue()
|
||||||
|
{
|
||||||
|
int value = 0;
|
||||||
|
foreach (var clusterLocation in AbyssResources)
|
||||||
|
{
|
||||||
|
foreach (var resource in clusterLocation.Resources)
|
||||||
|
{
|
||||||
|
value += resource.Prefab.DefaultPrice?.Price ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -3204,7 +3232,6 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <param name="rotation">Used by clients to set the rotation for the resources</param>
|
|
||||||
public List<Item> GenerateMissionResources(ItemPrefab prefab, int requiredAmount, PositionType positionType, IEnumerable<Cave> targetCaves = null)
|
public List<Item> GenerateMissionResources(ItemPrefab prefab, int requiredAmount, PositionType positionType, IEnumerable<Cave> targetCaves = null)
|
||||||
{
|
{
|
||||||
var allValidLocations = GetAllValidClusterLocations();
|
var allValidLocations = GetAllValidClusterLocations();
|
||||||
@@ -5150,6 +5177,7 @@ namespace Barotrauma
|
|||||||
renderer.Dispose();
|
renderer.Dispose();
|
||||||
renderer = null;
|
renderer = null;
|
||||||
}
|
}
|
||||||
|
backgroundCreatureManager?.Clear();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (LevelObjectManager != null)
|
if (LevelObjectManager != null)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace Barotrauma
|
|||||||
partial class LevelObject : ISpatialEntity, IDamageable, ISerializableEntity
|
partial class LevelObject : ISpatialEntity, IDamageable, ISerializableEntity
|
||||||
{
|
{
|
||||||
public readonly LevelObjectPrefab Prefab;
|
public readonly LevelObjectPrefab Prefab;
|
||||||
public Vector3 Position;
|
public Vector3 Position { get; set; }
|
||||||
|
|
||||||
public float NetworkUpdateTimer;
|
public float NetworkUpdateTimer;
|
||||||
|
|
||||||
|
|||||||
@@ -457,7 +457,7 @@ namespace Barotrauma
|
|||||||
if (newObject.NeedsUpdate) { updateableObjects.Add(newObject); }
|
if (newObject.NeedsUpdate) { updateableObjects.Add(newObject); }
|
||||||
//add some variance to the Z position to prevent z-fighting
|
//add some variance to the Z position to prevent z-fighting
|
||||||
//(based on the x and y position of the object, scaled to be visually insignificant)
|
//(based on the x and y position of the object, scaled to be visually insignificant)
|
||||||
newObject.Position.Z += (minX + minY) % 100.0f * 0.00001f;
|
newObject.Position += new Vector3(0, 0, (minX + minY) % 100.0f * 0.00001f);
|
||||||
|
|
||||||
int xStart = (int)Math.Floor(minX / GridSize);
|
int xStart = (int)Math.Floor(minX / GridSize);
|
||||||
int xEnd = (int)Math.Floor(maxX / GridSize);
|
int xEnd = (int)Math.Floor(maxX / GridSize);
|
||||||
|
|||||||
@@ -348,11 +348,22 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplierAffiliated, includeSaved: false));
|
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplierAffiliated, includeSaved: false));
|
||||||
price *= 1f - characters.Max(static c => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplierAffiliated, Tags.StatIdentifierTargetAll));
|
price *= 1f - characters.Max(static c => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplierAffiliated, Tags.StatIdentifierTargetAll));
|
||||||
price *= 1f - characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplierAffiliated, tag)));
|
price *= 1f - characters.Max(c => GetStatValuesForItem(c, item, StatTypes.StoreBuyMultiplierAffiliated));
|
||||||
}
|
}
|
||||||
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplier, includeSaved: false));
|
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplier, includeSaved: false));
|
||||||
price *= 1f - characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplier, tag)));
|
price *= 1f - characters.Max(c => GetStatValuesForItem(c, item, StatTypes.StoreBuyMultiplier));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static float GetStatValuesForItem(Character character, ItemPrefab item, StatTypes statType)
|
||||||
|
{
|
||||||
|
float statValueSum = 0.0f;
|
||||||
|
foreach (Identifier itemTag in item.Tags)
|
||||||
|
{
|
||||||
|
statValueSum += character.Info.GetSavedStatValue(statType, itemTag);
|
||||||
|
}
|
||||||
|
return statValueSum;
|
||||||
|
}
|
||||||
|
|
||||||
// Price should never go below 1 mk
|
// Price should never go below 1 mk
|
||||||
return Math.Max((int)price, 1);
|
return Math.Max((int)price, 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -487,7 +487,19 @@ namespace Barotrauma
|
|||||||
var portrait = new Sprite(subElement, lazyLoad: true);
|
var portrait = new Sprite(subElement, lazyLoad: true);
|
||||||
if (portrait != null)
|
if (portrait != null)
|
||||||
{
|
{
|
||||||
|
#if CLIENT
|
||||||
|
if (!File.Exists(portrait.FilePath))
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"Error in location type \"{Identifier}\": cannot find the location portrait \"{portrait.FilePath}\".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
portraitsList.Add(portrait);
|
||||||
|
}
|
||||||
|
#elif SERVER
|
||||||
|
// Add without checking the path, since servers don't parse the file path of the sprite
|
||||||
portraitsList.Add(portrait);
|
portraitsList.Add(portrait);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "store":
|
case "store":
|
||||||
|
|||||||
@@ -261,15 +261,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var endLocation in EndLocations)
|
AssignEndLocationLevelData(campaign);
|
||||||
{
|
|
||||||
if (endLocation.Type?.ForceLocationName is { IsEmpty: false })
|
|
||||||
{
|
|
||||||
endLocation.ForceName(endLocation.Type.ForceLocationName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AssignEndLocationLevelData();
|
|
||||||
|
|
||||||
//backwards compatibility: if locations go out of bounds (map saved with different generation parameters before width/height were included in the xml)
|
//backwards compatibility: if locations go out of bounds (map saved with different generation parameters before width/height were included in the xml)
|
||||||
float maxX = Locations.Select(l => l.MapPosition.X).Max();
|
float maxX = Locations.Select(l => l.MapPosition.X).Max();
|
||||||
@@ -973,13 +965,43 @@ namespace Barotrauma
|
|||||||
previousToEndLocation.Connections.Add(endConnection);
|
previousToEndLocation.Connections.Add(endConnection);
|
||||||
endLocation.Connections.Add(endConnection);
|
endLocation.Connections.Add(endConnection);
|
||||||
|
|
||||||
AssignEndLocationLevelData();
|
AssignEndLocationLevelData(campaign);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AssignEndLocationLevelData()
|
/// <summary>
|
||||||
|
/// Assigns the correct outpost generation parameters to the end locations. Also checks and ensures that all of them are correctly assigned to the end biome, and have a location type that can be generated in the end biome.
|
||||||
|
/// Strangely shaped custom maps may sometimes generate in a way that there aren't enough locations in the last biome to assign as the end locations, and we may end up choosing locations in the second-to-last biome instead - let's correct that here.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="campaign"></param>
|
||||||
|
/// <exception cref="InvalidOperationException"></exception>
|
||||||
|
private void AssignEndLocationLevelData(CampaignMode campaign)
|
||||||
{
|
{
|
||||||
|
Biome endBiome = Biome.Prefabs.OrderBy(p => p.UintIdentifier).FirstOrDefault(b => b.IsEndBiome) ?? throw new InvalidOperationException("Could not find an end biome to assign to the end locations.");
|
||||||
|
LocationType endLocationType =
|
||||||
|
LocationType.Prefabs
|
||||||
|
.OrderBy(p => p.UintIdentifier)
|
||||||
|
.FirstOrDefault(IsSuitableEndLocationType)
|
||||||
|
?? throw new InvalidOperationException("Could not find an a location type to assign to the end locations.");
|
||||||
|
|
||||||
|
bool IsSuitableEndLocationType(LocationType lt)
|
||||||
|
{
|
||||||
|
return lt.AreaSettings.Any(s =>
|
||||||
|
s.Commonness > 0 &&
|
||||||
|
(s.MatchesBiome(endBiome.Identifier) || s.MatchesZone(generationParams.DifficultyZones)));
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < endLocations.Count; i++)
|
for (int i = 0; i < endLocations.Count; i++)
|
||||||
{
|
{
|
||||||
|
if (endLocations[i].Biome != endBiome)
|
||||||
|
{
|
||||||
|
endLocations[i].Biome = endBiome;
|
||||||
|
endLocations[i].LevelData = new LevelData(endLocations[i], this, endLocations[i].LevelData.Difficulty);
|
||||||
|
}
|
||||||
|
endLocations[i].ChangeType(campaign: campaign, endLocationType);
|
||||||
|
if (endLocationType.ForceLocationName is { IsEmpty: false })
|
||||||
|
{
|
||||||
|
endLocations[i].ForceName(endLocationType.ForceLocationName);
|
||||||
|
}
|
||||||
endLocations[i].LevelData.ReassignGenerationParams(Seed);
|
endLocations[i].LevelData.ReassignGenerationParams(Seed);
|
||||||
var outpostParams = OutpostGenerationParams.OutpostParams.FirstOrDefault(p => p.ForceToEndLocationIndex == i);
|
var outpostParams = OutpostGenerationParams.OutpostParams.FirstOrDefault(p => p.ForceToEndLocationIndex == i);
|
||||||
if (outpostParams != null)
|
if (outpostParams != null)
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
CreateStairBodies();
|
CreateStairBodies();
|
||||||
}
|
}
|
||||||
else if (HasBody)
|
else if (Prefab.Body)
|
||||||
{
|
{
|
||||||
CreateSections();
|
CreateSections();
|
||||||
UpdateSections();
|
UpdateSections();
|
||||||
@@ -346,7 +346,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
Rectangle oldRect = Rect;
|
Rectangle oldRect = Rect;
|
||||||
base.Rect = value;
|
base.Rect = value;
|
||||||
if (HasBody)
|
if (Prefab.Body)
|
||||||
{
|
{
|
||||||
CreateSections();
|
CreateSections();
|
||||||
UpdateSections();
|
UpdateSections();
|
||||||
@@ -668,7 +668,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
prevSections = Sections.ToArray();
|
prevSections = Sections.ToArray();
|
||||||
}
|
}
|
||||||
if (!HasBody)
|
if (!Prefab.Body)
|
||||||
{
|
{
|
||||||
if (FlippedX && IsHorizontal)
|
if (FlippedX && IsHorizontal)
|
||||||
{
|
{
|
||||||
@@ -685,7 +685,7 @@ namespace Barotrauma
|
|||||||
xsections = 1;
|
xsections = 1;
|
||||||
ysections = 1;
|
ysections = 1;
|
||||||
}
|
}
|
||||||
Sections = new WallSection[xsections];
|
Sections = new WallSection[Math.Max(xsections, ysections)];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1635,7 +1635,7 @@ namespace Barotrauma
|
|||||||
CreateStairBodies();
|
CreateStairBodies();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HasBody)
|
if (Prefab.Body)
|
||||||
{
|
{
|
||||||
CreateSections();
|
CreateSections();
|
||||||
UpdateSections();
|
UpdateSections();
|
||||||
@@ -1663,7 +1663,7 @@ namespace Barotrauma
|
|||||||
CreateStairBodies();
|
CreateStairBodies();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HasBody)
|
if (Prefab.Body)
|
||||||
{
|
{
|
||||||
CreateSections();
|
CreateSections();
|
||||||
UpdateSections();
|
UpdateSections();
|
||||||
|
|||||||
@@ -29,6 +29,31 @@ namespace Barotrauma
|
|||||||
|
|
||||||
partial class SubmarineInfo : IDisposable
|
partial class SubmarineInfo : IDisposable
|
||||||
{
|
{
|
||||||
|
public static HashSet<string> SubmarinePathsWithRemoteStorage { get; set; } = [];
|
||||||
|
|
||||||
|
public bool SaveToRemoteStorage
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (FilePath == null) { return false; }
|
||||||
|
|
||||||
|
return SubmarinePathsWithRemoteStorage.Contains(FilePath.CleanUpPathCrossPlatform(correctFilenameCase: false));
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (FilePath == null) { return; }
|
||||||
|
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
SubmarinePathsWithRemoteStorage.Add(FilePath.CleanUpPathCrossPlatform(correctFilenameCase: false));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SubmarinePathsWithRemoteStorage.Remove(FilePath.CleanUpPathCrossPlatform(correctFilenameCase: false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static List<SubmarineInfo> savedSubmarines = new List<SubmarineInfo>();
|
private static List<SubmarineInfo> savedSubmarines = new List<SubmarineInfo>();
|
||||||
public static IEnumerable<SubmarineInfo> SavedSubmarines => savedSubmarines;
|
public static IEnumerable<SubmarineInfo> SavedSubmarines => savedSubmarines;
|
||||||
|
|
||||||
@@ -197,6 +222,8 @@ namespace Barotrauma
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsFromRemoteStorage;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When enabled, the <see cref="SubmarineElement">XML element is not loaded</see> until it is accessed.
|
/// When enabled, the <see cref="SubmarineElement">XML element is not loaded</see> until it is accessed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -562,6 +562,19 @@ namespace Barotrauma
|
|||||||
IgnoredHints.Init(currentConfigDoc.Root.GetChildElement("ignoredhints"));
|
IgnoredHints.Init(currentConfigDoc.Root.GetChildElement("ignoredhints"));
|
||||||
DebugConsoleMapping.Init(currentConfigDoc.Root.GetChildElement("debugconsolemapping"));
|
DebugConsoleMapping.Init(currentConfigDoc.Root.GetChildElement("debugconsolemapping"));
|
||||||
CompletedTutorials.Init(currentConfigDoc.Root.GetChildElement("tutorials"));
|
CompletedTutorials.Init(currentConfigDoc.Root.GetChildElement("tutorials"));
|
||||||
|
var submarineSettings = currentConfigDoc.Root.GetChildElement("submarinesettings");
|
||||||
|
if (submarineSettings != null)
|
||||||
|
{
|
||||||
|
SubmarineInfo.SubmarinePathsWithRemoteStorage.Clear();
|
||||||
|
foreach (XElement subElement in submarineSettings.Elements("SubmarineWithRemoteStorage"))
|
||||||
|
{
|
||||||
|
string path = subElement.GetAttributeString("path", "");
|
||||||
|
if (!path.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
SubmarineInfo.SubmarinePathsWithRemoteStorage.Add(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -689,7 +702,14 @@ namespace Barotrauma
|
|||||||
|
|
||||||
XElement tutorialsElement = new XElement("tutorials"); root.Add(tutorialsElement);
|
XElement tutorialsElement = new XElement("tutorials"); root.Add(tutorialsElement);
|
||||||
CompletedTutorials.Instance.SaveTo(tutorialsElement);
|
CompletedTutorials.Instance.SaveTo(tutorialsElement);
|
||||||
|
|
||||||
|
XElement submarineSettings = new XElement("submarinesettings"); root.Add(submarineSettings);
|
||||||
|
|
||||||
|
SubmarineInfo.SubmarinePathsWithRemoteStorage.ForEach(path =>
|
||||||
|
{
|
||||||
|
submarineSettings.Add(new XElement("SubmarineWithRemoteStorage", new XAttribute("path", path)));
|
||||||
|
});
|
||||||
|
|
||||||
XElement keyMappingElement = new XElement("keymapping",
|
XElement keyMappingElement = new XElement("keymapping",
|
||||||
currentConfig.KeyMap.Bindings.Select(kvp
|
currentConfig.KeyMap.Bindings.Select(kvp
|
||||||
=> new XAttribute(kvp.Key.ToString(), kvp.Value.ToString())));
|
=> new XAttribute(kvp.Key.ToString(), kvp.Value.ToString())));
|
||||||
|
|||||||
@@ -1786,7 +1786,7 @@ namespace Barotrauma
|
|||||||
offset *= item.Scale;
|
offset *= item.Scale;
|
||||||
if (item.FlippedX) { offset.X *= -1; }
|
if (item.FlippedX) { offset.X *= -1; }
|
||||||
if (item.FlippedY) { offset.Y *= -1; }
|
if (item.FlippedY) { offset.Y *= -1; }
|
||||||
offset = Vector2.Transform(offset, Matrix.CreateRotationZ(-item.RotationRad));
|
offset = Vector2.Transform(offset, Matrix.CreateRotationZ(item.body?.Rotation ?? -item.RotationRad));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Barotrauma.IO;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Steamworks;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
namespace Barotrauma.Steam;
|
||||||
|
|
||||||
|
internal static partial class RemoteStorageHelper
|
||||||
|
{
|
||||||
|
public static readonly Color SteamColor = Color.DodgerBlue;
|
||||||
|
public static readonly string DebugPrefix = $"‖color:{SteamColor.ToStringHex()}‖[Remote Storage]‖end‖";
|
||||||
|
|
||||||
|
/// <summary>Attempts to read a file from remote storage into a byte array.</summary>
|
||||||
|
/// <param name="remoteFile">The remote file to read from.</param>
|
||||||
|
/// <param name="bytes">The bytes read from the remote file. Returns <see langword="null"/> if the operation failed.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true"/> if the operation was successful.<br/>
|
||||||
|
/// <see langword="false"/> if the operation failed.
|
||||||
|
/// </returns>
|
||||||
|
public static bool TryRead(this SteamRemoteStorage.RemoteFile remoteFile, [NotNullWhen(returnValue: true)] out byte[]? bytes, bool logError = true)
|
||||||
|
{
|
||||||
|
bytes = SteamRemoteStorage.FileRead(remoteFile.Filename);
|
||||||
|
bool success = bytes != null;
|
||||||
|
|
||||||
|
if (logError && !success)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"{DebugPrefix} Failed to read file \"{remoteFile.Filename}\" from remote storage: operation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Attempts to write a file to remote storage.</summary>
|
||||||
|
/// <param name="localPath">The path of the local file to read from.</param>
|
||||||
|
/// <param name="saveAs">The name of the remote file to write to. If <see langword="null"/>, the file name of <paramref name="localPath"/> is used.</param>
|
||||||
|
/// <param name="allowOverwrite">If <see langword="true"/>, overwriting existing remote files is allowed.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true"/> if the operation was successful.<br/>
|
||||||
|
/// <see langword="false"/> if the operation failed.
|
||||||
|
/// </returns>
|
||||||
|
public static bool TryWrite(string localPath, string? saveAs = null, bool allowOverwrite = false, bool logError = true)
|
||||||
|
{
|
||||||
|
string fileName = saveAs ?? Path.GetFileName(localPath);
|
||||||
|
|
||||||
|
if (!allowOverwrite && SteamRemoteStorage.FileExists(fileName))
|
||||||
|
{
|
||||||
|
if (logError)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"{DebugPrefix} Failed to write file \"{fileName}\" to remote storage: file already exists.");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] data;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
data = File.ReadAllBytes(localPath);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
if (logError)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"{DebugPrefix} Failed to read file \"{fileName}\" while writing to remote storage: {exception}");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = SteamRemoteStorage.FileWrite(fileName, data);
|
||||||
|
|
||||||
|
if (logError && !success)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"{DebugPrefix} Failed to write file \"{fileName}\" to remote storage: operation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Attempts to delete a file from remote storage.</summary>
|
||||||
|
/// <param name="fileName">The name of the remote file to delete.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true"/> if the operation was successful.<br/>
|
||||||
|
/// <see langword="false"/> if the operation failed.
|
||||||
|
/// </returns>
|
||||||
|
public static bool TryDelete(string fileName, bool logError = true)
|
||||||
|
{
|
||||||
|
bool success = SteamRemoteStorage.FileDelete(fileName);
|
||||||
|
|
||||||
|
if (logError && !success)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"{DebugPrefix} Failed to delete file \"{fileName}\" from remote storage: operation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Checks if a file is stored remotely.</summary>
|
||||||
|
/// <param name="fileName">The name of the remote file to check.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true"/> if the file is stored.<br/>
|
||||||
|
/// <see langword="false"/> if the file is not stored or the operation failed.
|
||||||
|
/// </returns>
|
||||||
|
public static bool IsStored(string fileName) => SteamRemoteStorage.FileExists(fileName);
|
||||||
|
}
|
||||||
@@ -1,4 +1,58 @@
|
|||||||
-------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
v1.13.3.1 (Summer Update 2026)
|
||||||
|
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Submarine reworks:
|
||||||
|
- Humpback, Orca 2, Azimuth, Typhon, and Herja have received their visual and gameplay reworks.
|
||||||
|
- The command room of the Orca 2 is now located at the center of the submarine.
|
||||||
|
- Typhon now comes with valves and pipe weakpoints.
|
||||||
|
- Herja has been upgraded with a power distributor, befitting its high-tech theme.
|
||||||
|
|
||||||
|
Changes and additions:
|
||||||
|
- Added an option to back up your custom submarines in the Steam Cloud. Can be enabled per-submarine using a checkbox in the sub editor's save dialog.
|
||||||
|
- Added quality parameter to the give/spawnitem console commands. Allows spawning in items with non-default quality.
|
||||||
|
- The teleportsub console command has a parameter for choosing which submarine to teleport.
|
||||||
|
- The spawncharacter console command has a parameter for renaming the spawned character.
|
||||||
|
- The showseed console command displays the map seed too if used in campaign mode.
|
||||||
|
|
||||||
|
Multiplayer:
|
||||||
|
- Fixed monster attacks that run over time (e.g. when fractal guardians fire the steam cannon) causing an excessive amount of network usage in multiplayer.
|
||||||
|
- Fixed an exploit that allowed modified clients to cause other clients to eventually get out of sync and disconnect.
|
||||||
|
- Fixed inability to drag and drop stacks of items to other players in multiplayer.
|
||||||
|
- Fixed submarine voting not working in campaign mode.
|
||||||
|
|
||||||
|
Miscellaneous fixes:
|
||||||
|
- Fixed security (or anyone else) not reacting to attacking stunned/incapacitated characters.
|
||||||
|
- Fixed the item pickup sound playing multiple times, for every item in a stack you're picking up.
|
||||||
|
- Fixed the item dropping sound playing twice when dropping an item.
|
||||||
|
- Fixed being unable to fabricate certain items with specific combinations of materials. Happened in some cases where the recipe accepted multiple different materials as ingredients: the fabricator would got through the requirements in order, and always take the first available items without considering that the item could've been necessary for another, more strict requirement.
|
||||||
|
- Followup to the "infinite explosion" fix in Summer Update 2025: the previous fix only applied to oxygen tank shelves, but it turned out oxygen generators could also cause the same kind of "explosion loop" where tanks keep exploding and getting refilled by the oxygen generator.
|
||||||
|
- Fixed "inspirational leader" talent not giving bonus XP like the description says it should.
|
||||||
|
- Fixed characters being able to drop off platforms while using a periscope (inconsistent with other movement inputs being disabled while on a periscope).
|
||||||
|
- Fixed bots being unable to extinguish fires in connected subs (e.g. in Remora's drone).
|
||||||
|
- Fixed parts of the CPR button not being clickable on the health HUD on certain resolutions (was getting blocked by the limb indicators).
|
||||||
|
- Fixed nuclear shells fabricated with the cheaper recipe variant not giving the "I am become death" achievement.
|
||||||
|
- Fixed gravity spheres (or more generally, any items with a triggercomponent) taking damage when you cut their trigger area with a plasma cutter, rather than the actual collider of the item.
|
||||||
|
- Fixed equip buttons being clickable despite the slot being hidden. Meant that when you had equipped an item in your hand, you could click an invisible button at the left side of the inventory where the hand slots would appear.
|
||||||
|
- Fixed turrets not showing the ammo on the HUD if the ammo is inside the turret itself, rather than a linked loader.
|
||||||
|
- If one of the unique hireable characters (e.g. Ignatius May, Aunt Doris) dies in the outpost before you hire them, they can no longer appear elsewhere or be hired.
|
||||||
|
- Fixed cargo scooter lights working, but not draining the battery, when the battery is in another slot than the battery slot.
|
||||||
|
- Fixed custom interaction messages set on items in the sub editor no longer appearing in-game.
|
||||||
|
- Allow combining defense bot ammo boxes the same way as other ammo boxes and magazines (merging their ammo together).
|
||||||
|
- Fixed the character deconstruction bag staying in the deconstructor if you do a level transition while a character is inside the deconstructor.
|
||||||
|
- Fixed items duplicating if a character gets deconstructed without dying first (possible e.g. by taking advantage of the Miracle Worker talent).
|
||||||
|
- Fixed crafting blueprint tooltips not showing whether the recipe has been unlocked or not.
|
||||||
|
- Fixed valves potentially getting stuck in a non-interactable state if the round ends immediately after one's been toggled.
|
||||||
|
|
||||||
|
Modding:
|
||||||
|
- Fixed "LockedTalents" PermanentStat locking the talent for everyone (not used in any vanilla talent).
|
||||||
|
- Clients are allowed to use colored text in their chat messages when they have the "chat spam immunity" permission. Colored text was disabled in client-sent chat messages in the previous update due to some ways in which it can be abused, but turns out there were some users relying on this functionality.
|
||||||
|
- Fixed OnDeconstructed status effect triggering when the item is not deconstructed in some cases (e.g. researching unidentified genetic material without stabilozine).
|
||||||
|
- Fixed the special locations at the end of the campaign map generating incorrectly on very short maps.
|
||||||
|
- Fixed status effects using OffsetCopiesEntityTransform not taking physics body rotation into account.
|
||||||
|
- Fixed TagAction's Team setting being ignored when tagging characters in certain ways (e.g. traitors, non-traitors, bots, human prefab tags).
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
v1.12.7.0
|
v1.12.7.0
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ You need a version of Visual Studio that supports C# 10 to compile game. If you
|
|||||||
When installing on Windows, make sure you select ".NET desktop development" during the install process to make sure you have the required features to work with Barotrauma.
|
When installing on Windows, make sure you select ".NET desktop development" during the install process to make sure you have the required features to work with Barotrauma.
|
||||||
|
|
||||||
#### Linux
|
#### Linux
|
||||||
You will need to install the .NET 6 SDK according to the instructions laid out on Microsoft's docs: https://docs.microsoft.com/en-us/dotnet/core/install/linux
|
You will need to install the .NET 8 SDK according to the instructions laid out on Microsoft's docs: https://docs.microsoft.com/en-us/dotnet/core/install/linux
|
||||||
|
|
||||||
To edit the source code, we recommend using [Visual Studio Code](https://code.visualstudio.com/) with [Microsoft's C# extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp).
|
To edit the source code, we recommend using [Visual Studio Code](https://code.visualstudio.com/) with [Microsoft's C# extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp).
|
||||||
|
|
||||||
|
|||||||
@@ -114,8 +114,8 @@ namespace Barotrauma
|
|||||||
public static void ResetCache()
|
public static void ResetCache()
|
||||||
{
|
{
|
||||||
CachedNonAbstractTypes.Clear();
|
CachedNonAbstractTypes.Clear();
|
||||||
CachedNonAbstractTypes.TryAdd(typeof(ReflectionUtils).Assembly, typeof(ReflectionUtils).Assembly.GetTypes().Where(t => !t.IsAbstract).ToImmutableArray());
|
|
||||||
TypeSearchCache.Clear();
|
TypeSearchCache.Clear();
|
||||||
|
CachedNonAbstractTypes.TryAdd(typeof(ReflectionUtils).Assembly, typeof(ReflectionUtils).Assembly.GetTypes().Where(t => !t.IsAbstract).ToImmutableArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Type? GetType(string nameWithNamespace)
|
public static Type? GetType(string nameWithNamespace)
|
||||||
|
|||||||
Reference in New Issue
Block a user