Compare commits
160 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 61f05d25d0 | |||
| 9794fe50b4 | |||
| c1073ff3d2 | |||
| 06616535f4 | |||
| 1cd0178e0a | |||
| a729269763 | |||
| c452493555 | |||
| d420e1efbb | |||
| eb22fbddff | |||
| 1f5ee454c0 | |||
| d80ac4a38f | |||
| cfde6f3579 | |||
| 81f57ea3e7 | |||
| ada9a09410 | |||
| 66717c7fe7 | |||
| 59bc21973a | |||
| 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 | |||
| 2cfa1e6ffd | |||
| 5207b381b7 | |||
| 679ffd380b | |||
| de1f1c599f | |||
| d16707a2f3 | |||
| 740ace88f0 | |||
| 8bfe8a2c37 | |||
| f3c22315a1 | |||
| 59da9211f6 | |||
| 82d26b5bb8 | |||
| 4c151a4cf1 | |||
| 85b71f1dd6 | |||
| 24484496d2 | |||
| 190c98d8f2 | |||
| 046483b9da | |||
| 177cf89756 | |||
| e167a34f32 | |||
| bd1e624eb1 | |||
| 1db14631df | |||
| 59bf2749dd | |||
| ad0bcddaa4 | |||
| f485583621 | |||
| 49355fe32b | |||
| 356ae6cf74 | |||
| 770f76a658 | |||
| 42ba733cd4 | |||
| 45312af297 | |||
| baee73e132 | |||
| 279f40c82e | |||
| aaf0763e09 | |||
| c5fa49405f | |||
| 3d96e4adb6 | |||
| 42af6f2ec0 | |||
| 31812d524d | |||
| 21a2863a1a | |||
| bd643503b3 | |||
| 51a1fb1235 | |||
| 8f0eec7031 | |||
| 6dd36a1575 | |||
| 46595b1399 | |||
| 90962b2328 | |||
| edd50ef181 | |||
| ced6dca7a3 | |||
| 559aeb3c3f | |||
| 16131e0acc | |||
| e8f377c20d | |||
| 27896d53ef | |||
| a44d89f953 | |||
| 75465f2f21 | |||
| 3aadff7a3c | |||
| c65101537d | |||
| b35eee5561 | |||
| cdfe87c81b | |||
| 7e899d900a | |||
| a037e616bf | |||
| f7650bd6df | |||
| 716a35701c | |||
| 6032010847 | |||
| 5446795196 | |||
| 31ed540939 | |||
| e715fdc835 | |||
| 81f44969ee | |||
| fc35d888fc | |||
| eb01597dd3 | |||
| 572430cc2b | |||
| 7b263676e0 | |||
| c8123383d5 | |||
| bdd6c52e4e | |||
| de65cd88bc | |||
| 83ca51a99b | |||
| 1def7b7b8d | |||
| dadd6c598f | |||
| b891ec7793 | |||
| 5b823d8684 | |||
| b146fa326d | |||
| bab02fca8a | |||
| 0b6882c37f | |||
| b1072b7a50 | |||
| 70a039da83 | |||
| 7efb112058 | |||
| 9c2f300325 | |||
| 4d4f1f8351 | |||
| 9dc98192f4 | |||
| b1a9757b40 | |||
| 522f58511c | |||
| d98f9de5d4 | |||
| 7d9642a5b1 | |||
| dc08b6b92a | |||
| 220ccb2eac | |||
| eb8c59efb5 | |||
| f87e943a1a | |||
| 2f845b40ca | |||
| d0a5d13a0e | |||
| fff157d5ca |
@@ -73,7 +73,7 @@ body:
|
||||
label: Version
|
||||
description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu.
|
||||
options:
|
||||
- v1.11.5.0 (Winter Update 2025 Hotfix 1)
|
||||
- v1.13.3.1 (Summer Update 2026)
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -4,6 +4,20 @@ name: Publish release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target:
|
||||
description: "The git ref to checkout, build from and release"
|
||||
required: true
|
||||
type: string
|
||||
tag:
|
||||
description: "The tag of the release"
|
||||
required: true
|
||||
type: string
|
||||
prerelease:
|
||||
description: "Prerelease"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
workflow_call:
|
||||
inputs:
|
||||
target:
|
||||
@@ -23,13 +37,13 @@ on:
|
||||
env:
|
||||
CI_DIR: 2049ef39-42a2-46d2-b513-ee6d2e3a7b15
|
||||
RELEASES: |
|
||||
windows:client:Windows/Client
|
||||
windows:server:Windows/Server
|
||||
linux:client:Linux/Client
|
||||
linux:server:Linux/Server
|
||||
mac:client:Mac/Client/Barotrauma.app/Contents/MacOS
|
||||
mac:server:Mac/Server
|
||||
ARCHIVE_BASE_NAME: luacsforbarotrauma
|
||||
windows:client:Windows/Client
|
||||
linux:client:Linux/Client
|
||||
mac:client:Mac/Client/Barotrauma.app/Contents/MacOS
|
||||
ARCHIVE_BASE_NAME: luacsforbarotraumaEP
|
||||
# XXX: these file names are subject to shell expansion.
|
||||
# Be careful when using special characters.
|
||||
ARCHIVE_FILES_SERVER: |
|
||||
|
||||
@@ -60,3 +60,4 @@ Deploy/DeployAll/PrivateKey.*
|
||||
|
||||
#Rider
|
||||
*.DotSettings.user
|
||||
.vscode/settings.json
|
||||
|
||||
@@ -258,7 +258,9 @@ namespace Barotrauma
|
||||
//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) =>
|
||||
{
|
||||
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.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
|
||||
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.17f, 0.17f), characterIndicatorArea.RectTransform, Anchor.BottomLeft, scaleBasis: ScaleBasis.Smallest), text: "", style: "CPRButton")
|
||||
cprButton = new GUIButton(new RectTransform(new Vector2(0.75f), left.RectTransform, Anchor.BottomLeft, scaleBasis: ScaleBasis.Smallest), text: "", style: "CPRButton")
|
||||
{
|
||||
UserData = UIHighlightAction.ElementId.CPRButton,
|
||||
OnClicked = (button, userData) =>
|
||||
@@ -316,12 +321,11 @@ namespace Barotrauma
|
||||
|
||||
return true;
|
||||
},
|
||||
ToolTip = TextManager.Get("doctor.cprobjective"),
|
||||
IgnoreLayoutGroups = true,
|
||||
ToolTip = TextManager.Get("tutorial.roles.medic.objective.cpr"),
|
||||
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) =>
|
||||
{
|
||||
DrawHealthWindow(spriteBatch, component.RectTransform.Rect, true);
|
||||
@@ -368,8 +372,6 @@ namespace Barotrauma
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
characterIndicatorArea.Recalculate();
|
||||
|
||||
healthBarHolder = new GUIFrame(new RectTransform(Point.Zero, GUI.Canvas), style: null)
|
||||
{
|
||||
HoverCursor = CursorState.Hand
|
||||
|
||||
@@ -867,6 +867,19 @@ namespace Barotrauma
|
||||
});
|
||||
}, 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) =>
|
||||
{
|
||||
SubEditorScreen.ShouldDrawGrid = !SubEditorScreen.ShouldDrawGrid;
|
||||
@@ -3544,6 +3557,11 @@ namespace Barotrauma
|
||||
ContentPackageManager.RegularPackages.Select(p => p.Name).ToArray()
|
||||
}));
|
||||
|
||||
commands.Add(new Command("ShowServerPerf", "Immediately log server performance info", (string[] args) =>
|
||||
{
|
||||
// TODO: Not yet :)
|
||||
}));
|
||||
|
||||
#if WINDOWS
|
||||
commands.Add(new Command("startdedicatedserver", "", (string[] args) =>
|
||||
{
|
||||
@@ -3577,6 +3595,14 @@ namespace Barotrauma
|
||||
}
|
||||
}));*/
|
||||
|
||||
AssignOnClientExecute(
|
||||
"ShowServerPerf",
|
||||
(string[] args) =>
|
||||
{
|
||||
GameMain.Client?.SendConsoleCommand("ShowServerPerf");
|
||||
}
|
||||
);
|
||||
|
||||
AssignOnClientExecute(
|
||||
"giveperm",
|
||||
(string[] args) =>
|
||||
@@ -4441,17 +4467,24 @@ namespace Barotrauma
|
||||
|
||||
public static void StartLocalMPSession(int numClients = 2)
|
||||
{
|
||||
string extraArguments = "-multiclienttestmode";
|
||||
if (NetConfig.UseLenientHandshake)
|
||||
{
|
||||
extraArguments += " -lenienthandshake";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (Process.GetProcessesByName("DedicatedServer").Length == 0)
|
||||
{
|
||||
#if WINDOWS
|
||||
Process.Start("DedicatedServer.exe", arguments: "-multiclienttestmode");
|
||||
Process.Start("DedicatedServer.exe", arguments: extraArguments);
|
||||
#else
|
||||
Process.Start("./DedicatedServer", arguments: "-multiclienttestmode");
|
||||
Process.Start("./DedicatedServer", arguments: extraArguments);
|
||||
#endif
|
||||
System.Threading.Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
GameClient.MultiClientTestMode = true;
|
||||
#endif
|
||||
@@ -4465,10 +4498,13 @@ namespace Barotrauma
|
||||
for (int i = 2; i <= numClients; i++)
|
||||
{
|
||||
System.Threading.Thread.Sleep(1000);
|
||||
|
||||
string clientArguments = $"-connect server localhost -username client{i} -skipintro";
|
||||
|
||||
#if WINDOWS
|
||||
Process.Start("Barotrauma.exe", arguments: "-connect server localhost -username client" + i + " -multiclienttestmode");
|
||||
Process.Start("Barotrauma.exe", arguments: $"{clientArguments} {extraArguments}");
|
||||
#else
|
||||
Process.Start("./Barotrauma", arguments: "-connect server localhost -username client" + i + " -multiclienttestmode");
|
||||
Process.Start("./Barotrauma", arguments: $"{clientArguments} {extraArguments}");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -650,11 +651,11 @@ namespace Barotrauma
|
||||
|
||||
if (MouseOn != null)
|
||||
{
|
||||
MouseOn.OnDrawToolTip?.Invoke(MouseOn);
|
||||
if (!MouseOn.ToolTip.IsNullOrWhiteSpace())
|
||||
{
|
||||
MouseOn.DrawToolTip(spriteBatch);
|
||||
}
|
||||
MouseOn.OnDrawToolTip?.Invoke(MouseOn);
|
||||
}
|
||||
|
||||
if (SubEditorScreen.IsSubEditor())
|
||||
@@ -2323,10 +2324,10 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Creates a 7-segment display.
|
||||
/// </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="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)
|
||||
{
|
||||
@@ -2337,7 +2338,7 @@ namespace Barotrauma
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -384,14 +384,14 @@ namespace Barotrauma
|
||||
CaretIndex = forcedCaretIndex == - 1 ? textBlock.GetCaretIndexFromScreenPos(PlayerInput.MousePosition) : forcedCaretIndex;
|
||||
CalculateCaretPos();
|
||||
ClearSelection();
|
||||
bool wasSelected = selected;
|
||||
selected = true;
|
||||
GUI.KeyboardDispatcher.Subscriber = this;
|
||||
OnSelected?.Invoke(this, Keys.None);
|
||||
if (!wasSelected && PlaySoundOnSelect && !ignoreSelectSound)
|
||||
if (!selected && PlaySoundOnSelect && !ignoreSelectSound)
|
||||
{
|
||||
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()
|
||||
|
||||
@@ -113,7 +113,10 @@ namespace Barotrauma
|
||||
|
||||
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);
|
||||
|
||||
double noiseT = Timing.TotalTime * 0.02f;
|
||||
|
||||
@@ -278,6 +278,12 @@ namespace Barotrauma
|
||||
GameClient.MultiClientTestMode = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ConsoleArguments.Contains("-lenienthandshake"))
|
||||
{
|
||||
NetConfig.UseLenientHandshake = true;
|
||||
}
|
||||
|
||||
GUI.KeyboardDispatcher = new EventInput.KeyboardDispatcher(Window);
|
||||
|
||||
PerformanceCounter = new PerformanceCounter();
|
||||
|
||||
@@ -128,12 +128,6 @@ namespace Barotrauma
|
||||
(int)SlotPositions[i].Y,
|
||||
(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>();
|
||||
if (itemContainer != null)
|
||||
{
|
||||
@@ -622,6 +616,7 @@ namespace Barotrauma
|
||||
|
||||
for (int i = 0; i < capacity; i++)
|
||||
{
|
||||
if (HideSlot(i)) { continue; }
|
||||
var item = slots[i].FirstOrDefault();
|
||||
if (item != null)
|
||||
{
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayMsg = TextManager.ParseInputTypes(TextManager.Get(Msg));
|
||||
DisplayMsg = TextManager.ParseInputTypes(TextManager.Get(Msg)).Fallback(Msg);
|
||||
}
|
||||
|
||||
CharacterHUD.RecreateHudTextsIfControlling(Character.Controlled);
|
||||
|
||||
+42
-8
@@ -12,9 +12,12 @@ namespace Barotrauma.Items.Components
|
||||
private partial class PowerGroup
|
||||
{
|
||||
private GUIFrame? frame;
|
||||
private GUIFrame? groupContent;
|
||||
private GUILayoutGroup? nameGroup;
|
||||
private GUITextBox? nameBox;
|
||||
private GUITextBlock? loadDisplayNameLabel;
|
||||
private GUIScrollBar? ratioSlider;
|
||||
private readonly List<GUITextBlock> powerUnitLabels = new List<GUITextBlock>();
|
||||
private readonly List<GUITextBlock> powerUnitLabels = [];
|
||||
private GUIFrame? divider;
|
||||
|
||||
public bool IsVisible { get; private set; } = true;
|
||||
@@ -22,9 +25,9 @@ namespace Barotrauma.Items.Components
|
||||
public void CreateGUI()
|
||||
{
|
||||
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
|
||||
};
|
||||
@@ -37,7 +40,7 @@ namespace Barotrauma.Items.Components
|
||||
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,
|
||||
OverflowClip = true,
|
||||
@@ -48,17 +51,40 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
};
|
||||
nameBox.OnSelected += (tb, _) =>
|
||||
{
|
||||
if (tb.Selected) { return; }
|
||||
tb.Text = Name;
|
||||
};
|
||||
nameBox.OnDeselected += (tb, _) =>
|
||||
{
|
||||
Name = tb.Text;
|
||||
tb.CaretIndex = 0;
|
||||
if (GameMain.Client == null) { return; }
|
||||
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) },
|
||||
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();
|
||||
|
||||
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")
|
||||
{
|
||||
Step = SupplyRatioStep,
|
||||
@@ -78,16 +104,17 @@ namespace Barotrauma.Items.Components
|
||||
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),
|
||||
out GUITextBlock? _, out GUITextBlock _,
|
||||
out GUITextBlock? _, out GUITextBlock ratioDisplayUnitLabel,
|
||||
rightLabelText: "%");
|
||||
ratioDisplay.TextGetter = () => DisplayRatio.ToString();
|
||||
|
||||
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);
|
||||
outputDisplay.TextGetter = () => distributor.IsShortCircuited(PowerOut) ? "err" : MathUtils.RoundToInt(distributor.CalculatePowerOut(this)).ToString();
|
||||
|
||||
powerUnitLabels.Add(loadDisplayUnitLabel);
|
||||
powerUnitLabels.Add(ratioDisplayUnitLabel);
|
||||
powerUnitLabels.Add(outputDisplayUnitLabel);
|
||||
GUITextBlock.AutoScaleAndNormalize(powerUnitLabels);
|
||||
|
||||
@@ -111,7 +138,14 @@ namespace Barotrauma.Items.Components
|
||||
IsVisible = PowerOut.Wires.Count >= 1;
|
||||
frame!.Visible = IsVisible;
|
||||
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);
|
||||
|
||||
List<Item> availableAmmo = new List<Item>();
|
||||
List<Item> availableAmmo = [];
|
||||
AddAmmoFromContainer(item.GetComponent<ItemContainer>());
|
||||
foreach (MapEntity e in item.linkedTo)
|
||||
{
|
||||
if (!(e is Item linkedItem)) { continue; }
|
||||
var itemContainer = linkedItem.GetComponent<ItemContainer>();
|
||||
if (itemContainer == null) { continue; }
|
||||
if (e is not Item linkedItem) { continue; }
|
||||
AddAmmoFromContainer(linkedItem.GetComponent<ItemContainer>());
|
||||
}
|
||||
|
||||
void AddAmmoFromContainer(ItemContainer itemContainer)
|
||||
{
|
||||
if (itemContainer == null) { return; }
|
||||
availableAmmo.AddRange(itemContainer.Inventory.AllItems);
|
||||
//add empty slots too
|
||||
for (int i = 0; i < itemContainer.Inventory.Capacity - itemContainer.Inventory.AllItems.Count(); i++)
|
||||
{
|
||||
availableAmmo.Add(null);
|
||||
|
||||
@@ -339,6 +339,19 @@ namespace Barotrauma
|
||||
toolTip += $"‖color:{conditionColorStr}‖ ({(int)item.ConditionPercentage} %)‖color:end‖";
|
||||
}
|
||||
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)
|
||||
{
|
||||
colorStr = XMLExtensions.ToStringHex(Color.MediumPurple);
|
||||
@@ -357,18 +370,6 @@ namespace Barotrauma
|
||||
#if DEBUG
|
||||
toolTip += $" ({item.Prefab.Identifier})";
|
||||
#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))
|
||||
{
|
||||
toolTip += $"\n‖color:gui.blue‖{TextManager.ParseInputTypes(TextManager.Get("itemmsgcontextualorders"))}‖color:end‖";
|
||||
@@ -1301,7 +1302,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
SoundPlayer.PlayUISound(GUISoundType.DropItem);
|
||||
bool removed = false;
|
||||
if (Screen.Selected is SubEditorScreen editor)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ public sealed class SettingControl : SettingBase, ISettingControl
|
||||
public SettingControl(IConfigInfo configInfo, Func<OneOf<string, XElement, object>, bool> valueChangePredicate) : base(configInfo)
|
||||
{
|
||||
_valueChangePredicate = valueChangePredicate;
|
||||
TrySetValue(configInfo.Element);
|
||||
TrySetSerializedValue(configInfo.Element);
|
||||
}
|
||||
|
||||
protected override void OnDispose()
|
||||
@@ -37,7 +37,7 @@ public sealed class SettingControl : SettingBase, ISettingControl
|
||||
public override string GetStringValue() => Value.ToString();
|
||||
public override string GetDefaultStringValue() => new KeyOrMouse(Keys.NumLock).ToString();
|
||||
|
||||
public override bool TrySetValue(OneOf<string, XElement> value)
|
||||
public override bool TrySetSerializedValue(OneOf<string, XElement> value)
|
||||
{
|
||||
var newVal = value.Match<KeyOrMouse>(
|
||||
(string v) => GetKeyOrMouse(v),
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
using System;
|
||||
using Barotrauma.CharacterEditor;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.LuaCs;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Barotrauma.CharacterEditor;
|
||||
using Barotrauma.LuaCs;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using static System.Collections.Specialized.BitVector32;
|
||||
|
||||
// ReSharper disable ObjectCreationAsStatement
|
||||
|
||||
@@ -16,92 +18,31 @@ namespace Barotrauma
|
||||
{
|
||||
partial class LuaCsSetup
|
||||
{
|
||||
private bool _isClientPromptActive;
|
||||
private bool _isCsEnabledForSession = false;
|
||||
|
||||
public void CheckRunConditionalHostingCsEnabled(Action onReadyToRun)
|
||||
public void PromptCSharpMods(Action<bool> onSelection, bool joiningServer)
|
||||
{
|
||||
var res = ReadyToRunNoPrompt();
|
||||
if (res.ShouldRun)
|
||||
{
|
||||
onReadyToRun?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
DisplayCsModsPromptClient(res.Item2, (selectedYes) =>
|
||||
{
|
||||
if (selectedYes)
|
||||
{
|
||||
onReadyToRun?.Invoke();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private (bool ShouldRun, ImmutableArray<ContentPackage> PromptPackages) ReadyToRunNoPrompt()
|
||||
{
|
||||
if (this.IsCsEnabled)
|
||||
{
|
||||
return (true, ImmutableArray<ContentPackage>.Empty);
|
||||
}
|
||||
|
||||
if (!ShouldPromptForCs)
|
||||
{
|
||||
return (true, ImmutableArray<ContentPackage>.Empty);
|
||||
}
|
||||
|
||||
ImmutableArray<ContentPackage> contentPackages = PackageManagementService.GetLoadedAssemblyPackages()
|
||||
.Where(p => p.Name != PackageId)
|
||||
ImmutableArray<ContentPackage> contentPackages = PackageManagementService.GetLoadedUnrestrictedPackages()
|
||||
.Where(p => p.Name != PackageName)
|
||||
.ToImmutableArray();
|
||||
|
||||
return (contentPackages.IsEmpty, contentPackages);
|
||||
}
|
||||
|
||||
partial void CheckReadyToRun(Action onReadyToRun)
|
||||
{
|
||||
var res = ReadyToRunNoPrompt();
|
||||
if (res.ShouldRun)
|
||||
if (_csRunPolicy?.Value is "Enabled")
|
||||
{
|
||||
onReadyToRun?.Invoke();
|
||||
IsCsEnabledForSession = true;
|
||||
onSelection(true);
|
||||
return;
|
||||
}
|
||||
else if (_csRunPolicy?.Value is "Disabled")
|
||||
{
|
||||
IsCsEnabledForSession = false;
|
||||
onSelection(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameMain.Client?.ClientPeer is P2POwnerPeer)
|
||||
if (contentPackages.None())
|
||||
{
|
||||
SetCsPolicyAndContinue(true);
|
||||
onSelection(true);
|
||||
return;
|
||||
}
|
||||
|
||||
DisplayCsModsPromptClient(res.PromptPackages, (selectedYes) =>
|
||||
{
|
||||
SetCsPolicyAndContinue(selectedYes);
|
||||
return;
|
||||
});
|
||||
|
||||
void SetCsPolicyAndContinue(bool csSessionExecutionPolicy)
|
||||
{
|
||||
var prevRunState = this.CurrentRunState;
|
||||
if (CurrentRunState >= RunState.Running)
|
||||
{
|
||||
SetRunState(RunState.LoadedNoExec);
|
||||
}
|
||||
this._isCsEnabledForSession = csSessionExecutionPolicy;
|
||||
CoroutineManager.Invoke(() =>
|
||||
{
|
||||
if (CurrentRunState != prevRunState)
|
||||
{
|
||||
SetRunState(prevRunState);
|
||||
}
|
||||
onReadyToRun?.Invoke();
|
||||
}, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayCsModsPromptClient(ImmutableArray<ContentPackage> contentPackages, Action<bool> onSelection)
|
||||
{
|
||||
if (_isClientPromptActive) { return; }
|
||||
|
||||
_isClientPromptActive = true;
|
||||
|
||||
GUIMessageBox messageBox = new GUIMessageBox(
|
||||
TextManager.Get("warning"),
|
||||
relativeSize: new Vector2(0.3f, 0.55f),
|
||||
@@ -115,7 +56,7 @@ namespace Barotrauma
|
||||
Stretch = true
|
||||
};
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), msgBoxLayout.RectTransform), "The following mods contain CSharp code",
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), msgBoxLayout.RectTransform), "The following mods contain CSharp code OR Unsandboxed Lua Code",
|
||||
font: GUIStyle.SubHeadingFont, wrap: true, textAlignment: Alignment.Center);
|
||||
|
||||
GUIListBox packageListBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.4f), msgBoxLayout.RectTransform))
|
||||
@@ -126,22 +67,39 @@ namespace Barotrauma
|
||||
foreach (ContentPackage package in contentPackages)
|
||||
{
|
||||
GUIFrame packageFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.15f), packageListBox.Content.RectTransform), style: "ListBoxElement");
|
||||
new GUITextBlock(new RectTransform(new Vector2(1f, 1f), packageFrame.RectTransform), package.Name);
|
||||
GUILayoutGroup packageLayout = new GUILayoutGroup(new RectTransform(Vector2.One, packageFrame.RectTransform), true, Anchor.CenterLeft);
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.7f, 1f), packageLayout.RectTransform), package.Name);
|
||||
new GUIButton(new RectTransform(new Vector2(0.3f, 1f), packageLayout.RectTransform, Anchor.CenterRight), "Open Folder", style: "GUIButtonSmall")
|
||||
{
|
||||
OnClicked = (GUIButton button, object obj) =>
|
||||
{
|
||||
string directory = package.Dir;
|
||||
if (string.IsNullOrEmpty(directory)) { return false; }
|
||||
|
||||
ToolBox.OpenFileWithShell(directory);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0f), msgBoxLayout.RectTransform), "C# mods are not sandboxed, meaning that they have unrestrictive access to your computer, please make sure you trust these mods before you continue. If you are not hosting a server, selecting cancel will only run Lua mods.", wrap: true)
|
||||
string bodyText =
|
||||
joiningServer ?
|
||||
"You are joining a server that includes mods with C# code OR unrestricted Lua code. These mods are not sandboxed and may access your computer without restrictions. If you trust these mods, select 'Enable C# for this session'. Otherwise, select 'Cancel' to run only Lua mods."
|
||||
: "You have enabled mods that include C# code. These mods are not sandboxed and may access your computer without restrictions. If you trust these mods, select 'Enable C# for this session'. Otherwise, select 'Cancel' to run only Sandboxed Lua mods.";
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0f), msgBoxLayout.RectTransform), bodyText, wrap: true)
|
||||
{
|
||||
Wrap = true
|
||||
};
|
||||
|
||||
GUILayoutGroup buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), messageBox.Content.RectTransform, Anchor.BottomCenter), isHorizontal: false, childAnchor: Anchor.TopCenter);
|
||||
|
||||
new GUIButton(new RectTransform(new Vector2(0.8f, 0.0f), buttonLayout.RectTransform), "Continue")
|
||||
new GUIButton(new RectTransform(new Vector2(0.8f, 0.0f), buttonLayout.RectTransform), "Enable C# for this session")
|
||||
{
|
||||
TextBlock = { AutoScaleHorizontal = true },
|
||||
OnClicked = (btn, userdata) =>
|
||||
{
|
||||
_isClientPromptActive = false;
|
||||
IsCsEnabledForSession = true;
|
||||
onSelection(true);
|
||||
messageBox.Close();
|
||||
return true;
|
||||
@@ -152,7 +110,7 @@ namespace Barotrauma
|
||||
{
|
||||
OnClicked = (btn, userdata) =>
|
||||
{
|
||||
_isClientPromptActive = false;
|
||||
IsCsEnabledForSession = false;
|
||||
onSelection(false);
|
||||
messageBox.Close();
|
||||
return true;
|
||||
@@ -201,10 +159,18 @@ namespace Barotrauma
|
||||
case SpriteEditorScreen:
|
||||
case SubEditorScreen:
|
||||
case TestScreen: // notes: TestScreen is a Linux edge case editor screen and is deprecated.
|
||||
CheckReadyToRun(() =>
|
||||
|
||||
if (screen is NetLobbyScreen && CurrentRunState != RunState.Running && GameMain.Client?.ClientPeer is not P2POwnerPeer)
|
||||
{
|
||||
PromptCSharpMods(selection =>
|
||||
{
|
||||
SetRunState(RunState.Running);
|
||||
}, joiningServer: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetRunState(RunState.Running);
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Logger.LogError(
|
||||
|
||||
@@ -32,11 +32,11 @@ partial class NetworkingService : INetworkingService, IEventServerConnected, IEv
|
||||
}
|
||||
}
|
||||
|
||||
public void OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader)
|
||||
public bool? OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader)
|
||||
{
|
||||
if (serverPacketHeader != ServerHeader)
|
||||
{
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
ServerToClient luaCsHeader = (ServerToClient)netMessage.ReadByte();
|
||||
@@ -55,6 +55,8 @@ partial class NetworkingService : INetworkingService, IEventServerConnected, IEv
|
||||
ReadIds(netMessage);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SendSyncMessage()
|
||||
|
||||
+178
-32
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Linq;
|
||||
@@ -20,23 +21,87 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
||||
private string _selectedSearchQuery = string.Empty;
|
||||
private ContentPackage _selectedContentPackage;
|
||||
private string _selectedCategory = string.Empty;
|
||||
private ImmutableArray<ISettingBase> _currentlyDisplayedSettings;
|
||||
private ILoggerService _loggerService;
|
||||
|
||||
private bool _promptOpen = false;
|
||||
|
||||
|
||||
// Note: "static" instead of "const" for Hot Reload and to allow changing at runtime.
|
||||
// ReSharper disable FieldCanBeMadeReadOnly.Local
|
||||
|
||||
// --- UI controls ---
|
||||
private static float MenuTitleHeight = 0.06f; // (ContentDisplayAreaHeightContainer + MenuTitleHeight) < 1f
|
||||
private static float ContentDisplayAreaHeightContainer = 0.93f;
|
||||
private static float ContentDisplayAreaHeightInnerCategories = 0.99f;
|
||||
private static float ContentDisplayAreaHeightInnerSettings = 0.97f;
|
||||
private static float ContentLeftRightSplitPosition = 0.3f;
|
||||
|
||||
// Search Bar
|
||||
private static float SearchBarLayoutHeight = 0.06f;
|
||||
private static float SearchBarLabelWidth = 0.1f;
|
||||
private static float SearchBarLabelBoxSpacing = 0.05f;
|
||||
|
||||
private static float SearchBarTextBoxWidth = 1f - SearchBarLabelWidth - SearchBarLabelBoxSpacing;
|
||||
|
||||
// Categories, Packages Display Area
|
||||
private static float CategoriesDisplayListHeight = 0.945f;
|
||||
private static float CategoryButtonHeightRelative = 0.122f;
|
||||
private static float PackageSelectionButtonHeight = 0.07f;
|
||||
|
||||
private static Color CategoryButtonHoverSelectColor = new Color(50, 50, 50, 255);
|
||||
private static Color CategoryButtonTextColor = Color.PeachPuff;
|
||||
private static Color CategoryButtonTextColorSelected = Color.White;
|
||||
private static Color CategoryButtonColorPressed = Color.TransparentBlack;
|
||||
|
||||
// Settings Display Area
|
||||
private static float SettingLabelWidth = 0.6f;
|
||||
private static float SettingControlWidth = 0.4f;
|
||||
private static float SettingHeight = 0.05625f/ContentDisplayAreaHeightContainer/ContentDisplayAreaHeightInnerSettings;
|
||||
private static Color SettingEntryLabelTextColor = Color.PeachPuff;
|
||||
private static string SettingGUIFrameStyle = "";
|
||||
private static Color? SettingGUIFrameColor = null;
|
||||
|
||||
// settings reset
|
||||
private static Vector2 SettingsResetButtonTopSpacer = new Vector2(0f, 0.02f);
|
||||
private static Vector2 SettingsResetButtonDimensions = new Vector2(0.3f, 0.05f);
|
||||
private static string SettingsResetButtonStyle = "GUIButtonSmall";
|
||||
private static Color SettingsResetButtonColor = Color.DarkOliveGreen;
|
||||
private static Color SettingsResetButtonHoverColor = Color.Olive;
|
||||
private static Color SettingsResetButtonTextColor = Color.PeachPuff;
|
||||
private static Color SettingsResetButtonTextColorSelected = Color.White;
|
||||
|
||||
private static Vector2 ResetConfirmationPromptDimensions = new Vector2(0.15f, 0.2f);
|
||||
|
||||
|
||||
// ReSharper restore FieldCanBeMadeReadOnly.Local
|
||||
private const string SettingsResetButtonText = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetVisibleSettings";
|
||||
private const string SettingsResetPromptTitle = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetPrompt.Title";
|
||||
private const string SettingsResetPromptContents = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetPrompt.Message";
|
||||
private const string SettingsResetPromptYesText = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetPrompt.Yes";
|
||||
private const string SettingsResetPromptNoText = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetPrompt.No";
|
||||
|
||||
|
||||
private event Action OnApplyInstalledModsChanges;
|
||||
|
||||
public ModsGameplaySettingsMenu(GUIFrame contentFrame,
|
||||
IPackageManagementService packageManagementService,
|
||||
IConfigService configService,
|
||||
ILoggerService loggerService,
|
||||
SettingsMenu settingsMenuInstance) : base(contentFrame, packageManagementService, configService, settingsMenuInstance)
|
||||
{
|
||||
_settingsInstancesGameplay = configService.GetDisplayableConfigs()
|
||||
.ToImmutableArray();
|
||||
|
||||
_loggerService = loggerService;
|
||||
|
||||
var mainLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), contentFrame.RectTransform, Anchor.Center), false, Anchor.TopLeft);
|
||||
// page title
|
||||
var menuTitleLayoutGroup = new GUILayoutGroup(
|
||||
new RectTransform(new Vector2(1f, 0.06f), mainLayoutGroup.RectTransform, Anchor.TopLeft), true, Anchor.TopLeft);
|
||||
GUIUtil.Label(menuTitleLayoutGroup, "Mods Gameplay Settings", GUIStyle.LargeFont, new Vector2(1f, 1f));
|
||||
new RectTransform(new Vector2(1f, MenuTitleHeight), mainLayoutGroup.RectTransform, Anchor.TopLeft), true, Anchor.TopLeft);
|
||||
GUIUtil.Label(menuTitleLayoutGroup,
|
||||
GetLocalizedString($"{LuaCsSetup.PackageName}.SettingsMenu.ModGameplayButton", "Mod Gameplay Settings"),
|
||||
GUIStyle.LargeFont, new Vector2(1f, 1f));
|
||||
|
||||
// page contents
|
||||
var contentAreaLayoutGroup = new GUILayoutGroup(
|
||||
@@ -44,10 +109,10 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
||||
Anchor.TopLeft);
|
||||
|
||||
var searchBarLayoutGroup = new GUILayoutGroup(
|
||||
new RectTransform(new Vector2(1f, 0.06f), contentAreaLayoutGroup.RectTransform, Anchor.TopCenter), true, Anchor.CenterLeft);
|
||||
GUIUtil.Label(searchBarLayoutGroup, "Search: ", GUIStyle.SubHeadingFont, new Vector2(0.1f, 1f));
|
||||
new RectTransform(new Vector2(1f, SearchBarLayoutHeight), contentAreaLayoutGroup.RectTransform, Anchor.TopCenter), true, Anchor.CenterLeft);
|
||||
GUIUtil.Label(searchBarLayoutGroup, "Search: ", GUIStyle.SubHeadingFont, new Vector2(SearchBarLabelWidth, 1f));
|
||||
var searchBar = new GUITextBox(
|
||||
new RectTransform(new Vector2(0.85f, 0.1f), searchBarLayoutGroup.RectTransform, Anchor.TopLeft),
|
||||
new RectTransform(new Vector2(SearchBarTextBoxWidth, 0.1f), searchBarLayoutGroup.RectTransform, Anchor.TopLeft),
|
||||
createClearButton: true)
|
||||
{
|
||||
OnTextChangedDelegate = (btn, txt) =>
|
||||
@@ -56,12 +121,13 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// main display area
|
||||
var settingsContentAreaGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.90f), contentAreaLayoutGroup.RectTransform, Anchor.BottomCenter));
|
||||
var settingsContentAreaGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, ContentDisplayAreaHeightContainer), contentAreaLayoutGroup.RectTransform, Anchor.BottomCenter));
|
||||
GUIUtil.Spacer(settingsContentAreaGroup, Vector2.One);
|
||||
(_modCategoryDisplayGroup, _settingsDisplayGroup) = GUIUtil.CreateSidebars(settingsContentAreaGroup, true);
|
||||
_modCategoryDisplayGroup.RectTransform.RelativeSize = new Vector2(0.3f, 1f);
|
||||
_settingsDisplayGroup.RectTransform.RelativeSize = new Vector2(0.7f, 1f);
|
||||
_modCategoryDisplayGroup.RectTransform.RelativeSize = new Vector2(ContentLeftRightSplitPosition, ContentDisplayAreaHeightInnerCategories);
|
||||
_settingsDisplayGroup.RectTransform.RelativeSize = new Vector2(1f-ContentLeftRightSplitPosition, ContentDisplayAreaHeightInnerSettings);
|
||||
|
||||
// default category
|
||||
_selectedCategory = "All";
|
||||
@@ -202,10 +268,11 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
||||
_selectedCategory = string.Empty;
|
||||
GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetTargetPackagesList(), GetDisplayCategoriesList());
|
||||
GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList());
|
||||
}, new Vector2(1f, 0.07f));
|
||||
var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.945f), layoutGroup.RectTransform));
|
||||
const float entryHeight = 0.122f;
|
||||
float sizeY = MathF.Max(categories.Length * entryHeight, 1f);
|
||||
}, new Vector2(1f, PackageSelectionButtonHeight));
|
||||
var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, CategoriesDisplayListHeight), layoutGroup.RectTransform));
|
||||
|
||||
|
||||
float sizeY = MathF.Max(categories.Length * CategoryButtonHeightRelative, 1f);
|
||||
var displayedCategoriesFrame = new GUIFrame(new RectTransform(new Vector2(1f, sizeY), containerBox.Content.RectTransform), style: null, color: Color.Black)
|
||||
{
|
||||
CanBeFocused = false
|
||||
@@ -214,17 +281,18 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
||||
|
||||
foreach (var category in categories)
|
||||
{
|
||||
var btn = new GUIButton(new RectTransform(new Vector2(1f, entryHeight), displayCategoriesLayout.RectTransform),
|
||||
var btn = new GUIButton(new RectTransform(new Vector2(1f, CategoryButtonHeightRelative), displayCategoriesLayout.RectTransform),
|
||||
text: category, color: Color.TransparentBlack)
|
||||
{
|
||||
CanBeFocused = true,
|
||||
CanBeSelected = true,
|
||||
TextColor = Color.PeachPuff,
|
||||
HoverColor = new Color(50, 50, 50, 255),
|
||||
HoverTextColor = Color.White,
|
||||
SelectedColor = new Color(50, 50, 50, 255),
|
||||
SelectedTextColor = Color.White,
|
||||
OnPressed = () =>
|
||||
TextColor = CategoryButtonTextColor,
|
||||
HoverColor = CategoryButtonHoverSelectColor,
|
||||
HoverTextColor = CategoryButtonTextColorSelected,
|
||||
PressedColor = CategoryButtonColorPressed,
|
||||
SelectedColor = CategoryButtonHoverSelectColor,
|
||||
SelectedTextColor = CategoryButtonHoverSelectColor,
|
||||
OnClicked = (btn, obj) =>
|
||||
{
|
||||
_selectedCategory = category;
|
||||
GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList());
|
||||
@@ -237,26 +305,47 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
||||
void GenerateSettingsListDisplay(GUILayoutGroup layoutGroup, ImmutableArray<ISettingBase> settings)
|
||||
{
|
||||
layoutGroup.ClearChildren();
|
||||
const float settingHeight = 0.0625f;
|
||||
_currentlyDisplayedSettings = settings;
|
||||
|
||||
var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f), layoutGroup.RectTransform));
|
||||
var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f-SettingsResetButtonDimensions.Y), layoutGroup.RectTransform));
|
||||
foreach (var setting in settings)
|
||||
{
|
||||
var entry = AddSettingToDisplay(
|
||||
setting,
|
||||
containerBox.Content.RectTransform,
|
||||
settingHeight: settingHeight,
|
||||
labelSize: new Vector2(0.6f, 1f),
|
||||
controlSize: new Vector2(0.4f, 1f));
|
||||
|
||||
|
||||
settingHeight: SettingHeight,
|
||||
labelSize: new Vector2(SettingLabelWidth, 1f),
|
||||
controlSize: new Vector2(SettingControlWidth, 1f));
|
||||
}
|
||||
|
||||
var spacer = new GUIFrame(new RectTransform(SettingsResetButtonTopSpacer, layoutGroup.RectTransform),
|
||||
style: null, color: Color.TransparentBlack);
|
||||
|
||||
var resetSettingsButton = new GUIButton(
|
||||
new RectTransform(SettingsResetButtonDimensions, layoutGroup.RectTransform),
|
||||
GetLocalizedString(SettingsResetButtonText, "Reset Visible Settings"),
|
||||
style: SettingsResetButtonStyle)
|
||||
{
|
||||
CanBeSelected = true,
|
||||
CanBeFocused = true,
|
||||
Color = SettingsResetButtonColor,
|
||||
HoverColor = SettingsResetButtonHoverColor,
|
||||
SelectedColor = SettingsResetButtonHoverColor,
|
||||
SelectedTextColor = SettingsResetButtonTextColorSelected,
|
||||
TextColor = SettingsResetButtonTextColor,
|
||||
OnClicked = (btn, obj) =>
|
||||
{
|
||||
DisplayResetConfirmationPrompt(settings);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
(GUIFrame entryFrame, GUILayoutGroup entryLayoutGroup) AddSettingToDisplay(ISettingBase setting,
|
||||
RectTransform parent, float settingHeight, Vector2 labelSize, Vector2 controlSize)
|
||||
(GUIFrame entryFrame, GUILayoutGroup entryLayoutGroup)
|
||||
AddSettingToDisplay(ISettingBase setting, RectTransform parent, float settingHeight, Vector2 labelSize, Vector2 controlSize)
|
||||
{
|
||||
GUIFrame entryFrame = new GUIFrame(new RectTransform(new Vector2(1f, settingHeight), parent))
|
||||
GUIFrame entryFrame = new GUIFrame(new RectTransform(new Vector2(1f, settingHeight), parent),
|
||||
style: SettingGUIFrameStyle, color: SettingGUIFrameColor)
|
||||
{
|
||||
Color = Color.DarkGray
|
||||
};
|
||||
@@ -266,9 +355,10 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
||||
new GUIFrame(new RectTransform(new Vector2(0.02f, 1f), entryLayoutGroup.RectTransform),
|
||||
color: Color.TransparentBlack);
|
||||
|
||||
// setting label
|
||||
new GUITextBlock(new RectTransform(labelSize - new Vector2(0.05f, 0f), entryLayoutGroup.RectTransform),
|
||||
GetLocalizedString(setting.GetDisplayInfo().DisplayName, setting.GetDisplayInfo().DisplayName),
|
||||
textColor: Color.PeachPuff,
|
||||
textColor: SettingEntryLabelTextColor,
|
||||
font: GUIStyle.SmallFont,
|
||||
textAlignment: Alignment.Left)
|
||||
{
|
||||
@@ -281,6 +371,58 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
||||
});
|
||||
return (entryFrame, entryLayoutGroup);
|
||||
}
|
||||
|
||||
void DisplayResetConfirmationPrompt(ImmutableArray<ISettingBase> settings)
|
||||
{
|
||||
if (_promptOpen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_promptOpen = true;
|
||||
|
||||
var msgBox = new GUIMessageBox(GetLocalizedString(SettingsResetPromptTitle, "Reset Visible Settings"),
|
||||
GetLocalizedString(SettingsResetPromptContents,
|
||||
"Are you sure you want to reset the values for currently displayed settings?"),
|
||||
new LocalizedString[]
|
||||
{
|
||||
GetLocalizedString(SettingsResetPromptYesText, "Yes"),
|
||||
GetLocalizedString(SettingsResetPromptNoText, "No")
|
||||
}, ResetConfirmationPromptDimensions);
|
||||
msgBox.Buttons[0].OnClicked = (btn, obj) =>
|
||||
{
|
||||
ResetValuesForDisplayedSettings(settings);
|
||||
btn.Visible = false;
|
||||
_promptOpen = false;
|
||||
msgBox.Close();
|
||||
return true;
|
||||
};
|
||||
msgBox.Buttons[1].OnClicked = (btn, obj) =>
|
||||
{
|
||||
btn.Visible = false;
|
||||
_promptOpen = false;
|
||||
msgBox.Close();
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
void ResetValuesForDisplayedSettings(ImmutableArray<ISettingBase> settings)
|
||||
{
|
||||
if (settings.IsDefaultOrEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NewValuesCache.Clear();
|
||||
foreach (var setting in settings)
|
||||
{
|
||||
var str = setting.GetDefaultStringValue();
|
||||
NewValuesCache[setting] = str;
|
||||
loggerService.LogDebug($"Resetting value for {setting.InternalName} to '{str}'");
|
||||
}
|
||||
|
||||
ApplyInstalledModChanges();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -303,8 +445,12 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
||||
continue;
|
||||
}
|
||||
|
||||
kvp.Key.TrySetValue(kvp.Value);
|
||||
ConfigService.SaveConfigValue(kvp.Key);
|
||||
var success = kvp.Key.TrySetSerializedValue(kvp.Value);
|
||||
if (success)
|
||||
{
|
||||
ConfigService.SaveConfigValue(kvp.Key);
|
||||
_loggerService.LogDebug($"Applied save value for {kvp.Key.InternalName} of {kvp.Value.ToString()}");
|
||||
}
|
||||
}
|
||||
NewValuesCache.Clear();
|
||||
OnApplyInstalledModsChanges?.Invoke();
|
||||
|
||||
+3
-1
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Microsoft.Xna.Framework;
|
||||
using OneOf;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
@@ -12,7 +14,7 @@ internal abstract class ModsSettingsMenuBase : IDisposable
|
||||
protected IPackageManagementService PackageManagementService { get; private set; }
|
||||
protected IConfigService ConfigService { get; private set; }
|
||||
protected SettingsMenu SettingsMenuInstance { get; private set; }
|
||||
protected readonly ConcurrentDictionary<ISettingBase, string> NewValuesCache = new();
|
||||
protected readonly ConcurrentDictionary<ISettingBase, OneOf<string, XElement>> NewValuesCache = new();
|
||||
|
||||
protected ModsSettingsMenuBase(GUIFrame contentFrame,
|
||||
IPackageManagementService packageManagementService,
|
||||
|
||||
+7
-4
@@ -18,12 +18,14 @@ public class SettingsMenuSystem : ISettingsMenuSystem
|
||||
private readonly Harmony _harmony;
|
||||
private readonly IPackageManagementService _packageManagementService;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly ILoggerService _loggerService;
|
||||
private static SettingsMenuSystem SystemInstance;
|
||||
|
||||
public SettingsMenuSystem(IPackageManagementService packageManagementService, IConfigService configService)
|
||||
public SettingsMenuSystem(IPackageManagementService packageManagementService, IConfigService configService, ILoggerService loggerService)
|
||||
{
|
||||
_packageManagementService = packageManagementService;
|
||||
_configService = configService;
|
||||
_loggerService = loggerService;
|
||||
SystemInstance = this;
|
||||
_harmony = Harmony.CreateAndPatchAll(typeof(SettingsMenuSystem));
|
||||
}
|
||||
@@ -44,12 +46,13 @@ public class SettingsMenuSystem : ISettingsMenuSystem
|
||||
var tabControlsIndex = (SettingsMenu.Tab)tabCount+1;
|
||||
|
||||
_gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance,
|
||||
"SettingsMenuTab.Mods", "LuaCsForBarotrauma.SettingsMenu.ModGameplayButton");
|
||||
GUIStyle.ComponentStyles.ContainsKey("SettingsMenuTab.LuaCsSettings") ? "SettingsMenuTab.LuaCsSettings" : "SettingsMenuTab.Mods",
|
||||
$"{LuaCsSetup.PackageName}.SettingsMenu.ModGameplayButton");
|
||||
/*_controlsContentFrame = CreateNewContentTab(tabControlsIndex, __instance,
|
||||
"SettingsMenuTab.Controls", "LuaCsForBarotrauma.SettingsMenu.ModControlsButton");
|
||||
"SettingsMenuTab.Controls", $"{LuaCsSetup.PackageName}.SettingsMenu.ModControlsButton");
|
||||
*/
|
||||
|
||||
_gameplayMenuInstance = new ModsGameplaySettingsMenu(_gameplayContentFrame, _packageManagementService, _configService, __instance);
|
||||
_gameplayMenuInstance = new ModsGameplaySettingsMenu(_gameplayContentFrame, _packageManagementService, _configService, _loggerService, __instance);
|
||||
//_controlsMenuInstance = new ModsControlsSettingsMenu(_controlsContentFrame, _packageManagementService, _configService, __instance);
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -8,7 +8,7 @@ using System.Xml.Linq;
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
class BackgroundCreature : ISteerable
|
||||
class BackgroundCreature : ISteerable, ILevelRenderableObject
|
||||
{
|
||||
const float MaxDepth = 10000.0f;
|
||||
|
||||
@@ -76,6 +76,8 @@ namespace Barotrauma
|
||||
set;
|
||||
}
|
||||
|
||||
public Vector3 Position => new Vector3(position.X, position.Y, Depth);
|
||||
|
||||
public BackgroundCreature(BackgroundCreaturePrefab prefab, Vector2 position)
|
||||
{
|
||||
this.Prefab = prefab;
|
||||
|
||||
+3
-53
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -15,52 +14,11 @@ namespace Barotrauma
|
||||
|
||||
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()
|
||||
{
|
||||
/*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 IEnumerable<BackgroundCreature> VisibleCreatures => visibleCreatures;
|
||||
|
||||
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)
|
||||
{
|
||||
foreach (BackgroundCreature creature in visibleCreatures)
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace Barotrauma
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class LevelObject
|
||||
partial class LevelObject : ILevelRenderableObject
|
||||
{
|
||||
public float SwingTimer;
|
||||
public float ScaleOscillateTimer;
|
||||
|
||||
+136
-67
@@ -8,13 +8,20 @@ using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public interface ILevelRenderableObject
|
||||
{
|
||||
public Vector3 Position { get; }
|
||||
|
||||
}
|
||||
|
||||
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?
|
||||
private readonly List<LevelObject> visibleObjectsBack = new List<LevelObject>(MaxVisibleObjects);
|
||||
private readonly List<LevelObject> visibleObjectsMid = new List<LevelObject>(MaxVisibleObjects);
|
||||
private readonly List<LevelObject> visibleObjectsFront = new List<LevelObject>(MaxVisibleObjects);
|
||||
private readonly HashSet<LevelObject> allVisibleObjects = new HashSet<LevelObject>(MaxVisibleObjects);
|
||||
private readonly List<ILevelRenderableObject> visibleObjectsBack = new List<ILevelRenderableObject>(MaxVisibleObjects);
|
||||
private readonly List<ILevelRenderableObject> visibleObjectsMid = new List<ILevelRenderableObject>(MaxVisibleObjects);
|
||||
private readonly List<ILevelRenderableObject> visibleObjectsFront = new List<ILevelRenderableObject>(MaxVisibleObjects);
|
||||
private readonly HashSet<ILevelRenderableObject> allVisibleObjects = new HashSet<ILevelRenderableObject>(MaxVisibleObjects);
|
||||
|
||||
private double NextRefreshTime;
|
||||
|
||||
@@ -35,35 +42,44 @@ namespace Barotrauma
|
||||
|
||||
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>
|
||||
/// Returns all visible objects, but not in order, because internally uses a HashSet.
|
||||
/// </summary>
|
||||
public IEnumerable<LevelObject> GetAllVisibleObjects()
|
||||
public IEnumerable<ILevelRenderableObject> GetAllVisibleObjects()
|
||||
{
|
||||
allVisibleObjects.Clear();
|
||||
foreach (LevelObject obj in visibleObjectsBack)
|
||||
foreach (ILevelRenderableObject obj in visibleObjectsBack)
|
||||
{
|
||||
allVisibleObjects.Add(obj);
|
||||
}
|
||||
foreach (LevelObject obj in visibleObjectsMid)
|
||||
foreach (ILevelRenderableObject obj in visibleObjectsMid)
|
||||
{
|
||||
allVisibleObjects.Add(obj);
|
||||
}
|
||||
foreach (LevelObject obj in visibleObjectsFront)
|
||||
foreach (ILevelRenderableObject obj in visibleObjectsFront)
|
||||
{
|
||||
allVisibleObjects.Add(obj);
|
||||
}
|
||||
@@ -73,7 +89,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Checks which level objects are in camera view and adds them to the visibleObjects lists
|
||||
/// </summary>
|
||||
private void RefreshVisibleObjects(Rectangle currentIndices, float zoom)
|
||||
private void RefreshVisibleObjects(Rectangle currentIndices, BackgroundCreatureManager backgroundCreatureManager, float zoom)
|
||||
{
|
||||
visibleObjectsBack.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
|
||||
//(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
|
||||
@@ -165,28 +202,28 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Draw the objects behind the level walls
|
||||
/// </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>
|
||||
/// Draw the objects in front of the level walls, but behind characters
|
||||
/// </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>
|
||||
/// Draw the objects in front of the level walls and characters
|
||||
/// </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;
|
||||
indices.X = (int)Math.Floor(cam.WorldView.X / (float)GridSize);
|
||||
@@ -207,7 +244,7 @@ namespace Barotrauma
|
||||
float z = 0.0f;
|
||||
if (ForceRefreshVisibleObjects || (currentGridIndices != indices && Timing.TotalTime > NextRefreshTime))
|
||||
{
|
||||
RefreshVisibleObjects(indices, cam.Zoom);
|
||||
RefreshVisibleObjects(indices, backgroundCreatureManager, cam.Zoom);
|
||||
ForceRefreshVisibleObjects = false;
|
||||
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;
|
||||
|
||||
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();
|
||||
}
|
||||
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)
|
||||
if (levelObject.CurrentSpriteDeformation != null)
|
||||
{
|
||||
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);
|
||||
levelObject.ActivePrefab.DeformableSprite.Deform(levelObject.CurrentSpriteDeformation);
|
||||
}
|
||||
trigger.PhysicsBody.UpdateDrawPosition();
|
||||
trigger.PhysicsBody.DebugDraw(spriteBatch, trigger.IsTriggered ? Color.Cyan : Color.DarkCyan);
|
||||
else
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -214,8 +214,9 @@ namespace Barotrauma
|
||||
//calculate the sum of the forces of nearby level triggers
|
||||
//and use it to move the water texture and water distortion effect
|
||||
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; }
|
||||
//use the largest water flow velocity of all the triggers
|
||||
Vector2 objectMaxFlow = Vector2.Zero;
|
||||
@@ -274,11 +275,7 @@ namespace Barotrauma
|
||||
SamplerState.LinearWrap, DepthStencilState.DepthRead, null, null,
|
||||
cam.Transform);
|
||||
|
||||
backgroundSpriteManager?.DrawObjectsBack(spriteBatch, cam);
|
||||
if (cam.Zoom > 0.05f)
|
||||
{
|
||||
backgroundCreatureManager?.Draw(spriteBatch, cam);
|
||||
}
|
||||
backgroundSpriteManager?.DrawObjectsBack(spriteBatch, backgroundCreatureManager, cam);
|
||||
|
||||
level.GenerationParams.DrawWaterParticles(spriteBatch, cam, waterParticleOffset);
|
||||
|
||||
@@ -292,17 +289,18 @@ namespace Barotrauma
|
||||
BlendState.NonPremultiplied,
|
||||
SamplerState.LinearClamp, DepthStencilState.DepthRead, null, null,
|
||||
cam.Transform);
|
||||
backgroundSpriteManager?.DrawObjectsMid(spriteBatch, cam);
|
||||
backgroundSpriteManager?.DrawObjectsMid(spriteBatch, backgroundCreatureManager, cam);
|
||||
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,
|
||||
BlendState.NonPremultiplied,
|
||||
SamplerState.LinearClamp, DepthStencilState.DepthRead, null, null,
|
||||
cam.Transform);
|
||||
backgroundSpriteManager?.DrawObjectsFront(spriteBatch, cam);
|
||||
backgroundSpriteManager?.DrawObjectsFront(spriteBatch, backgroundCreatureManager, cam);
|
||||
spriteBatch.End();
|
||||
}
|
||||
|
||||
|
||||
@@ -2547,7 +2547,7 @@ namespace Barotrauma.Networking
|
||||
segmentTable.StartNewSegment(ClientNetSegment.SyncIds);
|
||||
//outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
|
||||
outmsg.WriteUInt16(ChatMessage.LastID);
|
||||
outmsg.WriteUInt16(EntityEventManager.LastReceivedID);
|
||||
outmsg.WriteUInt16(SpoofEntityManagerReceivedId ? (ushort)1 : EntityEventManager.LastReceivedID);
|
||||
outmsg.WriteUInt16(LastClientListUpdateID);
|
||||
|
||||
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
|
||||
@@ -3370,6 +3370,12 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
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;
|
||||
|
||||
public bool TypingChatMessage(GUITextBox textBox, string text)
|
||||
|
||||
@@ -89,6 +89,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
byte queueId = msg.ReadByte();
|
||||
float distanceFactor = msg.ReadRangedSingle(0.0f, 1.0f, 8);
|
||||
bool isRadio = msg.ReadBoolean();
|
||||
VoipQueue queue = queues.Find(q => q.QueueID == queueId);
|
||||
|
||||
if (queue == null)
|
||||
@@ -117,19 +118,21 @@ namespace Barotrauma.Networking
|
||||
float rangeMultiplier = spectating ? 2.0f : 1.0f;
|
||||
WifiComponent senderRadio = null;
|
||||
|
||||
var messageType =
|
||||
!client.VoipQueue.ForceLocal &&
|
||||
ChatMessage.CanUseRadio(client.Character, out senderRadio) &&
|
||||
(spectating || (ChatMessage.CanUseRadio(Character.Controlled, out var recipientRadio) && senderRadio.CanReceive(recipientRadio)))
|
||||
? ChatMessageType.Radio : ChatMessageType.Default;
|
||||
var messageType = isRadio ? ChatMessageType.Radio : ChatMessageType.Default;
|
||||
|
||||
client.Character.ShowTextlessSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
|
||||
|
||||
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters;
|
||||
client.RadioNoise = 0.0f;
|
||||
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.SetRange(senderRadio.Range * RangeNear * speechImpedimentMultiplier * rangeMultiplier, senderRadio.Range * speechImpedimentMultiplier * rangeMultiplier);
|
||||
client.VoipSound.SetRange(senderRadioRange * RangeNear * speechImpedimentMultiplier * rangeMultiplier, senderRadioRange * speechImpedimentMultiplier * rangeMultiplier);
|
||||
if (distanceFactor > RangeNear && !spectating)
|
||||
{
|
||||
//noise starts increasing exponentially after 40% range
|
||||
|
||||
@@ -533,6 +533,18 @@ namespace Barotrauma
|
||||
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
|
||||
|
||||
var minButtonSize = new Point(120, 20);
|
||||
@@ -1015,25 +1027,20 @@ namespace Barotrauma
|
||||
|
||||
private void TryStartServer()
|
||||
{
|
||||
LuaCsSetup.Instance.CheckRunConditionalHostingCsEnabled(() =>
|
||||
if (SubmarineInfo.SavedSubmarines.Any(s => s.CalculatingHash))
|
||||
{
|
||||
if (SubmarineInfo.SavedSubmarines.Any(s => s.CalculatingHash))
|
||||
var waitBox = new GUIMessageBox(TextManager.Get("pleasewait"), TextManager.Get("waitforsubmarinehashcalculations"), new LocalizedString[] { TextManager.Get("cancel") });
|
||||
var waitCoroutine = CoroutineManager.StartCoroutine(WaitForSubmarineHashCalculations(waitBox), "WaitForSubmarineHashCalculations");
|
||||
waitBox.Buttons[0].OnClicked += (btn, userdata) =>
|
||||
{
|
||||
var waitBox = new GUIMessageBox(TextManager.Get("pleasewait"), TextManager.Get("waitforsubmarinehashcalculations"), new LocalizedString[] { TextManager.Get("cancel") });
|
||||
var waitCoroutine = CoroutineManager.StartCoroutine(WaitForSubmarineHashCalculations(waitBox), "WaitForSubmarineHashCalculations");
|
||||
waitBox.Buttons[0].OnClicked += (btn, userdata) =>
|
||||
{
|
||||
CoroutineManager.StopCoroutines(waitCoroutine);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
StartServer();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
CoroutineManager.StopCoroutines(waitCoroutine);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
StartServer();
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<CoroutineStatus> WaitForSubmarineHashCalculations(GUIMessageBox messageBox)
|
||||
@@ -1131,6 +1138,11 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
|
||||
if (NetConfig.UseLenientHandshake)
|
||||
{
|
||||
arguments.Add("-lenienthandshake");
|
||||
}
|
||||
|
||||
var processInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Sounds;
|
||||
using Barotrauma.Steam;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Steamworks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.LuaCs.Events;
|
||||
using Barotrauma.Sounds;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -2044,6 +2047,8 @@ namespace Barotrauma
|
||||
|
||||
private bool SaveSubToFile(string name, ContentPackage packageToSaveTo)
|
||||
{
|
||||
bool remoteStorageWasEnabled = Submarine.MainSub.Info.SaveToRemoteStorage;
|
||||
|
||||
Type subFileType = DetermineSubFileType(MainSub?.Info.Type ?? SubmarineType.Player);
|
||||
|
||||
static string getExistingFilePath(ContentPackage package, string fileName)
|
||||
@@ -2272,6 +2277,16 @@ namespace Barotrauma
|
||||
linkedSubBox.AddItem(sub.Name, sub);
|
||||
}
|
||||
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));
|
||||
|
||||
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);
|
||||
|
||||
GUIButton createTabberBtn(string labelTag)
|
||||
@@ -3746,7 +3797,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
var package = GetLocalPackageThatOwnsSub(subInfo);
|
||||
if (package != null)
|
||||
if (package != null || subInfo.IsFromRemoteStorage)
|
||||
{
|
||||
deleteBtn.Enabled = true;
|
||||
}
|
||||
@@ -3771,13 +3822,29 @@ namespace Barotrauma
|
||||
searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = sender.Text.IsNullOrEmpty(); };
|
||||
searchBox.OnTextChanged += (textBox, text) => { FilterSubs(subList, text); return true; };
|
||||
|
||||
var sortedSubs = GetLoadableSubs()
|
||||
.OrderBy(s => s.Type)
|
||||
.ThenBy(s => s.Name)
|
||||
.ToList();
|
||||
List<SubmarineInfo> allSubs = [.. SubmarineInfo.SavedSubmarines];
|
||||
foreach (SteamRemoteStorage.RemoteFile remoteFile in SteamRemoteStorage.Files.Where(file => file.Filename.EndsWith(".sub")))
|
||||
{
|
||||
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;
|
||||
|
||||
foreach (SubmarineInfo sub in sortedSubs)
|
||||
{
|
||||
if (prevSub == null || prevSub.Type != sub.Type)
|
||||
@@ -3795,35 +3862,44 @@ namespace Barotrauma
|
||||
prevSub = sub;
|
||||
}
|
||||
|
||||
string pathWithoutUserName = Path.GetFullPath(sub.FilePath);
|
||||
string saveFolder = Path.GetFullPath(SaveUtil.DefaultSaveFolder);
|
||||
if (pathWithoutUserName.StartsWith(saveFolder))
|
||||
string displayPath = sub.FilePath;
|
||||
if (sub.IsFromRemoteStorage)
|
||||
{
|
||||
pathWithoutUserName = "..." + pathWithoutUserName[saveFolder.Length..];
|
||||
displayPath += $" {TextManager.Get("RemoteStorage")}";
|
||||
}
|
||||
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) },
|
||||
ToolBox.LimitString(sub.Name, GUIStyle.Font, subList.Rect.Width - 80))
|
||||
LocalizedString limitedName = 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,
|
||||
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 &&
|
||||
ContentPackageManager.AllPackages.FirstOrDefault(p => p.Files.Any(f => f.Path == sub.FilePath)) is ContentPackage subPackage)
|
||||
{
|
||||
//workshop mod
|
||||
// workshop mod
|
||||
textBlock.OverrideTextColor(Color.MediumPurple);
|
||||
}
|
||||
else
|
||||
{
|
||||
//local mod
|
||||
// local mod
|
||||
textBlock.OverrideTextColor(GUIStyle.TextColorBright);
|
||||
}
|
||||
}
|
||||
@@ -4018,10 +4094,9 @@ namespace Barotrauma
|
||||
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 (ownerPackage is null)
|
||||
if (!selectedSubInfo.IsFromRemoteStorage && GetLocalPackageThatOwnsSub(selectedSubInfo) is null)
|
||||
{
|
||||
if (IsVanillaSub(selectedSubInfo))
|
||||
{
|
||||
@@ -4183,21 +4258,23 @@ namespace Barotrauma
|
||||
{
|
||||
if (sub == null) { return; }
|
||||
|
||||
//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.
|
||||
//(deleting from the Submarines folder is also currently allowed, but this is temporary)
|
||||
var subPackage = GetLocalPackageThatOwnsSub(sub);
|
||||
if (!ContentPackageManager.LocalPackages.Regular.Contains(subPackage)) { return; }
|
||||
// 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.
|
||||
// (deleting from the Submarines folder is also currently allowed, but this is temporary)
|
||||
ContentPackage subPackage = GetLocalPackageThatOwnsSub(sub);
|
||||
if (!ContentPackageManager.LocalPackages.Regular.Contains(subPackage)) { subPackage = null; }
|
||||
if (!sub.IsFromRemoteStorage && subPackage == null) { return; }
|
||||
|
||||
var msgBox = new GUIMessageBox(
|
||||
TextManager.Get("DeleteDialogLabel"),
|
||||
TextManager.GetWithVariable("DeleteDialogQuestion", "[file]", sub.Name),
|
||||
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("Cancel") });
|
||||
GUIMessageBox msgBox = new(TextManager.Get("DeleteDialogLabel"), TextManager.GetWithVariable("DeleteDialogQuestion", "[file]", sub.Name), [TextManager.Get("Yes"), TextManager.Get("Cancel")]);
|
||||
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);
|
||||
ModProject modProject = new ModProject(subPackage);
|
||||
@@ -4209,16 +4286,16 @@ namespace Barotrauma
|
||||
MainSub.Info.FilePath = null;
|
||||
}
|
||||
}
|
||||
sub.Dispose();
|
||||
CreateLoadScreen();
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowErrorLocalized(TextManager.GetWithVariable("DeleteFileError", "[file]", sub.FilePath), e);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowErrorLocalized(TextManager.GetWithVariable("DeleteFileError", "[file]", sub.FilePath), e);
|
||||
}
|
||||
return true;
|
||||
|
||||
sub.Dispose();
|
||||
CreateLoadScreen();
|
||||
return msgBox.Close(btn, userData);
|
||||
};
|
||||
msgBox.Buttons[0].OnClicked += msgBox.Close;
|
||||
msgBox.Buttons[1].OnClicked += msgBox.Close;
|
||||
}
|
||||
|
||||
|
||||
@@ -445,7 +445,7 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.NewMessage("Missing Localization for property: " + propertyTag);
|
||||
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
|
||||
@@ -467,7 +467,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (toolTip.IsNullOrEmpty())
|
||||
{
|
||||
toolTip = property.GetAttribute<Serialize>().Description;
|
||||
toolTip = property.GetAttribute<Serialize>()?.Description;
|
||||
}
|
||||
|
||||
GUIComponent propertyField = null;
|
||||
|
||||
@@ -174,9 +174,9 @@ namespace Barotrauma
|
||||
public static RectTransform NewItemRectT(GUILayoutGroup parent)
|
||||
=> new RectTransform((1.0f, 0.06f), parent.RectTransform, Anchor.CenterLeft);
|
||||
|
||||
public static void Spacer(GUILayoutGroup parent)
|
||||
public static void Spacer(GUILayoutGroup parent, float height = 0.03f)
|
||||
{
|
||||
new GUIFrame(new RectTransform((1.0f, 0.03f), parent.RectTransform, Anchor.CenterLeft), style: null);
|
||||
new GUIFrame(new RectTransform((1.0f, height), parent.RectTransform, Anchor.CenterLeft), style: null);
|
||||
}
|
||||
|
||||
public static GUITextBlock Label(GUILayoutGroup parent, LocalizedString str, GUIFont font)
|
||||
@@ -507,6 +507,47 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
};
|
||||
#if OSX
|
||||
Spacer(voiceChat, 0.003f);
|
||||
|
||||
// On macOS, microphone permission can apparently sometimes end up in a broken state when the app binary changes (eg. after a Steam update).
|
||||
// The device seems to be there, but won't receive anything, even if the mic permission is fine.
|
||||
// This button lets the user reset it and reboot the game, so the mic permission check will be retriggered on next run.
|
||||
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), voiceChat.RectTransform),
|
||||
text: TextManager.Get("MacResetMicPermissions"),
|
||||
style: "GUIButtonSmall")
|
||||
{
|
||||
ToolTip = TextManager.Get("MacResetMicPermissionsToolTip"),
|
||||
OnClicked = (btn, obj) =>
|
||||
{
|
||||
var confirmBox = new GUIMessageBox(
|
||||
TextManager.Get("MacResetMicPermissions"),
|
||||
TextManager.Get("MacResetMicPermissionsConfirm"),
|
||||
[TextManager.Get("OK"), TextManager.Get("Cancel")]);
|
||||
confirmBox.Buttons[0].OnClicked = (_, _) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = "tccutil",
|
||||
Arguments = "reset Microphone com.FakeFish.Barotrauma",
|
||||
UseShellExecute = false
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.NewMessage($"Failed to reset microphone permission: {e.Message}", Color.Orange);
|
||||
}
|
||||
GameMain.Instance.Exit();
|
||||
confirmBox.Close();
|
||||
return true;
|
||||
};
|
||||
confirmBox.Buttons[1].OnClicked = confirmBox.Close;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
Spacer(voiceChat);
|
||||
|
||||
Label(voiceChat, TextManager.Get("VCInputMode"), GUIStyle.SubHeadingFont);
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,10 @@ namespace Barotrauma.Steam
|
||||
{
|
||||
public const int MaxThumbnailSize = 1024 * 1024;
|
||||
|
||||
/// <summary>
|
||||
/// Tags the players can choose for their workshop items. These must match the ones defined in the Steamworks backend. They're case insensitive, but must otherwise match exactly for the tag filtering to work correctly.
|
||||
/// The localized names for these are fetched from the loca files with the identifier "workshop.contenttag.{tag.RemoveWhitespace()}".
|
||||
/// </summary>
|
||||
public static readonly ImmutableArray<Identifier> Tags = new []
|
||||
{
|
||||
"submarine",
|
||||
@@ -25,7 +29,7 @@ namespace Barotrauma.Steam
|
||||
"monster",
|
||||
"mission",
|
||||
"outpost",
|
||||
"beaconstation",
|
||||
"beacon station",
|
||||
"wreck",
|
||||
"ruin",
|
||||
"weapons",
|
||||
@@ -34,14 +38,14 @@ namespace Barotrauma.Steam
|
||||
"art",
|
||||
"event set",
|
||||
"total conversion",
|
||||
"gamemode",
|
||||
"gameplaymechanics",
|
||||
"game mode",
|
||||
"gameplay mechanics",
|
||||
"environment",
|
||||
"item assembly",
|
||||
"language",
|
||||
"qol",
|
||||
"clientside",
|
||||
"serverside",
|
||||
"client-side",
|
||||
"server-side",
|
||||
"outdated",
|
||||
"library"
|
||||
}.ToIdentifiers().ToImmutableArray();
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>1.12.6.2</Version>
|
||||
<Version>1.13.3.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>1.12.6.2</Version>
|
||||
<Version>1.13.3.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>1.12.6.2</Version>
|
||||
<Version>1.13.3.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>1.12.6.2</Version>
|
||||
<Version>1.13.3.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>1.12.6.2</Version>
|
||||
<Version>1.13.3.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -1799,22 +1799,35 @@ namespace Barotrauma
|
||||
(Client client, Vector2 cursorWorldPos, string[] args) =>
|
||||
{
|
||||
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))
|
||||
{
|
||||
Submarine.MainSub.SetPosition(cursorWorldPos);
|
||||
submarineToTeleport.SetPosition(cursorWorldPos);
|
||||
}
|
||||
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))
|
||||
{
|
||||
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))
|
||||
{
|
||||
Submarine.MainSub.SetPosition(Level.Loaded.EndExitPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height);
|
||||
var submarineDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == Submarine.MainSub);
|
||||
submarineToTeleport.SetPosition(Level.Loaded.EndExitPosition - Vector2.UnitY * submarineToTeleport.Borders.Height);
|
||||
var submarineDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == submarineToTeleport);
|
||||
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);
|
||||
@@ -2735,6 +2748,19 @@ namespace Barotrauma
|
||||
}
|
||||
);
|
||||
|
||||
commands.Add(new Command("ShowServerPerf", "Immediately log server performance info in ServerMessage", (string[] args) =>
|
||||
{
|
||||
GameServer.Log(PerformanceMonitor.PM.ToString(), ServerLog.MessageType.ServerMessage);
|
||||
}));
|
||||
|
||||
AssignOnClientRequestExecute(
|
||||
"ShowServerPerf",
|
||||
(senderClient, cursorWorldPos, args) =>
|
||||
{
|
||||
GameMain.Server.SendConsoleMessage(PerformanceMonitor.PM.ToString(), senderClient);
|
||||
}
|
||||
);
|
||||
|
||||
#if DEBUG
|
||||
commands.Add(new Command("spamevents", "A debug command that creates a ton of entity events.", (string[] args) =>
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ using System.Xml.Linq;
|
||||
using MoonSharp.Interpreter;
|
||||
using System.Net;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.LuaCs.Events;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -329,8 +330,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Stopwatch performanceCounterTimer = Stopwatch.StartNew();
|
||||
|
||||
stopwatch = Stopwatch.StartNew();
|
||||
|
||||
PerformanceMonitor PM = new PerformanceMonitor();
|
||||
|
||||
long prevTicks = stopwatch.ElapsedTicks;
|
||||
while (ShouldRun)
|
||||
{
|
||||
@@ -380,6 +383,7 @@ namespace Barotrauma
|
||||
|
||||
Timing.Accumulator -= Timing.Step;
|
||||
updateCount++;
|
||||
PM.Update();
|
||||
}
|
||||
|
||||
#if !DEBUG
|
||||
@@ -427,6 +431,9 @@ namespace Barotrauma
|
||||
updateCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
PerformanceMonitor.PM.Dispose();
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
CloseServer();
|
||||
|
||||
@@ -1179,6 +1179,7 @@ namespace Barotrauma
|
||||
NetWalletTransfer transfer = INetSerializableStruct.Read<NetWalletTransfer>(msg);
|
||||
|
||||
if (GameMain.Server is null) { return; }
|
||||
if (transfer.Amount <= 0) { return; }
|
||||
|
||||
if (transfer.Sender.TryUnwrap(out var id))
|
||||
{
|
||||
|
||||
@@ -193,13 +193,6 @@ namespace Barotrauma
|
||||
(item.PreviousParentInventory == null ||
|
||||
!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
|
||||
if (item.HasTag(Tags.HandLockerItem) && !itemAccessDenied)
|
||||
{
|
||||
@@ -219,7 +212,7 @@ namespace Barotrauma
|
||||
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++)
|
||||
{
|
||||
if (slots[j].Contains(item) && !receivedItemIdsFromClient[j].Contains(item.ID))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Barotrauma.LuaCs;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -13,7 +14,7 @@ namespace Barotrauma
|
||||
|
||||
if (luaPackage == null)
|
||||
{
|
||||
GameMain.Server.SendChatMessage("Couldn't find the LuaCsForBarotrauma content package.", ChatMessageType.ServerMessageBox);
|
||||
GameMain.Server.SendChatMessage("Couldn't find the ProjectEP package.", ChatMessageType.ServerMessageBox);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -51,7 +52,7 @@ namespace Barotrauma
|
||||
}
|
||||
catch (UnauthorizedAccessException e)
|
||||
{
|
||||
LuaCsLogger.LogError($"Unauthorized file access exception. This usually means you already have LuaCs installed. ${e}", LuaCsMessageOrigin.LuaCs);
|
||||
LuaCsLogger.LogError($"Unauthorized file access exception. This usually means you already have ProjectEP installed. ${e}", LuaCsMessageOrigin.LuaCs);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -62,7 +63,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
GameMain.Server.SendChatMessage("Client-Side LuaCs installed, restart your game to apply changes.", ChatMessageType.ServerMessageBox);
|
||||
GameMain.Server.SendChatMessage("Client-Side ProjectEP installed, restart your game to apply changes.", ChatMessageType.ServerMessageBox);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +35,11 @@ partial class NetworkingService : INetworkingService, IEventClientRawNetMessageR
|
||||
return message;
|
||||
}
|
||||
|
||||
public void OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader clientPacketHeader, NetworkConnection sender)
|
||||
public bool? OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader clientPacketHeader, NetworkConnection sender)
|
||||
{
|
||||
if (clientPacketHeader != ClientHeader)
|
||||
{
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
Client client = GameMain.Server.ConnectedClients.First(c => c.Connection == sender);
|
||||
@@ -64,6 +64,8 @@ partial class NetworkingService : INetworkingService, IEventClientRawNetMessageR
|
||||
RequestIdSingle(netMessage, client);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void HandleNetMessageId(IReadMessage netMessage, Client client = null)
|
||||
|
||||
@@ -8,6 +8,17 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
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)
|
||||
{
|
||||
c.KickAFKTimer = 0.0f;
|
||||
@@ -69,8 +80,7 @@ namespace Barotrauma.Networking
|
||||
txt = msg.ReadString() ?? "";
|
||||
}
|
||||
|
||||
// Sanitize incoming text message from client so they can't use RichString features
|
||||
txt = txt.Replace('‖', ' ');
|
||||
txt = SanitizeText(c, txt);
|
||||
|
||||
if (!NetIdUtils.IdMoreRecent(ID, c.LastSentChatMsgID)) { return; }
|
||||
|
||||
|
||||
@@ -904,7 +904,10 @@ namespace Barotrauma.Networking
|
||||
string subHash = inc.ReadString();
|
||||
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)
|
||||
{
|
||||
@@ -1150,7 +1153,9 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else
|
||||
{
|
||||
KickClient(c, errorStr);
|
||||
//Is it necessary to kick a client for a non-existing entity?
|
||||
//there are plenty of things have been done if received an non-existing entity update.
|
||||
//KickClient(c, errorStr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1214,18 +1219,25 @@ namespace Barotrauma.Networking
|
||||
|
||||
errorLines.Add("");
|
||||
errorLines.Add("EntitySpawner events:");
|
||||
foreach (var entityEvent in entityEventManager.UniqueEvents)
|
||||
try
|
||||
{
|
||||
if (entityEvent.Entity is EntitySpawner)
|
||||
foreach (var entityEvent in entityEventManager.UniqueEvents.ToList())
|
||||
{
|
||||
var spawnData = entityEvent.Data as EntitySpawner.SpawnOrRemove;
|
||||
errorLines.Add(
|
||||
entityEvent.ID + ": " +
|
||||
(spawnData is EntitySpawner.RemoveEntity ? "Remove " : "Create ") +
|
||||
spawnData.Entity.ToString() +
|
||||
" (" + spawnData.ID + ", " + spawnData.Entity.ID + ")");
|
||||
if (entityEvent.Entity is EntitySpawner)
|
||||
{
|
||||
var spawnData = entityEvent.Data as EntitySpawner.SpawnOrRemove;
|
||||
errorLines.Add(
|
||||
entityEvent.ID + ": " +
|
||||
(spawnData is EntitySpawner.RemoveEntity ? "Remove " : "Create ") +
|
||||
spawnData.Entity.ToString() +
|
||||
" (" + spawnData.ID + ", " + spawnData.Entity.ID + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
errorLines.Add("Failed to write EntitySpawner events.");
|
||||
}
|
||||
|
||||
errorLines.Add("");
|
||||
errorLines.Add("Last debug messages:");
|
||||
|
||||
+198
-64
@@ -1,9 +1,16 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using static Barotrauma.EosInterface.Ownership;
|
||||
|
||||
|
||||
// DO NOT TOUCH ANYTHING HERE
|
||||
// OR EVERYTHING WILL FAIL
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class ServerEntityEvent : NetEntityEvent
|
||||
@@ -44,6 +51,8 @@ namespace Barotrauma.Networking
|
||||
|
||||
class ServerEntityEventManager : NetEntityEventManager
|
||||
{
|
||||
static public ServerEntityEventManager SEM;
|
||||
|
||||
private readonly List<ServerEntityEvent> events;
|
||||
|
||||
//list of unique events (i.e. !IsDuplicate) created during the round
|
||||
@@ -102,60 +111,137 @@ namespace Barotrauma.Networking
|
||||
private readonly GameServer server;
|
||||
|
||||
private double lastEventCountHighWarning;
|
||||
private class PendingCreateEvent
|
||||
{
|
||||
public IServerSerializable Entity;
|
||||
public NetEntityEvent.IData Data;
|
||||
public PendingCreateEvent(IServerSerializable entity, NetEntityEvent.IData data)
|
||||
{
|
||||
Entity = entity;
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ConcurrentQueue<PendingCreateEvent> pendingCreateQueue;
|
||||
|
||||
private readonly Task createEventTask;
|
||||
|
||||
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
||||
private readonly SemaphoreSlim eventSignal = new SemaphoreSlim(0);
|
||||
|
||||
public ServerEntityEventManager(GameServer server)
|
||||
{
|
||||
events = new List<ServerEntityEvent>();
|
||||
|
||||
this.server = server;
|
||||
|
||||
bufferedEvents = new List<BufferedEvent>();
|
||||
|
||||
uniqueEvents = new List<ServerEntityEvent>();
|
||||
|
||||
pendingCreateQueue = new ConcurrentQueue<PendingCreateEvent>();
|
||||
lastWarningTime = -10.0;
|
||||
SEM = this;
|
||||
|
||||
createEventTask = Task.Run(() => CreateEventProcessorLoop(cancellationTokenSource.Token));
|
||||
}
|
||||
|
||||
private async Task CreateEventProcessorLoop(CancellationToken token)
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await eventSignal.WaitAsync(100, token);
|
||||
ProcessPendingCreateEvents();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessPendingCreateEvents()
|
||||
{
|
||||
// Dequeue and process all pending events currently in the queue.
|
||||
// Use a lock to synchronize modifications to shared lists / ID.
|
||||
while (pendingCreateQueue.TryDequeue(out PendingCreateEvent pending))
|
||||
{
|
||||
// The original CreateEvent logic (mostly unchanged) but executed under a lock
|
||||
if (pending == null || pending.Entity == null) { continue; }
|
||||
|
||||
var entity = pending.Entity;
|
||||
var extraData = pending.Data;
|
||||
|
||||
var newEvent = new ServerEntityEvent(entity, (UInt16)(ID + 1));
|
||||
if (extraData != null) newEvent.SetData(extraData);
|
||||
|
||||
bool inGameClientsPresent = server.ConnectedClients.Count(c => c.InGame) > 0;
|
||||
//remove old events that have been sent to all clients, they are redundant now
|
||||
// keep at least one event in the list (lastSentToAll == e.ID) so we can use it to keep track of the latest ID
|
||||
// and events less than 15 seconds old to give disconnected clients a bit of time to reconnect without getting desynced
|
||||
if (GameMain.GameSession.RoundDuration > server.ServerSettings.RoundStartSyncDuration)
|
||||
{
|
||||
events.RemoveAll(e =>
|
||||
(NetIdUtils.IdMoreRecent(lastSentToAll, e.ID) || !inGameClientsPresent) &&
|
||||
e.CreateTime < Timing.TotalTime - server.ServerSettings.EventRemovalTime);
|
||||
}
|
||||
|
||||
bool duplicateFound = false;
|
||||
for (int i = events.Count - 1; i >= 0; i--)
|
||||
{
|
||||
//we already have an identical event that's waiting to be sent
|
||||
// -> no need to add a new one
|
||||
if (events[i].IsDuplicate(newEvent) && !events[i].Sent)
|
||||
{
|
||||
duplicateFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (duplicateFound) { continue; }
|
||||
|
||||
ID++;
|
||||
|
||||
events.Add(newEvent);
|
||||
|
||||
if (!uniqueEvents.Any(e => e.IsDuplicate(newEvent)))
|
||||
{
|
||||
//create a copy of the event and give it a new ID
|
||||
var uniqueEvent = new ServerEntityEvent(entity, (UInt16)(uniqueEvents.Count + 1));
|
||||
uniqueEvent.SetData(extraData);
|
||||
|
||||
uniqueEvents.Add(uniqueEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
public void CreateEvent(IServerSerializable entity, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
if (!ValidateEntity(entity)) { return; }
|
||||
|
||||
var newEvent = new ServerEntityEvent(entity, (UInt16)(ID + 1));
|
||||
if (extraData != null) newEvent.SetData(extraData);
|
||||
// enqueue and let background task handle the rest
|
||||
pendingCreateQueue.Enqueue(new PendingCreateEvent(entity, extraData));
|
||||
|
||||
bool inGameClientsPresent = server.ConnectedClients.Count(c => c.InGame) > 0;
|
||||
|
||||
//remove old events that have been sent to all clients, they are redundant now
|
||||
// keep at least one event in the list (lastSentToAll == e.ID) so we can use it to keep track of the latest ID
|
||||
// and events less than 15 seconds old to give disconnected clients a bit of time to reconnect without getting desynced
|
||||
if (GameMain.GameSession.RoundDuration > server.ServerSettings.RoundStartSyncDuration)
|
||||
if (eventSignal.CurrentCount == 0)
|
||||
{
|
||||
events.RemoveAll(e =>
|
||||
(NetIdUtils.IdMoreRecent(lastSentToAll, e.ID) || !inGameClientsPresent) &&
|
||||
e.CreateTime < Timing.TotalTime - server.ServerSettings.EventRemovalTime);
|
||||
}
|
||||
|
||||
for (int i = events.Count - 1; i >= 0; i--)
|
||||
{
|
||||
//we already have an identical event that's waiting to be sent
|
||||
// -> no need to add a new one
|
||||
if (events[i].IsDuplicate(newEvent) && !events[i].Sent) return;
|
||||
}
|
||||
|
||||
ID++;
|
||||
|
||||
events.Add(newEvent);
|
||||
|
||||
if (!uniqueEvents.Any(e => e.IsDuplicate(newEvent)))
|
||||
{
|
||||
//create a copy of the event and give it a new ID
|
||||
var uniqueEvent = new ServerEntityEvent(entity, (UInt16)(uniqueEvents.Count + 1));
|
||||
uniqueEvent.SetData(extraData);
|
||||
|
||||
uniqueEvents.Add(uniqueEvent);
|
||||
eventSignal.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
cancellationTokenSource.Cancel();
|
||||
eventSignal.Release();
|
||||
try
|
||||
{
|
||||
createEventTask?.Wait(2000);
|
||||
}
|
||||
catch (AggregateException) { }
|
||||
finally
|
||||
{
|
||||
cancellationTokenSource.Dispose();
|
||||
eventSignal.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Due to intensive access demend and time it takes to refactor, we use try-catch when facing thread-safety issue to skip to next update :(
|
||||
public void Update(List<Client> clients)
|
||||
{
|
||||
foreach (BufferedEvent bufferedEvent in bufferedEvents)
|
||||
@@ -203,26 +289,55 @@ namespace Barotrauma.Networking
|
||||
bufferedEvent.IsProcessed = true;
|
||||
}
|
||||
|
||||
var inGameClients = clients.FindAll(c => c.InGame && !c.NeedsMidRoundSync);
|
||||
if (inGameClients.Count > 0)
|
||||
{
|
||||
lastSentToAnyone = inGameClients[0].LastRecvEntityEventID;
|
||||
lastSentToAll = inGameClients[0].LastRecvEntityEventID;
|
||||
List<Client> inGameClients = null;
|
||||
List<Client> midRoundSyncClients = null;
|
||||
Client ownerClient = null;
|
||||
|
||||
if (server.OwnerConnection != null)
|
||||
foreach (var c in clients)
|
||||
{
|
||||
if (c.InGame)
|
||||
{
|
||||
var owner = clients.Find(c => c.Connection == server.OwnerConnection);
|
||||
if (owner != null)
|
||||
if (c.NeedsMidRoundSync)
|
||||
{
|
||||
lastSentToAll = owner.LastRecvEntityEventID;
|
||||
(midRoundSyncClients ??= new List<Client>()).Add(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
(inGameClients ??= new List<Client>()).Add(c);
|
||||
}
|
||||
}
|
||||
inGameClients.ForEach(c =>
|
||||
if (server.OwnerConnection != null && c.Connection == server.OwnerConnection)
|
||||
{
|
||||
if (NetIdUtils.IdMoreRecent(lastSentToAll, c.LastRecvEntityEventID)) { lastSentToAll = c.LastRecvEntityEventID; }
|
||||
if (NetIdUtils.IdMoreRecent(c.LastRecvEntityEventID, lastSentToAnyone)) { lastSentToAnyone = c.LastRecvEntityEventID; }
|
||||
});
|
||||
lastSentToAnyoneTime = events.Find(e => e.ID == lastSentToAnyone)?.CreateTime ?? Timing.TotalTime;
|
||||
ownerClient = c;
|
||||
}
|
||||
}
|
||||
|
||||
if (inGameClients != null && inGameClients.Count > 0)
|
||||
{
|
||||
lastSentToAnyone = inGameClients[0].LastRecvEntityEventID;
|
||||
lastSentToAll = ownerClient?.LastRecvEntityEventID ?? inGameClients[0].LastRecvEntityEventID;
|
||||
|
||||
foreach (var c in inGameClients)
|
||||
{
|
||||
if (NetIdUtils.IdMoreRecent(lastSentToAll, c.LastRecvEntityEventID))
|
||||
{
|
||||
lastSentToAll = c.LastRecvEntityEventID;
|
||||
}
|
||||
if (NetIdUtils.IdMoreRecent(c.LastRecvEntityEventID, lastSentToAnyone))
|
||||
{
|
||||
lastSentToAnyone = c.LastRecvEntityEventID;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
lastSentToAnyoneTime = events.ToList().Find(e => e.ID == lastSentToAnyone)?.CreateTime ?? Timing.TotalTime;
|
||||
}
|
||||
catch
|
||||
{
|
||||
lastSentToAnyoneTime = Timing.TotalTime;
|
||||
}
|
||||
|
||||
|
||||
if (Timing.TotalTime - lastWarningTime > 5.0 &&
|
||||
Timing.TotalTime - lastSentToAnyoneTime > 10.0 &&
|
||||
@@ -238,7 +353,16 @@ namespace Barotrauma.Networking
|
||||
|
||||
clients.Where(c => c.NeedsMidRoundSync).ForEach(c => { if (NetIdUtils.IdMoreRecent(lastSentToAll, c.FirstNewEventID)) lastSentToAll = (ushort)(c.FirstNewEventID - 1); });
|
||||
|
||||
ServerEntityEvent firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1));
|
||||
ServerEntityEvent firstEventToResend;
|
||||
try
|
||||
{
|
||||
firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1));
|
||||
}
|
||||
catch
|
||||
{
|
||||
firstEventToResend = null;
|
||||
}
|
||||
|
||||
if (firstEventToResend != null &&
|
||||
GameMain.GameSession.RoundDuration > server.ServerSettings.RoundStartSyncDuration &&
|
||||
((lastSentToAnyoneTime - firstEventToResend.CreateTime) > server.ServerSettings.OldReceivedEventKickTime || (Timing.TotalTime - firstEventToResend.CreateTime) > server.ServerSettings.OldEventKickTime))
|
||||
@@ -249,17 +373,17 @@ namespace Barotrauma.Networking
|
||||
// in which case we'll wait until the timeout runs out before kicking the client
|
||||
List<Client> toKick = inGameClients.FindAll(c =>
|
||||
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 =>
|
||||
{
|
||||
DebugConsole.NewMessage(c.Name + " was kicked because they were expecting a very old network event (" + (c.LastRecvEntityEventID + 1).ToString() + ")", Color.Red);
|
||||
GameServer.Log(GameServer.ClientLogName(c) + " was kicked because they were expecting a very old network event ("
|
||||
+ (c.LastRecvEntityEventID + 1).ToString() +
|
||||
" (created " + (Timing.TotalTime - firstEventToResend.CreateTime).ToString("0.##") + " s ago, " +
|
||||
(lastSentToAnyoneTime - firstEventToResend.CreateTime).ToString("0.##") + " s older than last event sent to anyone)" +
|
||||
" Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.Error);
|
||||
server.DisconnectClient(c, PeerDisconnectPacket.WithReason(DisconnectReason.ExcessiveDesyncOldEvent));
|
||||
}
|
||||
{
|
||||
DebugConsole.NewMessage(c.Name + " was kicked because they were expecting a very old network event (" + (c.LastRecvEntityEventID + 1).ToString() + ")", Color.Red);
|
||||
GameServer.Log(GameServer.ClientLogName(c) + " was kicked because they were expecting a very old network event ("
|
||||
+ (c.LastRecvEntityEventID + 1).ToString() +
|
||||
" (created " + (Timing.TotalTime - firstEventToResend.CreateTime).ToString("0.##") + " s ago, " +
|
||||
(lastSentToAnyoneTime - firstEventToResend.CreateTime).ToString("0.##") + " s older than last event sent to anyone)" +
|
||||
" Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.Error);
|
||||
server.DisconnectClient(c, PeerDisconnectPacket.WithReason(DisconnectReason.ExcessiveDesyncOldEvent));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -277,11 +401,21 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
var timedOutClients = clients.FindAll(c => c.Connection != GameMain.Server.OwnerConnection && c.InGame && c.NeedsMidRoundSync && Timing.TotalTime > c.MidRoundSyncTimeOut);
|
||||
foreach (Client timedOutClient in timedOutClients)
|
||||
if (midRoundSyncClients != null)
|
||||
{
|
||||
GameServer.Log("Disconnecting client " + GameServer.ClientLogName(timedOutClient) + ". Syncing the client with the server took too long.", ServerLog.MessageType.Error);
|
||||
GameMain.Server.DisconnectClient(timedOutClient, PeerDisconnectPacket.WithReason(DisconnectReason.SyncTimeout));
|
||||
foreach (var c in midRoundSyncClients)
|
||||
{
|
||||
if (NetIdUtils.IdMoreRecent(lastSentToAll, c.FirstNewEventID))
|
||||
{
|
||||
lastSentToAll = (ushort)(c.FirstNewEventID - 1);
|
||||
}
|
||||
|
||||
if (c.Connection != GameMain.Server.OwnerConnection && Timing.TotalTime > c.MidRoundSyncTimeOut)
|
||||
{
|
||||
GameServer.Log("Disconnecting client " + GameServer.ClientLogName(c) + ". Syncing took too long.", ServerLog.MessageType.Error);
|
||||
GameMain.Server.DisconnectClient(c, PeerDisconnectPacket.WithReason(DisconnectReason.SyncTimeout));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bufferedEvents.RemoveAll(b => b.IsProcessed);
|
||||
|
||||
@@ -54,13 +54,14 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
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();
|
||||
|
||||
msg.WriteByte((byte)ServerPacketHeader.VOICE);
|
||||
msg.WriteByte((byte)queue.QueueID);
|
||||
msg.WriteRangedSingle(distanceFactor, 0.0f, 1.0f, 8);
|
||||
msg.WriteBoolean(isRadio);
|
||||
queue.Write(msg);
|
||||
|
||||
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)
|
||||
{
|
||||
distanceFactor = 0.0f;
|
||||
isRadio = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
distanceFactor = 0.0f;
|
||||
isRadio = false;
|
||||
|
||||
//no-one can hear muted players
|
||||
if (sender.Muted) { return false; }
|
||||
@@ -109,12 +112,14 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (recipientSpectating)
|
||||
{
|
||||
isRadio = true;
|
||||
if (recipient.SpectatePos == null) { return true; }
|
||||
distanceFactor = MathHelper.Clamp(Vector2.Distance(sender.Character.WorldPosition, recipient.SpectatePos.Value) / senderRadio.Range, 0.0f, 1.0f);
|
||||
return distanceFactor < 1.0f;
|
||||
}
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class PerformanceMonitor
|
||||
{
|
||||
static public PerformanceMonitor PM;
|
||||
|
||||
private Stopwatch PMStopwatch = new Stopwatch();
|
||||
|
||||
private double tickratetimer = 0;
|
||||
|
||||
private double tickrate60stimer = 0;
|
||||
|
||||
private static Queue<double> tickrate60s = new Queue<double>(61);
|
||||
|
||||
public int ItemCount
|
||||
{
|
||||
get{ return Item.ItemList.Count; }
|
||||
}
|
||||
|
||||
public int CharacterCount
|
||||
{
|
||||
get { return Character.CharacterList.Count; }
|
||||
}
|
||||
|
||||
public int PhysicsBodyCount
|
||||
{
|
||||
get { return PhysicsBody.List.Count; }
|
||||
}
|
||||
public int ConnectClients
|
||||
{
|
||||
get { return GameMain.Server.ConnectedClients.Count; }
|
||||
}
|
||||
|
||||
public double RealTickRate
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public long TotalTicks
|
||||
{
|
||||
get;set;
|
||||
}
|
||||
|
||||
public int LastSecondTicks
|
||||
{
|
||||
get; set;
|
||||
} = 0;
|
||||
|
||||
public float AverageTickRate
|
||||
{
|
||||
get
|
||||
{
|
||||
return TotalTicks / (float)TotalTimeElapsed * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
public double AverageTickRate10s
|
||||
{
|
||||
get
|
||||
{
|
||||
return tickrate60s.Count > 0 ? tickrate60s.Average() : 60;
|
||||
}
|
||||
}
|
||||
|
||||
public double TotalTimeElapsed
|
||||
{
|
||||
get
|
||||
{
|
||||
return PMStopwatch.Elapsed.TotalMilliseconds;
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan TimeElapsed
|
||||
{
|
||||
get
|
||||
{
|
||||
return TimeSpan.FromMilliseconds(TotalTimeElapsed);
|
||||
}
|
||||
}
|
||||
|
||||
public float MemoryUsage
|
||||
{
|
||||
get
|
||||
{
|
||||
Process proc = Process.GetCurrentProcess();
|
||||
float memory = MathF.Round(proc.PrivateMemorySize64 / (1024 * 1024), 2);
|
||||
proc.Dispose();
|
||||
|
||||
return memory;
|
||||
}
|
||||
}
|
||||
|
||||
public double TickRateLow
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public double TickRateHigh
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public PerformanceMonitor()
|
||||
{
|
||||
PM = this;
|
||||
RealTickRate = 60;
|
||||
TotalTicks = 0;
|
||||
LastSecondTicks = 60;
|
||||
TickRateLow = 60;
|
||||
TickRateHigh = 60;
|
||||
PMStopwatch.Start();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
TotalTicks += 1;
|
||||
LastSecondTicks += 1;
|
||||
if (tickrate60s.Count > 60)
|
||||
{
|
||||
tickrate60s.Dequeue();
|
||||
}
|
||||
if (TotalTimeElapsed - 1000 >= tickratetimer)
|
||||
{
|
||||
RealTickRate = LastSecondTicks / (TotalTimeElapsed - tickratetimer) * 1000;
|
||||
tickrate60s.Enqueue(RealTickRate);
|
||||
tickratetimer = TotalTimeElapsed;
|
||||
LastSecondTicks = 0;
|
||||
}
|
||||
if (TotalTimeElapsed - 60000 >= tickrate60stimer)
|
||||
{
|
||||
GameServer.Log(PM.ToString(), ServerLog.MessageType.ServerMessage);
|
||||
TickRateLow = 60;
|
||||
TickRateHigh = 60;
|
||||
tickrate60stimer = TotalTimeElapsed;
|
||||
}
|
||||
if (RealTickRate > TickRateHigh)
|
||||
{
|
||||
TickRateHigh = RealTickRate;
|
||||
}
|
||||
if (RealTickRate < TickRateLow)
|
||||
{
|
||||
TickRateLow = RealTickRate;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
PMStopwatch.Reset();
|
||||
PM = null;
|
||||
}
|
||||
override public string ToString()
|
||||
{
|
||||
return $"Server Performance Info \n" +
|
||||
$"Item Count: {ItemCount}\n" +
|
||||
$"Character Count: {CharacterCount}\n" +
|
||||
$"Clients Count {ConnectClients}\n " +
|
||||
$"PhysicsBody Count: {PhysicsBodyCount}\n" +
|
||||
$"Tick Rate: {RealTickRate}\n" +
|
||||
$"Min Tick Rate: {TickRateLow}\n" +
|
||||
$"Max Tick Rate: {TickRateHigh}\n" +
|
||||
$"Total Ticks: {TotalTicks}\n" +
|
||||
$"All time Average Tick Rate: {AverageTickRate}\n" +
|
||||
$"60s Average Tick Rate: {AverageTickRate10s}\n" +
|
||||
$"Server Run Time: {TimeElapsed}\n" +
|
||||
$"Memory Usage: {MemoryUsage}\n";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ namespace Barotrauma
|
||||
GameMain.ShouldRun = false;
|
||||
};
|
||||
#endif
|
||||
Console.WriteLine("Barotrauma Dedicated Server " + GameMain.Version +
|
||||
Console.WriteLine("Barotrauma Dedicated Server(EP) " + GameMain.Version +
|
||||
" (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")");
|
||||
if (Console.IsOutputRedirected)
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>1.12.6.2</Version>
|
||||
<Version>1.13.3.1</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
</Values>
|
||||
</Setting>
|
||||
<Setting Name="UseCaching" Type="bool" Value="true" AllowChangesWhileExecuting="false"/>
|
||||
<Setting Name="CsRunPolicySession" Type="bool" AllowChangesWhileExecuting="false" ShowInMenus="false"/>
|
||||
<Setting Name="IsCsEnabledForSession" Type="bool" AllowChangesWhileExecuting="false" ShowInMenus="false" Value="false"/>
|
||||
</Settings>
|
||||
</Configuration>
|
||||
|
||||
@@ -35,44 +35,4 @@ if not CSActive then
|
||||
end
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
Networking.Receive("_luastart", function (message, client)
|
||||
local num = message.ReadUInt16()
|
||||
|
||||
local packages = {}
|
||||
|
||||
for i = 1, num, 1 do
|
||||
table.insert(packages, {
|
||||
Name = message.ReadString(),
|
||||
Version = message.ReadString(),
|
||||
Id = message.ReadUInt64(),
|
||||
Hash = message.ReadString()
|
||||
})
|
||||
end
|
||||
|
||||
Hook.Call("client.packages", client, packages)
|
||||
end)
|
||||
elseif Game.IsMultiplayer then
|
||||
local message = Networking.Start("_luastart")
|
||||
|
||||
local packageCount = 0
|
||||
for package in ContentPackageManager.EnabledPackages.All do packageCount = packageCount + 1 end
|
||||
|
||||
message.WriteUInt16(packageCount)
|
||||
|
||||
for package in ContentPackageManager.EnabledPackages.All do
|
||||
local id = package.UgcId
|
||||
local hash = package.Hash and package.Hash.StringRepresentation or ""
|
||||
|
||||
if id == nil then id = 0 end
|
||||
|
||||
message.WriteString(package.Name)
|
||||
message.WriteString(package.ModVersion)
|
||||
message.WriteUInt64(UInt64(id))
|
||||
message.WriteString(hash)
|
||||
end
|
||||
|
||||
Networking.Send(message)
|
||||
end
|
||||
|
||||
LuaSetup = nil
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<style>
|
||||
<SettingsMenuTab.LuaCsSettings color="169,212,187,255" hovercolor="220,220,220,255" selectedcolor="255,255,255,255" pressedcolor="100,100,100,255" disabledcolor="125,125,125,125">
|
||||
<Sprite name="LuaCsSettings" texture="%ModDir%/LuaCsSettingsIcon.png" sourcerect="0,0,64,64" tile="false" maintainaspectratio="true" origin="0.5,0.5"/>
|
||||
</SettingsMenuTab.LuaCsSettings>
|
||||
</style>
|
||||
@@ -2,6 +2,11 @@
|
||||
<infotexts language="English" nowhitespace="false" translatedname="English">
|
||||
<LuaCsForBarotrauma.SettingsMenu.ModControlsButton>Mod Controls Settings</LuaCsForBarotrauma.SettingsMenu.ModControlsButton>
|
||||
<LuaCsForBarotrauma.SettingsMenu.ModGameplayButton>Mod Gameplay Settings</LuaCsForBarotrauma.SettingsMenu.ModGameplayButton>
|
||||
<LuaCsForBarotrauma.SettingsMenu.ResetVisibleSettings>Reset Displayed Settings</LuaCsForBarotrauma.SettingsMenu.ResetVisibleSettings>
|
||||
<LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Title>Reset Visible Settings</LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Title>
|
||||
<LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Message>Are you sure you want to reset the values for currently displayed settings?</LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Message>
|
||||
<LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Yes>Yes</LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Yes>
|
||||
<LuaCsForBarotrauma.SettingsMenu.ResetPrompt.No>No</LuaCsForBarotrauma.SettingsMenu.ResetPrompt.No>
|
||||
<!-- Settings -->
|
||||
<!-- Is Cs Enabled-->
|
||||
<LuaCsForBarotrauma.CsRunPolicy.DisplayName>Are C# Mods Allowed</LuaCsForBarotrauma.CsRunPolicy.DisplayName>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<contentpackage name="LuaCsForBarotrauma">
|
||||
<Text file="%ModDir%/Texts/English.xml"/>
|
||||
<UIStyle file="%ModDir%/Style.xml" />
|
||||
<!--<Text file="%ModDir%/Texts/Portuguese.xml"/>-->
|
||||
</contentpackage>
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
AttachedByDefault="true" DisallowAttachingOverTags="container,planter,refuelableitem" DisallowAttachingOverSize="115,130">
|
||||
</Holdable>
|
||||
|
||||
<ItemContainer hideitems="false" drawinventory="true" ItemsUseInventoryPlacement="true" capacity="1" maxstacksize="1" canbeselected="true" itempos="-25,-20" iteminterval="0,0" itemrotation="0" msg="ItemMsgOxygenRefill" containedspritedepth="0.1">
|
||||
<ItemContainer hideitems="false" drawinventory="true" ItemsUseInventoryPlacement="true" capacity="1" maxstacksize="1" canbeselected="true" itempos="32,-83" iteminterval="0,0" itemrotation="0" msg="ItemMsgOxygenRefill" containedspritedepth="0.1">
|
||||
<GuiFrame relativesize="0.2,0.25" anchor="Center" minsize="140,170" maxsize="280,280" style="ItemUI" />
|
||||
<SlotIcon slotindex="0" texture="Content/UI/StatusMonitorUI.png" sourcerect="64,448,64,64" origin="0.5,0.5" />
|
||||
<Containable items="oxygensource" />
|
||||
|
||||
BIN
Binary file not shown.
+64
@@ -23,6 +23,70 @@
|
||||
</Holdable>
|
||||
</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">
|
||||
<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">
|
||||
|
||||
@@ -24,29 +24,52 @@ Hook.Add("missionsEnded", "test", function()
|
||||
print("missionsEnded")
|
||||
end)
|
||||
|
||||
-- cfg tests
|
||||
local str = "CLIENT: "
|
||||
|
||||
if SERVER then
|
||||
str = "SERVER: "
|
||||
end
|
||||
|
||||
function OnChanged(cfg)
|
||||
print(str, "cfg value for ", cfg.InternalName, " changed to ", cfg.Value)
|
||||
end
|
||||
|
||||
local failed, package = trygetpackage("[DebugOnlyTest]TestLuaMod")
|
||||
|
||||
print("packageFailed=", failed)
|
||||
print("package", package.Name)
|
||||
|
||||
local success, config = ConfigService.TryGetConfig(SettingBase.Single, package, "TestFloat")
|
||||
local success, config = ConfigService.TryGetConfig(SettingBase.Int32, package, "TestSynchroServer")
|
||||
local success2, config2 = ConfigService.TryGetConfig(SettingBase.Int32, package, "TestSynchroClient")
|
||||
|
||||
local success2, config2 = ConfigService.TryGetConfig(SettingBase.Int32, package, "TestSynchroServer")
|
||||
local success3, config3 = ConfigService.TryGetConfig(SettingBase.Int32, package, "TestSynchroClient")
|
||||
if not success or not success2 then
|
||||
print("Failed to get configs.")
|
||||
return
|
||||
end
|
||||
|
||||
print("config ", success, " ", config.Value)
|
||||
print("config testsynchrosrv", success2, " ", config2.Value)
|
||||
print("config testsynchrocli", success3, " ", config3.Value)
|
||||
config.OnValueChanged.add(OnChanged)
|
||||
config2.OnValueChanged.add(OnChanged)
|
||||
|
||||
local lastTime = 0
|
||||
print(str, " testsynchroclient=", config2.Value)
|
||||
print(str, " testsynchroserver=", config.Value)
|
||||
|
||||
-- The server should keep updating the value and it should show up on the client.
|
||||
-- The client should try updating and it should fail.
|
||||
|
||||
local lastTime = Timer.Time + 30 -- give time to join
|
||||
|
||||
Hook.Add("think", "printconfig", function()
|
||||
if lastTime > Timer.Time then return end
|
||||
if lastTime > Timer.Time then return end
|
||||
lastTime = Timer.Time + 10
|
||||
|
||||
lastTime = Timer.Time + 10
|
||||
print(config.Value)
|
||||
if SERVER then
|
||||
local succ = config.TrySetValue(config.Value + 1)
|
||||
print("Success of setting value on server for '", config.InternalName,"': ", succ)
|
||||
end
|
||||
if CLIENT then
|
||||
local succ = config.TrySetValue(config.Value + 1)
|
||||
print("Success of setting value on client for '", config.InternalName,"': ", succ, " | This should fail if permissions are not set for client.")
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
config.TrySetValue(config.Value + 1)
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -2,4 +2,6 @@
|
||||
<ModConfig>
|
||||
<Lua File="%ModDir%/Lua/init.lua" IsAutorun="true" />
|
||||
<Config File="%ModDir%/Settings.xml"/>
|
||||
<Config File="%ModDir%/SettingsClient.xml" Target="Client"/>
|
||||
<Config File="%ModDir%/SettingsServer.xml" Target="Server"/>
|
||||
</ModConfig>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Configuration>
|
||||
<Settings>
|
||||
<Setting Name="TestTickbox" Type="bool"/>
|
||||
<Setting Name="TestSynchroClient" Type="int" NetSync="TwoWay" />
|
||||
<Setting Name="TestSynchroServer" Type="int" NetSync="ServerAuthority" />
|
||||
<Setting Name="TestFloat" Type="float" NetSync="ServerAuthority" />
|
||||
<Setting Name="TestHidden" Type="bool" ShowInMenus="false"/>
|
||||
<Setting Name="TestRangeFloat" Type="rangeFloat" Min="0" Max="25" Steps="11"/>
|
||||
<Setting Name="TestRangeInt" Type="rangeInt" Min="0" Max="10" Steps="11"/>
|
||||
<Setting Name="TestString" Type="string" />
|
||||
<Setting Name="TestTickbox" Type="bool" Value="true"/>
|
||||
<Setting Name="TestSynchroClient" Type="int" NetSync="TwoWay" Value="40"/>
|
||||
<Setting Name="TestSynchroServer" Type="int" NetSync="ServerAuthority" Value="25"/>
|
||||
<Setting Name="TestFloat" Type="float" Value="3498"/>
|
||||
<Setting Name="TestHidden" Type="bool" ShowInMenus="false" Value="false"/>
|
||||
<Setting Name="TestRangeFloat" Type="rangeFloat" Min="0" Max="25" Steps="11" Value="4.5"/>
|
||||
<Setting Name="TestRangeInt" Type="rangeInt" Min="0" Max="10" Steps="11" Value="4"/>
|
||||
<Setting Name="TestString" Type="string" Value="ok"/>
|
||||
<Setting Name="TestControl" Type="control" Value="A"/>
|
||||
<Setting Name="TestDropdownList" Type="listString" Value="Hi">
|
||||
<Values>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Configuration>
|
||||
<!-- Should match 'SettingsServer'. We define these on the client and server separately to give different values. -->
|
||||
<Settings>
|
||||
<Setting Name="TestSynchroClient" Type="int" NetSync="TwoWay" Value="3545" ShowInMenus="false"/>
|
||||
<Setting Name="TestSynchroServer" Type="int" NetSync="ServerAuthority" Value="577" ShowInMenus="false"/>
|
||||
</Settings>
|
||||
</Configuration>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Configuration>
|
||||
<!-- Should match 'SettingsClient'. We define these on the client and server separately to give different values. -->
|
||||
<Settings>
|
||||
<Setting Name="TestSynchroClient" Type="int" NetSync="TwoWay" Value="40" ShowInMenus="false"/>
|
||||
<Setting Name="TestSynchroServer" Type="int" NetSync="ServerAuthority" Value="25" ShowInMenus="false"/>
|
||||
</Settings>
|
||||
</Configuration>
|
||||
@@ -524,8 +524,7 @@ namespace Barotrauma
|
||||
{
|
||||
UnlockAchievement(causeOfDeath.Killer, "killpoison".ToIdentifier());
|
||||
}
|
||||
else if (item.Prefab.Identifier == "nuclearshell" ||
|
||||
item.Prefab.Identifier == "nucleardepthcharge")
|
||||
else if (item.Prefab.Tags.Contains("nuclearexplosive"))
|
||||
{
|
||||
UnlockAchievement(causeOfDeath.Killer, "killnuke".ToIdentifier());
|
||||
}
|
||||
|
||||
@@ -197,6 +197,18 @@ namespace Barotrauma
|
||||
|
||||
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 SwarmBehavior SwarmBehavior { get; private set; }
|
||||
public PetBehavior PetBehavior { get; private set; }
|
||||
@@ -242,10 +254,13 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The monster won't try to damage these submarines
|
||||
/// The monster won't try to damage these submarines. Applies to hulls, structures and static items (items without a physics body) belonging to these submarines. Does not apply to non-static items, e.g. flares or other provocative items.
|
||||
/// </summary>
|
||||
private readonly HashSet<Submarine> unattackableSubmarines = new HashSet<Submarine>();
|
||||
private readonly HashSet<Submarine> unattackableSubmarines = [];
|
||||
|
||||
/// <summary>
|
||||
/// Set the submarine(s) the monster won't attack. Applies to hulls, structures and static items (items without a physics body) belonging to these submarines. Does not apply to non-static items, e.g. flares or other provocative items.
|
||||
/// </summary>
|
||||
public void SetUnattackableSubmarines(Submarine submarine, bool includeOwnSub = true, bool includeConnectedSubs = true, bool clearExisting = true)
|
||||
{
|
||||
if (clearExisting)
|
||||
@@ -2676,11 +2691,19 @@ namespace Barotrauma
|
||||
if (!ActiveAttack.IsRunning)
|
||||
{
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.SetAttackTargetEventData(
|
||||
AttackLimb,
|
||||
damageTarget,
|
||||
targetLimb,
|
||||
SimPosition));
|
||||
if (Timing.TotalTime > lastSetAttackTargetEventTime + MinSetAttackTargetEventInterval ||
|
||||
damageTarget != lastDamageTarget || AttackLimb != lastAttackLimb || targetLimb != lastTargetLimb)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.SetAttackTargetEventData(
|
||||
AttackLimb,
|
||||
damageTarget,
|
||||
targetLimb,
|
||||
SimPosition));
|
||||
lastSetAttackTargetEventTime = Timing.TotalTime;
|
||||
lastDamageTarget = damageTarget;
|
||||
lastAttackLimb = AttackLimb;
|
||||
lastTargetLimb = targetLimb;
|
||||
}
|
||||
#else
|
||||
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
|
||||
#endif
|
||||
@@ -3075,11 +3098,17 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ignore all structures, items, and hulls inside these subs.
|
||||
if (aiTarget.Entity.Submarine != null)
|
||||
{
|
||||
//ignore all items, structures and hulls in wrecks and beacon stations
|
||||
//(we don't want monsters to be distracted by them during missions,
|
||||
//nor have monsters inside them attack "their home" rather than the player)
|
||||
if (aiTarget.Entity.Submarine.Info.IsWreck ||
|
||||
aiTarget.Entity.Submarine.Info.IsBeacon ||
|
||||
aiTarget.Entity.Submarine.Info.IsBeacon)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (aiTarget.Entity is Structure or Hull or Item { body: null } &&
|
||||
unattackableSubmarines.Contains(aiTarget.Entity.Submarine))
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace Barotrauma
|
||||
|
||||
private float respondToAttackTimer;
|
||||
private const float RespondToAttackInterval = 1.0f;
|
||||
private bool wasConscious;
|
||||
private bool wasDead;
|
||||
|
||||
private bool freezeAI;
|
||||
|
||||
@@ -201,7 +201,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (isIncapacitated) { return; }
|
||||
|
||||
wasConscious = true;
|
||||
wasDead = false;
|
||||
|
||||
respondToAttackTimer -= deltaTime;
|
||||
if (respondToAttackTimer <= 0.0f)
|
||||
@@ -1256,14 +1256,15 @@ namespace Barotrauma
|
||||
|
||||
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 (wasConscious && (Character.IsIncapacitated || Character.Stun > 0.0f))
|
||||
// If the character is incapacitated or dead, respond to the attack anyway to let other nearby characters react to it
|
||||
// (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);
|
||||
wasConscious = false;
|
||||
wasDead = Character.IsDead;
|
||||
return;
|
||||
}
|
||||
if (Character.IsDead) { return; }
|
||||
if (attacker == null || Character.IsPlayer)
|
||||
{
|
||||
// The player characters need to "respond" to the attack always, because the update loop doesn't run for them.
|
||||
@@ -1468,9 +1469,9 @@ namespace Barotrauma
|
||||
otherCharacter.CanSeeTarget(attacker, seeThroughWindows: true);
|
||||
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;
|
||||
}
|
||||
if (otherHumanAI.objectiveManager.HasOrders())
|
||||
@@ -1494,6 +1495,14 @@ namespace Barotrauma
|
||||
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);
|
||||
otherHumanAI.AddCombatObjective(combatMode, attacker, delay);
|
||||
}
|
||||
@@ -1926,12 +1935,12 @@ namespace Barotrauma
|
||||
character.IsCriminal = true;
|
||||
character.IsActingOffensively = true;
|
||||
}
|
||||
if (!TriggerSecurity(otherHumanAI, combatMode))
|
||||
if (!TriggerSecurity(otherHumanAI, character, combatMode))
|
||||
{
|
||||
// Else call the others
|
||||
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
|
||||
return;
|
||||
@@ -1941,25 +1950,25 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
bool TriggerSecurity(HumanAIController humanAI, AIObjectiveCombat.CombatMode combatMode)
|
||||
{
|
||||
if (humanAI == null) { return false; }
|
||||
if (!humanAI.Character.IsSecurity) { return false; }
|
||||
if (humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveCombat>()) { return false; }
|
||||
humanAI.AddCombatObjective(combatMode, character, delay: GetReactionTime(),
|
||||
onCompleted: () =>
|
||||
}
|
||||
private static bool TriggerSecurity(HumanAIController humanAI, Character targetCharacter, AIObjectiveCombat.CombatMode combatMode)
|
||||
{
|
||||
if (humanAI == null) { return false; }
|
||||
if (!humanAI.Character.IsSecurity) { return false; }
|
||||
if (humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveCombat>()) { return false; }
|
||||
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(character);
|
||||
}
|
||||
anyAI.structureDamageAccumulator?.Remove(targetCharacter);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void ItemTaken(Item item, Character thief)
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@ namespace Barotrauma
|
||||
bool operateExtinguisher = !moveCloser || (dist < extinguisher.Range * 1.2f && character.CanSeeTarget(targetHull));
|
||||
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;
|
||||
character.CursorPosition += VectorExtensions.Forward(extinguisherItem.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, fromCharacterToFireSource.Length() / 2);
|
||||
if (extinguisherItem.RequireAimToUse)
|
||||
|
||||
+10
@@ -173,6 +173,16 @@ namespace Barotrauma
|
||||
if (character.CanInteractWith(Item, out _, checkLinked: false))
|
||||
{
|
||||
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; }
|
||||
|
||||
HumanAIController.FaceTarget(Item);
|
||||
|
||||
@@ -1193,6 +1193,10 @@ namespace Barotrauma
|
||||
|
||||
public void Teleport(Vector2 moveAmount, Vector2 velocityChange, bool detachProjectiles = true)
|
||||
{
|
||||
// Hopefully this will fix some crashes :(
|
||||
// If Collider was null then no need to procced: nothing is there already
|
||||
if (Collider == null) { return; }
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
@@ -1214,6 +1218,7 @@ namespace Barotrauma
|
||||
|
||||
character.DisableImpactDamageTimer = 0.25f;
|
||||
|
||||
// Why they did null check below but didn't do it here????
|
||||
SetPosition(Collider.SimPosition + moveAmount);
|
||||
character.CursorPosition += moveAmount;
|
||||
|
||||
@@ -2235,8 +2240,9 @@ namespace Barotrauma
|
||||
if (limb == null)
|
||||
{
|
||||
// Didn't seek or find a (valid) limb of the matching type. If there's multiple limbs of the same type, check the other limbs.
|
||||
foreach (var l in limbs)
|
||||
foreach (var l in Limbs)
|
||||
{
|
||||
if (l == null) { continue; }
|
||||
if (l.Removed) { continue; }
|
||||
if (useSecondaryType)
|
||||
{
|
||||
|
||||
@@ -758,6 +758,11 @@ namespace Barotrauma
|
||||
public float CoolDownTimer { get; set; }
|
||||
public float CurrentRandomCoolDown { get; private 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 float AfterAttackTimer { get; set; }
|
||||
|
||||
@@ -997,6 +997,9 @@ namespace Barotrauma
|
||||
public bool IsForceRagdolled;
|
||||
public bool FollowCursor = true;
|
||||
|
||||
/// <summary>
|
||||
/// Is the character currently dead, unconscious or paralyzed?
|
||||
/// </summary>
|
||||
public bool IsIncapacitated
|
||||
{
|
||||
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
|
||||
{
|
||||
get { return CharacterHealth.IsUnconscious; }
|
||||
@@ -1673,7 +1679,8 @@ namespace Barotrauma
|
||||
AnimController.FindHull(setInWater: true);
|
||||
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);
|
||||
|
||||
@@ -2262,7 +2269,10 @@ namespace Barotrauma
|
||||
{
|
||||
Vector2 targetMovement = GetTargetMovement();
|
||||
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)
|
||||
@@ -3432,21 +3442,9 @@ namespace Barotrauma
|
||||
|
||||
characterUpdateTick++;
|
||||
|
||||
if (characterUpdateTick % CharacterUpdateInterval == 0)
|
||||
for (int i = 0; i < CharacterList.Count; i++)
|
||||
{
|
||||
for (int i = 0; i < CharacterList.Count; i++)
|
||||
{
|
||||
if (LuaCsSetup.Instance.Game.UpdatePriorityCharacters.Contains(CharacterList[i])) continue;
|
||||
|
||||
CharacterList[i].Update(deltaTime * CharacterUpdateInterval, cam);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Character character in LuaCsSetup.Instance.Game.UpdatePriorityCharacters)
|
||||
{
|
||||
if (character.Removed) { continue; }
|
||||
Debug.Assert(character is { Removed: false });
|
||||
character.Update(deltaTime, cam);
|
||||
CharacterList[i].Update(deltaTime, cam);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
@@ -3520,9 +3518,11 @@ namespace Barotrauma
|
||||
|
||||
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; }
|
||||
@@ -5813,6 +5813,12 @@ namespace Barotrauma
|
||||
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()
|
||||
{
|
||||
if (TalentTree.JobTalentTrees.TryGet(Info.Job.Prefab.Identifier, out TalentTree talentTree))
|
||||
|
||||
@@ -505,17 +505,19 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public float GetResistance(AfflictionPrefab afflictionPrefab, LimbType limbType)
|
||||
{
|
||||
// This is a % resistance (0 to 1.0)
|
||||
float resistance = 0.0f;
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||
{
|
||||
var affliction = kvp.Key;
|
||||
resistance += affliction.GetResistance(afflictionPrefab.Identifier, limbType);
|
||||
lock (afflictions) {
|
||||
// This is a % resistance (0 to 1.0)
|
||||
float resistance = 0.0f;
|
||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||
{
|
||||
var affliction = kvp.Key;
|
||||
resistance += affliction.GetResistance(afflictionPrefab.Identifier, limbType);
|
||||
}
|
||||
// This is a multiplier, ie. 0.0 = 100% resistance and 1.0 = 0% resistance
|
||||
float abilityResistanceMultiplier = Character.GetAbilityResistance(afflictionPrefab);
|
||||
// The returned value is calculated to be a % resistance again
|
||||
return 1 - ((1 - resistance) * abilityResistanceMultiplier);
|
||||
}
|
||||
// This is a multiplier, ie. 0.0 = 100% resistance and 1.0 = 0% resistance
|
||||
float abilityResistanceMultiplier = Character.GetAbilityResistance(afflictionPrefab);
|
||||
// The returned value is calculated to be a % resistance again
|
||||
return 1 - ((1 - resistance) * abilityResistanceMultiplier);
|
||||
}
|
||||
|
||||
public float GetStatValue(StatTypes statType)
|
||||
|
||||
@@ -282,16 +282,27 @@ namespace Barotrauma
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && Entity.Spawner != null && createNetworkEvents)
|
||||
{
|
||||
if (GameMain.Server.EntityEventManager.UniqueEvents.Any(ev => ev.Entity == item))
|
||||
try
|
||||
{
|
||||
string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created.";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item);
|
||||
GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item);
|
||||
if (GameMain.Server.EntityEventManager.UniqueEvents.ToList().Any(ev => ev.Entity == item))
|
||||
{
|
||||
string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created.";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item);
|
||||
GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
#if SERVER
|
||||
Networking.GameServer.Log("Try making UniqueEvents snapshot failed", Networking.ServerLog.MessageType.Error);
|
||||
#endif
|
||||
}
|
||||
finally
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
}
|
||||
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
}
|
||||
#endif
|
||||
if (itemElement.GetAttributeBool("equip", false))
|
||||
|
||||
@@ -170,16 +170,28 @@ namespace Barotrauma
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && Entity.Spawner != null)
|
||||
{
|
||||
if (GameMain.Server.EntityEventManager.UniqueEvents.Any(ev => ev.Entity == item))
|
||||
try
|
||||
{
|
||||
string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created.";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item);
|
||||
GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item);
|
||||
if (GameMain.Server.EntityEventManager.UniqueEvents.ToList().Any(ev => ev.Entity == item))
|
||||
{
|
||||
string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created.";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item);
|
||||
GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
#if SERVER
|
||||
Networking.GameServer.Log("Try making UniqueEvents snapshot failed", Networking.ServerLog.MessageType.Error);
|
||||
#endif
|
||||
}
|
||||
finally
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
}
|
||||
|
||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1022,6 +1022,15 @@ namespace Barotrauma
|
||||
|
||||
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>();
|
||||
/// <summary>
|
||||
/// 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);
|
||||
}
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new Character.ExecuteAttackEventData(
|
||||
attackLimb: this, targetEntity: damageTarget, targetLimb: targetLimb,
|
||||
targetSimPos: attackSimPos));
|
||||
if (Timing.TotalTime > lastExecuteAttackEventTime + MinExecuteAttackEventInterval)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new Character.ExecuteAttackEventData(
|
||||
attackLimb: this, targetEntity: damageTarget, targetLimb: targetLimb,
|
||||
targetSimPos: attackSimPos));
|
||||
lastExecuteAttackEventTime = Timing.TotalTime;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -43,14 +43,14 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
identifiers.Add(value.GetRandomUnsynced());
|
||||
|
||||
@@ -131,7 +131,7 @@ namespace Barotrauma
|
||||
if (character.Info.GetTotalTalentPoints() - selectedTalents.Count <= 0) { 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))
|
||||
{
|
||||
@@ -163,16 +163,6 @@ namespace Barotrauma
|
||||
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)
|
||||
{
|
||||
List<Identifier> viableTalents = new List<Identifier>();
|
||||
|
||||
@@ -590,6 +590,11 @@ namespace Barotrauma
|
||||
package.UgcId.TryUnwrap(out var ugcId) && ugcId is SteamWorkshopId workshopId && workshopId.Value == childUgcItemId.Value));
|
||||
foreach (var missingChild in missingChildren)
|
||||
{
|
||||
if (missingChild.ToString() == "2559634234" ||
|
||||
missingChild.ToString() == "2795927223")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
enabledPackage.AddMissingDependency(missingChild);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -252,7 +252,7 @@ namespace Barotrauma
|
||||
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 =
|
||||
@@ -271,7 +271,7 @@ namespace Barotrauma
|
||||
};
|
||||
}, 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) =>
|
||||
{
|
||||
if (Character.Controlled == null)
|
||||
@@ -294,7 +294,10 @@ namespace Barotrauma
|
||||
{
|
||||
return new string[][]
|
||||
{
|
||||
GetItemNameOrIdParams().ToArray()
|
||||
GetItemNameOrIdParams().ToArray(),
|
||||
new string[] { "1" },
|
||||
new string[] { "100" },
|
||||
ItemQualityNames.ToArray()
|
||||
};
|
||||
}, isCheat: true));
|
||||
|
||||
@@ -311,7 +314,7 @@ namespace Barotrauma
|
||||
};
|
||||
}, 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) =>
|
||||
{
|
||||
TrySpawnItem(args);
|
||||
@@ -321,7 +324,10 @@ namespace Barotrauma
|
||||
return new string[][]
|
||||
{
|
||||
GetItemNameOrIdParams().ToArray(),
|
||||
GetSpawnPosParams().ToArray()
|
||||
GetSpawnPosParams().ToArray(),
|
||||
new string[] { "1" },
|
||||
new string[] { "100" },
|
||||
ItemQualityNames.ToArray()
|
||||
};
|
||||
}, isCheat: true));
|
||||
|
||||
@@ -1324,6 +1330,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GameMain.GameSession?.Map is Map map) { NewMessage("Map seed: " + map.Seed); }
|
||||
NewMessage("Level seed: " + Level.Loaded.Seed);
|
||||
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()));
|
||||
@@ -1333,17 +1340,29 @@ namespace Barotrauma
|
||||
}
|
||||
},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) =>
|
||||
{
|
||||
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 SERVER
|
||||
ThrowError("Cannot teleport the sub to the position of the cursor. Use \"start\" or \"end\", or execute the command as a client.");
|
||||
#else
|
||||
Submarine.MainSub.SetPosition(Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition));
|
||||
submarineToTeleport.SetPosition(Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition));
|
||||
#endif
|
||||
}
|
||||
else if (args[0].Equals("start", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -1356,9 +1375,9 @@ namespace Barotrauma
|
||||
Vector2 pos = Level.Loaded.StartPosition;
|
||||
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))
|
||||
{
|
||||
@@ -1370,9 +1389,9 @@ namespace Barotrauma
|
||||
Vector2 pos = Level.Loaded.EndPosition;
|
||||
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))
|
||||
{
|
||||
@@ -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);
|
||||
return;
|
||||
}
|
||||
Submarine.MainSub.SetPosition(Level.Loaded.EndExitPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height);
|
||||
var submarineDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == Submarine.MainSub);
|
||||
submarineToTeleport.SetPosition(Level.Loaded.EndExitPosition - Vector2.UnitY * submarineToTeleport.Borders.Height);
|
||||
var submarineDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == submarineToTeleport);
|
||||
var outpostDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == Level.Loaded.EndOutpost);
|
||||
if (submarineDockingPort != null && outpostDockingPort != null)
|
||||
{
|
||||
@@ -1394,7 +1413,8 @@ namespace Barotrauma
|
||||
{
|
||||
return new string[][]
|
||||
{
|
||||
new string[] { "start", "end", "endoutpost", "cursor" }
|
||||
new string[] { "start", "end", "endoutpost", "cursor" },
|
||||
ListAvailableSubmarines()
|
||||
};
|
||||
}, isCheat: true));
|
||||
|
||||
@@ -2570,6 +2590,16 @@ namespace Barotrauma
|
||||
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)
|
||||
{
|
||||
if (Submarine.MainSub is Submarine mainSub && string.Equals(locationName, "mainsub", StringComparison.InvariantCultureIgnoreCase))
|
||||
@@ -2952,7 +2982,7 @@ namespace Barotrauma
|
||||
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)
|
||||
{
|
||||
@@ -2983,6 +3013,14 @@ namespace Barotrauma
|
||||
CharacterInfo characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: job, variant: variant);
|
||||
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);
|
||||
newCharacter.GiveJobItems(isPvPMode: GameMain.GameSession?.GameMode is PvPMode, 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;
|
||||
spawnPoint = null;
|
||||
@@ -3096,6 +3134,12 @@ namespace Barotrauma
|
||||
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 "inventory";
|
||||
yield return "cargo";
|
||||
|
||||
#if SERVER
|
||||
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)
|
||||
{
|
||||
try
|
||||
@@ -3203,6 +3250,7 @@ namespace Barotrauma
|
||||
|
||||
int amount = 1;
|
||||
int conditionPrc = 100;
|
||||
int itemQuality = 0;
|
||||
|
||||
if (TryGetSpawnPosParam(out string spawnLocation, out int spawnLocationIndex))
|
||||
{
|
||||
@@ -3223,7 +3271,7 @@ namespace Barotrauma
|
||||
break;
|
||||
default:
|
||||
var matchingCharacter = FindMatchingCharacter(args.Skip(1).Take(1).ToArray());
|
||||
if (matchingCharacter != null){ spawnInventory = matchingCharacter.Inventory; }
|
||||
if (matchingCharacter != null) { spawnInventory = matchingCharacter.Inventory; }
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3235,7 +3283,18 @@ namespace Barotrauma
|
||||
|
||||
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
|
||||
{
|
||||
Entity.Spawner?.AddItemToSpawnQueue(itemPrefab, spawnPos.Value, condition: itemCondition);
|
||||
Entity.Spawner?.AddItemToSpawnQueue(itemPrefab, spawnPos.Value, condition: itemCondition, quality: itemQuality);
|
||||
}
|
||||
}
|
||||
else if (spawnInventory != null)
|
||||
@@ -3284,6 +3343,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
item.Condition = item.Health * Math.Clamp(conditionPrc / 100f, 0f, 1f);
|
||||
item.Quality = itemQuality;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,12 +31,17 @@ namespace Barotrauma
|
||||
Actions = new List<EventAction>();
|
||||
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.",
|
||||
contentPackage: element.ContentPackage);
|
||||
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);
|
||||
if (action != null) { Actions.Add(action); }
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ namespace Barotrauma
|
||||
AddTargetPredicate(
|
||||
Tags.Traitor,
|
||||
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()
|
||||
@@ -131,7 +131,7 @@ namespace Barotrauma
|
||||
AddTargetPredicate(
|
||||
Tags.NonTraitor,
|
||||
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()
|
||||
@@ -139,7 +139,7 @@ namespace Barotrauma
|
||||
AddTargetPredicate(
|
||||
Tags.NonTraitorPlayer,
|
||||
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)
|
||||
@@ -151,7 +151,8 @@ namespace Barotrauma
|
||||
e is Character c &&
|
||||
c.IsBot &&
|
||||
(!c.IsIncapacitated || !IgnoreIncapacitatedCharacters) &&
|
||||
(!playerCrewOnly || c.TeamID == CharacterTeamType.Team1));
|
||||
(!playerCrewOnly || c.TeamID == CharacterTeamType.Team1) &&
|
||||
CharacterTeamMatches(c));
|
||||
}
|
||||
|
||||
private void TagCrew()
|
||||
@@ -171,7 +172,7 @@ namespace Barotrauma
|
||||
|
||||
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)
|
||||
|
||||
@@ -168,6 +168,9 @@ namespace Barotrauma.Items.Components
|
||||
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)."+
|
||||
" 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
|
||||
@@ -178,7 +181,9 @@ namespace Barotrauma.Items.Components
|
||||
//the distance from the holding characters elbow to center of the physics body of the item
|
||||
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)."+
|
||||
" 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
|
||||
@@ -279,6 +284,9 @@ namespace Barotrauma.Items.Components
|
||||
/// <summary>
|
||||
/// For setting the handle positions using status effects
|
||||
/// </summary>
|
||||
#if DEBUG
|
||||
[Editable]
|
||||
#endif
|
||||
public Vector2 Handle1
|
||||
{
|
||||
get { return ConvertUnits.ToDisplayUnits(handlePos[0]); }
|
||||
@@ -299,6 +307,9 @@ namespace Barotrauma.Items.Components
|
||||
/// <summary>
|
||||
/// For setting the handle positions using status effects
|
||||
/// </summary>
|
||||
#if DEBUG
|
||||
[Editable]
|
||||
#endif
|
||||
public Vector2 Handle2
|
||||
{
|
||||
get { return ConvertUnits.ToDisplayUnits(handlePos[1]); }
|
||||
|
||||
@@ -119,7 +119,7 @@ namespace Barotrauma.Items.Components
|
||||
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 (item.GetComponents<Pickable>().Any())
|
||||
@@ -156,7 +156,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
ApplyStatusEffects(ActionType.OnPicked, 1.0f, picker);
|
||||
#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);
|
||||
#endif
|
||||
if (pickDroppedStack)
|
||||
@@ -164,7 +164,7 @@ namespace Barotrauma.Items.Components
|
||||
foreach (var droppedItem in droppedStack)
|
||||
{
|
||||
if (droppedItem == item) { continue; }
|
||||
droppedItem.GetComponent<Pickable>().OnPicked(picker, pickDroppedStack: false);
|
||||
droppedItem.GetComponent<Pickable>().OnPicked(picker, pickDroppedStack: false, playSound: false);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize("0.0,0.0", IsPropertySaveable.No, description: "The position where the contained items get drawn at. In the case of items with a physics body, the offset is from the center of the body, on items without one from the top-left corner of the sprite. In pixels.")]
|
||||
[Serialize("0.0,0.0", IsPropertySaveable.No, description: "The position where the contained items get drawn at (offset from the upper left corner of the sprite in pixels).")]
|
||||
public Vector2 ItemPos { get; set; }
|
||||
|
||||
[Serialize("0.0,0.0", IsPropertySaveable.No, description: "The interval at which the contained items are spaced apart from each other (in pixels).")]
|
||||
@@ -1093,25 +1093,21 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (item.body == null)
|
||||
{
|
||||
//if the item is a holdable item currently attached to a wall (i.e. normally has a physics body, but the body is now disabled),
|
||||
//we must position the contained items using the center as the origin since the item positions have been configured with the assumption the item has a body
|
||||
bool isAttachedHoldable = item.GetComponent<Holdable>() is { Attached: true };
|
||||
bool useCenterAsOrigin = isAttachedHoldable;
|
||||
if (flippedX)
|
||||
{
|
||||
transformedItemPos.X = -transformedItemPos.X;
|
||||
if (!useCenterAsOrigin) { transformedItemPos.X += item.Rect.Width; }
|
||||
transformedItemPos.X += item.Rect.Width;
|
||||
transformedItemInterval.X = -transformedItemInterval.X;
|
||||
transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
|
||||
}
|
||||
if (flippedY)
|
||||
{
|
||||
transformedItemPos.Y = -transformedItemPos.Y;
|
||||
if (!useCenterAsOrigin) { transformedItemPos.Y -= item.Rect.Height; }
|
||||
transformedItemPos.Y -= item.Rect.Height;
|
||||
transformedItemInterval.Y = -transformedItemInterval.Y;
|
||||
transformedItemIntervalVertical.Y = -transformedItemIntervalVertical.Y;
|
||||
}
|
||||
transformedItemPos += useCenterAsOrigin ? item.Position : new Vector2(item.Rect.X, item.Rect.Y);
|
||||
transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y);
|
||||
if (drawPosition)
|
||||
{
|
||||
if (item.Submarine != null) { transformedItemPos += item.Submarine.DrawPosition; }
|
||||
@@ -1129,6 +1125,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.GetComponent<Holdable>() is { Attachable: true })
|
||||
{
|
||||
//if the item is attachable to walls, we need a bit of special logic because the item can either
|
||||
//have or not have a body depending on whether it's attached.
|
||||
|
||||
//since it seems previously the contained item positions have always been configured as if the item had no body (using the top-left corner as the origin),
|
||||
//let's modify the position here to position the items correctly even when the body is active (moving the origin from the center of the body to the top-left corner)
|
||||
transformedItemPos -= item.Rect.Size.FlipY().ToVector2() / 2;
|
||||
}
|
||||
|
||||
Matrix transform = Matrix.CreateRotationZ(drawPosition ? item.body.DrawRotation : item.body.Rotation);
|
||||
if (bodyFlipped)
|
||||
{
|
||||
|
||||
+15
@@ -42,6 +42,9 @@ namespace Barotrauma.Items.Components
|
||||
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 bool DoesBleed => Character?.DoesBleed == true;
|
||||
@@ -50,6 +53,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public LinkedControllerCharacterComponent(Item item, ContentXElement element) : base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
#if CLIENT
|
||||
spriteOverrides = element.Elements()
|
||||
.Where(static e => e.Name.LocalName.ToLowerInvariant() == "spriteoverride")
|
||||
@@ -58,6 +63,16 @@ namespace Barotrauma.Items.Components
|
||||
#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)
|
||||
{
|
||||
Character = character;
|
||||
|
||||
@@ -525,7 +525,7 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
if (containerToSpawnOnSelectedItem.Inventory.AllItems.Any(x => x.Prefab == spawnItemOnSelectedPrefab))
|
||||
if (containerToSpawnOnSelectedItem.Inventory.AllItems.Any(item => item == spawnedItemOnSelected))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -862,10 +862,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (CanBeSelectedByCharacters)
|
||||
{
|
||||
#if SERVER
|
||||
GameServer.Log($"{GameServer.CharacterLogName(activator)} entered {item.Name}", ServerLog.MessageType.ItemInteraction);
|
||||
#endif
|
||||
|
||||
activator.DeselectCharacter();
|
||||
|
||||
User = activator;
|
||||
|
||||
@@ -345,6 +345,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (targetItem.AllowDeconstruct && allowRemove)
|
||||
{
|
||||
ApplyDeconstructionStatusEffects(targetItem, ActionType.OnDeconstructed, 1f);
|
||||
|
||||
//drop all items that are inside the deconstructed item
|
||||
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
|
||||
MoveItemsFromCharacterToOutput();
|
||||
|
||||
character.Kill(CauseOfDeathType.Unknown, causeOfDeathAffliction: null);
|
||||
Entity.Spawner?.AddEntityToRemoveQueue(character);
|
||||
}, 0.1f);
|
||||
}
|
||||
|
||||
@@ -732,6 +732,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -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
|
||||
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;
|
||||
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; }
|
||||
|
||||
@@ -811,6 +826,16 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
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)
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public LocalizedString? DisplayName { get; private set; }
|
||||
public LocalizedString DisplayName { get; private set; }
|
||||
|
||||
private float supplyRatio = 1f;
|
||||
public float SupplyRatio
|
||||
@@ -80,6 +80,7 @@ namespace Barotrauma.Items.Components
|
||||
SupplyRatio = element.GetAttributeFloat("ratio", SupplyRatio);
|
||||
}
|
||||
|
||||
DisplayName = TextManager.Get(name).Fallback(name);
|
||||
#if CLIENT
|
||||
CreateGUI();
|
||||
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))
|
||||
{
|
||||
UserData = item
|
||||
UserData = this
|
||||
};
|
||||
}
|
||||
else
|
||||
@@ -260,7 +260,7 @@ namespace Barotrauma.Items.Components
|
||||
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))
|
||||
{
|
||||
UserData = item
|
||||
UserData = this
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -734,8 +734,8 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Hides the condition displayed in the item's tooltip.")]
|
||||
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.")]
|
||||
public Identifier UnlockedRecipeInToolTip { get; set; }
|
||||
[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; }
|
||||
|
||||
//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
|
||||
|
||||
@@ -11,6 +11,7 @@ public interface ILuaCsHook : ILuaPatcher, ILuaCsShim
|
||||
void Add(string eventName, string identifier, LuaCsFunc callback, object owner = null);
|
||||
[Obsolete("ACsMod is deprecated. Use ILuaEventService.Add() instead.")]
|
||||
void Add(string eventName, LuaCsFunc callback, object owner = null);
|
||||
void Remove(string eventName, string identifier);
|
||||
// Does anyone use this? TODO: Analyze old Lua mods for usage scenarios.
|
||||
//bool Exists(string eventName, string identifier);
|
||||
object Call(string eventName, params object[] args);
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
using Barotrauma;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
class LuaCsConfig
|
||||
{
|
||||
private enum ValueType
|
||||
{
|
||||
None,
|
||||
Text,
|
||||
Integer,
|
||||
Decimal,
|
||||
Boolean,
|
||||
Collection,
|
||||
Object,
|
||||
Enum
|
||||
}
|
||||
|
||||
private static Type[] LoadDocTypes(XElement typesElem)
|
||||
{
|
||||
var result = new List<Type>();
|
||||
var loadedTypes = AssemblyLoadContext.All
|
||||
.Where(alc => alc != AssemblyLoadContext.Default)
|
||||
.SelectMany(alc => alc.Assemblies)
|
||||
.SelectMany(asm => asm.GetTypes())
|
||||
.ToImmutableArray();
|
||||
|
||||
foreach (var elem in typesElem.Elements())
|
||||
{
|
||||
var typesFound = loadedTypes.Where(t => t.FullName?.EndsWith(elem.Value) ?? false).ToImmutableList();
|
||||
if (!typesFound.Any())
|
||||
{
|
||||
ModUtils.Logging.PrintError(
|
||||
$"{nameof(LuaCsConfig)}::{nameof(LoadDocTypes)}() | Unable to find a matching type for {elem.Value}");
|
||||
continue;
|
||||
}
|
||||
result.AddRange(typesFound);
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
private static IEnumerable<XElement> SaveDocTypes(IEnumerable<Type> types)
|
||||
{
|
||||
return types.Select(t => new XElement("Type", t.ToString()));
|
||||
}
|
||||
|
||||
private static Type GetTypeAttr(Type[] types, XElement elem)
|
||||
{
|
||||
var idx = elem.GetAttributeInt("Type", -1);
|
||||
if (idx < 0 || idx >= types.Length) throw new Exception($"Type index '{idx}' is outside of saved types bounds");
|
||||
return types[idx];
|
||||
}
|
||||
private static ValueType GetValueType(XElement elem)
|
||||
{
|
||||
Enum.TryParse(typeof(ValueType), elem.Attribute("Value")?.Value, out object result);
|
||||
if (result != null) return (ValueType)result;
|
||||
else return ValueType.None;
|
||||
}
|
||||
private static object ParseValue(Type[] types, XElement elem)
|
||||
{
|
||||
var type = GetValueType(elem);
|
||||
|
||||
if (elem.IsEmpty) return null;
|
||||
if (type == ValueType.Enum)
|
||||
{
|
||||
var tType = GetTypeAttr(types, elem);
|
||||
if (tType == null || !tType.IsSubclassOf(typeof(Enum))) return null;
|
||||
if (Enum.TryParse(tType, elem.Value, out object result)) return result;
|
||||
else return null;
|
||||
}
|
||||
if (type == ValueType.Collection)
|
||||
{
|
||||
var tType = GetTypeAttr(types, elem);
|
||||
var tInt = tType.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
|
||||
var gArg = tInt.GetGenericArguments()[0];
|
||||
if (tType == null || !tType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))) return null;
|
||||
|
||||
object result = null;
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
var ctor = tType.GetConstructors(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(c =>
|
||||
{
|
||||
var param = c.GetParameters();
|
||||
return param.Count() == 1 && param.Any(p => p.ParameterType.IsGenericType && p.ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>));
|
||||
});
|
||||
if (ctor != null)
|
||||
{
|
||||
var elements = elem.Elements().Select(x => ParseValue(types, x));
|
||||
var castElems = typeof(Enumerable).GetMethod("Cast").MakeGenericMethod(gArg).Invoke(elements, new object[] { elements });
|
||||
result = ctor.Invoke(new object[] { castElems });
|
||||
}
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
var ctor = tType.GetConstructors(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(c => c.GetParameters().Count() == 0);
|
||||
var addMethod = tType.GetMethods(BindingFlags.Instance | BindingFlags.Public).FirstOrDefault(m =>
|
||||
{
|
||||
if (m.Name != "Add") return false;
|
||||
var param = m.GetParameters();
|
||||
return param.Count() == 1 && param[0].ParameterType == gArg;
|
||||
});
|
||||
if (ctor != null && addMethod != null)
|
||||
{
|
||||
var elements = elem.Elements().Select(x => ParseValue(types, x));
|
||||
result = ctor.Invoke(null);
|
||||
foreach (var el in elements) addMethod.Invoke(result, new object[] { el });
|
||||
}
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
var ctor = tType.GetConstructors(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault();
|
||||
var setMethod = tType.GetMethods(BindingFlags.Instance | BindingFlags.Public).FirstOrDefault(m =>
|
||||
{
|
||||
if (m.Name != "Set") return false;
|
||||
var param = m.GetParameters();
|
||||
return param.Count() == 2 && param[0].ParameterType == typeof(int) && param[1].ParameterType == gArg;
|
||||
});
|
||||
if (ctor != null || setMethod != null)
|
||||
{
|
||||
var elements = elem.Elements().Select(x => ParseValue(types, x));
|
||||
result = ctor.Invoke(new object[] { elements.Count() });
|
||||
int i = 0;
|
||||
foreach (var el in elements)
|
||||
{
|
||||
setMethod.Invoke(result, new object[] { i, el });
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
else if (type == ValueType.Text) return elem.Value;
|
||||
else if (type == ValueType.Integer)
|
||||
{
|
||||
int.TryParse(elem.Value, out var num);
|
||||
return num;
|
||||
}
|
||||
else if (type == ValueType.Decimal)
|
||||
{
|
||||
float.TryParse(elem.Value, out var num);
|
||||
return num;
|
||||
}
|
||||
else if (type == ValueType.Boolean)
|
||||
{
|
||||
bool.TryParse(elem.Value, out var boolean);
|
||||
return boolean;
|
||||
}
|
||||
else if (type == ValueType.Object)
|
||||
{
|
||||
var tType = GetTypeAttr(types, elem);
|
||||
if (tType == null) return null;
|
||||
|
||||
IEnumerable<FieldInfo> fields = tType.GetFields(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Concat(tType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic));
|
||||
IEnumerable<PropertyInfo> properties = tType.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.GetSetMethod() != null)
|
||||
.Concat(tType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic).Where(p => p.GetSetMethod() != null));
|
||||
|
||||
object result = null;
|
||||
var ctor = tType.GetConstructors(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(c => c.GetParameters().Count() == 0);
|
||||
if (ctor == null)
|
||||
{
|
||||
if (!tType.IsValueType) return null;
|
||||
result = Activator.CreateInstance(tType);
|
||||
}
|
||||
else result = ctor.Invoke(null);
|
||||
|
||||
foreach (var el in elem.Elements())
|
||||
{
|
||||
var value = ParseValue(types, el);
|
||||
|
||||
var field = fields.FirstOrDefault(f => f.Name == el.Name.LocalName);
|
||||
if (field != null) field.SetValue(result, value);
|
||||
var property = properties.FirstOrDefault(p => p.Name == el.Name.LocalName);
|
||||
if (property != null) property.SetValue(result, value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else return elem.Value;
|
||||
|
||||
}
|
||||
|
||||
private static void AddTypeAttr(List<Type> types, Type type, XElement elem)
|
||||
{
|
||||
if (!types.Contains(type)) types.Add(type);
|
||||
elem.SetAttributeValue("Type", types.IndexOf(type));
|
||||
}
|
||||
|
||||
private static XElement ParseObject(List<Type> types, string name, object value)
|
||||
{
|
||||
XElement result = new XElement(name);
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
var tType = value.GetType();
|
||||
|
||||
if (tType.IsEnum)
|
||||
{
|
||||
result.SetAttributeValue("Value", ValueType.Enum);
|
||||
AddTypeAttr(types, tType, result);
|
||||
|
||||
result.Value = Enum.GetName(tType, value) ?? "";
|
||||
}
|
||||
else if (value is string str)
|
||||
{
|
||||
result.SetAttributeValue("Value", ValueType.Text);
|
||||
result.Value = str;
|
||||
}
|
||||
else if (value is int integer)
|
||||
{
|
||||
result.SetAttributeValue("Value", ValueType.Integer);
|
||||
result.Value = integer.ToString();
|
||||
}
|
||||
else if (value is float || value is double)
|
||||
{
|
||||
result.SetAttributeValue("Value", ValueType.Decimal);
|
||||
result.Value = value.ToString();
|
||||
}
|
||||
else if (value is bool boolean)
|
||||
{
|
||||
result.SetAttributeValue("Value", ValueType.Boolean);
|
||||
result.Value = boolean.ToString();
|
||||
}
|
||||
else if (tType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
|
||||
{
|
||||
result.SetAttributeValue("Value", ValueType.Collection);
|
||||
AddTypeAttr(types, tType, result);
|
||||
|
||||
var enumerator = (IEnumerator)tType.GetMethod("GetEnumerator").Invoke(value, null);
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
var elVal = ParseObject(types, "Item", enumerator.Current);
|
||||
result.Add(elVal);
|
||||
}
|
||||
}
|
||||
else if (tType.IsClass || tType.IsValueType)
|
||||
{
|
||||
result.SetAttributeValue("Value", ValueType.Object);
|
||||
AddTypeAttr(types, tType, result);
|
||||
|
||||
IEnumerable<FieldInfo> fields = tType.GetFields(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Concat(tType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic));
|
||||
IEnumerable<PropertyInfo> properties = tType.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.GetSetMethod() != null)
|
||||
.Concat(tType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic).Where(p => p.GetSetMethod() != null));
|
||||
|
||||
foreach (var field in fields) result.Add(ParseObject(types, field.Name, field.GetValue(value)));
|
||||
foreach (var property in properties) result.Add(ParseObject(types, property.Name, property.GetValue(value)));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.SetAttributeValue("Value", ValueType.None);
|
||||
result.Value = value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static T Load<T>(FileStream file)
|
||||
{
|
||||
var doc = XDocument.Load(file);
|
||||
|
||||
var rootElems = doc.Root.Elements().ToArray();
|
||||
var types = rootElems[0];
|
||||
var elem = rootElems[1];
|
||||
|
||||
var dict = ParseValue(LoadDocTypes(types), elem);
|
||||
if (dict.GetType() == typeof(T)) return (T)dict;
|
||||
else throw new Exception($"Loaded configuration is not of the type '{typeof(T).Name}'");
|
||||
}
|
||||
|
||||
public static void Save(FileStream file, object obj)
|
||||
{
|
||||
var types = new List<Type>();
|
||||
var elem = ParseObject(types, "Root", obj);
|
||||
var root = new XElement("Configuration", new XElement("Types", SaveDocTypes(types)), elem);
|
||||
|
||||
var doc = new XDocument(root);
|
||||
doc.Save(file);
|
||||
}
|
||||
|
||||
public static T Load<T>(string path)
|
||||
{
|
||||
using (var file = LuaCsFile.OpenRead(path)) return Load<T>(file);
|
||||
}
|
||||
|
||||
public static void Save(string path, object obj)
|
||||
{
|
||||
using (var file = LuaCsFile.OpenWrite(path)) Save(file, obj);
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,7 @@ public record ConfigResourceInfo : BaseResourceInfo, IConfigResourceInfo {}
|
||||
public record LuaScriptsResourceInfo : BaseResourceInfo, ILuaScriptResourceInfo
|
||||
{
|
||||
public bool IsAutorun { get; init; }
|
||||
public bool RunUnrestricted { get; init; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -21,6 +21,12 @@ public interface ILuaScriptResourceInfo : IBaseResourceInfo
|
||||
/// </summary>
|
||||
[XmlAttribute("IsAutorun")]
|
||||
public bool IsAutorun { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that this lua resources needs to run outside sandbox/requires unrestricted access.
|
||||
/// </summary>
|
||||
[XmlAttribute("RunUnrestricted")]
|
||||
public bool RunUnrestricted { get; }
|
||||
}
|
||||
|
||||
public interface IAssemblyResourceInfo : IBaseResourceInfo
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Xml.Linq;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Barotrauma.LuaCs;
|
||||
using Barotrauma.Networking;
|
||||
using OneOf;
|
||||
|
||||
namespace Barotrauma.LuaCs.Data;
|
||||
|
||||
@@ -34,7 +35,7 @@ public partial interface ISettingBase : IDataInfo, IEquatable<ISettingBase>, IDi
|
||||
Type GetValueType();
|
||||
string GetStringValue();
|
||||
string GetDefaultStringValue();
|
||||
bool TrySetValue(OneOf.OneOf<string, XElement> value);
|
||||
bool TrySetSerializedValue(OneOf<string, XElement> value);
|
||||
event Action<ISettingBase> OnValueChanged;
|
||||
OneOf.OneOf<string, XElement> GetSerializableValue();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Microsoft.Toolkit.Diagnostics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using OneOf;
|
||||
|
||||
@@ -10,6 +12,7 @@ public abstract class SettingBase : ISettingBase
|
||||
{
|
||||
protected SettingBase(IConfigInfo configInfo)
|
||||
{
|
||||
Guard.IsNotNull(configInfo, nameof(configInfo));
|
||||
ConfigInfo = configInfo;
|
||||
}
|
||||
|
||||
@@ -57,8 +60,8 @@ public abstract class SettingBase : ISettingBase
|
||||
public abstract Type GetValueType();
|
||||
public abstract string GetStringValue();
|
||||
public abstract string GetDefaultStringValue();
|
||||
public abstract bool TrySetSerializedValue(OneOf<string, XElement> value);
|
||||
|
||||
public abstract bool TrySetValue(OneOf<string, XElement> value);
|
||||
public abstract event Action<ISettingBase> OnValueChanged;
|
||||
public abstract OneOf<string, XElement> GetSerializableValue();
|
||||
#if CLIENT
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user