Sync with upstream

* Update bug-reports.yml

* Fix modifyChatMessage hook

* Add LuaCsSetup.Lua back for compatibility

* Fix Game.AssignOnExecute having command arguments be passed as varargs instead of a table

* Actually use the PackageId const everywhere we need to refer to our content package

* Load languages files even if the package is disabled

* Fix Hook.Remove not being implemented properly

* - Changed event aliases to be case insensitive.

* - Fixed assembly logging style.
- Fixed double logging during execution.

* Fix garbage network data being read by the game when reading LuaCs network messages

* PackageId -> PackageName

* Added caching toggle to PluginManagementService

* Fix LuaCs initializing too late for singleplayer campaigns and rework the C# prompt to only show when enabling mods/joining server

* Oops, fix NRE crash

* Fix hide username in logs config not doing anything

* Fix Cs prompt showing up more than one between rounds

* Fix server host being prompted twice with the C# popup

* Ignore our workshop packages from the game's dependency thing since it doesn't really make sense

* Load console commands after executing and possible fix for the not console command permitted

* Added fallback friendly name resolution for ModConfig assembly contents.

* Register Voronoi2 stuff

* Added configinfo null check to SettingBase.cs

* Add safety check so this stops crashing when we look at it the wrong way

* Fixed "Folder" attribute files not being found.

* Keep the LuaCsConfig class laying around for compatibility, not sure anywhere in our code base (and shouldn't be)

* Added fallback compilation for UseInternalsAwareAssembly if the publicized script compilation fails.

* Added legacy overload of AddCommand for mod compat.

* Added LoggerService to Lua env. Made ILoggerService compliant with LuaCsLogger API.

* Changed csharp script compilation algorithm to be best effort.

* Added "RunUnrestricted" mode for lua scripts that need to run outside of sandbox.

* - Fixed networking sync vars failing to sync initially.
- Fixed lua failing to differentiate overloads ISettingBase.

* Add alias for human.CPRSuccess and human.CPRFailed

* - Fixed up the settings menu.
- Made SettingEntry throw an error if "Value" attribute is not found in XML.
- Fixed saved values for settings sometimes not reloading after disabling and re-enabling a package.

* Fix LuaCs net messages received during connection initialization to be read incorrectly, happened because we would reset the BitPosition in our harmony patch which would cause the message to be read incorrectly later

* Allow reloadlua to force the state to running

* New icon for settings and make the top left text more user friendly

* Fix client.packages hook sending normal packages

* Fixed OnUpdate() not passing in deltaTime instead of totalTime.

* Missing diffs from bb21a09244

* Added networking tests for configs.

* Added missing diffs for f61f852a25.

* Some tweaks to the text

* Remove missing Value error, it should just use the default value if it's not specified

* Fix UseInternalAccessName

* Always purge cashes for plugin content on unloading.

* Fix texture not multiple of 4

* v1.12.7.0 (Spring Update 2026 Hotfix 1)

---------

Co-authored-by: Joonas Rikkonen <poe.regalis@gmail.com>
Co-authored-by: Evil Factory <36804725+evilfactory@users.noreply.github.com>
Co-authored-by: MapleWheels <njainanan@hotmail.com>
This commit is contained in:
NotAlwaysTrue
2026-04-25 12:10:24 +08:00
committed by GitHub
parent 928cfb4fde
commit 9b35f6b23f
65 changed files with 1253 additions and 396 deletions

View File

@@ -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.12.6.2 (Spring Update 2026)
- Other
validations:
required: true

View File

@@ -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),

View File

@@ -1,106 +1,47 @@
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
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;
}
if (GameMain.Client?.ClientPeer is P2POwnerPeer)
else if (_csRunPolicy?.Value is "Disabled")
{
SetCsPolicyAndContinue(true);
IsCsEnabledForSession = false;
onSelection(false);
return;
}
DisplayCsModsPromptClient(res.PromptPackages, (selectedYes) =>
if (contentPackages.None())
{
SetCsPolicyAndContinue(selectedYes);
onSelection(true);
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"),
@@ -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(

View File

@@ -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()

View File

@@ -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 = "LuaCsForBarotrauma.SettingsMenu.ResetVisibleSettings";
private const string SettingsResetPromptTitle = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Title";
private const string SettingsResetPromptContents = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Message";
private const string SettingsResetPromptYesText = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Yes";
private const string SettingsResetPromptNoText = "LuaCsForBarotrauma.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("LuaCsForBarotrauma.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));
}
}
(GUIFrame entryFrame, GUILayoutGroup entryLayoutGroup) AddSettingToDisplay(ISettingBase setting,
RectTransform parent, float settingHeight, Vector2 labelSize, Vector2 controlSize)
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 = 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();

View File

@@ -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,

View File

@@ -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));
}
@@ -43,13 +45,14 @@ public class SettingsMenuSystem : ISettingsMenuSystem
var tabGameplayIndex = (SettingsMenu.Tab)tabCount;
var tabControlsIndex = (SettingsMenu.Tab)tabCount+1;
_gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance,
"SettingsMenuTab.Mods", "LuaCsForBarotrauma.SettingsMenu.ModGameplayButton");
_gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance,
GUIStyle.ComponentStyles.ContainsKey("SettingsMenuTab.LuaCsSettings") ? "SettingsMenuTab.LuaCsSettings" : "SettingsMenuTab.Mods",
"LuaCsForBarotrauma.SettingsMenu.ModGameplayButton");
/*_controlsContentFrame = CreateNewContentTab(tabControlsIndex, __instance,
"SettingsMenuTab.Controls", "LuaCsForBarotrauma.SettingsMenu.ModControlsButton");
*/
_gameplayMenuInstance = new ModsGameplaySettingsMenu(_gameplayContentFrame, _packageManagementService, _configService, __instance);
_gameplayMenuInstance = new ModsGameplaySettingsMenu(_gameplayContentFrame, _packageManagementService, _configService, _loggerService, __instance);
//_controlsMenuInstance = new ModsControlsSettingsMenu(_controlsContentFrame, _packageManagementService, _configService, __instance);
}

View File

@@ -1015,25 +1015,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)

View File

@@ -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);

View File

@@ -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();

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.12.6.2</Version>
<Version>1.12.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.12.6.2</Version>
<Version>1.12.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.12.6.2</Version>
<Version>1.12.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -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.12.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -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.12.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -2,6 +2,7 @@
using System;
using System.IO;
using System.Linq;
using Barotrauma.LuaCs;
namespace Barotrauma
{

View File

@@ -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)

View File

@@ -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.12.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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
config.TrySetValue(config.Value + 1)
end
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
end)

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -242,10 +242,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 = [];
/// <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>
private readonly HashSet<Submarine> unattackableSubmarines = new HashSet<Submarine>();
public void SetUnattackableSubmarines(Submarine submarine, bool includeOwnSub = true, bool includeConnectedSubs = true, bool clearExisting = true)
{
if (clearExisting)
@@ -3075,11 +3078,17 @@ namespace Barotrauma
}
else
{
// Ignore all structures, items, and hulls inside these subs.
if (aiTarget.Entity.Submarine != null)
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;

View File

@@ -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);
}
});

View File

@@ -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)
{

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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,8 +35,8 @@ public partial interface ISettingBase : IDataInfo, IEquatable<ISettingBase>, IDi
Type GetValueType();
string GetStringValue();
string GetDefaultStringValue();
bool TrySetValue(OneOf.OneOf<string, XElement> value);
event Action<ISettingBase> OnValueChanged;
bool TrySetSerializedValue(OneOf<string, XElement> value);
event Action<ISettingBase> OnValueChanged;
OneOf.OneOf<string, XElement> GetSerializableValue();
}

View File

@@ -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

View File

@@ -147,12 +147,10 @@ public partial class SettingEntry<T> : SettingBase, ISettingBase<T>, INetworkSyn
}
public override Type GetValueType() => typeof(T);
public override string GetStringValue() => Value?.ToString() ?? string.Empty;
public override string GetDefaultStringValue() => DefaultValue.ToString();
public override string GetDefaultStringValue() => DefaultValue?.ToString() ?? string.Empty;
public override bool TrySetValue(OneOf<string, XElement> value)
public override bool TrySetSerializedValue(OneOf<string, XElement> value)
{
bool isFailed = false;
var typeConvertedValue = value.Match<T>(
@@ -181,7 +179,6 @@ public partial class SettingEntry<T> : SettingBase, ISettingBase<T>, INetworkSyn
return default(T);
}
});
return !isFailed && TrySetValue(typeConvertedValue);
}

View File

@@ -105,7 +105,13 @@ internal interface IEventModifyChatMessage : IEvent<IEventModifyChatMessage>
/// <returns>Whether to <b><i>reject</i></b> the message.</returns>
public bool? OnModifyMessagePredicate(ChatMessage message, WifiComponent senderRadio)
{
return (bool?)LuaFuncs[nameof(OnModifyMessagePredicate)](message, senderRadio);
object result = LuaFuncs[nameof(OnModifyMessagePredicate)](message, senderRadio);
if (result is DynValue dynValue && dynValue.Type == DataType.Boolean)
{
return dynValue.Boolean;
}
return null;
}
}
}
@@ -953,7 +959,7 @@ interface IEventInventoryItemSwap : IEvent<IEventInventoryItemSwap>
#if SERVER
public interface IEventClientRawNetMessageReceived : IEvent<IEventClientRawNetMessageReceived>
{
void OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader clientPacketHeader, NetworkConnection sender);
bool? OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader clientPacketHeader, NetworkConnection sender);
static IEventClientRawNetMessageReceived IEvent<IEventClientRawNetMessageReceived>.GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc)
=> new LuaWrapper(luaFunc);
@@ -964,15 +970,22 @@ public interface IEventClientRawNetMessageReceived : IEvent<IEventClientRawNetMe
{
}
public void OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader clientPacketHeader, NetworkConnection sender)
public bool? OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader clientPacketHeader, NetworkConnection sender)
{
if (GameMain.Server == null) { return; }
if (GameMain.Server == null) { return null; }
Client client = GameMain.Server.ConnectedClients.FirstOrDefault(c => c.Connection == sender);
if (client == null) { return; }
if (client == null) { return null; }
LuaFuncs[nameof(OnReceivedClientNetMessage)](netMessage, clientPacketHeader, client);
var result = LuaFuncs[nameof(OnReceivedClientNetMessage)](netMessage, clientPacketHeader, client);
if (result is DynValue dynValue && dynValue.Type == DataType.Boolean)
{
return dynValue.Boolean;
}
return null;
}
}
}
@@ -1063,7 +1076,7 @@ interface IEventJobsAssigned : IEvent<IEventJobsAssigned>
public interface IEventServerRawNetMessageReceived : IEvent<IEventServerRawNetMessageReceived>
{
void OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader);
bool? OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader);
static IEventServerRawNetMessageReceived IEvent<IEventServerRawNetMessageReceived>.GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc)
=> new LuaWrapper(luaFunc);
@@ -1074,9 +1087,15 @@ public interface IEventServerRawNetMessageReceived : IEvent<IEventServerRawNetMe
{
}
public void OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader)
public bool? OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader)
{
LuaFuncs[nameof(OnReceivedServerNetMessage)](netMessage, serverPacketHeader);
var result = LuaFuncs[nameof(OnReceivedServerNetMessage)](netMessage, serverPacketHeader);
if (result is DynValue dynValue && dynValue.Type == DataType.Boolean)
{
return dynValue.Boolean;
}
return null;
}
}
}

View File

@@ -25,11 +25,16 @@ namespace Barotrauma
partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventEnabledPackageListChanged,
IEventReloadAllPackages
{
public const string PackageId = "LuaCsForBarotrauma";
public const string PackageName = "LuaCsForBarotrauma";
private static LuaCsSetup _luaCsSetup;
public static LuaCsSetup Instance => _luaCsSetup ??= new LuaCsSetup();
/// <summary>
/// The index of the last Vanilla command.
/// </summary>
public static int DebugConsoleCommandVanillaIndex { get; private set; }
private LuaCsSetup()
{
if (_luaCsSetup != null)
@@ -37,6 +42,8 @@ namespace Barotrauma
throw new Exception("Tried to create another LuaCsSetup instance");
}
DebugConsoleCommandVanillaIndex = DebugConsole.Commands.Count;
// == startup
_servicesProvider = SetupServicesProvider();
_runStateMachine = SetupStateMachine();
@@ -83,7 +90,26 @@ namespace Barotrauma
// hotpath performance ref cache
private LuaGame _game;
public LuaGame Game => _game ??= _servicesProvider.GetService<LuaGame>();
public Script Lua => LuaScriptManagementService.InternalScript;
private ISettingBase<bool> _isCsEnabledForSession;
public bool IsCsEnabledForSession
{
get => _isCsEnabledForSession?.Value ?? false;
internal set
{
_isCsEnabledForSession?.TrySetValue(value);
if (_isCsEnabledForSession != null)
{
if (_isCsEnabledForSession.GetConfigInfo() == null)
{
Logger.LogError($"Config info was nil while trying to save {IsCsEnabledForSession}");
return;
}
ConfigService.SaveConfigValue(_isCsEnabledForSession);
}
}
}
/// <summary>
/// Whether C# plugin code is enabled.
@@ -91,15 +117,17 @@ namespace Barotrauma
public bool IsCsEnabled
{
#if CLIENT
get => _csRunPolicy?.Value == "Enabled" || _isCsEnabledForSession;
get => _csRunPolicy?.Value == "Enabled" || IsCsEnabledForSession;
#elif SERVER
// cs settings cannot be changed on the server after launch
get => _csRunPolicy?.Value is "Enabled" or "Prompt";
#endif
}
private ISettingList<string> _csRunPolicy;
private bool ShouldPromptForCs => _csRunPolicy?.Value is "Prompt";
public string CsRunPolicyValue => _csRunPolicy?.Value ?? "Prompt";
/// <summary>
/// Whether usernames are anonymized or show in logs.
/// </summary>
@@ -118,9 +146,9 @@ namespace Barotrauma
public static ContentPackage GetLuaCsPackage()
{
return ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(PackageId), null)
?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(PackageId))
?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(PackageId));
return ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(PackageName), null)
?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(PackageName))
?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(PackageName));
}
void LoadLuaCsConfig()
@@ -139,6 +167,17 @@ namespace Barotrauma
ConfigService.TryGetConfig<ISettingBase<bool>>(luaCsPackage, "UseCaching", out var val5)
? val5
: null;
_isCsEnabledForSession =
ConfigService.TryGetConfig<ISettingBase<bool>>(luaCsPackage, "IsCsEnabledForSession", out var val6)
? val6
: null;
if (!ContentPackageManager.EnabledPackages.All.Contains(luaCsPackage))
{
// sorry perfidius (not sorry)
luaCsPackage.UnloadFilesOfType<TextFile>();
luaCsPackage.LoadFilesOfType<TextFile>();
}
}
private IServicesProvider SetupServicesProvider()
@@ -223,22 +262,13 @@ namespace Barotrauma
public void OnReloadAllPackages()
{
if (CurrentRunState <= RunState.Unloaded)
{
return;
}
CoroutineManager.Invoke(() =>
{
#if CLIENT
bool prevCsEnabled = _isCsEnabledForSession;
#endif
var state = CurrentRunState;
SetRunState(RunState.Unloaded);
#if CLIENT
_isCsEnabledForSession = prevCsEnabled;
#endif
SetRunState(state);
CoroutineManager.Invoke(() =>
{
SetRunState(RunState.Running);
},0.25f);
});
}
@@ -256,10 +286,11 @@ namespace Barotrauma
}
this.Logger.LogResults(PackageManagementService.SyncLoadedPackagesList(GetLuaCsEnabledPackagesList(packages)));
ConfigService.LoadSavedConfigsValues();
SetRunState(state); // restore
}
private void SetRunState(RunState targetRunState)
public void SetRunState(RunState targetRunState)
{
if (CurrentRunState == targetRunState)
{
@@ -274,17 +305,13 @@ namespace Barotrauma
private ImmutableArray<ContentPackage> GetLuaCsEnabledPackagesList(ImmutableArray<ContentPackage> enabledRegular)
{
if (!enabledRegular.Any(
p => p.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase)
|| p.Name.Equals("Lua for Barotrauma", StringComparison.InvariantCultureIgnoreCase)))
if (!enabledRegular.Any(p => p.Name.Equals(PackageName, StringComparison.InvariantCultureIgnoreCase)))
{
var luaCs = ContentPackageManager.AllPackages.FirstOrDefault(
p => p.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase)
|| p.Name.Equals("Lua For Barotrauma", StringComparison.InvariantCultureIgnoreCase));
var luaCs = ContentPackageManager.AllPackages.FirstOrDefault(p => p.Name.Equals(PackageName, StringComparison.InvariantCultureIgnoreCase));
if (luaCs is null)
{
DebugConsole.ThrowError($"The 'LuaCsForBarotrauma' mod could not be found. Please subscribe to it and add it to the EnabledPackages List!",
new NullReferenceException($"The 'LuaCsForBarotrauma' mod could not be found. Please subscribe to it and add it to the EnabledPackages List!"),
DebugConsole.ThrowError($"The '{PackageName}' mod could not be found. Please subscribe to it and add it to the EnabledPackages List!",
new NullReferenceException($"The '{PackageName}' mod could not be found. Please subscribe to it and add it to the EnabledPackages List!"),
createMessageBox: true);
return enabledRegular;
}
@@ -383,6 +410,11 @@ namespace Barotrauma
EventService.PublishEvent<IEventServerConnected>(static p => p.OnServerConnected());
}
#endif
#if SERVER
GameMain.Server.ServerSettings.LoadClientPermissions();
#endif
CurrentRunState = RunState.Running;
}
@@ -390,9 +422,6 @@ namespace Barotrauma
void RunStateRunning_OnExit(State<RunState> currentState)
{
EventService.Call("stop");
#if CLIENT
_isCsEnabledForSession = false;
#endif
Logger.LogResults(PackageManagementService.StopRunningPackages());
Logger.LogMessage("LuaCs running state exited");
}

View File

@@ -315,24 +315,16 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
StringBuilder sb = new StringBuilder();
foreach (var resultDiagnostic in result.Diagnostics)
{
if (resultDiagnostic.Severity != DiagnosticSeverity.Error)
if (resultDiagnostic.IsWarningAsError || resultDiagnostic.Severity == DiagnosticSeverity.Error)
{
continue;
//sb.AppendLine($">>> {resultDiagnostic.GetMessage()} | Location: {resultDiagnostic.Location.SourceTree?.GetLineSpan(resultDiagnostic.Location.SourceSpan)} ");
sb.AppendLine($"\n{resultDiagnostic}");
}
sb.AppendLine($">>> {resultDiagnostic.GetMessage()} | Location: {resultDiagnostic.Location.SourceTree?.GetLineSpan(resultDiagnostic.Location.SourceSpan)} ");
}
var res = new FluentResults.Result().WithError(
new Error($"Package Error: {OwnerPackage.Name}: Compilation failed for assembly {assemblyName}! {sb.ToString()}\n")
new Error($"Package Error: {OwnerPackage.Name}: Compilation failed for assembly {assemblyName}!\n {sb.ToString()}\n")
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, syntaxTrees));
var failuresDiag =
result.Diagnostics.Where(d => d.IsWarningAsError || d.Severity == DiagnosticSeverity.Error);
foreach (var diag in failuresDiag)
{
res = res.WithError(new Error(diag.GetMessage())
.WithMetadata(MetadataType.ExceptionObject, this)
.WithMetadata(MetadataType.ExceptionDetails, diag.Descriptor.Description));
}
return res;
}

View File

@@ -239,7 +239,7 @@ public sealed partial class ConfigService : IConfigService
return;
}
if (setting.TrySetValue(valueString))
if (setting.TrySetSerializedValue(valueString))
{
_logger.LogMessage($"Set config {internalName} value to {valueString}", Color.Green);
if (SaveConfigValue(setting) is { IsFailed: true } res)
@@ -445,7 +445,7 @@ public sealed partial class ConfigService : IConfigService
{
if (_settingsInstances.TryGetValue((info.OwnerPackage, value.SettingName), out var instance))
{
instance.TrySetValue(value.Element);
instance.TrySetSerializedValue(value.Element);
}
}
}
@@ -495,7 +495,7 @@ public sealed partial class ConfigService : IConfigService
return FluentResults.Result.Ok();
}
return FluentResults.Result.OkIf(setting.TrySetValue(cfgValueElement), new Error($"Failed to set value for [{setting.OwnerPackage.Name}.{setting.InternalName}]"));
return FluentResults.Result.OkIf(setting.TrySetSerializedValue(cfgValueElement), new Error($"Failed to set value for [{setting.OwnerPackage.Name}.{setting.InternalName}]"));
}
public FluentResults.Result LoadSavedConfigsValues()
@@ -544,7 +544,7 @@ public sealed partial class ConfigService : IConfigService
continue;
}
if (!instance.TrySetValue(profileValue.Element))
if (!instance.TrySetSerializedValue(profileValue.Element))
{
result.WithError(new Error($"{nameof(ApplyConfigProfile)}: Failed to set value for [{profileValue.SettingName}]."));
}

View File

@@ -24,14 +24,14 @@ public partial class EventService : IEventService
public TypeStringKey(Type type)
{
Type = type ?? throw new ArgumentNullException(nameof(type));
TypeName = type.Name;
TypeName = type.Name.ToLowerInvariant();
HashCode = TypeName.GetHashCode();
}
public TypeStringKey(string typeName)
{
Type = null;
TypeName = typeName ?? throw new ArgumentNullException(nameof(typeName));
TypeName = typeName?.ToLowerInvariant() ?? throw new ArgumentNullException(nameof(typeName));
HashCode = TypeName.GetHashCode();
}
@@ -56,7 +56,7 @@ public partial class EventService : IEventService
private readonly AsyncReaderWriterLock _operationsLock = new();
private readonly ConcurrentDictionary<TypeStringKey, ConcurrentDictionary<OneOf<IEvent, string>, IEvent>> _subscribers = new();
private readonly ConcurrentDictionary<TypeStringKey, (TypeStringKey Event, Func<LuaCsFunc, IEvent> RunnerFactory)> _luaAliasEventFactory = new();
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, LuaCsFunc>> _luaLegacyEventsSubscribers = new();
private readonly ConcurrentDictionary<TypeStringKey, ConcurrentDictionary<TypeStringKey, LuaCsFunc>> _luaLegacyEventsSubscribers = new();
private readonly ConcurrentDictionary<IEventService, IEventService> _subscribedEventDispatchers = new();
#region LifeCycle
@@ -118,7 +118,7 @@ public partial class EventService : IEventService
}
else
{
var eventSubs = _luaLegacyEventsSubscribers.GetOrAdd(eventName, key => new ConcurrentDictionary<string, LuaCsFunc>());
var eventSubs = _luaLegacyEventsSubscribers.GetOrAdd(eventName, key => new ConcurrentDictionary<TypeStringKey, LuaCsFunc>());
eventSubs[identifier] = callback;
}
}
@@ -200,6 +200,29 @@ public partial class EventService : IEventService
}
public void Remove(string eventName, string identifier)
{
Guard.IsNotNullOrWhiteSpace(eventName, nameof(eventName));
Guard.IsNotNullOrWhiteSpace(identifier, nameof(identifier));
using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
IService.CheckDisposed(this);
if (_luaAliasEventFactory.TryGetValue(eventName, out var eventFunc))
{
if (_subscribers.TryGetValue(eventFunc.Event, out var eventSubs))
{
eventSubs.TryRemove(identifier, out _);
}
}
else
{
if (_luaLegacyEventsSubscribers.TryGetValue(eventName, out var eventSubs))
{
eventSubs.TryRemove(identifier, out _);
}
}
}
public void Unsubscribe(string eventName, string identifier)
{
Guard.IsNotNullOrWhiteSpace(eventName, nameof(eventName));
Guard.IsNotNullOrWhiteSpace(identifier, nameof(identifier));

View File

@@ -2,6 +2,7 @@
using Barotrauma.LuaCs;
using Barotrauma.LuaCs.Events;
using Barotrauma.Networking;
using Barotrauma.Steam;
using HarmonyLib;
using Microsoft.Xna.Framework;
using System;
@@ -27,16 +28,12 @@ internal class HarmonyEventPatchesService : ISystem
private static ILoggerService _loggerService;
private readonly Harmony Harmony;
private static int debugConsoleCommandVanillaIndex;
public HarmonyEventPatchesService(IEventService eventService, ILoggerService loggerService)
{
_eventService = eventService;
_loggerService = loggerService;
Harmony = new Harmony("LuaCsForBarotrauma.Events");
Patch();
debugConsoleCommandVanillaIndex = DebugConsole.Commands.Count;
}
private void Patch()
@@ -56,7 +53,7 @@ internal class HarmonyEventPatchesService : ISystem
[HarmonyPatch(typeof(CoroutineManager), nameof(CoroutineManager.Update)), HarmonyPostfix]
public static void CoroutineManager_Update_Post()
{
_eventService.PublishEvent<IEventUpdate>(x => x.OnUpdate(Timing.TotalTime));
_eventService.PublishEvent<IEventUpdate>(x => x.OnUpdate(CoroutineManager.DeltaTime));
_loggerService.ProcessLogs();
}
@@ -95,6 +92,27 @@ internal class HarmonyEventPatchesService : ISystem
_eventService.PublishEvent<IEventScreenSelected>(x => x.OnScreenSelected(__instance));
}
#if CLIENT
[HarmonyPatch(typeof(MainMenuScreen), "StartGame"), HarmonyPostfix]
public static void MainMenuScreen_StartGame_Pre(Screen __instance)
{
LuaCsSetup.Instance.SetRunState(RunState.Running);
}
[HarmonyPatch(typeof(MainMenuScreen), "LoadGame"), HarmonyPostfix]
public static void MainMenuScreen_LoadGame_Pre(Screen __instance)
{
LuaCsSetup.Instance.SetRunState(RunState.Running);
}
[HarmonyPatch(typeof(MutableWorkshopMenu), nameof(MutableWorkshopMenu.Apply)), HarmonyPostfix]
public static void MutableWorkshopMenu_Apply_Post(Screen __instance)
{
LuaCsSetup.Instance.PromptCSharpMods(selection => { }, joiningServer: false);
}
#endif
[HarmonyPatch(typeof(ContentPackageManager.PackageSource), nameof(ContentPackageManager.PackageSource.Refresh)), HarmonyPostfix]
public static void PackageSource_Refresh_Post()
{
@@ -122,11 +140,20 @@ internal class HarmonyEventPatchesService : ISystem
#if CLIENT
[HarmonyPatch(typeof(GameClient), "ReadDataMessage"), HarmonyPrefix]
public static void GameClient_ReadDataMessage_Pre(IReadMessage inc)
public static bool GameClient_ReadDataMessage_Pre(IReadMessage inc)
{
int prevBitPosition = inc.BitPosition;
ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte();
_eventService.PublishEvent<IEventServerRawNetMessageReceived>(x => x.OnReceivedServerNetMessage(inc, header));
inc.BitPosition -= 8; // rewind so the game can read the message
bool? skip = null;
_eventService.PublishEvent<IEventServerRawNetMessageReceived>(x => skip = x.OnReceivedServerNetMessage(inc, header) ?? skip);
if (skip == true)
{
return false;
}
inc.BitPosition = prevBitPosition; // rewind so the game can read the message
return true;
}
[HarmonyPatch(typeof(SubEditorScreen), nameof(SubEditorScreen.Select), new Type[] { }), HarmonyPostfix]
@@ -146,7 +173,7 @@ internal class HarmonyEventPatchesService : ISystem
{
DebugConsole.Command c = DebugConsole.FindCommand(command.Value);
if (DebugConsole.Commands.IndexOf(c) >= debugConsoleCommandVanillaIndex)
if (DebugConsole.Commands.IndexOf(c) >= LuaCsSetup.DebugConsoleCommandVanillaIndex)
{
__result = true;
return false;
@@ -158,11 +185,21 @@ internal class HarmonyEventPatchesService : ISystem
#elif SERVER
[HarmonyPatch(typeof(GameServer), "ReadDataMessage"), HarmonyPrefix]
public static void GameServer_ReadDataMessage_Pre(NetworkConnection sender, IReadMessage inc)
public static bool GameServer_ReadDataMessage_Pre(NetworkConnection sender, IReadMessage inc)
{
int prevBitPosition = inc.BitPosition;
ClientPacketHeader header = (ClientPacketHeader)inc.ReadByte();
_eventService.PublishEvent<IEventClientRawNetMessageReceived>(x => x.OnReceivedClientNetMessage(inc, header, sender));
inc.BitPosition -= 8; // rewind so the game can read the message
bool? skip = null;
_eventService.PublishEvent<IEventClientRawNetMessageReceived>(x => skip = x.OnReceivedClientNetMessage(inc, header, sender) ?? skip);
if (skip == true)
{
return false;
}
inc.BitPosition = prevBitPosition; // rewind so the game can read the message
return true;
}
[HarmonyPatch(typeof(GameServer), "OnInitializationComplete"), HarmonyPostfix]

View File

@@ -12,8 +12,6 @@ namespace Barotrauma.LuaCs;
public partial class LoggerService : ILoggerService
{
public bool HideUserNames = true;
private List<ILoggerSubscriber> logSubscribers = [];
private ConcurrentQueue<PendingLog> logQueue = [];
@@ -94,7 +92,7 @@ public partial class LoggerService : ILoggerService
public void Log(string message, Color? color = null, ServerLog.MessageType messageType = ServerLog.MessageType.ServerMessage)
{
if (HideUserNames && !Environment.UserName.IsNullOrEmpty())
if (LuaCsSetup.Instance.HideUserNamesInLogs && !Environment.UserName.IsNullOrEmpty())
{
message = message.Replace(Environment.UserName, "USERNAME");
}
@@ -186,14 +184,13 @@ public partial class LoggerService : ILoggerService
else
{
LogError($"FluentResults::IError: {error.Message}");
}
if (error.Reasons != null)
{
foreach (var reason in error.Reasons)
/*if (error.Reasons != null)
{
LogError($" - {reason.Message}");
}
foreach (var reason in error.Reasons)
{
LogError($" - {reason.Message}");
}
}*/
}
}
}

View File

@@ -20,9 +20,9 @@ public sealed class LuaCsInfoProvider : ILuaCsInfoProvider
{
get
{
return ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageId), null)
?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageId))
?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageId));
return ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageName), null)
?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageName))
?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageName));
}
}
}

View File

@@ -1,27 +1,19 @@
#nullable enable
using Barotrauma.LuaCs;
using Barotrauma.LuaCs.Compatibility;
using Barotrauma.LuaCs.Data;
using Barotrauma.LuaCs.Events;
using Barotrauma.Networking;
using FluentResults;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Toolkit.Diagnostics;
using MonoMod.RuntimeDetour;
using MoonSharp.Interpreter;
using MoonSharp.Interpreter.Interop;
using MoonSharp.Interpreter.Loaders;
using RestSharp.Validation;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
@@ -285,6 +277,8 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService,
_eventService.RegisterLuaEventAlias<IEventGiveCharacterJobItems>("character.giveJobItems", nameof(IEventGiveCharacterJobItems.OnGiveCharacterJobItems));
_eventService.RegisterLuaEventAlias<IEventHumanCPRSuccess>("character.CPRSuccess", nameof(IEventHumanCPRSuccess.OnCharacterCPRSuccess));
_eventService.RegisterLuaEventAlias<IEventHumanCPRFailed>("character.CPRFailed", nameof(IEventHumanCPRFailed.OnCharacterCPRFailed));
_eventService.RegisterLuaEventAlias<IEventHumanCPRSuccess>("human.CPRSuccess", nameof(IEventHumanCPRSuccess.OnCharacterCPRSuccess));
_eventService.RegisterLuaEventAlias<IEventHumanCPRFailed>("human.CPRFailed", nameof(IEventHumanCPRFailed.OnCharacterCPRFailed));
_eventService.RegisterLuaEventAlias<IEventCharacterApplyDamage>("character.applyDamage", nameof(IEventCharacterApplyDamage.OnCharacterApplyDamage));
_eventService.RegisterLuaEventAlias<IEventCharacterApplyAffliction>("character.applyAffliction", nameof(IEventCharacterApplyAffliction.OnCharacterApplyAffliction));
@@ -368,8 +362,10 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService,
UserData.RegisterType(typeof(IUserDataDescriptor));
UserData.RegisterType(typeof(INetworkingService));
UserData.RegisterType(typeof(ILuaConfigService));
UserData.RegisterType(typeof(ILoggerService));
//UserData.RegisterType(typeof(ISettingBase));
UserData.RegisterType(typeof(ISettingBase));
UserData.RegisterType(typeof(IDataInfo));
Type[] settingBaseTypes = [
typeof(ISettingBase<bool>),
@@ -451,6 +447,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService,
_script.Globals["Networking"] = _networkingService;
_script.Globals["trygetpackage"] = (string name, out ContentPackage package) =>
_packageManagementService.Value.TryGetLoadedPackageByName(name, out package);
_script.Globals["Logger"] = _loggerService;
//_script.Globals["Steam"] = Steam;
if (enableSandbox)
@@ -517,6 +514,61 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService,
Table package = (Table)_script.Globals["package"];
package.Set("path", DynValue.FromObject(_script, packages));
#if CLIENT
if (GameMain.NetworkMember is { IsClient: true })
{
var startMessage = _networkingService.Start("_luastart");
var packagesToReport = ContentPackageManager.EnabledPackages.All
.Where(p => _packageManagementService.Value.PackageContainsAnyRunnableResource(p))
.Where(p => !p.NameMatches(LuaCsSetup.PackageName))
.ToList();
startMessage.WriteUInt16((UInt16)packagesToReport.Count());
foreach (var enabledPackage in packagesToReport)
{
var id = enabledPackage.UgcId;
string hash = enabledPackage.Hash.StringRepresentation ?? "";
startMessage.WriteString(enabledPackage.Name);
startMessage.WriteString(enabledPackage.ModVersion);
if (id.TryUnwrap(out ContentPackageId? packageId) && packageId is SteamWorkshopId steamId)
{
startMessage.WriteUInt64(steamId.Value);
}
else
{
startMessage.WriteUInt64(0);
}
startMessage.WriteString(hash);
}
_networkingService.Send(startMessage);
}
#elif SERVER
_networkingService.Receive("_luastart", (message, client) =>
{
var num = message.ReadUInt16();
List<Table> packages = new List<Table>();
for (int i = 0; i < num; i++)
{
Table table = new Table(_script);
table.Set("Name", DynValue.NewString(message.ReadString()));
table.Set("Version", DynValue.NewString(message.ReadString()));
table.Set("Id", DynValue.NewString(message.ReadUInt64().ToString()));
table.Set("Hash", DynValue.NewString(message.ReadString()));
packages.Add(table);
}
_eventService.Call("client.packages", client, packages);
});
#endif
foreach (ILuaScriptResourceInfo resource in executionOrder.Where(l => l.IsAutorun))
{
foreach (ContentPath filePath in resource.FilePaths)

View File

@@ -43,11 +43,32 @@ internal class MainMenuPatch : ISystem, IEventScreenSelected
{
if (mainMenuUIAdded) { return; }
new GUITextBlock(new RectTransform(new Point(300, 30), screen.Frame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(10, 10) }, $"Using LuaCsForBarotrauma revision {AssemblyInfo.GitRevision}", Color.Red)
var textBlock = new GUITextBlock(new RectTransform(new Point(300, 30), screen.Frame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(10, 10) }, "", Color.Red)
{
IgnoreLayoutGroups = false
};
textBlock.OnAddedToGUIUpdateList = (GUIComponent component) =>
{
string mode = LuaCsSetup.Instance.CsRunPolicyValue;
if (mode is "Prompt")
{
string sessionState = LuaCsSetup.Instance.IsCsEnabledForSession ? "yes" : "no";
mode = $"enabled (prompt mode, allowed for this session: {sessionState})";
}
else if (mode is "Enabled")
{
mode = "always enabled";
}
else
{
mode = "disabled";
}
textBlock.Text = $"LuaCsForBarotrauma active (revision {AssemblyInfo.GitRevision}), C# is currently {mode}\nNew settings available in the game settings menu.";
};
mainMenuUIAdded = true;
}
#endif

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml.Linq;
using Barotrauma.LuaCs.Data;
@@ -60,8 +61,9 @@ public sealed partial class ModConfigFileParserService :
if (CheckThrowNullRefs(src, "Assembly") is { IsFailed: true } fail)
return fail;
var isScript = src.Element.GetAttributeBool("IsScript", false);
var runtimeEnv = GetRuntimeEnvironment(src.Element);
var fileResults = await UnsafeGetCheckedFiles(src.Element, src.Owner, ".dll");
var fileResults = await UnsafeGetCheckedFiles(src.Element, src.Owner, isScript ? ".cs" : ".dll");
if (fileResults.IsFailed)
return FluentResults.Result.Fail(fileResults.Errors);
@@ -78,11 +80,31 @@ public sealed partial class ModConfigFileParserService :
RequiredPackages = src.Required,
IncompatiblePackages = src.Incompatible,
// Type Specific
FriendlyName = src.Element.GetAttributeString("FriendlyName", string.Empty),
IsScript = src.Element.GetAttributeBool("IsScript", false),
FriendlyName = src.Element.GetAttributeString("FriendlyName", GetFallbackCompliantAssemblyName(src.Owner)),
IsScript = isScript,
UseInternalAccessName = src.Element.GetAttributeBool("UseInternalAccessName", false),
IsReferenceModeOnly = src.Element.GetAttributeBool("IsReferenceModeOnly", false)
};
// helper methods
string GetFallbackCompliantAssemblyName(ContentPackage package)
{
if (package.Name.IsNullOrWhiteSpace())
{
return "FallbackAssemblyName";
}
// replace non az chars with '_'
var sanitizedPackageName = Regex.Replace(package.Name, @"[^a-zA-Z0-9_]", "_");
if (char.IsDigit(sanitizedPackageName[0]))
{
sanitizedPackageName = "ASM" + sanitizedPackageName;
}
// replace consecutive '_'
return Regex.Replace(sanitizedPackageName, @"[_.]{2,}", "_");
}
}
async Task<ImmutableArray<Result<IAssemblyResourceInfo>>> IParserServiceAsync<ResourceParserInfo, IAssemblyResourceInfo>.TryParseResourcesAsync(IEnumerable<ResourceParserInfo> sources)
@@ -152,7 +174,8 @@ public sealed partial class ModConfigFileParserService :
RequiredPackages = src.Required,
IncompatiblePackages = src.Incompatible,
// Type Specific
IsAutorun = src.Element.GetAttributeBool("IsAutorun", false)
IsAutorun = src.Element.GetAttributeBool("IsAutorun", false),
RunUnrestricted = src.Element.GetAttributeBool("RunUnrestricted", false)
};
}
@@ -184,7 +207,7 @@ public sealed partial class ModConfigFileParserService :
var res = new FluentResults.Result<ImmutableArray<ContentPath>>();
if (!filePath.IsNullOrWhiteSpace())
if ((!filePath?.Value.IsNullOrWhiteSpace()) ?? false)
{
if (_storageService.FileExists(filePath.FullPath) is { IsSuccess: true, Value: true })
{
@@ -203,11 +226,12 @@ public sealed partial class ModConfigFileParserService :
}
}
if (!folderPath.IsNullOrWhiteSpace())
if ((!folderPath?.Value.IsNullOrWhiteSpace()) ?? false)
{
if (_storageService.DirectoryExists(folderPath.FullPath) is { IsSuccess: true, Value: true })
{
var files = _storageService.FindFilesInPackage(srcOwner, folderPath.Value, fileExtension, true);
var searchLocation = System.IO.Path.GetRelativePath(srcOwner.Dir, folderPath.Value);
var files = _storageService.FindFilesInPackage(srcOwner, searchLocation, "*"+fileExtension, true);
if (files.IsFailed)
{
res.WithError($"{srcOwner.Name}: Failed to load files from {folderPath}!");

View File

@@ -335,10 +335,10 @@ public sealed class ModConfigService : IModConfigService
.Select(fp => ContentPath.FromRaw(srcPackage,
$"%ModDir%/{Path.GetRelativePath(srcPackage.Dir, fp)}".CleanUpPathCrossPlatform()))
.Concat(sharedFiles).ToImmutableArray(),
FriendlyName = IAssemblyLoaderService.InternalsAwareAssemblyName,
FriendlyName = IAssemblyLoaderService.InternalsAwareAssemblyName, // give the best chance of success (InternalsAware + Publicizer)
IncompatiblePackages = ImmutableArray<Identifier>.Empty,
RequiredPackages = ImmutableArray<Identifier>.Empty,
UseInternalAccessName = true,
UseInternalAccessName = false, //compile as public and then fallback to internals
IsScript = true,
IsReferenceModeOnly = false
});
@@ -357,7 +357,7 @@ public sealed class ModConfigService : IModConfigService
FriendlyName = IAssemblyLoaderService.InternalsAwareAssemblyName,
IncompatiblePackages = ImmutableArray<Identifier>.Empty,
RequiredPackages = ImmutableArray<Identifier>.Empty,
UseInternalAccessName = true,
UseInternalAccessName = false,
IsScript = true,
IsReferenceModeOnly = false
});
@@ -405,6 +405,7 @@ public sealed class ModConfigService : IModConfigService
IncompatiblePackages = ImmutableArray<Identifier>.Empty,
RequiredPackages = ImmutableArray<Identifier>.Empty,
IsAutorun = true,
RunUnrestricted = false
});
builder.Add(new LuaScriptsResourceInfo()
@@ -418,6 +419,7 @@ public sealed class ModConfigService : IModConfigService
IncompatiblePackages = ImmutableArray<Identifier>.Empty,
RequiredPackages = ImmutableArray<Identifier>.Empty,
IsAutorun = false,
RunUnrestricted = false
});
}

View File

@@ -345,6 +345,8 @@ public sealed class PackageManagementService : IPackageManagementService
//lua scripts
var luaScripts = SelectCompatible(loadingOrderedPackages
.Where(pkg => executeCsAssemblies
|| !pkg.Value.LuaScripts.Any(scr => scr.RunUnrestricted))
.SelectMany(pkg => pkg.Value.LuaScripts)
.ToImmutableArray(), toLoadPackagesIndents, loadOrderByPackage);
@@ -357,11 +359,7 @@ public sealed class PackageManagementService : IPackageManagementService
{
_runningPackages[package.Key] = package.Value;
}
if (result.IsFailed)
{
_logger.LogResults(result);
}
return result;
}
@@ -517,14 +515,44 @@ public sealed class PackageManagementService : IPackageManagementService
return !_runningPackages.IsEmpty;
}
public ImmutableArray<ContentPackage> GetLoadedAssemblyPackages()
public ImmutableArray<ContentPackage> GetLoadedUnrestrictedPackages()
{
using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
IService.CheckDisposed(this);
if (_loadedPackages.IsEmpty)
return ImmutableArray<ContentPackage>.Empty;
return [.._loadedPackages.Values
.Where(cfg => !cfg.Assemblies.IsDefaultOrEmpty)
.Where(cfg => !cfg.Assemblies.IsDefaultOrEmpty || cfg.LuaScripts.Any(scr => scr.RunUnrestricted))
.Select(cfg => cfg.Package)];
}
public bool PackageContainsAnyRunnableResource(ContentPackage package)
{
using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
IService.CheckDisposed(this);
var result = GetModConfigForPackage(package);
if (result.IsSuccess)
{
return result.Value.Assemblies.Any() || result.Value.LuaScripts.Any();
}
else
{
return false;
}
}
public Result<IModConfigInfo> GetModConfigForPackage(ContentPackage package)
{
using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
IService.CheckDisposed(this);
if (!_loadedPackages.TryGetValue(package, out var modConfig))
{
return FluentResults.Result.Fail($"Failed to find mod config for package {package.Name}");
}
return new FluentResults.Result<IModConfigInfo>().WithValue(modConfig);
}
}

View File

@@ -87,6 +87,9 @@ public class PluginManagementService : IAssemblyManagementService
ScriptParseOptions);
private ImmutableArray<MetadataReference> _baseMetadataReferences = ImmutableArray<MetadataReference>.Empty;
private ImmutableArray<MetadataReference> _baseMetadataReferencesNonPublicized = ImmutableArray<MetadataReference>.Empty;
private IEnumerable<MetadataReference> BaseMetadataReferences
{
get
@@ -110,6 +113,25 @@ public class PluginManagementService : IAssemblyManagementService
}
}
private IEnumerable<MetadataReference> BaseMetadataReferencesWithBarotrauma
{
get
{
if (_baseMetadataReferencesNonPublicized.IsDefaultOrEmpty)
{
_baseMetadataReferencesNonPublicized = Basic.Reference.Assemblies.Net80.References.All
.Union(AssemblyLoadContext.Default.Assemblies
.Where(ass => !ass.IsDynamic)
.Where(ass => !ass.Location.IsNullOrWhiteSpace())
.Select(MetadataReference (ass) => MetadataReference.CreateFromFile(ass.Location)))
.Where(ar => ar is not null)
.ToImmutableArray();
}
return _baseMetadataReferencesNonPublicized;
}
}
#endregion
#region Disposal
@@ -129,6 +151,7 @@ public class PluginManagementService : IAssemblyManagementService
_logger = null;
_configService = null;
_luaScriptManagementService = null;
_luaCsInfoProvider = null;
GC.SuppressFinalize(this);
}
@@ -199,6 +222,7 @@ public class PluginManagementService : IAssemblyManagementService
private IEventService _pluginEventService;
private Lazy<ILuaPatcher> _pluginLuaPatcherService;
private Func<IConsoleCommandsService> _consoleCommandServiceFactory;
private ILuaCsInfoProvider _luaCsInfoProvider;
private readonly ConcurrentDictionary<ContentPackage, IAssemblyLoaderService> _assemblyLoaders = new();
private readonly ConcurrentDictionary<Type, ContentPackage> _pluginPackageLookup = new();
private readonly ConcurrentDictionary<ContentPackage, ImmutableArray<IAssemblyPlugin>> _pluginInstances = new();
@@ -215,7 +239,8 @@ public class PluginManagementService : IAssemblyManagementService
Lazy<ILuaScriptManagementService> luaScriptManagementService,
Lazy<IConfigService> configService,
Lazy<ILuaPatcher> pluginLuaPatcherService,
Func<IConsoleCommandsService> consoleCommandServiceFactory)
Func<IConsoleCommandsService> consoleCommandServiceFactory,
ILuaCsInfoProvider luaCsInfoProvider)
{
_assemblyLoaderFactory = assemblyLoaderFactory;
_storageService = storageService;
@@ -225,6 +250,7 @@ public class PluginManagementService : IAssemblyManagementService
_configService = configService;
_pluginLuaPatcherService = pluginLuaPatcherService;
_consoleCommandServiceFactory = consoleCommandServiceFactory;
_luaCsInfoProvider = luaCsInfoProvider;
}
private ServiceContainer CreatePluginServiceContainer()
@@ -485,6 +511,12 @@ public class PluginManagementService : IAssemblyManagementService
}
using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
IService.CheckDisposed(this);
_storageService.UseCaching = _luaCsInfoProvider.UseCaching;
if (!_luaCsInfoProvider.UseCaching)
{
_storageService.PurgeCache();
}
var orderedContentPacks = resources.GroupBy(res => res.OwnerPackage)
.OrderBy(res => resources.FindIndex(r2 => r2.OwnerPackage == res.Key))
@@ -554,7 +586,8 @@ public class PluginManagementService : IAssemblyManagementService
return;
}
var metadataReferences = GetMetadataReferences();
var metadataReferences = GetMetadataReferences(false).ToImmutableArray();
var metadataReferencesNonPublicized = GetMetadataReferences(true).ToImmutableArray();
var assemblyLoader = _assemblyLoaders.GetOrAdd(contentPackRes.Key, (cp) => _assemblyLoaderFactory.CreateInstance(
new IAssemblyLoaderService.LoaderInitData(
@@ -578,7 +611,10 @@ public class PluginManagementService : IAssemblyManagementService
foreach (var resourceInfo in scripts)
{
if (!hasInternalsAwareBeenAdded && resourceInfo.UseInternalAccessName)
// this should be the same for the entire collection of src files so we just grab it from the collection
compileWithInternalName = resourceInfo.UseInternalAccessName;
if (!hasInternalsAwareBeenAdded)
{
hasInternalsAwareBeenAdded = true;
syntaxTreesBuilder.Add(BaseAssemblyImports);
@@ -597,9 +633,6 @@ public class PluginManagementService : IAssemblyManagementService
_logger.LogResults(loadRes.ToResult());
continue;
}
// this should be the same for the entire collection of src files so we just grab it from the collection
compileWithInternalName = resourceInfo.UseInternalAccessName;
CancellationToken token = CancellationToken.None;
@@ -622,14 +655,35 @@ public class PluginManagementService : IAssemblyManagementService
}
_logger.LogMessage($"Compiling assembly for {scripts.Key}, in ContentPackage {contentPackRes.Key.Name}");
result.WithReasons(assemblyLoader.CompileScriptAssembly(
var res = assemblyLoader.CompileScriptAssembly(
assemblyName: scripts.Key,
compileWithInternalAccess: compileWithInternalName,
syntaxTrees: syntaxTreesBuilder.ToImmutable(),
metadataReferences: metadataReferences.ToImmutableArray(),
compilationOptions: CompilationOptions)
.Reasons);
metadataReferences: compileWithInternalName ? metadataReferencesNonPublicized : metadataReferences,
compilationOptions: CompilationOptions);
// try with internal access instead for legacy mods
if (!compileWithInternalName && res.IsFailed)
{
_logger.LogMessage($"Attempted compilation of {scripts.Key} for package {contentPackRes.Key.Name}. Trying fallback method.");
var res2 = assemblyLoader.CompileScriptAssembly(
assemblyName: scripts.Key,
compileWithInternalAccess: true,
syntaxTrees: syntaxTreesBuilder.ToImmutable(),
metadataReferences: metadataReferencesNonPublicized,
compilationOptions: CompilationOptions);
// overwrite result with good compilation
if (res2.IsSuccess)
{
var reasonsStr = res.Reasons.Aggregate("", (accum, reason) => accum + "\n" + reason.Message);
_logger.LogWarning($"Attempted compilation of {scripts.Key} for package {contentPackRes.Key.Name} succeeded. Original errors were: \n {reasonsStr}");
res = res2;
}
}
result.WithReasons(res.Reasons);
}
}
@@ -644,14 +698,28 @@ public class PluginManagementService : IAssemblyManagementService
return res;
}
IEnumerable<MetadataReference> GetMetadataReferences()
IEnumerable<MetadataReference> GetMetadataReferences(bool useNonPublicizedAssemblies)
{
var builder = ImmutableArray.CreateBuilder<MetadataReference>();
builder.AddRange(BaseMetadataReferences);
foreach (var loaderService in _assemblyLoaders)
if (useNonPublicizedAssemblies)
{
builder.AddRange(loaderService.Value.AssemblyReferences.Where(ar => ar is not null));
builder.AddRange(BaseMetadataReferencesWithBarotrauma);
foreach (var loaderService in _assemblyLoaders
.Where(asl => !asl.Key.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase))
.ToImmutableArray())
{
builder.AddRange(loaderService.Value.AssemblyReferences.Where(ar => ar is not null));
}
}
else
{
builder.AddRange(BaseMetadataReferences);
foreach (var loaderService in _assemblyLoaders)
{
builder.AddRange(loaderService.Value.AssemblyReferences.Where(ar => ar is not null));
}
}
return builder.ToImmutable();
}
}
@@ -768,6 +836,7 @@ public class PluginManagementService : IAssemblyManagementService
}
_assemblyLoaders.Clear();
_storageService.PurgeCache();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true);
#if DEBUG

View File

@@ -34,4 +34,26 @@ public interface ILoggerService : IReusableService
void LogDebugError(string message);
#endregion
#region LegacyCompat_LuaCsLogger
public void HandleException(Exception ex, LuaCsMessageOrigin origin)
{
HandleException(ex, origin.ToString());
}
public void LogError(string message, LuaCsMessageOrigin origin)
{
LogError(message);
}
#endregion
}
public enum LuaCsMessageOrigin
{
LuaCs,
Unknown,
LuaMod,
CSharpMod,
}

View File

@@ -22,8 +22,10 @@ public interface IPackageManagementService : IReusableService
public FluentResults.Result UnloadPackages(ImmutableArray<ContentPackage> packages);
public FluentResults.Result UnloadAllPackages();
public ImmutableArray<ContentPackage> GetAllLoadedPackages();
public ImmutableArray<ContentPackage> GetLoadedAssemblyPackages();
public ImmutableArray<ContentPackage> GetLoadedUnrestrictedPackages();
public bool IsPackageRunning(ContentPackage package);
public bool IsAnyPackageLoaded();
public bool IsAnyPackageRunning();
public bool PackageContainsAnyRunnableResource(ContentPackage package);
public Result<IModConfigInfo> GetModConfigForPackage(ContentPackage package);
}

View File

@@ -154,6 +154,13 @@ public class DefaultLuaRegistrar : IDefaultLuaRegistrar
_userDataService.RegisterType("FarseerPhysics.Collision.ReferenceFace");
_userDataService.RegisterType("FarseerPhysics.Collision.Collision");
_userDataService.RegisterType("Voronoi2.DoubleVector2");
_userDataService.RegisterType("Voronoi2.Site");
_userDataService.RegisterType("Voronoi2.Edge");
_userDataService.RegisterType("Voronoi2.Halfedge");
_userDataService.RegisterType("Voronoi2.VoronoiCell");
_userDataService.RegisterType("Voronoi2.GraphEdge");
_userDataService.RegisterType("Barotrauma.PrefabCollection`1");
_userDataService.RegisterType("Barotrauma.PrefabSelector`1");
_userDataService.RegisterType("Barotrauma.Pair`2");

View File

@@ -19,7 +19,7 @@ public interface ILuaSafeEventService : ILuaService, ILuaCsHook
/// </summary>
/// <param name="eventName"></param>
/// <param name="identifier"></param>
void Remove(string eventName, string identifier);
void Unsubscribe(string eventName, string identifier);
/// <summary>
/// Send an event to all subscribers to an interface.
/// </summary>

View File

@@ -2,16 +2,10 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using MoonSharp.Interpreter;
using Barotrauma.LuaCs;
namespace Barotrauma
{
public enum LuaCsMessageOrigin
{
LuaCs,
Unknown,
LuaMod,
CSharpMod,
}
public partial class LuaCsLogger
{

View File

@@ -445,11 +445,33 @@ namespace Barotrauma.LuaCs
}
);
}
public void AddCommand(string name, LuaCsAction onExecute, LuaCsFunc getValidArgs = null, bool isCheat = false)
{
_consoleCommands.RegisterCommand(name, "",
(string[] args) =>
{
onExecute(new object[] { args });
},
() =>
{
if (getValidArgs == null) { return null; }
var validArgs = getValidArgs();
if (validArgs is DynValue luaValue)
{
return luaValue.ToObject<string[][]>();
}
return (string[][])validArgs;
}
);
}
public bool IsDisposed => throw new NotImplementedException();
public void AssignOnExecute(string names, LuaCsAction onExecute) =>
_consoleCommands.AssignOnExecute(names, args => onExecute(args));
public void AssignOnExecute(string names, object onExecute) => DebugConsole.AssignOnExecute(names, (string[] args) =>
{
LuaCsSetup.Instance.LuaScriptManagementService.CallFunctionSafe(onExecute, new object[] { args });
});
public void SaveGame(string path)
{

View File

@@ -816,7 +816,7 @@ namespace Barotrauma
public static float GetDistanceFactor(PhysicsBody triggererBody, PhysicsBody triggerBody, float colliderRadius)
{
return 1.0f - ConvertUnits.ToDisplayUnits(Vector2.Distance(triggererBody.SimPosition, triggerBody.SimPosition)) / colliderRadius;
return 1.0f - ConvertUnits.ToDisplayUnits(Vector2.Distance(triggererBody.SimPosition, triggerBody.SimPosition) - triggererBody.GetMaxExtent() / 2) / colliderRadius;
}
public Vector2 GetWaterFlowVelocity(Vector2 viewPosition)

View File

@@ -1,4 +1,17 @@
-------------------------------------------------------------------------------------------------------------------------------------------------
v1.12.7.0
-------------------------------------------------------------------------------------------------------------------------------------------------
- Reduced how much the new weak points in the reworked subs push bots around to make them more capable of fixing broken weak points.
- Fixed selecting any item that forces the character to some pose (chairs, periscopes) getting logged in the server console.
- Mac only: added a button for settings mic permissions to the audio settings. It seems that on Mac, the game updates may cause the OS to revoke the permissions.
- Fixed some of the Workshop tags you can choose in-game not working on Steam's side.
Modding:
- Fixed contained items being misaligned on attachable items (e.g. in mods that make diving suit cabinets attachable).
- Fixed monsters spawned by an event inside an outpost being unable to attack any items inside that outpost. To our knowledge, didn't affect vanilla events, but caused issues in certain mods.
-------------------------------------------------------------------------------------------------------------------------------------------------
v1.12.6.2
-------------------------------------------------------------------------------------------------------------------------------------------------
@@ -439,7 +452,6 @@ v1.8.8.1
Modding:
- Fixed transferring afflictions to a newly spawned character using status effects causing a crash if the original character had already been removed. Didn't affect any vanilla content.
>>>>>>> master
-------------------------------------------------------------------------------------------------------------------------------------------------
v1.8.7.0

View File

@@ -9,7 +9,7 @@ This is a Barotrauma modification that adds Lua and Cs modding support.
# Barotrauma
Copyright © FakeFish Ltd 2017-2024
Copyright © FakeFish Ltd 2017-2026
Before downloading the source code, please read the [EULA](EULA.txt).