diff --git a/.github/DISCUSSION_TEMPLATE/bug-reports.yml b/.github/DISCUSSION_TEMPLATE/bug-reports.yml index cdefa3753..71271780c 100644 --- a/.github/DISCUSSION_TEMPLATE/bug-reports.yml +++ b/.github/DISCUSSION_TEMPLATE/bug-reports.yml @@ -73,7 +73,7 @@ body: label: Version description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu. options: - - v1.11.5.0 (Winter Update 2025 Hotfix 1) + - v1.12.6.2 (Spring Update 2026) - Other validations: required: true diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/SettingControl.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/SettingControl.cs index 65ed65ecd..0314c6c5d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/SettingControl.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/SettingControl.cs @@ -24,7 +24,7 @@ public sealed class SettingControl : SettingBase, ISettingControl public SettingControl(IConfigInfo configInfo, Func, bool> valueChangePredicate) : base(configInfo) { _valueChangePredicate = valueChangePredicate; - TrySetValue(configInfo.Element); + TrySetSerializedValue(configInfo.Element); } protected override void OnDispose() @@ -37,7 +37,7 @@ public sealed class SettingControl : SettingBase, ISettingControl public override string GetStringValue() => Value.ToString(); public override string GetDefaultStringValue() => new KeyOrMouse(Keys.NumLock).ToString(); - public override bool TrySetValue(OneOf value) + public override bool TrySetSerializedValue(OneOf value) { var newVal = value.Match( (string v) => GetKeyOrMouse(v), diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index 1c67daf45..fea455064 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -1,106 +1,47 @@ -using System; +using Barotrauma.CharacterEditor; +using Barotrauma.Extensions; +using Barotrauma.LuaCs; +using Barotrauma.LuaCs.Data; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Text; -using Barotrauma.CharacterEditor; -using Barotrauma.LuaCs; -using Barotrauma.LuaCs.Data; -using Barotrauma.Networking; -using Microsoft.Xna.Framework; +using static System.Collections.Specialized.BitVector32; // ReSharper disable ObjectCreationAsStatement namespace Barotrauma { partial class LuaCsSetup - { - private bool _isClientPromptActive; - private bool _isCsEnabledForSession = false; - - public void CheckRunConditionalHostingCsEnabled(Action onReadyToRun) + { + public void PromptCSharpMods(Action onSelection, bool joiningServer) { - var res = ReadyToRunNoPrompt(); - if (res.ShouldRun) - { - onReadyToRun?.Invoke(); - return; - } - - DisplayCsModsPromptClient(res.Item2, (selectedYes) => - { - if (selectedYes) - { - onReadyToRun?.Invoke(); - } - }); - } - - private (bool ShouldRun, ImmutableArray PromptPackages) ReadyToRunNoPrompt() - { - if (this.IsCsEnabled) - { - return (true, ImmutableArray.Empty); - } - - if (!ShouldPromptForCs) - { - return (true, ImmutableArray.Empty); - } - - ImmutableArray contentPackages = PackageManagementService.GetLoadedAssemblyPackages() - .Where(p => p.Name != PackageId) + ImmutableArray contentPackages = PackageManagementService.GetLoadedUnrestrictedPackages() + .Where(p => p.Name != PackageName) .ToImmutableArray(); - return (contentPackages.IsEmpty, contentPackages); - } - - partial void CheckReadyToRun(Action onReadyToRun) - { - var res = ReadyToRunNoPrompt(); - if (res.ShouldRun) + if (_csRunPolicy?.Value is "Enabled") { - onReadyToRun?.Invoke(); + IsCsEnabledForSession = true; + onSelection(true); return; } - - if (GameMain.Client?.ClientPeer is P2POwnerPeer) + else if (_csRunPolicy?.Value is "Disabled") { - SetCsPolicyAndContinue(true); + IsCsEnabledForSession = false; + onSelection(false); return; } - DisplayCsModsPromptClient(res.PromptPackages, (selectedYes) => + if (contentPackages.None()) { - SetCsPolicyAndContinue(selectedYes); + onSelection(true); return; - }); - - void SetCsPolicyAndContinue(bool csSessionExecutionPolicy) - { - var prevRunState = this.CurrentRunState; - if (CurrentRunState >= RunState.Running) - { - SetRunState(RunState.LoadedNoExec); - } - this._isCsEnabledForSession = csSessionExecutionPolicy; - CoroutineManager.Invoke(() => - { - if (CurrentRunState != prevRunState) - { - SetRunState(prevRunState); - } - onReadyToRun?.Invoke(); - }, 0f); } - } - - void DisplayCsModsPromptClient(ImmutableArray contentPackages, Action onSelection) - { - if (_isClientPromptActive) { return; } - - _isClientPromptActive = true; GUIMessageBox messageBox = new GUIMessageBox( TextManager.Get("warning"), @@ -115,7 +56,7 @@ namespace Barotrauma Stretch = true }; - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), msgBoxLayout.RectTransform), "The following mods contain CSharp code", + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), msgBoxLayout.RectTransform), "The following mods contain CSharp code OR Unsandboxed Lua Code", font: GUIStyle.SubHeadingFont, wrap: true, textAlignment: Alignment.Center); GUIListBox packageListBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.4f), msgBoxLayout.RectTransform)) @@ -126,22 +67,39 @@ namespace Barotrauma foreach (ContentPackage package in contentPackages) { GUIFrame packageFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.15f), packageListBox.Content.RectTransform), style: "ListBoxElement"); - new GUITextBlock(new RectTransform(new Vector2(1f, 1f), packageFrame.RectTransform), package.Name); + GUILayoutGroup packageLayout = new GUILayoutGroup(new RectTransform(Vector2.One, packageFrame.RectTransform), true, Anchor.CenterLeft); + new GUITextBlock(new RectTransform(new Vector2(0.7f, 1f), packageLayout.RectTransform), package.Name); + new GUIButton(new RectTransform(new Vector2(0.3f, 1f), packageLayout.RectTransform, Anchor.CenterRight), "Open Folder", style: "GUIButtonSmall") + { + OnClicked = (GUIButton button, object obj) => + { + string directory = package.Dir; + if (string.IsNullOrEmpty(directory)) { return false; } + + ToolBox.OpenFileWithShell(directory); + return true; + } + }; } - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0f), msgBoxLayout.RectTransform), "C# mods are not sandboxed, meaning that they have unrestrictive access to your computer, please make sure you trust these mods before you continue. If you are not hosting a server, selecting cancel will only run Lua mods.", wrap: true) + string bodyText = + joiningServer ? + "You are joining a server that includes mods with C# code OR unrestricted Lua code. These mods are not sandboxed and may access your computer without restrictions. If you trust these mods, select 'Enable C# for this session'. Otherwise, select 'Cancel' to run only Lua mods." + : "You have enabled mods that include C# code. These mods are not sandboxed and may access your computer without restrictions. If you trust these mods, select 'Enable C# for this session'. Otherwise, select 'Cancel' to run only Sandboxed Lua mods."; + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0f), msgBoxLayout.RectTransform), bodyText, wrap: true) { Wrap = true }; GUILayoutGroup buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), messageBox.Content.RectTransform, Anchor.BottomCenter), isHorizontal: false, childAnchor: Anchor.TopCenter); - new GUIButton(new RectTransform(new Vector2(0.8f, 0.0f), buttonLayout.RectTransform), "Continue") + new GUIButton(new RectTransform(new Vector2(0.8f, 0.0f), buttonLayout.RectTransform), "Enable C# for this session") { TextBlock = { AutoScaleHorizontal = true }, OnClicked = (btn, userdata) => { - _isClientPromptActive = false; + IsCsEnabledForSession = true; onSelection(true); messageBox.Close(); return true; @@ -152,7 +110,7 @@ namespace Barotrauma { OnClicked = (btn, userdata) => { - _isClientPromptActive = false; + IsCsEnabledForSession = false; onSelection(false); messageBox.Close(); return true; @@ -201,10 +159,18 @@ namespace Barotrauma case SpriteEditorScreen: case SubEditorScreen: case TestScreen: // notes: TestScreen is a Linux edge case editor screen and is deprecated. - CheckReadyToRun(() => + + if (screen is NetLobbyScreen && CurrentRunState != RunState.Running && GameMain.Client?.ClientPeer is not P2POwnerPeer) + { + PromptCSharpMods(selection => + { + SetRunState(RunState.Running); + }, joiningServer: true); + } + else { SetRunState(RunState.Running); - }); + } break; default: Logger.LogError( diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/NetworkingService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/NetworkingService.cs index 4b7c5d771..fa3b2905e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/NetworkingService.cs @@ -32,11 +32,11 @@ partial class NetworkingService : INetworkingService, IEventServerConnected, IEv } } - public void OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader) + public bool? OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader) { if (serverPacketHeader != ServerHeader) { - return; + return null; } ServerToClient luaCsHeader = (ServerToClient)netMessage.ReadByte(); @@ -55,6 +55,8 @@ partial class NetworkingService : INetworkingService, IEventServerConnected, IEv ReadIds(netMessage); break; } + + return true; } private void SendSyncMessage() diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/ModsGameplaySettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/ModsGameplaySettingsMenu.cs index 8ff436ac3..9cfa2667e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/ModsGameplaySettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/ModsGameplaySettingsMenu.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.Xna.Framework; using System.Linq; @@ -20,23 +21,87 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase private string _selectedSearchQuery = string.Empty; private ContentPackage _selectedContentPackage; private string _selectedCategory = string.Empty; + private ImmutableArray _currentlyDisplayedSettings; + private ILoggerService _loggerService; + + private bool _promptOpen = false; + + + // Note: "static" instead of "const" for Hot Reload and to allow changing at runtime. + // ReSharper disable FieldCanBeMadeReadOnly.Local + + // --- UI controls --- + private static float MenuTitleHeight = 0.06f; // (ContentDisplayAreaHeightContainer + MenuTitleHeight) < 1f + private static float ContentDisplayAreaHeightContainer = 0.93f; + private static float ContentDisplayAreaHeightInnerCategories = 0.99f; + private static float ContentDisplayAreaHeightInnerSettings = 0.97f; + private static float ContentLeftRightSplitPosition = 0.3f; + + // Search Bar + private static float SearchBarLayoutHeight = 0.06f; + private static float SearchBarLabelWidth = 0.1f; + private static float SearchBarLabelBoxSpacing = 0.05f; + + private static float SearchBarTextBoxWidth = 1f - SearchBarLabelWidth - SearchBarLabelBoxSpacing; + + // Categories, Packages Display Area + private static float CategoriesDisplayListHeight = 0.945f; + private static float CategoryButtonHeightRelative = 0.122f; + private static float PackageSelectionButtonHeight = 0.07f; + private static Color CategoryButtonHoverSelectColor = new Color(50, 50, 50, 255); + private static Color CategoryButtonTextColor = Color.PeachPuff; + private static Color CategoryButtonTextColorSelected = Color.White; + private static Color CategoryButtonColorPressed = Color.TransparentBlack; + + // Settings Display Area + private static float SettingLabelWidth = 0.6f; + private static float SettingControlWidth = 0.4f; + private static float SettingHeight = 0.05625f/ContentDisplayAreaHeightContainer/ContentDisplayAreaHeightInnerSettings; + private static Color SettingEntryLabelTextColor = Color.PeachPuff; + private static string SettingGUIFrameStyle = ""; + private static Color? SettingGUIFrameColor = null; + + // settings reset + private static Vector2 SettingsResetButtonTopSpacer = new Vector2(0f, 0.02f); + private static Vector2 SettingsResetButtonDimensions = new Vector2(0.3f, 0.05f); + private static string SettingsResetButtonStyle = "GUIButtonSmall"; + private static Color SettingsResetButtonColor = Color.DarkOliveGreen; + private static Color SettingsResetButtonHoverColor = Color.Olive; + private static Color SettingsResetButtonTextColor = Color.PeachPuff; + private static Color SettingsResetButtonTextColorSelected = Color.White; + + private static Vector2 ResetConfirmationPromptDimensions = new Vector2(0.15f, 0.2f); + + + // ReSharper restore FieldCanBeMadeReadOnly.Local + private const string SettingsResetButtonText = "LuaCsForBarotrauma.SettingsMenu.ResetVisibleSettings"; + private const string SettingsResetPromptTitle = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Title"; + private const string SettingsResetPromptContents = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Message"; + private const string SettingsResetPromptYesText = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Yes"; + private const string SettingsResetPromptNoText = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.No"; + + private event Action OnApplyInstalledModsChanges; public ModsGameplaySettingsMenu(GUIFrame contentFrame, IPackageManagementService packageManagementService, IConfigService configService, + ILoggerService loggerService, SettingsMenu settingsMenuInstance) : base(contentFrame, packageManagementService, configService, settingsMenuInstance) { _settingsInstancesGameplay = configService.GetDisplayableConfigs() .ToImmutableArray(); - + + _loggerService = loggerService; var mainLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), contentFrame.RectTransform, Anchor.Center), false, Anchor.TopLeft); // page title var menuTitleLayoutGroup = new GUILayoutGroup( - new RectTransform(new Vector2(1f, 0.06f), mainLayoutGroup.RectTransform, Anchor.TopLeft), true, Anchor.TopLeft); - GUIUtil.Label(menuTitleLayoutGroup, "Mods Gameplay Settings", GUIStyle.LargeFont, new Vector2(1f, 1f)); + new RectTransform(new Vector2(1f, MenuTitleHeight), mainLayoutGroup.RectTransform, Anchor.TopLeft), true, Anchor.TopLeft); + GUIUtil.Label(menuTitleLayoutGroup, + GetLocalizedString("LuaCsForBarotrauma.SettingsMenu.ModGameplayButton", "Mod Gameplay Settings"), + GUIStyle.LargeFont, new Vector2(1f, 1f)); // page contents var contentAreaLayoutGroup = new GUILayoutGroup( @@ -44,10 +109,10 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase Anchor.TopLeft); var searchBarLayoutGroup = new GUILayoutGroup( - new RectTransform(new Vector2(1f, 0.06f), contentAreaLayoutGroup.RectTransform, Anchor.TopCenter), true, Anchor.CenterLeft); - GUIUtil.Label(searchBarLayoutGroup, "Search: ", GUIStyle.SubHeadingFont, new Vector2(0.1f, 1f)); + new RectTransform(new Vector2(1f, SearchBarLayoutHeight), contentAreaLayoutGroup.RectTransform, Anchor.TopCenter), true, Anchor.CenterLeft); + GUIUtil.Label(searchBarLayoutGroup, "Search: ", GUIStyle.SubHeadingFont, new Vector2(SearchBarLabelWidth, 1f)); var searchBar = new GUITextBox( - new RectTransform(new Vector2(0.85f, 0.1f), searchBarLayoutGroup.RectTransform, Anchor.TopLeft), + new RectTransform(new Vector2(SearchBarTextBoxWidth, 0.1f), searchBarLayoutGroup.RectTransform, Anchor.TopLeft), createClearButton: true) { OnTextChangedDelegate = (btn, txt) => @@ -56,12 +121,13 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase return true; } }; + // main display area - var settingsContentAreaGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.90f), contentAreaLayoutGroup.RectTransform, Anchor.BottomCenter)); + var settingsContentAreaGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, ContentDisplayAreaHeightContainer), contentAreaLayoutGroup.RectTransform, Anchor.BottomCenter)); GUIUtil.Spacer(settingsContentAreaGroup, Vector2.One); (_modCategoryDisplayGroup, _settingsDisplayGroup) = GUIUtil.CreateSidebars(settingsContentAreaGroup, true); - _modCategoryDisplayGroup.RectTransform.RelativeSize = new Vector2(0.3f, 1f); - _settingsDisplayGroup.RectTransform.RelativeSize = new Vector2(0.7f, 1f); + _modCategoryDisplayGroup.RectTransform.RelativeSize = new Vector2(ContentLeftRightSplitPosition, ContentDisplayAreaHeightInnerCategories); + _settingsDisplayGroup.RectTransform.RelativeSize = new Vector2(1f-ContentLeftRightSplitPosition, ContentDisplayAreaHeightInnerSettings); // default category _selectedCategory = "All"; @@ -202,10 +268,11 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase _selectedCategory = string.Empty; GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetTargetPackagesList(), GetDisplayCategoriesList()); GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList()); - }, new Vector2(1f, 0.07f)); - var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.945f), layoutGroup.RectTransform)); - const float entryHeight = 0.122f; - float sizeY = MathF.Max(categories.Length * entryHeight, 1f); + }, new Vector2(1f, PackageSelectionButtonHeight)); + var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, CategoriesDisplayListHeight), layoutGroup.RectTransform)); + + + float sizeY = MathF.Max(categories.Length * CategoryButtonHeightRelative, 1f); var displayedCategoriesFrame = new GUIFrame(new RectTransform(new Vector2(1f, sizeY), containerBox.Content.RectTransform), style: null, color: Color.Black) { CanBeFocused = false @@ -214,17 +281,18 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase foreach (var category in categories) { - var btn = new GUIButton(new RectTransform(new Vector2(1f, entryHeight), displayCategoriesLayout.RectTransform), + var btn = new GUIButton(new RectTransform(new Vector2(1f, CategoryButtonHeightRelative), displayCategoriesLayout.RectTransform), text: category, color: Color.TransparentBlack) { CanBeFocused = true, CanBeSelected = true, - TextColor = Color.PeachPuff, - HoverColor = new Color(50, 50, 50, 255), - HoverTextColor = Color.White, - SelectedColor = new Color(50, 50, 50, 255), - SelectedTextColor = Color.White, - OnPressed = () => + TextColor = CategoryButtonTextColor, + HoverColor = CategoryButtonHoverSelectColor, + HoverTextColor = CategoryButtonTextColorSelected, + PressedColor = CategoryButtonColorPressed, + SelectedColor = CategoryButtonHoverSelectColor, + SelectedTextColor = CategoryButtonHoverSelectColor, + OnClicked = (btn, obj) => { _selectedCategory = category; GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList()); @@ -237,26 +305,47 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase void GenerateSettingsListDisplay(GUILayoutGroup layoutGroup, ImmutableArray settings) { layoutGroup.ClearChildren(); - const float settingHeight = 0.0625f; + _currentlyDisplayedSettings = settings; - var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f), layoutGroup.RectTransform)); + var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f-SettingsResetButtonDimensions.Y), layoutGroup.RectTransform)); foreach (var setting in settings) { var entry = AddSettingToDisplay( setting, containerBox.Content.RectTransform, - settingHeight: settingHeight, - labelSize: new Vector2(0.6f, 1f), - controlSize: new Vector2(0.4f, 1f)); - - + settingHeight: SettingHeight, + labelSize: new Vector2(SettingLabelWidth, 1f), + controlSize: new Vector2(SettingControlWidth, 1f)); } - } - (GUIFrame entryFrame, GUILayoutGroup entryLayoutGroup) AddSettingToDisplay(ISettingBase setting, - RectTransform parent, float settingHeight, Vector2 labelSize, Vector2 controlSize) + var spacer = new GUIFrame(new RectTransform(SettingsResetButtonTopSpacer, layoutGroup.RectTransform), + style: null, color: Color.TransparentBlack); + + var resetSettingsButton = new GUIButton( + new RectTransform(SettingsResetButtonDimensions, layoutGroup.RectTransform), + GetLocalizedString(SettingsResetButtonText, "Reset Visible Settings"), + style: SettingsResetButtonStyle) + { + CanBeSelected = true, + CanBeFocused = true, + Color = SettingsResetButtonColor, + HoverColor = SettingsResetButtonHoverColor, + SelectedColor = SettingsResetButtonHoverColor, + SelectedTextColor = SettingsResetButtonTextColorSelected, + TextColor = SettingsResetButtonTextColor, + OnClicked = (btn, obj) => + { + DisplayResetConfirmationPrompt(settings); + return true; + } + }; + } + + (GUIFrame entryFrame, GUILayoutGroup entryLayoutGroup) + AddSettingToDisplay(ISettingBase setting, RectTransform parent, float settingHeight, Vector2 labelSize, Vector2 controlSize) { - GUIFrame entryFrame = new GUIFrame(new RectTransform(new Vector2(1f, settingHeight), parent)) + GUIFrame entryFrame = new GUIFrame(new RectTransform(new Vector2(1f, settingHeight), parent), + style: SettingGUIFrameStyle, color: SettingGUIFrameColor) { Color = Color.DarkGray }; @@ -266,9 +355,10 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase new GUIFrame(new RectTransform(new Vector2(0.02f, 1f), entryLayoutGroup.RectTransform), color: Color.TransparentBlack); + // setting label new GUITextBlock(new RectTransform(labelSize - new Vector2(0.05f, 0f), entryLayoutGroup.RectTransform), GetLocalizedString(setting.GetDisplayInfo().DisplayName, setting.GetDisplayInfo().DisplayName), - textColor: Color.PeachPuff, + textColor: SettingEntryLabelTextColor, font: GUIStyle.SmallFont, textAlignment: Alignment.Left) { @@ -281,6 +371,58 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase }); return (entryFrame, entryLayoutGroup); } + + void DisplayResetConfirmationPrompt(ImmutableArray 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 settings) + { + if (settings.IsDefaultOrEmpty) + { + return; + } + + NewValuesCache.Clear(); + foreach (var setting in settings) + { + var str = setting.GetDefaultStringValue(); + NewValuesCache[setting] = str; + loggerService.LogDebug($"Resetting value for {setting.InternalName} to '{str}'"); + } + + ApplyInstalledModChanges(); + } } @@ -303,8 +445,12 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase continue; } - kvp.Key.TrySetValue(kvp.Value); - ConfigService.SaveConfigValue(kvp.Key); + var success = kvp.Key.TrySetSerializedValue(kvp.Value); + if (success) + { + ConfigService.SaveConfigValue(kvp.Key); + _loggerService.LogDebug($"Applied save value for {kvp.Key.InternalName} of {kvp.Value.ToString()}"); + } } NewValuesCache.Clear(); OnApplyInstalledModsChanges?.Invoke(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/ModsSettingsMenuBase.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/ModsSettingsMenuBase.cs index 9cad5c561..95c626c8f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/ModsSettingsMenuBase.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/ModsSettingsMenuBase.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Concurrent; +using System.Xml.Linq; using Barotrauma.Extensions; using Barotrauma.LuaCs.Data; using Microsoft.Xna.Framework; +using OneOf; namespace Barotrauma.LuaCs; @@ -12,7 +14,7 @@ internal abstract class ModsSettingsMenuBase : IDisposable protected IPackageManagementService PackageManagementService { get; private set; } protected IConfigService ConfigService { get; private set; } protected SettingsMenu SettingsMenuInstance { get; private set; } - protected readonly ConcurrentDictionary NewValuesCache = new(); + protected readonly ConcurrentDictionary> NewValuesCache = new(); protected ModsSettingsMenuBase(GUIFrame contentFrame, IPackageManagementService packageManagementService, diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/SettingsMenuSystem.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/SettingsMenuSystem.cs index 9347809c7..1d5cb2d4d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/SettingsMenuSystem.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/SettingsMenuSystem.cs @@ -18,12 +18,14 @@ public class SettingsMenuSystem : ISettingsMenuSystem private readonly Harmony _harmony; private readonly IPackageManagementService _packageManagementService; private readonly IConfigService _configService; + private readonly ILoggerService _loggerService; private static SettingsMenuSystem SystemInstance; - public SettingsMenuSystem(IPackageManagementService packageManagementService, IConfigService configService) + public SettingsMenuSystem(IPackageManagementService packageManagementService, IConfigService configService, ILoggerService loggerService) { _packageManagementService = packageManagementService; _configService = configService; + _loggerService = loggerService; SystemInstance = this; _harmony = Harmony.CreateAndPatchAll(typeof(SettingsMenuSystem)); } @@ -43,13 +45,14 @@ public class SettingsMenuSystem : ISettingsMenuSystem var tabGameplayIndex = (SettingsMenu.Tab)tabCount; var tabControlsIndex = (SettingsMenu.Tab)tabCount+1; - _gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance, - "SettingsMenuTab.Mods", "LuaCsForBarotrauma.SettingsMenu.ModGameplayButton"); + _gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance, + GUIStyle.ComponentStyles.ContainsKey("SettingsMenuTab.LuaCsSettings") ? "SettingsMenuTab.LuaCsSettings" : "SettingsMenuTab.Mods", + "LuaCsForBarotrauma.SettingsMenu.ModGameplayButton"); /*_controlsContentFrame = CreateNewContentTab(tabControlsIndex, __instance, "SettingsMenuTab.Controls", "LuaCsForBarotrauma.SettingsMenu.ModControlsButton"); */ - _gameplayMenuInstance = new ModsGameplaySettingsMenu(_gameplayContentFrame, _packageManagementService, _configService, __instance); + _gameplayMenuInstance = new ModsGameplaySettingsMenu(_gameplayContentFrame, _packageManagementService, _configService, _loggerService, __instance); //_controlsMenuInstance = new ModsControlsSettingsMenu(_controlsContentFrame, _packageManagementService, _configService, __instance); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs index 063d4fcb7..2d1f03114 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs @@ -1015,25 +1015,20 @@ namespace Barotrauma private void TryStartServer() { - LuaCsSetup.Instance.CheckRunConditionalHostingCsEnabled(() => + if (SubmarineInfo.SavedSubmarines.Any(s => s.CalculatingHash)) { - if (SubmarineInfo.SavedSubmarines.Any(s => s.CalculatingHash)) + var waitBox = new GUIMessageBox(TextManager.Get("pleasewait"), TextManager.Get("waitforsubmarinehashcalculations"), new LocalizedString[] { TextManager.Get("cancel") }); + var waitCoroutine = CoroutineManager.StartCoroutine(WaitForSubmarineHashCalculations(waitBox), "WaitForSubmarineHashCalculations"); + waitBox.Buttons[0].OnClicked += (btn, userdata) => { - var waitBox = new GUIMessageBox(TextManager.Get("pleasewait"), TextManager.Get("waitforsubmarinehashcalculations"), new LocalizedString[] { TextManager.Get("cancel") }); - var waitCoroutine = CoroutineManager.StartCoroutine(WaitForSubmarineHashCalculations(waitBox), "WaitForSubmarineHashCalculations"); - waitBox.Buttons[0].OnClicked += (btn, userdata) => - { - CoroutineManager.StopCoroutines(waitCoroutine); - return true; - }; - } - else - { - StartServer(); - } - }); - - + CoroutineManager.StopCoroutines(waitCoroutine); + return true; + }; + } + else + { + StartServer(); + } } private IEnumerable WaitForSubmarineHashCalculations(GUIMessageBox messageBox) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs index 7f9a9214a..537ad5b60 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs @@ -174,9 +174,9 @@ namespace Barotrauma public static RectTransform NewItemRectT(GUILayoutGroup parent) => new RectTransform((1.0f, 0.06f), parent.RectTransform, Anchor.CenterLeft); - public static void Spacer(GUILayoutGroup parent) + public static void Spacer(GUILayoutGroup parent, float height = 0.03f) { - new GUIFrame(new RectTransform((1.0f, 0.03f), parent.RectTransform, Anchor.CenterLeft), style: null); + new GUIFrame(new RectTransform((1.0f, height), parent.RectTransform, Anchor.CenterLeft), style: null); } public static GUITextBlock Label(GUILayoutGroup parent, LocalizedString str, GUIFont font) @@ -507,6 +507,47 @@ namespace Barotrauma return true; } }; +#if OSX + Spacer(voiceChat, 0.003f); + + // On macOS, microphone permission can apparently sometimes end up in a broken state when the app binary changes (eg. after a Steam update). + // The device seems to be there, but won't receive anything, even if the mic permission is fine. + // This button lets the user reset it and reboot the game, so the mic permission check will be retriggered on next run. + new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), voiceChat.RectTransform), + text: TextManager.Get("MacResetMicPermissions"), + style: "GUIButtonSmall") + { + ToolTip = TextManager.Get("MacResetMicPermissionsToolTip"), + OnClicked = (btn, obj) => + { + var confirmBox = new GUIMessageBox( + TextManager.Get("MacResetMicPermissions"), + TextManager.Get("MacResetMicPermissionsConfirm"), + [TextManager.Get("OK"), TextManager.Get("Cancel")]); + confirmBox.Buttons[0].OnClicked = (_, _) => + { + try + { + System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo + { + FileName = "tccutil", + Arguments = "reset Microphone com.FakeFish.Barotrauma", + UseShellExecute = false + }); + } + catch (Exception e) + { + DebugConsole.NewMessage($"Failed to reset microphone permission: {e.Message}", Color.Orange); + } + GameMain.Instance.Exit(); + confirmBox.Close(); + return true; + }; + confirmBox.Buttons[1].OnClicked = confirmBox.Close; + return true; + } + }; +#endif Spacer(voiceChat); Label(voiceChat, TextManager.Get("VCInputMode"), GUIStyle.SubHeadingFont); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/Workshop.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/Workshop.cs index 5f33df3bc..dc31dc47c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Steam/Workshop.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/Workshop.cs @@ -18,6 +18,10 @@ namespace Barotrauma.Steam { public const int MaxThumbnailSize = 1024 * 1024; + /// + /// 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()}". + /// public static readonly ImmutableArray Tags = new [] { "submarine", @@ -25,7 +29,7 @@ namespace Barotrauma.Steam "monster", "mission", "outpost", - "beaconstation", + "beacon station", "wreck", "ruin", "weapons", @@ -34,14 +38,14 @@ namespace Barotrauma.Steam "art", "event set", "total conversion", - "gamemode", - "gameplaymechanics", + "game mode", + "gameplay mechanics", "environment", "item assembly", "language", "qol", - "clientside", - "serverside", + "client-side", + "server-side", "outdated", "library" }.ToIdentifiers().ToImmutableArray(); diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 2d618918b..20e0c832f 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.12.6.2 + 1.12.7.0 Copyright © FakeFish 2018-2024 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 921069239..cc877ab15 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.12.6.2 + 1.12.7.0 Copyright © FakeFish 2018-2024 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 218cb4b03..9bf9ca415 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.12.6.2 + 1.12.7.0 Copyright © FakeFish 2018-2024 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 680300ddc..411d2e5ee 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.12.6.2 + 1.12.7.0 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index ecf8c4d7b..fb79c6495 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.12.6.2 + 1.12.7.0 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsInstaller.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsInstaller.cs index 82081bd8b..b4a00dc11 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsInstaller.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsInstaller.cs @@ -2,6 +2,7 @@ using System; using System.IO; using System.Linq; +using Barotrauma.LuaCs; namespace Barotrauma { diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/_Services/NetworkingService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/_Services/NetworkingService.cs index 661a99d2b..5bd32a8a1 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/_Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/_Services/NetworkingService.cs @@ -35,11 +35,11 @@ partial class NetworkingService : INetworkingService, IEventClientRawNetMessageR return message; } - public void OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader clientPacketHeader, NetworkConnection sender) + public bool? OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader clientPacketHeader, NetworkConnection sender) { if (clientPacketHeader != ClientHeader) { - return; + return null; } Client client = GameMain.Server.ConnectedClients.First(c => c.Connection == sender); @@ -64,6 +64,8 @@ partial class NetworkingService : INetworkingService, IEventClientRawNetMessageR RequestIdSingle(netMessage, client); break; } + + return true; } private void HandleNetMessageId(IReadMessage netMessage, Client client = null) diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 025fde821..edf236deb 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.12.6.2 + 1.12.7.0 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml index 103091314..99fdf1f3f 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml @@ -10,6 +10,6 @@ - + diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua index f83071229..08e139173 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua @@ -35,44 +35,4 @@ if not CSActive then end end -if SERVER then - Networking.Receive("_luastart", function (message, client) - local num = message.ReadUInt16() - - local packages = {} - - for i = 1, num, 1 do - table.insert(packages, { - Name = message.ReadString(), - Version = message.ReadString(), - Id = message.ReadUInt64(), - Hash = message.ReadString() - }) - end - - Hook.Call("client.packages", client, packages) - end) -elseif Game.IsMultiplayer then - local message = Networking.Start("_luastart") - - local packageCount = 0 - for package in ContentPackageManager.EnabledPackages.All do packageCount = packageCount + 1 end - - message.WriteUInt16(packageCount) - - for package in ContentPackageManager.EnabledPackages.All do - local id = package.UgcId - local hash = package.Hash and package.Hash.StringRepresentation or "" - - if id == nil then id = 0 end - - message.WriteString(package.Name) - message.WriteString(package.ModVersion) - message.WriteUInt64(UInt64(id)) - message.WriteString(hash) - end - - Networking.Send(message) -end - LuaSetup = nil \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/LuaCsSettingsIcon.png b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/LuaCsSettingsIcon.png new file mode 100644 index 000000000..434b6f2f2 Binary files /dev/null and b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/LuaCsSettingsIcon.png differ diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Style.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Style.xml new file mode 100644 index 000000000..a364a50f3 --- /dev/null +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Style.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml index a0f8ccd72..2108b69ad 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml @@ -2,6 +2,11 @@ Mod Controls Settings Mod Gameplay Settings + Reset Displayed Settings + Reset Visible Settings + Are you sure you want to reset the values for currently displayed settings? + Yes + No Are C# Mods Allowed diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/filelist.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/filelist.xml index 445a2ad00..679f8d4c8 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/filelist.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/filelist.xml @@ -1,5 +1,6 @@  + diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/OxygenDispenserTest.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/OxygenDispenserTest.xml index 1cf6e86ca..82378a936 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/OxygenDispenserTest.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/OxygenDispenserTest.xml @@ -14,7 +14,7 @@ AttachedByDefault="true" DisallowAttachingOverTags="container,planter,refuelableitem" DisallowAttachingOverSize="115,130"> - + diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/RotationAndFlippingTests.sub b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/RotationAndFlippingTests.sub index 7eafd5c1c..10d816db3 100644 Binary files a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/RotationAndFlippingTests.sub and b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/RotationAndFlippingTests.sub differ diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua index 98d641dd8..0c6c821fc 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua @@ -24,29 +24,52 @@ Hook.Add("missionsEnded", "test", function() print("missionsEnded") end) +-- cfg tests +local str = "CLIENT: " + +if SERVER then + str = "SERVER: " +end + +function OnChanged(cfg) + print(str, "cfg value for ", cfg.InternalName, " changed to ", cfg.Value) +end + local failed, package = trygetpackage("[DebugOnlyTest]TestLuaMod") print("packageFailed=", failed) print("package", package.Name) -local success, config = ConfigService.TryGetConfig(SettingBase.Single, package, "TestFloat") +local success, config = ConfigService.TryGetConfig(SettingBase.Int32, package, "TestSynchroServer") +local success2, config2 = ConfigService.TryGetConfig(SettingBase.Int32, package, "TestSynchroClient") -local success2, config2 = ConfigService.TryGetConfig(SettingBase.Int32, package, "TestSynchroServer") -local success3, config3 = ConfigService.TryGetConfig(SettingBase.Int32, package, "TestSynchroClient") +if not success or not success2 then + print("Failed to get configs.") + return +end -print("config ", success, " ", config.Value) -print("config testsynchrosrv", success2, " ", config2.Value) -print("config testsynchrocli", success3, " ", config3.Value) +config.OnValueChanged.add(OnChanged) +config2.OnValueChanged.add(OnChanged) -local lastTime = 0 +print(str, " testsynchroclient=", config2.Value) +print(str, " testsynchroserver=", config.Value) + +-- The server should keep updating the value and it should show up on the client. +-- The client should try updating and it should fail. + +local lastTime = Timer.Time + 30 -- give time to join Hook.Add("think", "printconfig", function() - if lastTime > Timer.Time then return end + if lastTime > Timer.Time then return end + lastTime = Timer.Time + 10 - lastTime = Timer.Time + 10 - print(config.Value) - - if SERVER then - config.TrySetValue(config.Value + 1) - end + if SERVER then + local succ = config.TrySetValue(config.Value + 1) + print("Success of setting value on server for '", config.InternalName,"': ", succ) + end + if CLIENT then + local succ = config.TrySetValue(config.Value + 1) + print("Success of setting value on client for '", config.InternalName,"': ", succ, " | This should fail if permissions are not set for client.") + end + end) diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml index 1af981a06..b5f96babd 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml @@ -2,4 +2,6 @@ + + diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml index c05583cf2..5c7e4458f 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml @@ -1,14 +1,14 @@  - - - - - - - - + + + + + + + + diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/SettingsClient.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/SettingsClient.xml new file mode 100644 index 000000000..902b2a77b --- /dev/null +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/SettingsClient.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/SettingsServer.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/SettingsServer.xml new file mode 100644 index 000000000..3e04dae6b --- /dev/null +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/SettingsServer.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index ce74caefa..eeab5c9b6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -242,10 +242,13 @@ namespace Barotrauma } /// - /// 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. + /// + private readonly HashSet unattackableSubmarines = []; + + /// + /// 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. /// - private readonly HashSet unattackableSubmarines = new HashSet(); - public void SetUnattackableSubmarines(Submarine submarine, bool includeOwnSub = true, bool includeConnectedSubs = true, bool clearExisting = true) { if (clearExisting) @@ -3075,11 +3078,17 @@ namespace Barotrauma } else { - // Ignore all structures, items, and hulls inside these subs. - if (aiTarget.Entity.Submarine != null) + if (aiTarget.Entity.Submarine != null) { + //ignore all items, structures and hulls in wrecks and beacon stations + //(we don't want monsters to be distracted by them during missions, + //nor have monsters inside them attack "their home" rather than the player) if (aiTarget.Entity.Submarine.Info.IsWreck || - aiTarget.Entity.Submarine.Info.IsBeacon || + aiTarget.Entity.Submarine.Info.IsBeacon) + { + continue; + } + if (aiTarget.Entity is Structure or Hull or Item { body: null } && unattackableSubmarines.Contains(aiTarget.Entity.Submarine)) { continue; diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs index dba9f064b..50cf39e5b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs @@ -590,6 +590,11 @@ namespace Barotrauma package.UgcId.TryUnwrap(out var ugcId) && ugcId is SteamWorkshopId workshopId && workshopId.Value == childUgcItemId.Value)); foreach (var missingChild in missingChildren) { + if (missingChild.ToString() == "2559634234" || + missingChild.ToString() == "2795927223") + { + continue; + } enabledPackage.AddMissingDependency(missingChild); } }); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index 561fa6716..c105aee80 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs @@ -99,7 +99,7 @@ namespace Barotrauma.Items.Components } } - [Serialize("0.0,0.0", IsPropertySaveable.No, description: "The position where the contained items get drawn at. In the case of items with a physics body, the offset is from the center of the body, on items without one from the top-left corner of the sprite. In pixels.")] + [Serialize("0.0,0.0", IsPropertySaveable.No, description: "The position where the contained items get drawn at (offset from the upper left corner of the sprite in pixels).")] public Vector2 ItemPos { get; set; } [Serialize("0.0,0.0", IsPropertySaveable.No, description: "The interval at which the contained items are spaced apart from each other (in pixels).")] @@ -1093,25 +1093,21 @@ namespace Barotrauma.Items.Components { if (item.body == null) { - //if the item is a holdable item currently attached to a wall (i.e. normally has a physics body, but the body is now disabled), - //we must position the contained items using the center as the origin since the item positions have been configured with the assumption the item has a body - bool isAttachedHoldable = item.GetComponent() is { Attached: true }; - bool useCenterAsOrigin = isAttachedHoldable; if (flippedX) { transformedItemPos.X = -transformedItemPos.X; - if (!useCenterAsOrigin) { transformedItemPos.X += item.Rect.Width; } + transformedItemPos.X += item.Rect.Width; transformedItemInterval.X = -transformedItemInterval.X; transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X; } if (flippedY) { transformedItemPos.Y = -transformedItemPos.Y; - if (!useCenterAsOrigin) { transformedItemPos.Y -= item.Rect.Height; } + transformedItemPos.Y -= item.Rect.Height; transformedItemInterval.Y = -transformedItemInterval.Y; transformedItemIntervalVertical.Y = -transformedItemIntervalVertical.Y; } - transformedItemPos += useCenterAsOrigin ? item.Position : new Vector2(item.Rect.X, item.Rect.Y); + transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y); if (drawPosition) { if (item.Submarine != null) { transformedItemPos += item.Submarine.DrawPosition; } @@ -1129,6 +1125,16 @@ namespace Barotrauma.Items.Components } else { + if (item.GetComponent() is { Attachable: true }) + { + //if the item is attachable to walls, we need a bit of special logic because the item can either + //have or not have a body depending on whether it's attached. + + //since it seems previously the contained item positions have always been configured as if the item had no body (using the top-left corner as the origin), + //let's modify the position here to position the items correctly even when the body is active (moving the origin from the center of the body to the top-left corner) + transformedItemPos -= item.Rect.Size.FlipY().ToVector2() / 2; + } + Matrix transform = Matrix.CreateRotationZ(drawPosition ? item.body.DrawRotation : item.body.Rotation); if (bodyFlipped) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs index b9c3222ba..9e2d344fe 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs @@ -862,10 +862,6 @@ namespace Barotrauma.Items.Components } else if (CanBeSelectedByCharacters) { -#if SERVER - GameServer.Log($"{GameServer.CharacterLogName(activator)} entered {item.Name}", ServerLog.MessageType.ItemInteraction); -#endif - activator.DeselectCharacter(); User = activator; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsHook.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsHook.cs index 7dfdb8291..dbe6c4916 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsHook.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsHook.cs @@ -11,6 +11,7 @@ public interface ILuaCsHook : ILuaPatcher, ILuaCsShim void Add(string eventName, string identifier, LuaCsFunc callback, object owner = null); [Obsolete("ACsMod is deprecated. Use ILuaEventService.Add() instead.")] void Add(string eventName, LuaCsFunc callback, object owner = null); + void Remove(string eventName, string identifier); // Does anyone use this? TODO: Analyze old Lua mods for usage scenarios. //bool Exists(string eventName, string identifier); object Call(string eventName, params object[] args); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/LuaCsConfig.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/LuaCsConfig.cs new file mode 100644 index 000000000..06598ab2d --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/LuaCsConfig.cs @@ -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(); + 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 SaveDocTypes(IEnumerable 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 fields = tType.GetFields(BindingFlags.Instance | BindingFlags.Public) + .Concat(tType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)); + IEnumerable 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 types, Type type, XElement elem) + { + if (!types.Contains(type)) types.Add(type); + elem.SetAttributeValue("Type", types.IndexOf(type)); + } + + private static XElement ParseObject(List 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 fields = tType.GetFields(BindingFlags.Instance | BindingFlags.Public) + .Concat(tType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)); + IEnumerable 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(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(); + 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(string path) + { + using (var file = LuaCsFile.OpenRead(path)) return Load(file); + } + + public static void Save(string path, object obj) + { + using (var file = LuaCsFile.OpenWrite(path)) Save(file, obj); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs index 79c30579b..b6186231d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs @@ -56,6 +56,7 @@ public record ConfigResourceInfo : BaseResourceInfo, IConfigResourceInfo {} public record LuaScriptsResourceInfo : BaseResourceInfo, ILuaScriptResourceInfo { public bool IsAutorun { get; init; } + public bool RunUnrestricted { get; init; } } #endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs index 32d17a16e..ba315464f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs @@ -21,6 +21,12 @@ public interface ILuaScriptResourceInfo : IBaseResourceInfo /// [XmlAttribute("IsAutorun")] public bool IsAutorun { get; } + + /// + /// Indicates that this lua resources needs to run outside sandbox/requires unrestricted access. + /// + [XmlAttribute("RunUnrestricted")] + public bool RunUnrestricted { get; } } public interface IAssemblyResourceInfo : IBaseResourceInfo diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs index 49974fdc9..d82349eea 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs @@ -5,6 +5,7 @@ using System.Xml.Linq; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs; using Barotrauma.Networking; +using OneOf; namespace Barotrauma.LuaCs.Data; @@ -34,8 +35,8 @@ public partial interface ISettingBase : IDataInfo, IEquatable, IDi Type GetValueType(); string GetStringValue(); string GetDefaultStringValue(); - bool TrySetValue(OneOf.OneOf value); - event Action OnValueChanged; + bool TrySetSerializedValue(OneOf value); + event Action OnValueChanged; OneOf.OneOf GetSerializableValue(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs index d103ad0fe..f9f9826de 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Concurrent; using System.Xml.Linq; using Barotrauma.LuaCs.Data; +using Microsoft.Toolkit.Diagnostics; using Microsoft.Xna.Framework; using OneOf; @@ -10,6 +12,7 @@ public abstract class SettingBase : ISettingBase { protected SettingBase(IConfigInfo configInfo) { + Guard.IsNotNull(configInfo, nameof(configInfo)); ConfigInfo = configInfo; } @@ -57,8 +60,8 @@ public abstract class SettingBase : ISettingBase public abstract Type GetValueType(); public abstract string GetStringValue(); public abstract string GetDefaultStringValue(); + public abstract bool TrySetSerializedValue(OneOf value); - public abstract bool TrySetValue(OneOf value); public abstract event Action OnValueChanged; public abstract OneOf GetSerializableValue(); #if CLIENT diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs index 83879b195..5b1cacd23 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs @@ -147,12 +147,10 @@ public partial class SettingEntry : SettingBase, ISettingBase, INetworkSyn } public override Type GetValueType() => typeof(T); - public override string GetStringValue() => Value?.ToString() ?? string.Empty; - - public override string GetDefaultStringValue() => DefaultValue.ToString(); + public override string GetDefaultStringValue() => DefaultValue?.ToString() ?? string.Empty; - public override bool TrySetValue(OneOf value) + public override bool TrySetSerializedValue(OneOf value) { bool isFailed = false; var typeConvertedValue = value.Match( @@ -181,7 +179,6 @@ public partial class SettingEntry : SettingBase, ISettingBase, INetworkSyn return default(T); } }); - return !isFailed && TrySetValue(typeConvertedValue); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index d0da76844..ca52dab77 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -105,7 +105,13 @@ internal interface IEventModifyChatMessage : IEvent /// Whether to reject the message. 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 #if SERVER public interface IEventClientRawNetMessageReceived : IEvent { - void OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader clientPacketHeader, NetworkConnection sender); + bool? OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader clientPacketHeader, NetworkConnection sender); static IEventClientRawNetMessageReceived IEvent.GetLuaRunner(IDictionary luaFunc) => new LuaWrapper(luaFunc); @@ -964,15 +970,22 @@ public interface IEventClientRawNetMessageReceived : IEvent 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 public interface IEventServerRawNetMessageReceived : IEvent { - void OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader); + bool? OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader); static IEventServerRawNetMessageReceived IEvent.GetLuaRunner(IDictionary luaFunc) => new LuaWrapper(luaFunc); @@ -1074,9 +1087,15 @@ public interface IEventServerRawNetMessageReceived : IEvent _luaCsSetup ??= new LuaCsSetup(); - + + /// + /// The index of the last Vanilla command. + /// + public static int DebugConsoleCommandVanillaIndex { get; private set; } + private LuaCsSetup() { if (_luaCsSetup != null) @@ -37,6 +42,8 @@ namespace Barotrauma throw new Exception("Tried to create another LuaCsSetup instance"); } + DebugConsoleCommandVanillaIndex = DebugConsole.Commands.Count; + // == startup _servicesProvider = SetupServicesProvider(); _runStateMachine = SetupStateMachine(); @@ -83,7 +90,26 @@ namespace Barotrauma // hotpath performance ref cache private LuaGame _game; public LuaGame Game => _game ??= _servicesProvider.GetService(); + public Script Lua => LuaScriptManagementService.InternalScript; + private ISettingBase _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); + } + } + } /// /// Whether C# plugin code is enabled. @@ -91,15 +117,17 @@ namespace Barotrauma public bool IsCsEnabled { #if CLIENT - get => _csRunPolicy?.Value == "Enabled" || _isCsEnabledForSession; + get => _csRunPolicy?.Value == "Enabled" || IsCsEnabledForSession; #elif SERVER // cs settings cannot be changed on the server after launch get => _csRunPolicy?.Value is "Enabled" or "Prompt"; #endif } + private ISettingList _csRunPolicy; - private bool ShouldPromptForCs => _csRunPolicy?.Value is "Prompt"; - + + public string CsRunPolicyValue => _csRunPolicy?.Value ?? "Prompt"; + /// /// Whether usernames are anonymized or show in logs. /// @@ -118,9 +146,9 @@ namespace Barotrauma public static ContentPackage GetLuaCsPackage() { - return ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(PackageId), null) - ?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(PackageId)) - ?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(PackageId)); + return ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(PackageName), null) + ?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(PackageName)) + ?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(PackageName)); } void LoadLuaCsConfig() @@ -139,6 +167,17 @@ namespace Barotrauma ConfigService.TryGetConfig>(luaCsPackage, "UseCaching", out var val5) ? val5 : null; + _isCsEnabledForSession = + ConfigService.TryGetConfig>(luaCsPackage, "IsCsEnabledForSession", out var val6) + ? val6 + : null; + + if (!ContentPackageManager.EnabledPackages.All.Contains(luaCsPackage)) + { + // sorry perfidius (not sorry) + luaCsPackage.UnloadFilesOfType(); + luaCsPackage.LoadFilesOfType(); + } } private IServicesProvider SetupServicesProvider() @@ -223,22 +262,13 @@ namespace Barotrauma public void OnReloadAllPackages() { - if (CurrentRunState <= RunState.Unloaded) - { - return; - } - CoroutineManager.Invoke(() => { -#if CLIENT - bool prevCsEnabled = _isCsEnabledForSession; -#endif - var state = CurrentRunState; SetRunState(RunState.Unloaded); -#if CLIENT - _isCsEnabledForSession = prevCsEnabled; -#endif - SetRunState(state); + CoroutineManager.Invoke(() => + { + SetRunState(RunState.Running); + },0.25f); }); } @@ -256,10 +286,11 @@ namespace Barotrauma } this.Logger.LogResults(PackageManagementService.SyncLoadedPackagesList(GetLuaCsEnabledPackagesList(packages))); + ConfigService.LoadSavedConfigsValues(); SetRunState(state); // restore } - private void SetRunState(RunState targetRunState) + public void SetRunState(RunState targetRunState) { if (CurrentRunState == targetRunState) { @@ -274,17 +305,13 @@ namespace Barotrauma private ImmutableArray GetLuaCsEnabledPackagesList(ImmutableArray enabledRegular) { - if (!enabledRegular.Any( - p => p.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase) - || p.Name.Equals("Lua for Barotrauma", StringComparison.InvariantCultureIgnoreCase))) + if (!enabledRegular.Any(p => p.Name.Equals(PackageName, StringComparison.InvariantCultureIgnoreCase))) { - var luaCs = ContentPackageManager.AllPackages.FirstOrDefault( - p => p.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase) - || p.Name.Equals("Lua For Barotrauma", StringComparison.InvariantCultureIgnoreCase)); + var luaCs = ContentPackageManager.AllPackages.FirstOrDefault(p => p.Name.Equals(PackageName, StringComparison.InvariantCultureIgnoreCase)); if (luaCs is null) { - DebugConsole.ThrowError($"The 'LuaCsForBarotrauma' mod could not be found. Please subscribe to it and add it to the EnabledPackages List!", - new NullReferenceException($"The 'LuaCsForBarotrauma' mod could not be found. Please subscribe to it and add it to the EnabledPackages List!"), + DebugConsole.ThrowError($"The '{PackageName}' mod could not be found. Please subscribe to it and add it to the EnabledPackages List!", + new NullReferenceException($"The '{PackageName}' mod could not be found. Please subscribe to it and add it to the EnabledPackages List!"), createMessageBox: true); return enabledRegular; } @@ -383,6 +410,11 @@ namespace Barotrauma EventService.PublishEvent(static p => p.OnServerConnected()); } #endif + +#if SERVER + GameMain.Server.ServerSettings.LoadClientPermissions(); +#endif + CurrentRunState = RunState.Running; } @@ -390,9 +422,6 @@ namespace Barotrauma void RunStateRunning_OnExit(State currentState) { EventService.Call("stop"); -#if CLIENT - _isCsEnabledForSession = false; -#endif Logger.LogResults(PackageManagementService.StopRunningPackages()); Logger.LogMessage("LuaCs running state exited"); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs index 485690f8e..9b964cf65 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs @@ -315,24 +315,16 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService StringBuilder sb = new StringBuilder(); foreach (var resultDiagnostic in result.Diagnostics) { - if (resultDiagnostic.Severity != DiagnosticSeverity.Error) + if (resultDiagnostic.IsWarningAsError || resultDiagnostic.Severity == DiagnosticSeverity.Error) { - continue; + //sb.AppendLine($">>> {resultDiagnostic.GetMessage()} | Location: {resultDiagnostic.Location.SourceTree?.GetLineSpan(resultDiagnostic.Location.SourceSpan)} "); + sb.AppendLine($"\n{resultDiagnostic}"); } - sb.AppendLine($">>> {resultDiagnostic.GetMessage()} | Location: {resultDiagnostic.Location.SourceTree?.GetLineSpan(resultDiagnostic.Location.SourceSpan)} "); } var res = new FluentResults.Result().WithError( - new Error($"Package Error: {OwnerPackage.Name}: Compilation failed for assembly {assemblyName}! {sb.ToString()}\n") + new Error($"Package Error: {OwnerPackage.Name}: Compilation failed for assembly {assemblyName}!\n {sb.ToString()}\n") .WithMetadata(MetadataType.ExceptionObject, this) .WithMetadata(MetadataType.RootObject, syntaxTrees)); - var failuresDiag = - result.Diagnostics.Where(d => d.IsWarningAsError || d.Severity == DiagnosticSeverity.Error); - foreach (var diag in failuresDiag) - { - res = res.WithError(new Error(diag.GetMessage()) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.ExceptionDetails, diag.Descriptor.Description)); - } return res; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs index 442aa0185..b6a4732ec 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs @@ -239,7 +239,7 @@ public sealed partial class ConfigService : IConfigService return; } - if (setting.TrySetValue(valueString)) + if (setting.TrySetSerializedValue(valueString)) { _logger.LogMessage($"Set config {internalName} value to {valueString}", Color.Green); if (SaveConfigValue(setting) is { IsFailed: true } res) @@ -445,7 +445,7 @@ public sealed partial class ConfigService : IConfigService { if (_settingsInstances.TryGetValue((info.OwnerPackage, value.SettingName), out var instance)) { - instance.TrySetValue(value.Element); + instance.TrySetSerializedValue(value.Element); } } } @@ -495,7 +495,7 @@ public sealed partial class ConfigService : IConfigService return FluentResults.Result.Ok(); } - return FluentResults.Result.OkIf(setting.TrySetValue(cfgValueElement), new Error($"Failed to set value for [{setting.OwnerPackage.Name}.{setting.InternalName}]")); + return FluentResults.Result.OkIf(setting.TrySetSerializedValue(cfgValueElement), new Error($"Failed to set value for [{setting.OwnerPackage.Name}.{setting.InternalName}]")); } public FluentResults.Result LoadSavedConfigsValues() @@ -544,7 +544,7 @@ public sealed partial class ConfigService : IConfigService continue; } - if (!instance.TrySetValue(profileValue.Element)) + if (!instance.TrySetSerializedValue(profileValue.Element)) { result.WithError(new Error($"{nameof(ApplyConfigProfile)}: Failed to set value for [{profileValue.SettingName}].")); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs index e973d12f1..22eec4a69 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs @@ -24,14 +24,14 @@ public partial class EventService : IEventService public TypeStringKey(Type type) { Type = type ?? throw new ArgumentNullException(nameof(type)); - TypeName = type.Name; + TypeName = type.Name.ToLowerInvariant(); HashCode = TypeName.GetHashCode(); } public TypeStringKey(string typeName) { Type = null; - TypeName = typeName ?? throw new ArgumentNullException(nameof(typeName)); + TypeName = typeName?.ToLowerInvariant() ?? throw new ArgumentNullException(nameof(typeName)); HashCode = TypeName.GetHashCode(); } @@ -56,7 +56,7 @@ public partial class EventService : IEventService private readonly AsyncReaderWriterLock _operationsLock = new(); private readonly ConcurrentDictionary, IEvent>> _subscribers = new(); private readonly ConcurrentDictionary RunnerFactory)> _luaAliasEventFactory = new(); - private readonly ConcurrentDictionary> _luaLegacyEventsSubscribers = new(); + private readonly ConcurrentDictionary> _luaLegacyEventsSubscribers = new(); private readonly ConcurrentDictionary _subscribedEventDispatchers = new(); #region LifeCycle @@ -118,7 +118,7 @@ public partial class EventService : IEventService } else { - var eventSubs = _luaLegacyEventsSubscribers.GetOrAdd(eventName, key => new ConcurrentDictionary()); + var eventSubs = _luaLegacyEventsSubscribers.GetOrAdd(eventName, key => new ConcurrentDictionary()); eventSubs[identifier] = callback; } } @@ -200,6 +200,29 @@ public partial class EventService : IEventService } public void Remove(string eventName, string identifier) + { + Guard.IsNotNullOrWhiteSpace(eventName, nameof(eventName)); + Guard.IsNotNullOrWhiteSpace(identifier, nameof(identifier)); + + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + if (_luaAliasEventFactory.TryGetValue(eventName, out var eventFunc)) + { + if (_subscribers.TryGetValue(eventFunc.Event, out var eventSubs)) + { + eventSubs.TryRemove(identifier, out _); + } + } + else + { + if (_luaLegacyEventsSubscribers.TryGetValue(eventName, out var eventSubs)) + { + eventSubs.TryRemove(identifier, out _); + } + } + } + public void Unsubscribe(string eventName, string identifier) { Guard.IsNotNullOrWhiteSpace(eventName, nameof(eventName)); Guard.IsNotNullOrWhiteSpace(identifier, nameof(identifier)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs index 00e3536fa..fe54d9096 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs @@ -2,6 +2,7 @@ using Barotrauma.LuaCs; using Barotrauma.LuaCs.Events; using Barotrauma.Networking; +using Barotrauma.Steam; using HarmonyLib; using Microsoft.Xna.Framework; using System; @@ -27,16 +28,12 @@ internal class HarmonyEventPatchesService : ISystem private static ILoggerService _loggerService; private readonly Harmony Harmony; - private static int debugConsoleCommandVanillaIndex; - public HarmonyEventPatchesService(IEventService eventService, ILoggerService loggerService) { _eventService = eventService; _loggerService = loggerService; Harmony = new Harmony("LuaCsForBarotrauma.Events"); Patch(); - - debugConsoleCommandVanillaIndex = DebugConsole.Commands.Count; } private void Patch() @@ -56,7 +53,7 @@ internal class HarmonyEventPatchesService : ISystem [HarmonyPatch(typeof(CoroutineManager), nameof(CoroutineManager.Update)), HarmonyPostfix] public static void CoroutineManager_Update_Post() { - _eventService.PublishEvent(x => x.OnUpdate(Timing.TotalTime)); + _eventService.PublishEvent(x => x.OnUpdate(CoroutineManager.DeltaTime)); _loggerService.ProcessLogs(); } @@ -95,6 +92,27 @@ internal class HarmonyEventPatchesService : ISystem _eventService.PublishEvent(x => x.OnScreenSelected(__instance)); } +#if CLIENT + [HarmonyPatch(typeof(MainMenuScreen), "StartGame"), HarmonyPostfix] + public static void MainMenuScreen_StartGame_Pre(Screen __instance) + { + LuaCsSetup.Instance.SetRunState(RunState.Running); + } + + [HarmonyPatch(typeof(MainMenuScreen), "LoadGame"), HarmonyPostfix] + public static void MainMenuScreen_LoadGame_Pre(Screen __instance) + { + LuaCsSetup.Instance.SetRunState(RunState.Running); + } + + [HarmonyPatch(typeof(MutableWorkshopMenu), nameof(MutableWorkshopMenu.Apply)), HarmonyPostfix] + public static void MutableWorkshopMenu_Apply_Post(Screen __instance) + { + LuaCsSetup.Instance.PromptCSharpMods(selection => { }, joiningServer: false); + } + +#endif + [HarmonyPatch(typeof(ContentPackageManager.PackageSource), nameof(ContentPackageManager.PackageSource.Refresh)), HarmonyPostfix] public static void PackageSource_Refresh_Post() { @@ -122,11 +140,20 @@ internal class HarmonyEventPatchesService : ISystem #if CLIENT [HarmonyPatch(typeof(GameClient), "ReadDataMessage"), HarmonyPrefix] - public static void GameClient_ReadDataMessage_Pre(IReadMessage inc) + public static bool GameClient_ReadDataMessage_Pre(IReadMessage inc) { + int prevBitPosition = inc.BitPosition; ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte(); - _eventService.PublishEvent(x => x.OnReceivedServerNetMessage(inc, header)); - inc.BitPosition -= 8; // rewind so the game can read the message + bool? skip = null; + _eventService.PublishEvent(x => skip = x.OnReceivedServerNetMessage(inc, header) ?? skip); + + if (skip == true) + { + return false; + } + + inc.BitPosition = prevBitPosition; // rewind so the game can read the message + return true; } [HarmonyPatch(typeof(SubEditorScreen), nameof(SubEditorScreen.Select), new Type[] { }), HarmonyPostfix] @@ -146,7 +173,7 @@ internal class HarmonyEventPatchesService : ISystem { DebugConsole.Command c = DebugConsole.FindCommand(command.Value); - if (DebugConsole.Commands.IndexOf(c) >= debugConsoleCommandVanillaIndex) + if (DebugConsole.Commands.IndexOf(c) >= LuaCsSetup.DebugConsoleCommandVanillaIndex) { __result = true; return false; @@ -158,11 +185,21 @@ internal class HarmonyEventPatchesService : ISystem #elif SERVER [HarmonyPatch(typeof(GameServer), "ReadDataMessage"), HarmonyPrefix] - public static void GameServer_ReadDataMessage_Pre(NetworkConnection sender, IReadMessage inc) + public static bool GameServer_ReadDataMessage_Pre(NetworkConnection sender, IReadMessage inc) { + int prevBitPosition = inc.BitPosition; ClientPacketHeader header = (ClientPacketHeader)inc.ReadByte(); - _eventService.PublishEvent(x => x.OnReceivedClientNetMessage(inc, header, sender)); - inc.BitPosition -= 8; // rewind so the game can read the message + + bool? skip = null; + _eventService.PublishEvent(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] diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs index aeccae840..1c09be0e1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs @@ -12,8 +12,6 @@ namespace Barotrauma.LuaCs; public partial class LoggerService : ILoggerService { - public bool HideUserNames = true; - private List logSubscribers = []; private ConcurrentQueue logQueue = []; @@ -94,7 +92,7 @@ public partial class LoggerService : ILoggerService public void Log(string message, Color? color = null, ServerLog.MessageType messageType = ServerLog.MessageType.ServerMessage) { - if (HideUserNames && !Environment.UserName.IsNullOrEmpty()) + if (LuaCsSetup.Instance.HideUserNamesInLogs && !Environment.UserName.IsNullOrEmpty()) { message = message.Replace(Environment.UserName, "USERNAME"); } @@ -186,14 +184,13 @@ public partial class LoggerService : ILoggerService else { LogError($"FluentResults::IError: {error.Message}"); - } - - if (error.Reasons != null) - { - foreach (var reason in error.Reasons) + /*if (error.Reasons != null) { - LogError($" - {reason.Message}"); - } + foreach (var reason in error.Reasons) + { + LogError($" - {reason.Message}"); + } + }*/ } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs index e6d3df87a..25e5ab0b2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs @@ -20,9 +20,9 @@ public sealed class LuaCsInfoProvider : ILuaCsInfoProvider { get { - return ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageId), null) - ?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageId)) - ?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageId)); + return ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageName), null) + ?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageName)) + ?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(LuaCsSetup.PackageName)); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index adc0fd010..99032700d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -1,27 +1,19 @@ #nullable enable -using Barotrauma.LuaCs; using Barotrauma.LuaCs.Compatibility; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Events; using Barotrauma.Networking; using FluentResults; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.Toolkit.Diagnostics; -using MonoMod.RuntimeDetour; using MoonSharp.Interpreter; using MoonSharp.Interpreter.Interop; -using MoonSharp.Interpreter.Loaders; -using RestSharp.Validation; using System; -using System.Collections; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; @@ -285,6 +277,8 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, _eventService.RegisterLuaEventAlias("character.giveJobItems", nameof(IEventGiveCharacterJobItems.OnGiveCharacterJobItems)); _eventService.RegisterLuaEventAlias("character.CPRSuccess", nameof(IEventHumanCPRSuccess.OnCharacterCPRSuccess)); _eventService.RegisterLuaEventAlias("character.CPRFailed", nameof(IEventHumanCPRFailed.OnCharacterCPRFailed)); + _eventService.RegisterLuaEventAlias("human.CPRSuccess", nameof(IEventHumanCPRSuccess.OnCharacterCPRSuccess)); + _eventService.RegisterLuaEventAlias("human.CPRFailed", nameof(IEventHumanCPRFailed.OnCharacterCPRFailed)); _eventService.RegisterLuaEventAlias("character.applyDamage", nameof(IEventCharacterApplyDamage.OnCharacterApplyDamage)); _eventService.RegisterLuaEventAlias("character.applyAffliction", nameof(IEventCharacterApplyAffliction.OnCharacterApplyAffliction)); @@ -368,8 +362,10 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, UserData.RegisterType(typeof(IUserDataDescriptor)); UserData.RegisterType(typeof(INetworkingService)); UserData.RegisterType(typeof(ILuaConfigService)); + UserData.RegisterType(typeof(ILoggerService)); - //UserData.RegisterType(typeof(ISettingBase)); + UserData.RegisterType(typeof(ISettingBase)); + UserData.RegisterType(typeof(IDataInfo)); Type[] settingBaseTypes = [ typeof(ISettingBase), @@ -451,6 +447,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, _script.Globals["Networking"] = _networkingService; _script.Globals["trygetpackage"] = (string name, out ContentPackage package) => _packageManagementService.Value.TryGetLoadedPackageByName(name, out package); + _script.Globals["Logger"] = _loggerService; //_script.Globals["Steam"] = Steam; if (enableSandbox) @@ -517,6 +514,61 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, Table package = (Table)_script.Globals["package"]; package.Set("path", DynValue.FromObject(_script, packages)); +#if CLIENT + if (GameMain.NetworkMember is { IsClient: true }) + { + var startMessage = _networkingService.Start("_luastart"); + + var packagesToReport = ContentPackageManager.EnabledPackages.All + .Where(p => _packageManagementService.Value.PackageContainsAnyRunnableResource(p)) + .Where(p => !p.NameMatches(LuaCsSetup.PackageName)) + .ToList(); + + startMessage.WriteUInt16((UInt16)packagesToReport.Count()); + + foreach (var enabledPackage in packagesToReport) + { + var id = enabledPackage.UgcId; + string hash = enabledPackage.Hash.StringRepresentation ?? ""; + + startMessage.WriteString(enabledPackage.Name); + startMessage.WriteString(enabledPackage.ModVersion); + if (id.TryUnwrap(out ContentPackageId? packageId) && packageId is SteamWorkshopId steamId) + { + startMessage.WriteUInt64(steamId.Value); + } + else + { + startMessage.WriteUInt64(0); + } + startMessage.WriteString(hash); + } + + _networkingService.Send(startMessage); + } +#elif SERVER + _networkingService.Receive("_luastart", (message, client) => + { + var num = message.ReadUInt16(); + List packages = new List
(); + + for (int i = 0; i < num; i++) + { + Table table = new Table(_script); + + table.Set("Name", DynValue.NewString(message.ReadString())); + table.Set("Version", DynValue.NewString(message.ReadString())); + table.Set("Id", DynValue.NewString(message.ReadUInt64().ToString())); + table.Set("Hash", DynValue.NewString(message.ReadString())); + + packages.Add(table); + } + + _eventService.Call("client.packages", client, packages); + }); +#endif + + foreach (ILuaScriptResourceInfo resource in executionOrder.Where(l => l.IsAutorun)) { foreach (ContentPath filePath in resource.FilePaths) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/MainMenuPatch.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/MainMenuPatch.cs index 5dfdc794b..798a9a249 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/MainMenuPatch.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/MainMenuPatch.cs @@ -43,11 +43,32 @@ internal class MainMenuPatch : ISystem, IEventScreenSelected { if (mainMenuUIAdded) { return; } - new GUITextBlock(new RectTransform(new Point(300, 30), screen.Frame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(10, 10) }, $"Using LuaCsForBarotrauma revision {AssemblyInfo.GitRevision}", Color.Red) + var textBlock = new GUITextBlock(new RectTransform(new Point(300, 30), screen.Frame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(10, 10) }, "", Color.Red) { IgnoreLayoutGroups = false }; + textBlock.OnAddedToGUIUpdateList = (GUIComponent component) => + { + string mode = LuaCsSetup.Instance.CsRunPolicyValue; + + if (mode is "Prompt") + { + string sessionState = LuaCsSetup.Instance.IsCsEnabledForSession ? "yes" : "no"; + mode = $"enabled (prompt mode, allowed for this session: {sessionState})"; + } + else if (mode is "Enabled") + { + mode = "always enabled"; + } + else + { + mode = "disabled"; + } + + textBlock.Text = $"LuaCsForBarotrauma active (revision {AssemblyInfo.GitRevision}), C# is currently {mode}\nNew settings available in the game settings menu."; + }; + mainMenuUIAdded = true; } #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs index 980eac69f..e1dc53c7a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml.Linq; using Barotrauma.LuaCs.Data; @@ -60,8 +61,9 @@ public sealed partial class ModConfigFileParserService : if (CheckThrowNullRefs(src, "Assembly") is { IsFailed: true } fail) return fail; + var isScript = src.Element.GetAttributeBool("IsScript", false); var runtimeEnv = GetRuntimeEnvironment(src.Element); - var fileResults = await UnsafeGetCheckedFiles(src.Element, src.Owner, ".dll"); + var fileResults = await UnsafeGetCheckedFiles(src.Element, src.Owner, isScript ? ".cs" : ".dll"); if (fileResults.IsFailed) return FluentResults.Result.Fail(fileResults.Errors); @@ -78,11 +80,31 @@ public sealed partial class ModConfigFileParserService : RequiredPackages = src.Required, IncompatiblePackages = src.Incompatible, // Type Specific - FriendlyName = src.Element.GetAttributeString("FriendlyName", string.Empty), - IsScript = src.Element.GetAttributeBool("IsScript", false), + FriendlyName = src.Element.GetAttributeString("FriendlyName", GetFallbackCompliantAssemblyName(src.Owner)), + IsScript = isScript, UseInternalAccessName = src.Element.GetAttributeBool("UseInternalAccessName", false), IsReferenceModeOnly = src.Element.GetAttributeBool("IsReferenceModeOnly", false) }; + + + // helper methods + string GetFallbackCompliantAssemblyName(ContentPackage package) + { + if (package.Name.IsNullOrWhiteSpace()) + { + return "FallbackAssemblyName"; + } + + // replace non az chars with '_' + var sanitizedPackageName = Regex.Replace(package.Name, @"[^a-zA-Z0-9_]", "_"); + if (char.IsDigit(sanitizedPackageName[0])) + { + sanitizedPackageName = "ASM" + sanitizedPackageName; + } + + // replace consecutive '_' + return Regex.Replace(sanitizedPackageName, @"[_.]{2,}", "_"); + } } async Task>> IParserServiceAsync.TryParseResourcesAsync(IEnumerable sources) @@ -152,7 +174,8 @@ public sealed partial class ModConfigFileParserService : RequiredPackages = src.Required, IncompatiblePackages = src.Incompatible, // Type Specific - IsAutorun = src.Element.GetAttributeBool("IsAutorun", false) + IsAutorun = src.Element.GetAttributeBool("IsAutorun", false), + RunUnrestricted = src.Element.GetAttributeBool("RunUnrestricted", false) }; } @@ -184,7 +207,7 @@ public sealed partial class ModConfigFileParserService : var res = new FluentResults.Result>(); - if (!filePath.IsNullOrWhiteSpace()) + if ((!filePath?.Value.IsNullOrWhiteSpace()) ?? false) { if (_storageService.FileExists(filePath.FullPath) is { IsSuccess: true, Value: true }) { @@ -203,11 +226,12 @@ public sealed partial class ModConfigFileParserService : } } - if (!folderPath.IsNullOrWhiteSpace()) + if ((!folderPath?.Value.IsNullOrWhiteSpace()) ?? false) { if (_storageService.DirectoryExists(folderPath.FullPath) is { IsSuccess: true, Value: true }) { - var files = _storageService.FindFilesInPackage(srcOwner, folderPath.Value, fileExtension, true); + var searchLocation = System.IO.Path.GetRelativePath(srcOwner.Dir, folderPath.Value); + var files = _storageService.FindFilesInPackage(srcOwner, searchLocation, "*"+fileExtension, true); if (files.IsFailed) { res.WithError($"{srcOwner.Name}: Failed to load files from {folderPath}!"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs index 3882a0175..d7278c72b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs @@ -335,10 +335,10 @@ public sealed class ModConfigService : IModConfigService .Select(fp => ContentPath.FromRaw(srcPackage, $"%ModDir%/{Path.GetRelativePath(srcPackage.Dir, fp)}".CleanUpPathCrossPlatform())) .Concat(sharedFiles).ToImmutableArray(), - FriendlyName = IAssemblyLoaderService.InternalsAwareAssemblyName, + FriendlyName = IAssemblyLoaderService.InternalsAwareAssemblyName, // give the best chance of success (InternalsAware + Publicizer) IncompatiblePackages = ImmutableArray.Empty, RequiredPackages = ImmutableArray.Empty, - UseInternalAccessName = true, + UseInternalAccessName = false, //compile as public and then fallback to internals IsScript = true, IsReferenceModeOnly = false }); @@ -357,7 +357,7 @@ public sealed class ModConfigService : IModConfigService FriendlyName = IAssemblyLoaderService.InternalsAwareAssemblyName, IncompatiblePackages = ImmutableArray.Empty, RequiredPackages = ImmutableArray.Empty, - UseInternalAccessName = true, + UseInternalAccessName = false, IsScript = true, IsReferenceModeOnly = false }); @@ -405,6 +405,7 @@ public sealed class ModConfigService : IModConfigService IncompatiblePackages = ImmutableArray.Empty, RequiredPackages = ImmutableArray.Empty, IsAutorun = true, + RunUnrestricted = false }); builder.Add(new LuaScriptsResourceInfo() @@ -418,6 +419,7 @@ public sealed class ModConfigService : IModConfigService IncompatiblePackages = ImmutableArray.Empty, RequiredPackages = ImmutableArray.Empty, IsAutorun = false, + RunUnrestricted = false }); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs index 589f5c650..14b0d141f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs @@ -345,6 +345,8 @@ public sealed class PackageManagementService : IPackageManagementService //lua scripts var luaScripts = SelectCompatible(loadingOrderedPackages + .Where(pkg => executeCsAssemblies + || !pkg.Value.LuaScripts.Any(scr => scr.RunUnrestricted)) .SelectMany(pkg => pkg.Value.LuaScripts) .ToImmutableArray(), toLoadPackagesIndents, loadOrderByPackage); @@ -357,11 +359,7 @@ public sealed class PackageManagementService : IPackageManagementService { _runningPackages[package.Key] = package.Value; } - - if (result.IsFailed) - { - _logger.LogResults(result); - } + return result; } @@ -517,14 +515,44 @@ public sealed class PackageManagementService : IPackageManagementService return !_runningPackages.IsEmpty; } - public ImmutableArray GetLoadedAssemblyPackages() + public ImmutableArray GetLoadedUnrestrictedPackages() { using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); if (_loadedPackages.IsEmpty) return ImmutableArray.Empty; return [.._loadedPackages.Values - .Where(cfg => !cfg.Assemblies.IsDefaultOrEmpty) + .Where(cfg => !cfg.Assemblies.IsDefaultOrEmpty || cfg.LuaScripts.Any(scr => scr.RunUnrestricted)) .Select(cfg => cfg.Package)]; } + + public bool PackageContainsAnyRunnableResource(ContentPackage package) + { + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + var result = GetModConfigForPackage(package); + + if (result.IsSuccess) + { + return result.Value.Assemblies.Any() || result.Value.LuaScripts.Any(); + } + else + { + return false; + } + } + + public Result 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().WithValue(modConfig); + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index 7a0a52745..30210fc6f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -87,6 +87,9 @@ public class PluginManagementService : IAssemblyManagementService ScriptParseOptions); private ImmutableArray _baseMetadataReferences = ImmutableArray.Empty; + private ImmutableArray _baseMetadataReferencesNonPublicized = ImmutableArray.Empty; + + private IEnumerable BaseMetadataReferences { get @@ -110,6 +113,25 @@ public class PluginManagementService : IAssemblyManagementService } } + private IEnumerable BaseMetadataReferencesWithBarotrauma + { + get + { + if (_baseMetadataReferencesNonPublicized.IsDefaultOrEmpty) + { + _baseMetadataReferencesNonPublicized = Basic.Reference.Assemblies.Net80.References.All + .Union(AssemblyLoadContext.Default.Assemblies + .Where(ass => !ass.IsDynamic) + .Where(ass => !ass.Location.IsNullOrWhiteSpace()) + .Select(MetadataReference (ass) => MetadataReference.CreateFromFile(ass.Location))) + .Where(ar => ar is not null) + .ToImmutableArray(); + } + + return _baseMetadataReferencesNonPublicized; + } + } + #endregion #region Disposal @@ -129,6 +151,7 @@ public class PluginManagementService : IAssemblyManagementService _logger = null; _configService = null; _luaScriptManagementService = null; + _luaCsInfoProvider = null; GC.SuppressFinalize(this); } @@ -199,6 +222,7 @@ public class PluginManagementService : IAssemblyManagementService private IEventService _pluginEventService; private Lazy _pluginLuaPatcherService; private Func _consoleCommandServiceFactory; + private ILuaCsInfoProvider _luaCsInfoProvider; private readonly ConcurrentDictionary _assemblyLoaders = new(); private readonly ConcurrentDictionary _pluginPackageLookup = new(); private readonly ConcurrentDictionary> _pluginInstances = new(); @@ -215,7 +239,8 @@ public class PluginManagementService : IAssemblyManagementService Lazy luaScriptManagementService, Lazy configService, Lazy pluginLuaPatcherService, - Func consoleCommandServiceFactory) + Func consoleCommandServiceFactory, + ILuaCsInfoProvider luaCsInfoProvider) { _assemblyLoaderFactory = assemblyLoaderFactory; _storageService = storageService; @@ -225,6 +250,7 @@ public class PluginManagementService : IAssemblyManagementService _configService = configService; _pluginLuaPatcherService = pluginLuaPatcherService; _consoleCommandServiceFactory = consoleCommandServiceFactory; + _luaCsInfoProvider = luaCsInfoProvider; } private ServiceContainer CreatePluginServiceContainer() @@ -485,6 +511,12 @@ public class PluginManagementService : IAssemblyManagementService } using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); + + _storageService.UseCaching = _luaCsInfoProvider.UseCaching; + if (!_luaCsInfoProvider.UseCaching) + { + _storageService.PurgeCache(); + } var orderedContentPacks = resources.GroupBy(res => res.OwnerPackage) .OrderBy(res => resources.FindIndex(r2 => r2.OwnerPackage == res.Key)) @@ -554,7 +586,8 @@ public class PluginManagementService : IAssemblyManagementService return; } - var metadataReferences = GetMetadataReferences(); + var metadataReferences = GetMetadataReferences(false).ToImmutableArray(); + var metadataReferencesNonPublicized = GetMetadataReferences(true).ToImmutableArray(); var assemblyLoader = _assemblyLoaders.GetOrAdd(contentPackRes.Key, (cp) => _assemblyLoaderFactory.CreateInstance( new IAssemblyLoaderService.LoaderInitData( @@ -578,7 +611,10 @@ public class PluginManagementService : IAssemblyManagementService foreach (var resourceInfo in scripts) { - if (!hasInternalsAwareBeenAdded && resourceInfo.UseInternalAccessName) + // this should be the same for the entire collection of src files so we just grab it from the collection + compileWithInternalName = resourceInfo.UseInternalAccessName; + + if (!hasInternalsAwareBeenAdded) { hasInternalsAwareBeenAdded = true; syntaxTreesBuilder.Add(BaseAssemblyImports); @@ -597,9 +633,6 @@ public class PluginManagementService : IAssemblyManagementService _logger.LogResults(loadRes.ToResult()); continue; } - - // this should be the same for the entire collection of src files so we just grab it from the collection - compileWithInternalName = resourceInfo.UseInternalAccessName; CancellationToken token = CancellationToken.None; @@ -622,14 +655,35 @@ public class PluginManagementService : IAssemblyManagementService } _logger.LogMessage($"Compiling assembly for {scripts.Key}, in ContentPackage {contentPackRes.Key.Name}"); - - result.WithReasons(assemblyLoader.CompileScriptAssembly( + + var res = assemblyLoader.CompileScriptAssembly( assemblyName: scripts.Key, compileWithInternalAccess: compileWithInternalName, syntaxTrees: syntaxTreesBuilder.ToImmutable(), - metadataReferences: metadataReferences.ToImmutableArray(), - compilationOptions: CompilationOptions) - .Reasons); + metadataReferences: compileWithInternalName ? metadataReferencesNonPublicized : metadataReferences, + compilationOptions: CompilationOptions); + + // try with internal access instead for legacy mods + if (!compileWithInternalName && res.IsFailed) + { + _logger.LogMessage($"Attempted compilation of {scripts.Key} for package {contentPackRes.Key.Name}. Trying fallback method."); + var res2 = assemblyLoader.CompileScriptAssembly( + assemblyName: scripts.Key, + compileWithInternalAccess: true, + syntaxTrees: syntaxTreesBuilder.ToImmutable(), + metadataReferences: metadataReferencesNonPublicized, + compilationOptions: CompilationOptions); + + // overwrite result with good compilation + if (res2.IsSuccess) + { + var reasonsStr = res.Reasons.Aggregate("", (accum, reason) => accum + "\n" + reason.Message); + _logger.LogWarning($"Attempted compilation of {scripts.Key} for package {contentPackRes.Key.Name} succeeded. Original errors were: \n {reasonsStr}"); + res = res2; + } + } + + result.WithReasons(res.Reasons); } } @@ -644,14 +698,28 @@ public class PluginManagementService : IAssemblyManagementService return res; } - IEnumerable GetMetadataReferences() + IEnumerable GetMetadataReferences(bool useNonPublicizedAssemblies) { var builder = ImmutableArray.CreateBuilder(); - builder.AddRange(BaseMetadataReferences); - foreach (var loaderService in _assemblyLoaders) + if (useNonPublicizedAssemblies) { - builder.AddRange(loaderService.Value.AssemblyReferences.Where(ar => ar is not null)); + builder.AddRange(BaseMetadataReferencesWithBarotrauma); + foreach (var loaderService in _assemblyLoaders + .Where(asl => !asl.Key.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase)) + .ToImmutableArray()) + { + builder.AddRange(loaderService.Value.AssemblyReferences.Where(ar => ar is not null)); + } } + else + { + builder.AddRange(BaseMetadataReferences); + foreach (var loaderService in _assemblyLoaders) + { + builder.AddRange(loaderService.Value.AssemblyReferences.Where(ar => ar is not null)); + } + } + return builder.ToImmutable(); } } @@ -768,6 +836,7 @@ public class PluginManagementService : IAssemblyManagementService } _assemblyLoaders.Clear(); + _storageService.PurgeCache(); GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true); #if DEBUG diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILoggerService.cs index c5ac48768..7f2c3ed6e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILoggerService.cs @@ -34,4 +34,26 @@ public interface ILoggerService : IReusableService void LogDebugError(string message); #endregion + + #region LegacyCompat_LuaCsLogger + + public void HandleException(Exception ex, LuaCsMessageOrigin origin) + { + HandleException(ex, origin.ToString()); + } + + public void LogError(string message, LuaCsMessageOrigin origin) + { + LogError(message); + } + + #endregion +} + +public enum LuaCsMessageOrigin +{ + LuaCs, + Unknown, + LuaMod, + CSharpMod, } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPackageManagementService.cs index 8d050b3e0..568e66078 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPackageManagementService.cs @@ -22,8 +22,10 @@ public interface IPackageManagementService : IReusableService public FluentResults.Result UnloadPackages(ImmutableArray packages); public FluentResults.Result UnloadAllPackages(); public ImmutableArray GetAllLoadedPackages(); - public ImmutableArray GetLoadedAssemblyPackages(); + public ImmutableArray GetLoadedUnrestrictedPackages(); public bool IsPackageRunning(ContentPackage package); public bool IsAnyPackageLoaded(); public bool IsAnyPackageRunning(); + public bool PackageContainsAnyRunnableResource(ContentPackage package); + public Result GetModConfigForPackage(ContentPackage package); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs index 69107eb60..44e35bbdc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs @@ -154,6 +154,13 @@ public class DefaultLuaRegistrar : IDefaultLuaRegistrar _userDataService.RegisterType("FarseerPhysics.Collision.ReferenceFace"); _userDataService.RegisterType("FarseerPhysics.Collision.Collision"); + _userDataService.RegisterType("Voronoi2.DoubleVector2"); + _userDataService.RegisterType("Voronoi2.Site"); + _userDataService.RegisterType("Voronoi2.Edge"); + _userDataService.RegisterType("Voronoi2.Halfedge"); + _userDataService.RegisterType("Voronoi2.VoronoiCell"); + _userDataService.RegisterType("Voronoi2.GraphEdge"); + _userDataService.RegisterType("Barotrauma.PrefabCollection`1"); _userDataService.RegisterType("Barotrauma.PrefabSelector`1"); _userDataService.RegisterType("Barotrauma.Pair`2"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaEventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaEventService.cs index ae789afe5..3e6ed307c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaEventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaEventService.cs @@ -19,7 +19,7 @@ public interface ILuaSafeEventService : ILuaService, ILuaCsHook /// /// /// - void Remove(string eventName, string identifier); + void Unsubscribe(string eventName, string identifier); /// /// Send an event to all subscribers to an interface. /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs index b8533c663..d46b6ec27 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs @@ -2,16 +2,10 @@ using Barotrauma.Networking; using Microsoft.Xna.Framework; using MoonSharp.Interpreter; +using Barotrauma.LuaCs; namespace Barotrauma { - public enum LuaCsMessageOrigin - { - LuaCs, - Unknown, - LuaMod, - CSharpMod, - } public partial class LuaCsLogger { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs index 07f528c9f..75bc2a3dc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs @@ -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(); + } + return (string[][])validArgs; + } + ); + } public bool IsDisposed => throw new NotImplementedException(); - public void AssignOnExecute(string names, LuaCsAction onExecute) => - _consoleCommands.AssignOnExecute(names, args => onExecute(args)); + public void AssignOnExecute(string names, object onExecute) => DebugConsole.AssignOnExecute(names, (string[] args) => + { + LuaCsSetup.Instance.LuaScriptManagementService.CallFunctionSafe(onExecute, new object[] { args }); + }); public void SaveGame(string path) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs index 21a71b22b..ec3bafffc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelTrigger.cs @@ -816,7 +816,7 @@ namespace Barotrauma public static float GetDistanceFactor(PhysicsBody triggererBody, PhysicsBody triggerBody, float colliderRadius) { - return 1.0f - ConvertUnits.ToDisplayUnits(Vector2.Distance(triggererBody.SimPosition, triggerBody.SimPosition)) / colliderRadius; + return 1.0f - ConvertUnits.ToDisplayUnits(Vector2.Distance(triggererBody.SimPosition, triggerBody.SimPosition) - triggererBody.GetMaxExtent() / 2) / colliderRadius; } public Vector2 GetWaterFlowVelocity(Vector2 viewPosition) diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index 3c6b19da0..1e029d35c 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,4 +1,17 @@ ------------------------------------------------------------------------------------------------------------------------------------------------- +v1.12.7.0 +------------------------------------------------------------------------------------------------------------------------------------------------- + +- Reduced how much the new weak points in the reworked subs push bots around to make them more capable of fixing broken weak points. +- Fixed selecting any item that forces the character to some pose (chairs, periscopes) getting logged in the server console. +- Mac only: added a button for settings mic permissions to the audio settings. It seems that on Mac, the game updates may cause the OS to revoke the permissions. +- Fixed some of the Workshop tags you can choose in-game not working on Steam's side. + +Modding: +- Fixed contained items being misaligned on attachable items (e.g. in mods that make diving suit cabinets attachable). +- Fixed monsters spawned by an event inside an outpost being unable to attack any items inside that outpost. To our knowledge, didn't affect vanilla events, but caused issues in certain mods. + +------------------------------------------------------------------------------------------------------------------------------------------------- v1.12.6.2 ------------------------------------------------------------------------------------------------------------------------------------------------- @@ -439,7 +452,6 @@ v1.8.8.1 Modding: - Fixed transferring afflictions to a newly spawned character using status effects causing a crash if the original character had already been removed. Didn't affect any vanilla content. ->>>>>>> master ------------------------------------------------------------------------------------------------------------------------------------------------- v1.8.7.0 diff --git a/README.md b/README.md index 8aa2e40cf..dfde2babb 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This is a Barotrauma modification that adds Lua and Cs modding support. # Barotrauma -Copyright © FakeFish Ltd 2017-2024 +Copyright © FakeFish Ltd 2017-2026 Before downloading the source code, please read the [EULA](EULA.txt).