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
+1 -1
View File
@@ -73,7 +73,7 @@ body:
label: Version label: Version
description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu. description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu.
options: options:
- v1.11.5.0 (Winter Update 2025 Hotfix 1) - v1.12.6.2 (Spring Update 2026)
- Other - Other
validations: validations:
required: true required: true
@@ -24,7 +24,7 @@ public sealed class SettingControl : SettingBase, ISettingControl
public SettingControl(IConfigInfo configInfo, Func<OneOf<string, XElement, object>, bool> valueChangePredicate) : base(configInfo) public SettingControl(IConfigInfo configInfo, Func<OneOf<string, XElement, object>, bool> valueChangePredicate) : base(configInfo)
{ {
_valueChangePredicate = valueChangePredicate; _valueChangePredicate = valueChangePredicate;
TrySetValue(configInfo.Element); TrySetSerializedValue(configInfo.Element);
} }
protected override void OnDispose() protected override void OnDispose()
@@ -37,7 +37,7 @@ public sealed class SettingControl : SettingBase, ISettingControl
public override string GetStringValue() => Value.ToString(); public override string GetStringValue() => Value.ToString();
public override string GetDefaultStringValue() => new KeyOrMouse(Keys.NumLock).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>( var newVal = value.Match<KeyOrMouse>(
(string v) => GetKeyOrMouse(v), (string v) => GetKeyOrMouse(v),
@@ -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.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Barotrauma.CharacterEditor; using static System.Collections.Specialized.BitVector32;
using Barotrauma.LuaCs;
using Barotrauma.LuaCs.Data;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
// ReSharper disable ObjectCreationAsStatement // ReSharper disable ObjectCreationAsStatement
namespace Barotrauma namespace Barotrauma
{ {
partial class LuaCsSetup partial class LuaCsSetup
{ {
private bool _isClientPromptActive; public void PromptCSharpMods(Action<bool> onSelection, bool joiningServer)
private bool _isCsEnabledForSession = false;
public void CheckRunConditionalHostingCsEnabled(Action onReadyToRun)
{ {
var res = ReadyToRunNoPrompt(); ImmutableArray<ContentPackage> contentPackages = PackageManagementService.GetLoadedUnrestrictedPackages()
if (res.ShouldRun) .Where(p => p.Name != PackageName)
{
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)
.ToImmutableArray(); .ToImmutableArray();
return (contentPackages.IsEmpty, contentPackages); if (_csRunPolicy?.Value is "Enabled")
}
partial void CheckReadyToRun(Action onReadyToRun)
{
var res = ReadyToRunNoPrompt();
if (res.ShouldRun)
{ {
onReadyToRun?.Invoke(); IsCsEnabledForSession = true;
onSelection(true);
return; return;
} }
else if (_csRunPolicy?.Value is "Disabled")
if (GameMain.Client?.ClientPeer is P2POwnerPeer)
{ {
SetCsPolicyAndContinue(true); IsCsEnabledForSession = false;
onSelection(false);
return; return;
} }
DisplayCsModsPromptClient(res.PromptPackages, (selectedYes) => if (contentPackages.None())
{ {
SetCsPolicyAndContinue(selectedYes); onSelection(true);
return; 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( GUIMessageBox messageBox = new GUIMessageBox(
TextManager.Get("warning"), TextManager.Get("warning"),
@@ -115,7 +56,7 @@ namespace Barotrauma
Stretch = true 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); font: GUIStyle.SubHeadingFont, wrap: true, textAlignment: Alignment.Center);
GUIListBox packageListBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.4f), msgBoxLayout.RectTransform)) 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) foreach (ContentPackage package in contentPackages)
{ {
GUIFrame packageFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.15f), packageListBox.Content.RectTransform), style: "ListBoxElement"); 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 Wrap = true
}; };
GUILayoutGroup buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), messageBox.Content.RectTransform, Anchor.BottomCenter), isHorizontal: false, childAnchor: Anchor.TopCenter); 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 }, TextBlock = { AutoScaleHorizontal = true },
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
_isClientPromptActive = false; IsCsEnabledForSession = true;
onSelection(true); onSelection(true);
messageBox.Close(); messageBox.Close();
return true; return true;
@@ -152,7 +110,7 @@ namespace Barotrauma
{ {
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
_isClientPromptActive = false; IsCsEnabledForSession = false;
onSelection(false); onSelection(false);
messageBox.Close(); messageBox.Close();
return true; return true;
@@ -201,10 +159,18 @@ namespace Barotrauma
case SpriteEditorScreen: case SpriteEditorScreen:
case SubEditorScreen: case SubEditorScreen:
case TestScreen: // notes: TestScreen is a Linux edge case editor screen and is deprecated. 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); SetRunState(RunState.Running);
}); }
break; break;
default: default:
Logger.LogError( Logger.LogError(
@@ -32,11 +32,11 @@ partial class NetworkingService : INetworkingService, IEventServerConnected, IEv
} }
} }
public void OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader) public bool? OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader)
{ {
if (serverPacketHeader != ServerHeader) if (serverPacketHeader != ServerHeader)
{ {
return; return null;
} }
ServerToClient luaCsHeader = (ServerToClient)netMessage.ReadByte(); ServerToClient luaCsHeader = (ServerToClient)netMessage.ReadByte();
@@ -55,6 +55,8 @@ partial class NetworkingService : INetworkingService, IEventServerConnected, IEv
ReadIds(netMessage); ReadIds(netMessage);
break; break;
} }
return true;
} }
private void SendSyncMessage() private void SendSyncMessage()
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System.Linq; using System.Linq;
@@ -20,23 +21,87 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
private string _selectedSearchQuery = string.Empty; private string _selectedSearchQuery = string.Empty;
private ContentPackage _selectedContentPackage; private ContentPackage _selectedContentPackage;
private string _selectedCategory = string.Empty; 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; private event Action OnApplyInstalledModsChanges;
public ModsGameplaySettingsMenu(GUIFrame contentFrame, public ModsGameplaySettingsMenu(GUIFrame contentFrame,
IPackageManagementService packageManagementService, IPackageManagementService packageManagementService,
IConfigService configService, IConfigService configService,
ILoggerService loggerService,
SettingsMenu settingsMenuInstance) : base(contentFrame, packageManagementService, configService, settingsMenuInstance) SettingsMenu settingsMenuInstance) : base(contentFrame, packageManagementService, configService, settingsMenuInstance)
{ {
_settingsInstancesGameplay = configService.GetDisplayableConfigs() _settingsInstancesGameplay = configService.GetDisplayableConfigs()
.ToImmutableArray(); .ToImmutableArray();
_loggerService = loggerService;
var mainLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), contentFrame.RectTransform, Anchor.Center), false, Anchor.TopLeft); var mainLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), contentFrame.RectTransform, Anchor.Center), false, Anchor.TopLeft);
// page title // page title
var menuTitleLayoutGroup = new GUILayoutGroup( var menuTitleLayoutGroup = new GUILayoutGroup(
new RectTransform(new Vector2(1f, 0.06f), mainLayoutGroup.RectTransform, Anchor.TopLeft), true, Anchor.TopLeft); new RectTransform(new Vector2(1f, MenuTitleHeight), mainLayoutGroup.RectTransform, Anchor.TopLeft), true, Anchor.TopLeft);
GUIUtil.Label(menuTitleLayoutGroup, "Mods Gameplay Settings", GUIStyle.LargeFont, new Vector2(1f, 1f)); GUIUtil.Label(menuTitleLayoutGroup,
GetLocalizedString("LuaCsForBarotrauma.SettingsMenu.ModGameplayButton", "Mod Gameplay Settings"),
GUIStyle.LargeFont, new Vector2(1f, 1f));
// page contents // page contents
var contentAreaLayoutGroup = new GUILayoutGroup( var contentAreaLayoutGroup = new GUILayoutGroup(
@@ -44,10 +109,10 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
Anchor.TopLeft); Anchor.TopLeft);
var searchBarLayoutGroup = new GUILayoutGroup( var searchBarLayoutGroup = new GUILayoutGroup(
new RectTransform(new Vector2(1f, 0.06f), contentAreaLayoutGroup.RectTransform, Anchor.TopCenter), true, Anchor.CenterLeft); new RectTransform(new Vector2(1f, SearchBarLayoutHeight), contentAreaLayoutGroup.RectTransform, Anchor.TopCenter), true, Anchor.CenterLeft);
GUIUtil.Label(searchBarLayoutGroup, "Search: ", GUIStyle.SubHeadingFont, new Vector2(0.1f, 1f)); GUIUtil.Label(searchBarLayoutGroup, "Search: ", GUIStyle.SubHeadingFont, new Vector2(SearchBarLabelWidth, 1f));
var searchBar = new GUITextBox( 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) createClearButton: true)
{ {
OnTextChangedDelegate = (btn, txt) => OnTextChangedDelegate = (btn, txt) =>
@@ -56,12 +121,13 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
return true; return true;
} }
}; };
// main display area // 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); GUIUtil.Spacer(settingsContentAreaGroup, Vector2.One);
(_modCategoryDisplayGroup, _settingsDisplayGroup) = GUIUtil.CreateSidebars(settingsContentAreaGroup, true); (_modCategoryDisplayGroup, _settingsDisplayGroup) = GUIUtil.CreateSidebars(settingsContentAreaGroup, true);
_modCategoryDisplayGroup.RectTransform.RelativeSize = new Vector2(0.3f, 1f); _modCategoryDisplayGroup.RectTransform.RelativeSize = new Vector2(ContentLeftRightSplitPosition, ContentDisplayAreaHeightInnerCategories);
_settingsDisplayGroup.RectTransform.RelativeSize = new Vector2(0.7f, 1f); _settingsDisplayGroup.RectTransform.RelativeSize = new Vector2(1f-ContentLeftRightSplitPosition, ContentDisplayAreaHeightInnerSettings);
// default category // default category
_selectedCategory = "All"; _selectedCategory = "All";
@@ -202,10 +268,11 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
_selectedCategory = string.Empty; _selectedCategory = string.Empty;
GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetTargetPackagesList(), GetDisplayCategoriesList()); GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetTargetPackagesList(), GetDisplayCategoriesList());
GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList()); GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList());
}, new Vector2(1f, 0.07f)); }, new Vector2(1f, PackageSelectionButtonHeight));
var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.945f), layoutGroup.RectTransform)); var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, CategoriesDisplayListHeight), layoutGroup.RectTransform));
const float entryHeight = 0.122f;
float sizeY = MathF.Max(categories.Length * entryHeight, 1f);
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) var displayedCategoriesFrame = new GUIFrame(new RectTransform(new Vector2(1f, sizeY), containerBox.Content.RectTransform), style: null, color: Color.Black)
{ {
CanBeFocused = false CanBeFocused = false
@@ -214,17 +281,18 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
foreach (var category in categories) 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) text: category, color: Color.TransparentBlack)
{ {
CanBeFocused = true, CanBeFocused = true,
CanBeSelected = true, CanBeSelected = true,
TextColor = Color.PeachPuff, TextColor = CategoryButtonTextColor,
HoverColor = new Color(50, 50, 50, 255), HoverColor = CategoryButtonHoverSelectColor,
HoverTextColor = Color.White, HoverTextColor = CategoryButtonTextColorSelected,
SelectedColor = new Color(50, 50, 50, 255), PressedColor = CategoryButtonColorPressed,
SelectedTextColor = Color.White, SelectedColor = CategoryButtonHoverSelectColor,
OnPressed = () => SelectedTextColor = CategoryButtonHoverSelectColor,
OnClicked = (btn, obj) =>
{ {
_selectedCategory = category; _selectedCategory = category;
GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList()); GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList());
@@ -237,26 +305,47 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
void GenerateSettingsListDisplay(GUILayoutGroup layoutGroup, ImmutableArray<ISettingBase> settings) void GenerateSettingsListDisplay(GUILayoutGroup layoutGroup, ImmutableArray<ISettingBase> settings)
{ {
layoutGroup.ClearChildren(); 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) foreach (var setting in settings)
{ {
var entry = AddSettingToDisplay( var entry = AddSettingToDisplay(
setting, setting,
containerBox.Content.RectTransform, containerBox.Content.RectTransform,
settingHeight: settingHeight, settingHeight: SettingHeight,
labelSize: new Vector2(0.6f, 1f), labelSize: new Vector2(SettingLabelWidth, 1f),
controlSize: new Vector2(0.4f, 1f)); controlSize: new Vector2(SettingControlWidth, 1f));
} }
}
(GUIFrame entryFrame, GUILayoutGroup entryLayoutGroup) AddSettingToDisplay(ISettingBase setting, var spacer = new GUIFrame(new RectTransform(SettingsResetButtonTopSpacer, layoutGroup.RectTransform),
RectTransform parent, float settingHeight, Vector2 labelSize, Vector2 controlSize) 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 Color = Color.DarkGray
}; };
@@ -266,9 +355,10 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
new GUIFrame(new RectTransform(new Vector2(0.02f, 1f), entryLayoutGroup.RectTransform), new GUIFrame(new RectTransform(new Vector2(0.02f, 1f), entryLayoutGroup.RectTransform),
color: Color.TransparentBlack); color: Color.TransparentBlack);
// setting label
new GUITextBlock(new RectTransform(labelSize - new Vector2(0.05f, 0f), entryLayoutGroup.RectTransform), new GUITextBlock(new RectTransform(labelSize - new Vector2(0.05f, 0f), entryLayoutGroup.RectTransform),
GetLocalizedString(setting.GetDisplayInfo().DisplayName, setting.GetDisplayInfo().DisplayName), GetLocalizedString(setting.GetDisplayInfo().DisplayName, setting.GetDisplayInfo().DisplayName),
textColor: Color.PeachPuff, textColor: SettingEntryLabelTextColor,
font: GUIStyle.SmallFont, font: GUIStyle.SmallFont,
textAlignment: Alignment.Left) textAlignment: Alignment.Left)
{ {
@@ -281,6 +371,58 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
}); });
return (entryFrame, entryLayoutGroup); 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; continue;
} }
kvp.Key.TrySetValue(kvp.Value); var success = kvp.Key.TrySetSerializedValue(kvp.Value);
ConfigService.SaveConfigValue(kvp.Key); if (success)
{
ConfigService.SaveConfigValue(kvp.Key);
_loggerService.LogDebug($"Applied save value for {kvp.Key.InternalName} of {kvp.Value.ToString()}");
}
} }
NewValuesCache.Clear(); NewValuesCache.Clear();
OnApplyInstalledModsChanges?.Invoke(); OnApplyInstalledModsChanges?.Invoke();
@@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Xml.Linq;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Data;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using OneOf;
namespace Barotrauma.LuaCs; namespace Barotrauma.LuaCs;
@@ -12,7 +14,7 @@ internal abstract class ModsSettingsMenuBase : IDisposable
protected IPackageManagementService PackageManagementService { get; private set; } protected IPackageManagementService PackageManagementService { get; private set; }
protected IConfigService ConfigService { get; private set; } protected IConfigService ConfigService { get; private set; }
protected SettingsMenu SettingsMenuInstance { 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, protected ModsSettingsMenuBase(GUIFrame contentFrame,
IPackageManagementService packageManagementService, IPackageManagementService packageManagementService,
@@ -18,12 +18,14 @@ public class SettingsMenuSystem : ISettingsMenuSystem
private readonly Harmony _harmony; private readonly Harmony _harmony;
private readonly IPackageManagementService _packageManagementService; private readonly IPackageManagementService _packageManagementService;
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly ILoggerService _loggerService;
private static SettingsMenuSystem SystemInstance; private static SettingsMenuSystem SystemInstance;
public SettingsMenuSystem(IPackageManagementService packageManagementService, IConfigService configService) public SettingsMenuSystem(IPackageManagementService packageManagementService, IConfigService configService, ILoggerService loggerService)
{ {
_packageManagementService = packageManagementService; _packageManagementService = packageManagementService;
_configService = configService; _configService = configService;
_loggerService = loggerService;
SystemInstance = this; SystemInstance = this;
_harmony = Harmony.CreateAndPatchAll(typeof(SettingsMenuSystem)); _harmony = Harmony.CreateAndPatchAll(typeof(SettingsMenuSystem));
} }
@@ -43,13 +45,14 @@ public class SettingsMenuSystem : ISettingsMenuSystem
var tabGameplayIndex = (SettingsMenu.Tab)tabCount; var tabGameplayIndex = (SettingsMenu.Tab)tabCount;
var tabControlsIndex = (SettingsMenu.Tab)tabCount+1; var tabControlsIndex = (SettingsMenu.Tab)tabCount+1;
_gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance, _gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance,
"SettingsMenuTab.Mods", "LuaCsForBarotrauma.SettingsMenu.ModGameplayButton"); GUIStyle.ComponentStyles.ContainsKey("SettingsMenuTab.LuaCsSettings") ? "SettingsMenuTab.LuaCsSettings" : "SettingsMenuTab.Mods",
"LuaCsForBarotrauma.SettingsMenu.ModGameplayButton");
/*_controlsContentFrame = CreateNewContentTab(tabControlsIndex, __instance, /*_controlsContentFrame = CreateNewContentTab(tabControlsIndex, __instance,
"SettingsMenuTab.Controls", "LuaCsForBarotrauma.SettingsMenu.ModControlsButton"); "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); //_controlsMenuInstance = new ModsControlsSettingsMenu(_controlsContentFrame, _packageManagementService, _configService, __instance);
} }
@@ -1015,25 +1015,20 @@ namespace Barotrauma
private void TryStartServer() 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") }); CoroutineManager.StopCoroutines(waitCoroutine);
var waitCoroutine = CoroutineManager.StartCoroutine(WaitForSubmarineHashCalculations(waitBox), "WaitForSubmarineHashCalculations"); return true;
waitBox.Buttons[0].OnClicked += (btn, userdata) => };
{ }
CoroutineManager.StopCoroutines(waitCoroutine); else
return true; {
}; StartServer();
} }
else
{
StartServer();
}
});
} }
private IEnumerable<CoroutineStatus> WaitForSubmarineHashCalculations(GUIMessageBox messageBox) private IEnumerable<CoroutineStatus> WaitForSubmarineHashCalculations(GUIMessageBox messageBox)
@@ -174,9 +174,9 @@ namespace Barotrauma
public static RectTransform NewItemRectT(GUILayoutGroup parent) public static RectTransform NewItemRectT(GUILayoutGroup parent)
=> new RectTransform((1.0f, 0.06f), parent.RectTransform, Anchor.CenterLeft); => 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) public static GUITextBlock Label(GUILayoutGroup parent, LocalizedString str, GUIFont font)
@@ -507,6 +507,47 @@ namespace Barotrauma
return true; 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); Spacer(voiceChat);
Label(voiceChat, TextManager.Get("VCInputMode"), GUIStyle.SubHeadingFont); Label(voiceChat, TextManager.Get("VCInputMode"), GUIStyle.SubHeadingFont);
@@ -18,6 +18,10 @@ namespace Barotrauma.Steam
{ {
public const int MaxThumbnailSize = 1024 * 1024; 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 [] public static readonly ImmutableArray<Identifier> Tags = new []
{ {
"submarine", "submarine",
@@ -25,7 +29,7 @@ namespace Barotrauma.Steam
"monster", "monster",
"mission", "mission",
"outpost", "outpost",
"beaconstation", "beacon station",
"wreck", "wreck",
"ruin", "ruin",
"weapons", "weapons",
@@ -34,14 +38,14 @@ namespace Barotrauma.Steam
"art", "art",
"event set", "event set",
"total conversion", "total conversion",
"gamemode", "game mode",
"gameplaymechanics", "gameplay mechanics",
"environment", "environment",
"item assembly", "item assembly",
"language", "language",
"qol", "qol",
"clientside", "client-side",
"serverside", "server-side",
"outdated", "outdated",
"library" "library"
}.ToIdentifiers().ToImmutableArray(); }.ToIdentifiers().ToImmutableArray();
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>1.12.6.2</Version> <Version>1.12.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2024</Copyright> <Copyright>Copyright © FakeFish 2018-2024</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>1.12.6.2</Version> <Version>1.12.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2024</Copyright> <Copyright>Copyright © FakeFish 2018-2024</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>1.12.6.2</Version> <Version>1.12.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2024</Copyright> <Copyright>Copyright © FakeFish 2018-2024</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>1.12.6.2</Version> <Version>1.12.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright> <Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>1.12.6.2</Version> <Version>1.12.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright> <Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -2,6 +2,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Barotrauma.LuaCs;
namespace Barotrauma namespace Barotrauma
{ {
@@ -35,11 +35,11 @@ partial class NetworkingService : INetworkingService, IEventClientRawNetMessageR
return message; return message;
} }
public void OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader clientPacketHeader, NetworkConnection sender) public bool? OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader clientPacketHeader, NetworkConnection sender)
{ {
if (clientPacketHeader != ClientHeader) if (clientPacketHeader != ClientHeader)
{ {
return; return null;
} }
Client client = GameMain.Server.ConnectedClients.First(c => c.Connection == sender); Client client = GameMain.Server.ConnectedClients.First(c => c.Connection == sender);
@@ -64,6 +64,8 @@ partial class NetworkingService : INetworkingService, IEventClientRawNetMessageR
RequestIdSingle(netMessage, client); RequestIdSingle(netMessage, client);
break; break;
} }
return true;
} }
private void HandleNetMessageId(IReadMessage netMessage, Client client = null) private void HandleNetMessageId(IReadMessage netMessage, Client client = null)
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>1.12.6.2</Version> <Version>1.12.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright> <Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -10,6 +10,6 @@
</Values> </Values>
</Setting> </Setting>
<Setting Name="UseCaching" Type="bool" Value="true" AllowChangesWhileExecuting="false"/> <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> </Settings>
</Configuration> </Configuration>
@@ -35,44 +35,4 @@ if not CSActive then
end end
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 LuaSetup = nil
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<style>
<SettingsMenuTab.LuaCsSettings color="169,212,187,255" hovercolor="220,220,220,255" selectedcolor="255,255,255,255" pressedcolor="100,100,100,255" disabledcolor="125,125,125,125">
<Sprite name="LuaCsSettings" texture="%ModDir%/LuaCsSettingsIcon.png" sourcerect="0,0,64,64" tile="false" maintainaspectratio="true" origin="0.5,0.5"/>
</SettingsMenuTab.LuaCsSettings>
</style>
@@ -2,6 +2,11 @@
<infotexts language="English" nowhitespace="false" translatedname="English"> <infotexts language="English" nowhitespace="false" translatedname="English">
<LuaCsForBarotrauma.SettingsMenu.ModControlsButton>Mod Controls Settings</LuaCsForBarotrauma.SettingsMenu.ModControlsButton> <LuaCsForBarotrauma.SettingsMenu.ModControlsButton>Mod Controls Settings</LuaCsForBarotrauma.SettingsMenu.ModControlsButton>
<LuaCsForBarotrauma.SettingsMenu.ModGameplayButton>Mod Gameplay Settings</LuaCsForBarotrauma.SettingsMenu.ModGameplayButton> <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 --> <!-- Settings -->
<!-- Is Cs Enabled--> <!-- Is Cs Enabled-->
<LuaCsForBarotrauma.CsRunPolicy.DisplayName>Are C# Mods Allowed</LuaCsForBarotrauma.CsRunPolicy.DisplayName> <LuaCsForBarotrauma.CsRunPolicy.DisplayName>Are C# Mods Allowed</LuaCsForBarotrauma.CsRunPolicy.DisplayName>
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<contentpackage name="LuaCsForBarotrauma"> <contentpackage name="LuaCsForBarotrauma">
<Text file="%ModDir%/Texts/English.xml"/> <Text file="%ModDir%/Texts/English.xml"/>
<UIStyle file="%ModDir%/Style.xml" />
<!--<Text file="%ModDir%/Texts/Portuguese.xml"/>--> <!--<Text file="%ModDir%/Texts/Portuguese.xml"/>-->
</contentpackage> </contentpackage>
@@ -14,7 +14,7 @@
AttachedByDefault="true" DisallowAttachingOverTags="container,planter,refuelableitem" DisallowAttachingOverSize="115,130"> AttachedByDefault="true" DisallowAttachingOverTags="container,planter,refuelableitem" DisallowAttachingOverSize="115,130">
</Holdable> </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" /> <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" /> <SlotIcon slotindex="0" texture="Content/UI/StatusMonitorUI.png" sourcerect="64,448,64,64" origin="0.5,0.5" />
<Containable items="oxygensource" /> <Containable items="oxygensource" />
@@ -24,29 +24,52 @@ Hook.Add("missionsEnded", "test", function()
print("missionsEnded") print("missionsEnded")
end) 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") local failed, package = trygetpackage("[DebugOnlyTest]TestLuaMod")
print("packageFailed=", failed) print("packageFailed=", failed)
print("package", package.Name) 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") if not success or not success2 then
local success3, config3 = ConfigService.TryGetConfig(SettingBase.Int32, package, "TestSynchroClient") print("Failed to get configs.")
return
end
print("config ", success, " ", config.Value) config.OnValueChanged.add(OnChanged)
print("config testsynchrosrv", success2, " ", config2.Value) config2.OnValueChanged.add(OnChanged)
print("config testsynchrocli", success3, " ", config3.Value)
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() 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 if SERVER then
print(config.Value) local succ = config.TrySetValue(config.Value + 1)
print("Success of setting value on server for '", config.InternalName,"': ", succ)
if SERVER then end
config.TrySetValue(config.Value + 1) if CLIENT then
end 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) end)
@@ -2,4 +2,6 @@
<ModConfig> <ModConfig>
<Lua File="%ModDir%/Lua/init.lua" IsAutorun="true" /> <Lua File="%ModDir%/Lua/init.lua" IsAutorun="true" />
<Config File="%ModDir%/Settings.xml"/> <Config File="%ModDir%/Settings.xml"/>
<Config File="%ModDir%/SettingsClient.xml" Target="Client"/>
<Config File="%ModDir%/SettingsServer.xml" Target="Server"/>
</ModConfig> </ModConfig>
@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Configuration> <Configuration>
<Settings> <Settings>
<Setting Name="TestTickbox" Type="bool"/> <Setting Name="TestTickbox" Type="bool" Value="true"/>
<Setting Name="TestSynchroClient" Type="int" NetSync="TwoWay" /> <Setting Name="TestSynchroClient" Type="int" NetSync="TwoWay" Value="40"/>
<Setting Name="TestSynchroServer" Type="int" NetSync="ServerAuthority" /> <Setting Name="TestSynchroServer" Type="int" NetSync="ServerAuthority" Value="25"/>
<Setting Name="TestFloat" Type="float" NetSync="ServerAuthority" /> <Setting Name="TestFloat" Type="float" Value="3498"/>
<Setting Name="TestHidden" Type="bool" ShowInMenus="false"/> <Setting Name="TestHidden" Type="bool" ShowInMenus="false" Value="false"/>
<Setting Name="TestRangeFloat" Type="rangeFloat" Min="0" Max="25" Steps="11"/> <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"/> <Setting Name="TestRangeInt" Type="rangeInt" Min="0" Max="10" Steps="11" Value="4"/>
<Setting Name="TestString" Type="string" /> <Setting Name="TestString" Type="string" Value="ok"/>
<Setting Name="TestControl" Type="control" Value="A"/> <Setting Name="TestControl" Type="control" Value="A"/>
<Setting Name="TestDropdownList" Type="listString" Value="Hi"> <Setting Name="TestDropdownList" Type="listString" Value="Hi">
<Values> <Values>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Configuration>
<!-- Should match 'SettingsServer'. We define these on the client and server separately to give different values. -->
<Settings>
<Setting Name="TestSynchroClient" Type="int" NetSync="TwoWay" Value="3545" ShowInMenus="false"/>
<Setting Name="TestSynchroServer" Type="int" NetSync="ServerAuthority" Value="577" ShowInMenus="false"/>
</Settings>
</Configuration>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Configuration>
<!-- Should match 'SettingsClient'. We define these on the client and server separately to give different values. -->
<Settings>
<Setting Name="TestSynchroClient" Type="int" NetSync="TwoWay" Value="40" ShowInMenus="false"/>
<Setting Name="TestSynchroServer" Type="int" NetSync="ServerAuthority" Value="25" ShowInMenus="false"/>
</Settings>
</Configuration>
@@ -242,10 +242,13 @@ namespace Barotrauma
} }
/// <summary> /// <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> /// </summary>
private readonly HashSet<Submarine> unattackableSubmarines = new HashSet<Submarine>();
public void SetUnattackableSubmarines(Submarine submarine, bool includeOwnSub = true, bool includeConnectedSubs = true, bool clearExisting = true) public void SetUnattackableSubmarines(Submarine submarine, bool includeOwnSub = true, bool includeConnectedSubs = true, bool clearExisting = true)
{ {
if (clearExisting) if (clearExisting)
@@ -3075,11 +3078,17 @@ namespace Barotrauma
} }
else 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 || 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)) unattackableSubmarines.Contains(aiTarget.Entity.Submarine))
{ {
continue; continue;
@@ -590,6 +590,11 @@ namespace Barotrauma
package.UgcId.TryUnwrap(out var ugcId) && ugcId is SteamWorkshopId workshopId && workshopId.Value == childUgcItemId.Value)); package.UgcId.TryUnwrap(out var ugcId) && ugcId is SteamWorkshopId workshopId && workshopId.Value == childUgcItemId.Value));
foreach (var missingChild in missingChildren) foreach (var missingChild in missingChildren)
{ {
if (missingChild.ToString() == "2559634234" ||
missingChild.ToString() == "2795927223")
{
continue;
}
enabledPackage.AddMissingDependency(missingChild); enabledPackage.AddMissingDependency(missingChild);
} }
}); });
@@ -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; } 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).")] [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 (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) if (flippedX)
{ {
transformedItemPos.X = -transformedItemPos.X; transformedItemPos.X = -transformedItemPos.X;
if (!useCenterAsOrigin) { transformedItemPos.X += item.Rect.Width; } transformedItemPos.X += item.Rect.Width;
transformedItemInterval.X = -transformedItemInterval.X; transformedItemInterval.X = -transformedItemInterval.X;
transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X; transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
} }
if (flippedY) if (flippedY)
{ {
transformedItemPos.Y = -transformedItemPos.Y; transformedItemPos.Y = -transformedItemPos.Y;
if (!useCenterAsOrigin) { transformedItemPos.Y -= item.Rect.Height; } transformedItemPos.Y -= item.Rect.Height;
transformedItemInterval.Y = -transformedItemInterval.Y; transformedItemInterval.Y = -transformedItemInterval.Y;
transformedItemIntervalVertical.Y = -transformedItemIntervalVertical.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 (drawPosition)
{ {
if (item.Submarine != null) { transformedItemPos += item.Submarine.DrawPosition; } if (item.Submarine != null) { transformedItemPos += item.Submarine.DrawPosition; }
@@ -1129,6 +1125,16 @@ namespace Barotrauma.Items.Components
} }
else 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); Matrix transform = Matrix.CreateRotationZ(drawPosition ? item.body.DrawRotation : item.body.Rotation);
if (bodyFlipped) if (bodyFlipped)
{ {
@@ -862,10 +862,6 @@ namespace Barotrauma.Items.Components
} }
else if (CanBeSelectedByCharacters) else if (CanBeSelectedByCharacters)
{ {
#if SERVER
GameServer.Log($"{GameServer.CharacterLogName(activator)} entered {item.Name}", ServerLog.MessageType.ItemInteraction);
#endif
activator.DeselectCharacter(); activator.DeselectCharacter();
User = activator; User = activator;
@@ -11,6 +11,7 @@ public interface ILuaCsHook : ILuaPatcher, ILuaCsShim
void Add(string eventName, string identifier, LuaCsFunc callback, object owner = null); void Add(string eventName, string identifier, LuaCsFunc callback, object owner = null);
[Obsolete("ACsMod is deprecated. Use ILuaEventService.Add() instead.")] [Obsolete("ACsMod is deprecated. Use ILuaEventService.Add() instead.")]
void Add(string eventName, LuaCsFunc callback, object owner = null); 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. // Does anyone use this? TODO: Analyze old Lua mods for usage scenarios.
//bool Exists(string eventName, string identifier); //bool Exists(string eventName, string identifier);
object Call(string eventName, params object[] args); object Call(string eventName, params object[] args);
@@ -0,0 +1,305 @@
using Barotrauma;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Xml.Linq;
namespace Barotrauma;
class LuaCsConfig
{
private enum ValueType
{
None,
Text,
Integer,
Decimal,
Boolean,
Collection,
Object,
Enum
}
private static Type[] LoadDocTypes(XElement typesElem)
{
var result = new List<Type>();
var loadedTypes = AssemblyLoadContext.All
.Where(alc => alc != AssemblyLoadContext.Default)
.SelectMany(alc => alc.Assemblies)
.SelectMany(asm => asm.GetTypes())
.ToImmutableArray();
foreach (var elem in typesElem.Elements())
{
var typesFound = loadedTypes.Where(t => t.FullName?.EndsWith(elem.Value) ?? false).ToImmutableList();
if (!typesFound.Any())
{
ModUtils.Logging.PrintError(
$"{nameof(LuaCsConfig)}::{nameof(LoadDocTypes)}() | Unable to find a matching type for {elem.Value}");
continue;
}
result.AddRange(typesFound);
}
return result.ToArray();
}
private static IEnumerable<XElement> SaveDocTypes(IEnumerable<Type> types)
{
return types.Select(t => new XElement("Type", t.ToString()));
}
private static Type GetTypeAttr(Type[] types, XElement elem)
{
var idx = elem.GetAttributeInt("Type", -1);
if (idx < 0 || idx >= types.Length) throw new Exception($"Type index '{idx}' is outside of saved types bounds");
return types[idx];
}
private static ValueType GetValueType(XElement elem)
{
Enum.TryParse(typeof(ValueType), elem.Attribute("Value")?.Value, out object result);
if (result != null) return (ValueType)result;
else return ValueType.None;
}
private static object ParseValue(Type[] types, XElement elem)
{
var type = GetValueType(elem);
if (elem.IsEmpty) return null;
if (type == ValueType.Enum)
{
var tType = GetTypeAttr(types, elem);
if (tType == null || !tType.IsSubclassOf(typeof(Enum))) return null;
if (Enum.TryParse(tType, elem.Value, out object result)) return result;
else return null;
}
if (type == ValueType.Collection)
{
var tType = GetTypeAttr(types, elem);
var tInt = tType.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
var gArg = tInt.GetGenericArguments()[0];
if (tType == null || !tType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))) return null;
object result = null;
if (result == null)
{
var ctor = tType.GetConstructors(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(c =>
{
var param = c.GetParameters();
return param.Count() == 1 && param.Any(p => p.ParameterType.IsGenericType && p.ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>));
});
if (ctor != null)
{
var elements = elem.Elements().Select(x => ParseValue(types, x));
var castElems = typeof(Enumerable).GetMethod("Cast").MakeGenericMethod(gArg).Invoke(elements, new object[] { elements });
result = ctor.Invoke(new object[] { castElems });
}
}
if (result == null)
{
var ctor = tType.GetConstructors(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(c => c.GetParameters().Count() == 0);
var addMethod = tType.GetMethods(BindingFlags.Instance | BindingFlags.Public).FirstOrDefault(m =>
{
if (m.Name != "Add") return false;
var param = m.GetParameters();
return param.Count() == 1 && param[0].ParameterType == gArg;
});
if (ctor != null && addMethod != null)
{
var elements = elem.Elements().Select(x => ParseValue(types, x));
result = ctor.Invoke(null);
foreach (var el in elements) addMethod.Invoke(result, new object[] { el });
}
}
if (result == null)
{
var ctor = tType.GetConstructors(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault();
var setMethod = tType.GetMethods(BindingFlags.Instance | BindingFlags.Public).FirstOrDefault(m =>
{
if (m.Name != "Set") return false;
var param = m.GetParameters();
return param.Count() == 2 && param[0].ParameterType == typeof(int) && param[1].ParameterType == gArg;
});
if (ctor != null || setMethod != null)
{
var elements = elem.Elements().Select(x => ParseValue(types, x));
result = ctor.Invoke(new object[] { elements.Count() });
int i = 0;
foreach (var el in elements)
{
setMethod.Invoke(result, new object[] { i, el });
i++;
}
}
}
return result;
}
else if (type == ValueType.Text) return elem.Value;
else if (type == ValueType.Integer)
{
int.TryParse(elem.Value, out var num);
return num;
}
else if (type == ValueType.Decimal)
{
float.TryParse(elem.Value, out var num);
return num;
}
else if (type == ValueType.Boolean)
{
bool.TryParse(elem.Value, out var boolean);
return boolean;
}
else if (type == ValueType.Object)
{
var tType = GetTypeAttr(types, elem);
if (tType == null) return null;
IEnumerable<FieldInfo> fields = tType.GetFields(BindingFlags.Instance | BindingFlags.Public)
.Concat(tType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic));
IEnumerable<PropertyInfo> properties = tType.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.GetSetMethod() != null)
.Concat(tType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic).Where(p => p.GetSetMethod() != null));
object result = null;
var ctor = tType.GetConstructors(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(c => c.GetParameters().Count() == 0);
if (ctor == null)
{
if (!tType.IsValueType) return null;
result = Activator.CreateInstance(tType);
}
else result = ctor.Invoke(null);
foreach (var el in elem.Elements())
{
var value = ParseValue(types, el);
var field = fields.FirstOrDefault(f => f.Name == el.Name.LocalName);
if (field != null) field.SetValue(result, value);
var property = properties.FirstOrDefault(p => p.Name == el.Name.LocalName);
if (property != null) property.SetValue(result, value);
}
return result;
}
else return elem.Value;
}
private static void AddTypeAttr(List<Type> types, Type type, XElement elem)
{
if (!types.Contains(type)) types.Add(type);
elem.SetAttributeValue("Type", types.IndexOf(type));
}
private static XElement ParseObject(List<Type> types, string name, object value)
{
XElement result = new XElement(name);
if (value != null)
{
var tType = value.GetType();
if (tType.IsEnum)
{
result.SetAttributeValue("Value", ValueType.Enum);
AddTypeAttr(types, tType, result);
result.Value = Enum.GetName(tType, value) ?? "";
}
else if (value is string str)
{
result.SetAttributeValue("Value", ValueType.Text);
result.Value = str;
}
else if (value is int integer)
{
result.SetAttributeValue("Value", ValueType.Integer);
result.Value = integer.ToString();
}
else if (value is float || value is double)
{
result.SetAttributeValue("Value", ValueType.Decimal);
result.Value = value.ToString();
}
else if (value is bool boolean)
{
result.SetAttributeValue("Value", ValueType.Boolean);
result.Value = boolean.ToString();
}
else if (tType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
{
result.SetAttributeValue("Value", ValueType.Collection);
AddTypeAttr(types, tType, result);
var enumerator = (IEnumerator)tType.GetMethod("GetEnumerator").Invoke(value, null);
while (enumerator.MoveNext())
{
var elVal = ParseObject(types, "Item", enumerator.Current);
result.Add(elVal);
}
}
else if (tType.IsClass || tType.IsValueType)
{
result.SetAttributeValue("Value", ValueType.Object);
AddTypeAttr(types, tType, result);
IEnumerable<FieldInfo> fields = tType.GetFields(BindingFlags.Instance | BindingFlags.Public)
.Concat(tType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic));
IEnumerable<PropertyInfo> properties = tType.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.GetSetMethod() != null)
.Concat(tType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic).Where(p => p.GetSetMethod() != null));
foreach (var field in fields) result.Add(ParseObject(types, field.Name, field.GetValue(value)));
foreach (var property in properties) result.Add(ParseObject(types, property.Name, property.GetValue(value)));
}
else
{
result.SetAttributeValue("Value", ValueType.None);
result.Value = value.ToString();
}
}
return result;
}
public static T Load<T>(FileStream file)
{
var doc = XDocument.Load(file);
var rootElems = doc.Root.Elements().ToArray();
var types = rootElems[0];
var elem = rootElems[1];
var dict = ParseValue(LoadDocTypes(types), elem);
if (dict.GetType() == typeof(T)) return (T)dict;
else throw new Exception($"Loaded configuration is not of the type '{typeof(T).Name}'");
}
public static void Save(FileStream file, object obj)
{
var types = new List<Type>();
var elem = ParseObject(types, "Root", obj);
var root = new XElement("Configuration", new XElement("Types", SaveDocTypes(types)), elem);
var doc = new XDocument(root);
doc.Save(file);
}
public static T Load<T>(string path)
{
using (var file = LuaCsFile.OpenRead(path)) return Load<T>(file);
}
public static void Save(string path, object obj)
{
using (var file = LuaCsFile.OpenWrite(path)) Save(file, obj);
}
}
@@ -56,6 +56,7 @@ public record ConfigResourceInfo : BaseResourceInfo, IConfigResourceInfo {}
public record LuaScriptsResourceInfo : BaseResourceInfo, ILuaScriptResourceInfo public record LuaScriptsResourceInfo : BaseResourceInfo, ILuaScriptResourceInfo
{ {
public bool IsAutorun { get; init; } public bool IsAutorun { get; init; }
public bool RunUnrestricted { get; init; }
} }
#endregion #endregion
@@ -21,6 +21,12 @@ public interface ILuaScriptResourceInfo : IBaseResourceInfo
/// </summary> /// </summary>
[XmlAttribute("IsAutorun")] [XmlAttribute("IsAutorun")]
public bool IsAutorun { get; } 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 public interface IAssemblyResourceInfo : IBaseResourceInfo
@@ -5,6 +5,7 @@ using System.Xml.Linq;
using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Data;
using Barotrauma.LuaCs; using Barotrauma.LuaCs;
using Barotrauma.Networking; using Barotrauma.Networking;
using OneOf;
namespace Barotrauma.LuaCs.Data; namespace Barotrauma.LuaCs.Data;
@@ -34,8 +35,8 @@ public partial interface ISettingBase : IDataInfo, IEquatable<ISettingBase>, IDi
Type GetValueType(); Type GetValueType();
string GetStringValue(); string GetStringValue();
string GetDefaultStringValue(); string GetDefaultStringValue();
bool TrySetValue(OneOf.OneOf<string, XElement> value); bool TrySetSerializedValue(OneOf<string, XElement> value);
event Action<ISettingBase> OnValueChanged; event Action<ISettingBase> OnValueChanged;
OneOf.OneOf<string, XElement> GetSerializableValue(); OneOf.OneOf<string, XElement> GetSerializableValue();
} }
@@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Concurrent;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Data;
using Microsoft.Toolkit.Diagnostics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using OneOf; using OneOf;
@@ -10,6 +12,7 @@ public abstract class SettingBase : ISettingBase
{ {
protected SettingBase(IConfigInfo configInfo) protected SettingBase(IConfigInfo configInfo)
{ {
Guard.IsNotNull(configInfo, nameof(configInfo));
ConfigInfo = configInfo; ConfigInfo = configInfo;
} }
@@ -57,8 +60,8 @@ public abstract class SettingBase : ISettingBase
public abstract Type GetValueType(); public abstract Type GetValueType();
public abstract string GetStringValue(); public abstract string GetStringValue();
public abstract string GetDefaultStringValue(); 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 event Action<ISettingBase> OnValueChanged;
public abstract OneOf<string, XElement> GetSerializableValue(); public abstract OneOf<string, XElement> GetSerializableValue();
#if CLIENT #if CLIENT
@@ -147,12 +147,10 @@ public partial class SettingEntry<T> : SettingBase, ISettingBase<T>, INetworkSyn
} }
public override Type GetValueType() => typeof(T); public override Type GetValueType() => typeof(T);
public override string GetStringValue() => Value?.ToString() ?? string.Empty; public override string GetStringValue() => Value?.ToString() ?? string.Empty;
public override string GetDefaultStringValue() => DefaultValue?.ToString() ?? string.Empty;
public override string GetDefaultStringValue() => DefaultValue.ToString();
public override bool TrySetValue(OneOf<string, XElement> value) public override bool TrySetSerializedValue(OneOf<string, XElement> value)
{ {
bool isFailed = false; bool isFailed = false;
var typeConvertedValue = value.Match<T>( var typeConvertedValue = value.Match<T>(
@@ -181,7 +179,6 @@ public partial class SettingEntry<T> : SettingBase, ISettingBase<T>, INetworkSyn
return default(T); return default(T);
} }
}); });
return !isFailed && TrySetValue(typeConvertedValue); return !isFailed && TrySetValue(typeConvertedValue);
} }
@@ -105,7 +105,13 @@ internal interface IEventModifyChatMessage : IEvent<IEventModifyChatMessage>
/// <returns>Whether to <b><i>reject</i></b> the message.</returns> /// <returns>Whether to <b><i>reject</i></b> the message.</returns>
public bool? OnModifyMessagePredicate(ChatMessage message, WifiComponent senderRadio) 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 #if SERVER
public interface IEventClientRawNetMessageReceived : IEvent<IEventClientRawNetMessageReceived> 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) static IEventClientRawNetMessageReceived IEvent<IEventClientRawNetMessageReceived>.GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc)
=> new LuaWrapper(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); 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> 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) static IEventServerRawNetMessageReceived IEvent<IEventServerRawNetMessageReceived>.GetLuaRunner(IDictionary<string, LuaCsFunc> luaFunc)
=> new LuaWrapper(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;
} }
} }
} }
@@ -25,11 +25,16 @@ namespace Barotrauma
partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventEnabledPackageListChanged, partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventEnabledPackageListChanged,
IEventReloadAllPackages IEventReloadAllPackages
{ {
public const string PackageId = "LuaCsForBarotrauma"; public const string PackageName = "LuaCsForBarotrauma";
private static LuaCsSetup _luaCsSetup; private static LuaCsSetup _luaCsSetup;
public static LuaCsSetup Instance => _luaCsSetup ??= new 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() private LuaCsSetup()
{ {
if (_luaCsSetup != null) if (_luaCsSetup != null)
@@ -37,6 +42,8 @@ namespace Barotrauma
throw new Exception("Tried to create another LuaCsSetup instance"); throw new Exception("Tried to create another LuaCsSetup instance");
} }
DebugConsoleCommandVanillaIndex = DebugConsole.Commands.Count;
// == startup // == startup
_servicesProvider = SetupServicesProvider(); _servicesProvider = SetupServicesProvider();
_runStateMachine = SetupStateMachine(); _runStateMachine = SetupStateMachine();
@@ -83,7 +90,26 @@ namespace Barotrauma
// hotpath performance ref cache // hotpath performance ref cache
private LuaGame _game; private LuaGame _game;
public LuaGame Game => _game ??= _servicesProvider.GetService<LuaGame>(); 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> /// <summary>
/// Whether C# plugin code is enabled. /// Whether C# plugin code is enabled.
@@ -91,15 +117,17 @@ namespace Barotrauma
public bool IsCsEnabled public bool IsCsEnabled
{ {
#if CLIENT #if CLIENT
get => _csRunPolicy?.Value == "Enabled" || _isCsEnabledForSession; get => _csRunPolicy?.Value == "Enabled" || IsCsEnabledForSession;
#elif SERVER #elif SERVER
// cs settings cannot be changed on the server after launch // cs settings cannot be changed on the server after launch
get => _csRunPolicy?.Value is "Enabled" or "Prompt"; get => _csRunPolicy?.Value is "Enabled" or "Prompt";
#endif #endif
} }
private ISettingList<string> _csRunPolicy; private ISettingList<string> _csRunPolicy;
private bool ShouldPromptForCs => _csRunPolicy?.Value is "Prompt";
public string CsRunPolicyValue => _csRunPolicy?.Value ?? "Prompt";
/// <summary> /// <summary>
/// Whether usernames are anonymized or show in logs. /// Whether usernames are anonymized or show in logs.
/// </summary> /// </summary>
@@ -118,9 +146,9 @@ namespace Barotrauma
public static ContentPackage GetLuaCsPackage() public static ContentPackage GetLuaCsPackage()
{ {
return ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(PackageId), null) return ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(PackageName), null)
?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(PackageId)) ?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(PackageName))
?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(PackageId)); ?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(PackageName));
} }
void LoadLuaCsConfig() void LoadLuaCsConfig()
@@ -139,6 +167,17 @@ namespace Barotrauma
ConfigService.TryGetConfig<ISettingBase<bool>>(luaCsPackage, "UseCaching", out var val5) ConfigService.TryGetConfig<ISettingBase<bool>>(luaCsPackage, "UseCaching", out var val5)
? val5 ? val5
: null; : 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() private IServicesProvider SetupServicesProvider()
@@ -223,22 +262,13 @@ namespace Barotrauma
public void OnReloadAllPackages() public void OnReloadAllPackages()
{ {
if (CurrentRunState <= RunState.Unloaded)
{
return;
}
CoroutineManager.Invoke(() => CoroutineManager.Invoke(() =>
{ {
#if CLIENT
bool prevCsEnabled = _isCsEnabledForSession;
#endif
var state = CurrentRunState;
SetRunState(RunState.Unloaded); SetRunState(RunState.Unloaded);
#if CLIENT CoroutineManager.Invoke(() =>
_isCsEnabledForSession = prevCsEnabled; {
#endif SetRunState(RunState.Running);
SetRunState(state); },0.25f);
}); });
} }
@@ -256,10 +286,11 @@ namespace Barotrauma
} }
this.Logger.LogResults(PackageManagementService.SyncLoadedPackagesList(GetLuaCsEnabledPackagesList(packages))); this.Logger.LogResults(PackageManagementService.SyncLoadedPackagesList(GetLuaCsEnabledPackagesList(packages)));
ConfigService.LoadSavedConfigsValues();
SetRunState(state); // restore SetRunState(state); // restore
} }
private void SetRunState(RunState targetRunState) public void SetRunState(RunState targetRunState)
{ {
if (CurrentRunState == targetRunState) if (CurrentRunState == targetRunState)
{ {
@@ -274,17 +305,13 @@ namespace Barotrauma
private ImmutableArray<ContentPackage> GetLuaCsEnabledPackagesList(ImmutableArray<ContentPackage> enabledRegular) private ImmutableArray<ContentPackage> GetLuaCsEnabledPackagesList(ImmutableArray<ContentPackage> enabledRegular)
{ {
if (!enabledRegular.Any( if (!enabledRegular.Any(p => p.Name.Equals(PackageName, StringComparison.InvariantCultureIgnoreCase)))
p => p.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase)
|| p.Name.Equals("Lua for Barotrauma", StringComparison.InvariantCultureIgnoreCase)))
{ {
var luaCs = ContentPackageManager.AllPackages.FirstOrDefault( var luaCs = ContentPackageManager.AllPackages.FirstOrDefault(p => p.Name.Equals(PackageName, StringComparison.InvariantCultureIgnoreCase));
p => p.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase)
|| p.Name.Equals("Lua For Barotrauma", StringComparison.InvariantCultureIgnoreCase));
if (luaCs is null) if (luaCs is null)
{ {
DebugConsole.ThrowError($"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 'LuaCsForBarotrauma' 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); createMessageBox: true);
return enabledRegular; return enabledRegular;
} }
@@ -383,6 +410,11 @@ namespace Barotrauma
EventService.PublishEvent<IEventServerConnected>(static p => p.OnServerConnected()); EventService.PublishEvent<IEventServerConnected>(static p => p.OnServerConnected());
} }
#endif #endif
#if SERVER
GameMain.Server.ServerSettings.LoadClientPermissions();
#endif
CurrentRunState = RunState.Running; CurrentRunState = RunState.Running;
} }
@@ -390,9 +422,6 @@ namespace Barotrauma
void RunStateRunning_OnExit(State<RunState> currentState) void RunStateRunning_OnExit(State<RunState> currentState)
{ {
EventService.Call("stop"); EventService.Call("stop");
#if CLIENT
_isCsEnabledForSession = false;
#endif
Logger.LogResults(PackageManagementService.StopRunningPackages()); Logger.LogResults(PackageManagementService.StopRunningPackages());
Logger.LogMessage("LuaCs running state exited"); Logger.LogMessage("LuaCs running state exited");
} }
@@ -315,24 +315,16 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
foreach (var resultDiagnostic in result.Diagnostics) 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( 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.ExceptionObject, this)
.WithMetadata(MetadataType.RootObject, syntaxTrees)); .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; return res;
} }
@@ -239,7 +239,7 @@ public sealed partial class ConfigService : IConfigService
return; return;
} }
if (setting.TrySetValue(valueString)) if (setting.TrySetSerializedValue(valueString))
{ {
_logger.LogMessage($"Set config {internalName} value to {valueString}", Color.Green); _logger.LogMessage($"Set config {internalName} value to {valueString}", Color.Green);
if (SaveConfigValue(setting) is { IsFailed: true } res) 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)) 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.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() public FluentResults.Result LoadSavedConfigsValues()
@@ -544,7 +544,7 @@ public sealed partial class ConfigService : IConfigService
continue; continue;
} }
if (!instance.TrySetValue(profileValue.Element)) if (!instance.TrySetSerializedValue(profileValue.Element))
{ {
result.WithError(new Error($"{nameof(ApplyConfigProfile)}: Failed to set value for [{profileValue.SettingName}].")); result.WithError(new Error($"{nameof(ApplyConfigProfile)}: Failed to set value for [{profileValue.SettingName}]."));
} }
@@ -24,14 +24,14 @@ public partial class EventService : IEventService
public TypeStringKey(Type type) public TypeStringKey(Type type)
{ {
Type = type ?? throw new ArgumentNullException(nameof(type)); Type = type ?? throw new ArgumentNullException(nameof(type));
TypeName = type.Name; TypeName = type.Name.ToLowerInvariant();
HashCode = TypeName.GetHashCode(); HashCode = TypeName.GetHashCode();
} }
public TypeStringKey(string typeName) public TypeStringKey(string typeName)
{ {
Type = null; Type = null;
TypeName = typeName ?? throw new ArgumentNullException(nameof(typeName)); TypeName = typeName?.ToLowerInvariant() ?? throw new ArgumentNullException(nameof(typeName));
HashCode = TypeName.GetHashCode(); HashCode = TypeName.GetHashCode();
} }
@@ -56,7 +56,7 @@ public partial class EventService : IEventService
private readonly AsyncReaderWriterLock _operationsLock = new(); private readonly AsyncReaderWriterLock _operationsLock = new();
private readonly ConcurrentDictionary<TypeStringKey, ConcurrentDictionary<OneOf<IEvent, string>, IEvent>> _subscribers = 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<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(); private readonly ConcurrentDictionary<IEventService, IEventService> _subscribedEventDispatchers = new();
#region LifeCycle #region LifeCycle
@@ -118,7 +118,7 @@ public partial class EventService : IEventService
} }
else else
{ {
var eventSubs = _luaLegacyEventsSubscribers.GetOrAdd(eventName, key => new ConcurrentDictionary<string, LuaCsFunc>()); var eventSubs = _luaLegacyEventsSubscribers.GetOrAdd(eventName, key => new ConcurrentDictionary<TypeStringKey, LuaCsFunc>());
eventSubs[identifier] = callback; eventSubs[identifier] = callback;
} }
} }
@@ -200,6 +200,29 @@ public partial class EventService : IEventService
} }
public void Remove(string eventName, string identifier) 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(eventName, nameof(eventName));
Guard.IsNotNullOrWhiteSpace(identifier, nameof(identifier)); Guard.IsNotNullOrWhiteSpace(identifier, nameof(identifier));
@@ -2,6 +2,7 @@
using Barotrauma.LuaCs; using Barotrauma.LuaCs;
using Barotrauma.LuaCs.Events; using Barotrauma.LuaCs.Events;
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Steam;
using HarmonyLib; using HarmonyLib;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
@@ -27,16 +28,12 @@ internal class HarmonyEventPatchesService : ISystem
private static ILoggerService _loggerService; private static ILoggerService _loggerService;
private readonly Harmony Harmony; private readonly Harmony Harmony;
private static int debugConsoleCommandVanillaIndex;
public HarmonyEventPatchesService(IEventService eventService, ILoggerService loggerService) public HarmonyEventPatchesService(IEventService eventService, ILoggerService loggerService)
{ {
_eventService = eventService; _eventService = eventService;
_loggerService = loggerService; _loggerService = loggerService;
Harmony = new Harmony("LuaCsForBarotrauma.Events"); Harmony = new Harmony("LuaCsForBarotrauma.Events");
Patch(); Patch();
debugConsoleCommandVanillaIndex = DebugConsole.Commands.Count;
} }
private void Patch() private void Patch()
@@ -56,7 +53,7 @@ internal class HarmonyEventPatchesService : ISystem
[HarmonyPatch(typeof(CoroutineManager), nameof(CoroutineManager.Update)), HarmonyPostfix] [HarmonyPatch(typeof(CoroutineManager), nameof(CoroutineManager.Update)), HarmonyPostfix]
public static void CoroutineManager_Update_Post() public static void CoroutineManager_Update_Post()
{ {
_eventService.PublishEvent<IEventUpdate>(x => x.OnUpdate(Timing.TotalTime)); _eventService.PublishEvent<IEventUpdate>(x => x.OnUpdate(CoroutineManager.DeltaTime));
_loggerService.ProcessLogs(); _loggerService.ProcessLogs();
} }
@@ -95,6 +92,27 @@ internal class HarmonyEventPatchesService : ISystem
_eventService.PublishEvent<IEventScreenSelected>(x => x.OnScreenSelected(__instance)); _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] [HarmonyPatch(typeof(ContentPackageManager.PackageSource), nameof(ContentPackageManager.PackageSource.Refresh)), HarmonyPostfix]
public static void PackageSource_Refresh_Post() public static void PackageSource_Refresh_Post()
{ {
@@ -122,11 +140,20 @@ internal class HarmonyEventPatchesService : ISystem
#if CLIENT #if CLIENT
[HarmonyPatch(typeof(GameClient), "ReadDataMessage"), HarmonyPrefix] [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(); ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte();
_eventService.PublishEvent<IEventServerRawNetMessageReceived>(x => x.OnReceivedServerNetMessage(inc, header)); bool? skip = null;
inc.BitPosition -= 8; // rewind so the game can read the message _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] [HarmonyPatch(typeof(SubEditorScreen), nameof(SubEditorScreen.Select), new Type[] { }), HarmonyPostfix]
@@ -146,7 +173,7 @@ internal class HarmonyEventPatchesService : ISystem
{ {
DebugConsole.Command c = DebugConsole.FindCommand(command.Value); DebugConsole.Command c = DebugConsole.FindCommand(command.Value);
if (DebugConsole.Commands.IndexOf(c) >= debugConsoleCommandVanillaIndex) if (DebugConsole.Commands.IndexOf(c) >= LuaCsSetup.DebugConsoleCommandVanillaIndex)
{ {
__result = true; __result = true;
return false; return false;
@@ -158,11 +185,21 @@ internal class HarmonyEventPatchesService : ISystem
#elif SERVER #elif SERVER
[HarmonyPatch(typeof(GameServer), "ReadDataMessage"), HarmonyPrefix] [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(); 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] [HarmonyPatch(typeof(GameServer), "OnInitializationComplete"), HarmonyPostfix]
@@ -12,8 +12,6 @@ namespace Barotrauma.LuaCs;
public partial class LoggerService : ILoggerService public partial class LoggerService : ILoggerService
{ {
public bool HideUserNames = true;
private List<ILoggerSubscriber> logSubscribers = []; private List<ILoggerSubscriber> logSubscribers = [];
private ConcurrentQueue<PendingLog> logQueue = []; 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) 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"); message = message.Replace(Environment.UserName, "USERNAME");
} }
@@ -186,14 +184,13 @@ public partial class LoggerService : ILoggerService
else else
{ {
LogError($"FluentResults::IError: {error.Message}"); LogError($"FluentResults::IError: {error.Message}");
} /*if (error.Reasons != null)
if (error.Reasons != null)
{
foreach (var reason in error.Reasons)
{ {
LogError($" - {reason.Message}"); foreach (var reason in error.Reasons)
} {
LogError($" - {reason.Message}");
}
}*/
} }
} }
} }
@@ -20,9 +20,9 @@ public sealed class LuaCsInfoProvider : ILuaCsInfoProvider
{ {
get get
{ {
return ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageId), null) return ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageName), null)
?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageId)) ?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageName))
?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageId)); ?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageName));
} }
} }
} }
@@ -1,27 +1,19 @@
#nullable enable #nullable enable
using Barotrauma.LuaCs;
using Barotrauma.LuaCs.Compatibility; using Barotrauma.LuaCs.Compatibility;
using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Data;
using Barotrauma.LuaCs.Events; using Barotrauma.LuaCs.Events;
using Barotrauma.Networking; using Barotrauma.Networking;
using FluentResults; using FluentResults;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Toolkit.Diagnostics; using Microsoft.Toolkit.Diagnostics;
using MonoMod.RuntimeDetour;
using MoonSharp.Interpreter; using MoonSharp.Interpreter;
using MoonSharp.Interpreter.Interop; using MoonSharp.Interpreter.Interop;
using MoonSharp.Interpreter.Loaders;
using RestSharp.Validation;
using System; using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -285,6 +277,8 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService,
_eventService.RegisterLuaEventAlias<IEventGiveCharacterJobItems>("character.giveJobItems", nameof(IEventGiveCharacterJobItems.OnGiveCharacterJobItems)); _eventService.RegisterLuaEventAlias<IEventGiveCharacterJobItems>("character.giveJobItems", nameof(IEventGiveCharacterJobItems.OnGiveCharacterJobItems));
_eventService.RegisterLuaEventAlias<IEventHumanCPRSuccess>("character.CPRSuccess", nameof(IEventHumanCPRSuccess.OnCharacterCPRSuccess)); _eventService.RegisterLuaEventAlias<IEventHumanCPRSuccess>("character.CPRSuccess", nameof(IEventHumanCPRSuccess.OnCharacterCPRSuccess));
_eventService.RegisterLuaEventAlias<IEventHumanCPRFailed>("character.CPRFailed", nameof(IEventHumanCPRFailed.OnCharacterCPRFailed)); _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<IEventCharacterApplyDamage>("character.applyDamage", nameof(IEventCharacterApplyDamage.OnCharacterApplyDamage));
_eventService.RegisterLuaEventAlias<IEventCharacterApplyAffliction>("character.applyAffliction", nameof(IEventCharacterApplyAffliction.OnCharacterApplyAffliction)); _eventService.RegisterLuaEventAlias<IEventCharacterApplyAffliction>("character.applyAffliction", nameof(IEventCharacterApplyAffliction.OnCharacterApplyAffliction));
@@ -368,8 +362,10 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService,
UserData.RegisterType(typeof(IUserDataDescriptor)); UserData.RegisterType(typeof(IUserDataDescriptor));
UserData.RegisterType(typeof(INetworkingService)); UserData.RegisterType(typeof(INetworkingService));
UserData.RegisterType(typeof(ILuaConfigService)); UserData.RegisterType(typeof(ILuaConfigService));
UserData.RegisterType(typeof(ILoggerService));
//UserData.RegisterType(typeof(ISettingBase)); UserData.RegisterType(typeof(ISettingBase));
UserData.RegisterType(typeof(IDataInfo));
Type[] settingBaseTypes = [ Type[] settingBaseTypes = [
typeof(ISettingBase<bool>), typeof(ISettingBase<bool>),
@@ -451,6 +447,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService,
_script.Globals["Networking"] = _networkingService; _script.Globals["Networking"] = _networkingService;
_script.Globals["trygetpackage"] = (string name, out ContentPackage package) => _script.Globals["trygetpackage"] = (string name, out ContentPackage package) =>
_packageManagementService.Value.TryGetLoadedPackageByName(name, out package); _packageManagementService.Value.TryGetLoadedPackageByName(name, out package);
_script.Globals["Logger"] = _loggerService;
//_script.Globals["Steam"] = Steam; //_script.Globals["Steam"] = Steam;
if (enableSandbox) if (enableSandbox)
@@ -517,6 +514,61 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService,
Table package = (Table)_script.Globals["package"]; Table package = (Table)_script.Globals["package"];
package.Set("path", DynValue.FromObject(_script, packages)); 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 (ILuaScriptResourceInfo resource in executionOrder.Where(l => l.IsAutorun))
{ {
foreach (ContentPath filePath in resource.FilePaths) foreach (ContentPath filePath in resource.FilePaths)
@@ -43,11 +43,32 @@ internal class MainMenuPatch : ISystem, IEventScreenSelected
{ {
if (mainMenuUIAdded) { return; } 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 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; mainMenuUIAdded = true;
} }
#endif #endif
@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Data;
@@ -60,8 +61,9 @@ public sealed partial class ModConfigFileParserService :
if (CheckThrowNullRefs(src, "Assembly") is { IsFailed: true } fail) if (CheckThrowNullRefs(src, "Assembly") is { IsFailed: true } fail)
return fail; return fail;
var isScript = src.Element.GetAttributeBool("IsScript", false);
var runtimeEnv = GetRuntimeEnvironment(src.Element); 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) if (fileResults.IsFailed)
return FluentResults.Result.Fail(fileResults.Errors); return FluentResults.Result.Fail(fileResults.Errors);
@@ -78,11 +80,31 @@ public sealed partial class ModConfigFileParserService :
RequiredPackages = src.Required, RequiredPackages = src.Required,
IncompatiblePackages = src.Incompatible, IncompatiblePackages = src.Incompatible,
// Type Specific // Type Specific
FriendlyName = src.Element.GetAttributeString("FriendlyName", string.Empty), FriendlyName = src.Element.GetAttributeString("FriendlyName", GetFallbackCompliantAssemblyName(src.Owner)),
IsScript = src.Element.GetAttributeBool("IsScript", false), IsScript = isScript,
UseInternalAccessName = src.Element.GetAttributeBool("UseInternalAccessName", false), UseInternalAccessName = src.Element.GetAttributeBool("UseInternalAccessName", false),
IsReferenceModeOnly = src.Element.GetAttributeBool("IsReferenceModeOnly", 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) async Task<ImmutableArray<Result<IAssemblyResourceInfo>>> IParserServiceAsync<ResourceParserInfo, IAssemblyResourceInfo>.TryParseResourcesAsync(IEnumerable<ResourceParserInfo> sources)
@@ -152,7 +174,8 @@ public sealed partial class ModConfigFileParserService :
RequiredPackages = src.Required, RequiredPackages = src.Required,
IncompatiblePackages = src.Incompatible, IncompatiblePackages = src.Incompatible,
// Type Specific // 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>>(); 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 }) 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 }) 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) if (files.IsFailed)
{ {
res.WithError($"{srcOwner.Name}: Failed to load files from {folderPath}!"); res.WithError($"{srcOwner.Name}: Failed to load files from {folderPath}!");
@@ -335,10 +335,10 @@ public sealed class ModConfigService : IModConfigService
.Select(fp => ContentPath.FromRaw(srcPackage, .Select(fp => ContentPath.FromRaw(srcPackage,
$"%ModDir%/{Path.GetRelativePath(srcPackage.Dir, fp)}".CleanUpPathCrossPlatform())) $"%ModDir%/{Path.GetRelativePath(srcPackage.Dir, fp)}".CleanUpPathCrossPlatform()))
.Concat(sharedFiles).ToImmutableArray(), .Concat(sharedFiles).ToImmutableArray(),
FriendlyName = IAssemblyLoaderService.InternalsAwareAssemblyName, FriendlyName = IAssemblyLoaderService.InternalsAwareAssemblyName, // give the best chance of success (InternalsAware + Publicizer)
IncompatiblePackages = ImmutableArray<Identifier>.Empty, IncompatiblePackages = ImmutableArray<Identifier>.Empty,
RequiredPackages = ImmutableArray<Identifier>.Empty, RequiredPackages = ImmutableArray<Identifier>.Empty,
UseInternalAccessName = true, UseInternalAccessName = false, //compile as public and then fallback to internals
IsScript = true, IsScript = true,
IsReferenceModeOnly = false IsReferenceModeOnly = false
}); });
@@ -357,7 +357,7 @@ public sealed class ModConfigService : IModConfigService
FriendlyName = IAssemblyLoaderService.InternalsAwareAssemblyName, FriendlyName = IAssemblyLoaderService.InternalsAwareAssemblyName,
IncompatiblePackages = ImmutableArray<Identifier>.Empty, IncompatiblePackages = ImmutableArray<Identifier>.Empty,
RequiredPackages = ImmutableArray<Identifier>.Empty, RequiredPackages = ImmutableArray<Identifier>.Empty,
UseInternalAccessName = true, UseInternalAccessName = false,
IsScript = true, IsScript = true,
IsReferenceModeOnly = false IsReferenceModeOnly = false
}); });
@@ -405,6 +405,7 @@ public sealed class ModConfigService : IModConfigService
IncompatiblePackages = ImmutableArray<Identifier>.Empty, IncompatiblePackages = ImmutableArray<Identifier>.Empty,
RequiredPackages = ImmutableArray<Identifier>.Empty, RequiredPackages = ImmutableArray<Identifier>.Empty,
IsAutorun = true, IsAutorun = true,
RunUnrestricted = false
}); });
builder.Add(new LuaScriptsResourceInfo() builder.Add(new LuaScriptsResourceInfo()
@@ -418,6 +419,7 @@ public sealed class ModConfigService : IModConfigService
IncompatiblePackages = ImmutableArray<Identifier>.Empty, IncompatiblePackages = ImmutableArray<Identifier>.Empty,
RequiredPackages = ImmutableArray<Identifier>.Empty, RequiredPackages = ImmutableArray<Identifier>.Empty,
IsAutorun = false, IsAutorun = false,
RunUnrestricted = false
}); });
} }
@@ -345,6 +345,8 @@ public sealed class PackageManagementService : IPackageManagementService
//lua scripts //lua scripts
var luaScripts = SelectCompatible(loadingOrderedPackages var luaScripts = SelectCompatible(loadingOrderedPackages
.Where(pkg => executeCsAssemblies
|| !pkg.Value.LuaScripts.Any(scr => scr.RunUnrestricted))
.SelectMany(pkg => pkg.Value.LuaScripts) .SelectMany(pkg => pkg.Value.LuaScripts)
.ToImmutableArray(), toLoadPackagesIndents, loadOrderByPackage); .ToImmutableArray(), toLoadPackagesIndents, loadOrderByPackage);
@@ -357,11 +359,7 @@ public sealed class PackageManagementService : IPackageManagementService
{ {
_runningPackages[package.Key] = package.Value; _runningPackages[package.Key] = package.Value;
} }
if (result.IsFailed)
{
_logger.LogResults(result);
}
return result; return result;
} }
@@ -517,14 +515,44 @@ public sealed class PackageManagementService : IPackageManagementService
return !_runningPackages.IsEmpty; return !_runningPackages.IsEmpty;
} }
public ImmutableArray<ContentPackage> GetLoadedAssemblyPackages() public ImmutableArray<ContentPackage> GetLoadedUnrestrictedPackages()
{ {
using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
IService.CheckDisposed(this); IService.CheckDisposed(this);
if (_loadedPackages.IsEmpty) if (_loadedPackages.IsEmpty)
return ImmutableArray<ContentPackage>.Empty; return ImmutableArray<ContentPackage>.Empty;
return [.._loadedPackages.Values return [.._loadedPackages.Values
.Where(cfg => !cfg.Assemblies.IsDefaultOrEmpty) .Where(cfg => !cfg.Assemblies.IsDefaultOrEmpty || cfg.LuaScripts.Any(scr => scr.RunUnrestricted))
.Select(cfg => cfg.Package)]; .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);
}
} }
@@ -87,6 +87,9 @@ public class PluginManagementService : IAssemblyManagementService
ScriptParseOptions); ScriptParseOptions);
private ImmutableArray<MetadataReference> _baseMetadataReferences = ImmutableArray<MetadataReference>.Empty; private ImmutableArray<MetadataReference> _baseMetadataReferences = ImmutableArray<MetadataReference>.Empty;
private ImmutableArray<MetadataReference> _baseMetadataReferencesNonPublicized = ImmutableArray<MetadataReference>.Empty;
private IEnumerable<MetadataReference> BaseMetadataReferences private IEnumerable<MetadataReference> BaseMetadataReferences
{ {
get 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 #endregion
#region Disposal #region Disposal
@@ -129,6 +151,7 @@ public class PluginManagementService : IAssemblyManagementService
_logger = null; _logger = null;
_configService = null; _configService = null;
_luaScriptManagementService = null; _luaScriptManagementService = null;
_luaCsInfoProvider = null;
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
@@ -199,6 +222,7 @@ public class PluginManagementService : IAssemblyManagementService
private IEventService _pluginEventService; private IEventService _pluginEventService;
private Lazy<ILuaPatcher> _pluginLuaPatcherService; private Lazy<ILuaPatcher> _pluginLuaPatcherService;
private Func<IConsoleCommandsService> _consoleCommandServiceFactory; private Func<IConsoleCommandsService> _consoleCommandServiceFactory;
private ILuaCsInfoProvider _luaCsInfoProvider;
private readonly ConcurrentDictionary<ContentPackage, IAssemblyLoaderService> _assemblyLoaders = new(); private readonly ConcurrentDictionary<ContentPackage, IAssemblyLoaderService> _assemblyLoaders = new();
private readonly ConcurrentDictionary<Type, ContentPackage> _pluginPackageLookup = new(); private readonly ConcurrentDictionary<Type, ContentPackage> _pluginPackageLookup = new();
private readonly ConcurrentDictionary<ContentPackage, ImmutableArray<IAssemblyPlugin>> _pluginInstances = new(); private readonly ConcurrentDictionary<ContentPackage, ImmutableArray<IAssemblyPlugin>> _pluginInstances = new();
@@ -215,7 +239,8 @@ public class PluginManagementService : IAssemblyManagementService
Lazy<ILuaScriptManagementService> luaScriptManagementService, Lazy<ILuaScriptManagementService> luaScriptManagementService,
Lazy<IConfigService> configService, Lazy<IConfigService> configService,
Lazy<ILuaPatcher> pluginLuaPatcherService, Lazy<ILuaPatcher> pluginLuaPatcherService,
Func<IConsoleCommandsService> consoleCommandServiceFactory) Func<IConsoleCommandsService> consoleCommandServiceFactory,
ILuaCsInfoProvider luaCsInfoProvider)
{ {
_assemblyLoaderFactory = assemblyLoaderFactory; _assemblyLoaderFactory = assemblyLoaderFactory;
_storageService = storageService; _storageService = storageService;
@@ -225,6 +250,7 @@ public class PluginManagementService : IAssemblyManagementService
_configService = configService; _configService = configService;
_pluginLuaPatcherService = pluginLuaPatcherService; _pluginLuaPatcherService = pluginLuaPatcherService;
_consoleCommandServiceFactory = consoleCommandServiceFactory; _consoleCommandServiceFactory = consoleCommandServiceFactory;
_luaCsInfoProvider = luaCsInfoProvider;
} }
private ServiceContainer CreatePluginServiceContainer() private ServiceContainer CreatePluginServiceContainer()
@@ -485,6 +511,12 @@ public class PluginManagementService : IAssemblyManagementService
} }
using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
IService.CheckDisposed(this); IService.CheckDisposed(this);
_storageService.UseCaching = _luaCsInfoProvider.UseCaching;
if (!_luaCsInfoProvider.UseCaching)
{
_storageService.PurgeCache();
}
var orderedContentPacks = resources.GroupBy(res => res.OwnerPackage) var orderedContentPacks = resources.GroupBy(res => res.OwnerPackage)
.OrderBy(res => resources.FindIndex(r2 => r2.OwnerPackage == res.Key)) .OrderBy(res => resources.FindIndex(r2 => r2.OwnerPackage == res.Key))
@@ -554,7 +586,8 @@ public class PluginManagementService : IAssemblyManagementService
return; return;
} }
var metadataReferences = GetMetadataReferences(); var metadataReferences = GetMetadataReferences(false).ToImmutableArray();
var metadataReferencesNonPublicized = GetMetadataReferences(true).ToImmutableArray();
var assemblyLoader = _assemblyLoaders.GetOrAdd(contentPackRes.Key, (cp) => _assemblyLoaderFactory.CreateInstance( var assemblyLoader = _assemblyLoaders.GetOrAdd(contentPackRes.Key, (cp) => _assemblyLoaderFactory.CreateInstance(
new IAssemblyLoaderService.LoaderInitData( new IAssemblyLoaderService.LoaderInitData(
@@ -578,7 +611,10 @@ public class PluginManagementService : IAssemblyManagementService
foreach (var resourceInfo in scripts) 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; hasInternalsAwareBeenAdded = true;
syntaxTreesBuilder.Add(BaseAssemblyImports); syntaxTreesBuilder.Add(BaseAssemblyImports);
@@ -597,9 +633,6 @@ public class PluginManagementService : IAssemblyManagementService
_logger.LogResults(loadRes.ToResult()); _logger.LogResults(loadRes.ToResult());
continue; 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; CancellationToken token = CancellationToken.None;
@@ -622,14 +655,35 @@ public class PluginManagementService : IAssemblyManagementService
} }
_logger.LogMessage($"Compiling assembly for {scripts.Key}, in ContentPackage {contentPackRes.Key.Name}"); _logger.LogMessage($"Compiling assembly for {scripts.Key}, in ContentPackage {contentPackRes.Key.Name}");
result.WithReasons(assemblyLoader.CompileScriptAssembly( var res = assemblyLoader.CompileScriptAssembly(
assemblyName: scripts.Key, assemblyName: scripts.Key,
compileWithInternalAccess: compileWithInternalName, compileWithInternalAccess: compileWithInternalName,
syntaxTrees: syntaxTreesBuilder.ToImmutable(), syntaxTrees: syntaxTreesBuilder.ToImmutable(),
metadataReferences: metadataReferences.ToImmutableArray(), metadataReferences: compileWithInternalName ? metadataReferencesNonPublicized : metadataReferences,
compilationOptions: CompilationOptions) compilationOptions: CompilationOptions);
.Reasons);
// 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; return res;
} }
IEnumerable<MetadataReference> GetMetadataReferences() IEnumerable<MetadataReference> GetMetadataReferences(bool useNonPublicizedAssemblies)
{ {
var builder = ImmutableArray.CreateBuilder<MetadataReference>(); var builder = ImmutableArray.CreateBuilder<MetadataReference>();
builder.AddRange(BaseMetadataReferences); if (useNonPublicizedAssemblies)
foreach (var loaderService in _assemblyLoaders)
{ {
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(); return builder.ToImmutable();
} }
} }
@@ -768,6 +836,7 @@ public class PluginManagementService : IAssemblyManagementService
} }
_assemblyLoaders.Clear(); _assemblyLoaders.Clear();
_storageService.PurgeCache();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true); GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true);
#if DEBUG #if DEBUG
@@ -34,4 +34,26 @@ public interface ILoggerService : IReusableService
void LogDebugError(string message); void LogDebugError(string message);
#endregion #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,
} }
@@ -22,8 +22,10 @@ public interface IPackageManagementService : IReusableService
public FluentResults.Result UnloadPackages(ImmutableArray<ContentPackage> packages); public FluentResults.Result UnloadPackages(ImmutableArray<ContentPackage> packages);
public FluentResults.Result UnloadAllPackages(); public FluentResults.Result UnloadAllPackages();
public ImmutableArray<ContentPackage> GetAllLoadedPackages(); public ImmutableArray<ContentPackage> GetAllLoadedPackages();
public ImmutableArray<ContentPackage> GetLoadedAssemblyPackages(); public ImmutableArray<ContentPackage> GetLoadedUnrestrictedPackages();
public bool IsPackageRunning(ContentPackage package); public bool IsPackageRunning(ContentPackage package);
public bool IsAnyPackageLoaded(); public bool IsAnyPackageLoaded();
public bool IsAnyPackageRunning(); public bool IsAnyPackageRunning();
public bool PackageContainsAnyRunnableResource(ContentPackage package);
public Result<IModConfigInfo> GetModConfigForPackage(ContentPackage package);
} }
@@ -154,6 +154,13 @@ public class DefaultLuaRegistrar : IDefaultLuaRegistrar
_userDataService.RegisterType("FarseerPhysics.Collision.ReferenceFace"); _userDataService.RegisterType("FarseerPhysics.Collision.ReferenceFace");
_userDataService.RegisterType("FarseerPhysics.Collision.Collision"); _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.PrefabCollection`1");
_userDataService.RegisterType("Barotrauma.PrefabSelector`1"); _userDataService.RegisterType("Barotrauma.PrefabSelector`1");
_userDataService.RegisterType("Barotrauma.Pair`2"); _userDataService.RegisterType("Barotrauma.Pair`2");
@@ -19,7 +19,7 @@ public interface ILuaSafeEventService : ILuaService, ILuaCsHook
/// </summary> /// </summary>
/// <param name="eventName"></param> /// <param name="eventName"></param>
/// <param name="identifier"></param> /// <param name="identifier"></param>
void Remove(string eventName, string identifier); void Unsubscribe(string eventName, string identifier);
/// <summary> /// <summary>
/// Send an event to all subscribers to an interface. /// Send an event to all subscribers to an interface.
/// </summary> /// </summary>
@@ -2,16 +2,10 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using MoonSharp.Interpreter; using MoonSharp.Interpreter;
using Barotrauma.LuaCs;
namespace Barotrauma namespace Barotrauma
{ {
public enum LuaCsMessageOrigin
{
LuaCs,
Unknown,
LuaMod,
CSharpMod,
}
public partial class LuaCsLogger public partial class LuaCsLogger
{ {
@@ -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 bool IsDisposed => throw new NotImplementedException();
public void AssignOnExecute(string names, LuaCsAction onExecute) => public void AssignOnExecute(string names, object onExecute) => DebugConsole.AssignOnExecute(names, (string[] args) =>
_consoleCommands.AssignOnExecute(names, args => onExecute(args)); {
LuaCsSetup.Instance.LuaScriptManagementService.CallFunctionSafe(onExecute, new object[] { args });
});
public void SaveGame(string path) public void SaveGame(string path)
{ {
@@ -816,7 +816,7 @@ namespace Barotrauma
public static float GetDistanceFactor(PhysicsBody triggererBody, PhysicsBody triggerBody, float colliderRadius) 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) public Vector2 GetWaterFlowVelocity(Vector2 viewPosition)
+13 -1
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 v1.12.6.2
------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------
@@ -439,7 +452,6 @@ v1.8.8.1
Modding: 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. - 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 v1.8.7.0
+1 -1
View File
@@ -9,7 +9,7 @@ This is a Barotrauma modification that adds Lua and Cs modding support.
# Barotrauma # Barotrauma
Copyright © FakeFish Ltd 2017-2024 Copyright © FakeFish Ltd 2017-2026
Before downloading the source code, please read the [EULA](EULA.txt). Before downloading the source code, please read the [EULA](EULA.txt).