From 886eebdbb2bf659fb3b117cf7265488746ce8a50 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Tue, 23 Dec 2025 20:47:40 -0300 Subject: [PATCH 001/288] Fix memory leak that happens when you press retry in singleplayer --- .../BarotraumaShared/SharedSource/GameSession/GameSession.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index ffe92f4fa..f3782f134 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -408,6 +408,7 @@ namespace Barotrauma public void LoadPreviousSave() { + GameMain.LuaCs.Hook.Call("roundEnd"); AchievementManager.OnRoundEnded(this, roundInterrupted: true); Submarine.Unload(); SaveUtil.LoadGame(DataPath); From 9f1c3fa8239fef0cd274130799030e3e21db5e15 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 31 Jan 2026 17:44:36 -0300 Subject: [PATCH 002/288] Move UserData checks out of Lua --- Barotrauma/BarotraumaShared/Lua/LuaSetup.lua | 5 +- .../BarotraumaShared/Lua/LuaUserData.lua | 97 --------- Barotrauma/BarotraumaShared/Lua/PostSetup.lua | 84 +------- .../LuaCs/Lua/LuaClasses/LuaSafeUserData.cs | 196 ++++++++++++++++++ .../LuaCs/Lua/LuaClasses/LuaUserData.cs | 72 ++++++- .../SharedSource/LuaCs/LuaCsSetup.cs | 5 +- 6 files changed, 279 insertions(+), 180 deletions(-) delete mode 100644 Barotrauma/BarotraumaShared/Lua/LuaUserData.lua create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaSafeUserData.cs diff --git a/Barotrauma/BarotraumaShared/Lua/LuaSetup.lua b/Barotrauma/BarotraumaShared/Lua/LuaSetup.lua index 05163a0dd..fc970c3b6 100644 --- a/Barotrauma/BarotraumaShared/Lua/LuaSetup.lua +++ b/Barotrauma/BarotraumaShared/Lua/LuaSetup.lua @@ -7,7 +7,7 @@ package.path = {path .. "/?.lua"} setmodulepaths(package.path) -- Setup Libraries -require("LuaUserData") +LuaSetup.LuaUserData = LuaUserData require("DefaultRegister/RegisterShared") @@ -35,9 +35,6 @@ AddTableToGlobal(require("CompatibilityLib")) require("DefaultHook") -Descriptors = LuaSetup.LuaUserData.Descriptors -LuaUserData = LuaSetup.LuaUserData - require("DefaultLib/Utils/Math") require("DefaultLib/Utils/String") require("DefaultLib/Utils/Util") diff --git a/Barotrauma/BarotraumaShared/Lua/LuaUserData.lua b/Barotrauma/BarotraumaShared/Lua/LuaUserData.lua deleted file mode 100644 index 387fc7f2b..000000000 --- a/Barotrauma/BarotraumaShared/Lua/LuaUserData.lua +++ /dev/null @@ -1,97 +0,0 @@ -local clrLuaUserData = LuaUserData -local luaUserData = {} - -luaUserData.Descriptors = {} - -LuaSetup.LuaUserData = luaUserData - -luaUserData.IsRegistered = clrLuaUserData.IsRegistered -luaUserData.UnregisterType = clrLuaUserData.UnregisterType -luaUserData.RegisterGenericType = clrLuaUserData.RegisterGenericType -luaUserData.RegisterExtensionType = clrLuaUserData.RegisterExtensionType -luaUserData.UnregisterGenericType = clrLuaUserData.UnregisterGenericType -luaUserData.IsTargetType = clrLuaUserData.IsTargetType -luaUserData.TypeOf = clrLuaUserData.TypeOf -luaUserData.GetType = clrLuaUserData.GetType -luaUserData.CreateEnumTable = clrLuaUserData.CreateEnumTable -luaUserData.MakeFieldAccessible = clrLuaUserData.MakeFieldAccessible -luaUserData.MakeMethodAccessible = clrLuaUserData.MakeMethodAccessible -luaUserData.MakePropertyAccessible = clrLuaUserData.MakePropertyAccessible -luaUserData.AddMethod = clrLuaUserData.AddMethod -luaUserData.AddField = clrLuaUserData.AddField -luaUserData.RemoveMember = clrLuaUserData.RemoveMember -luaUserData.CreateUserDataFromDescriptor = clrLuaUserData.CreateUserDataFromDescriptor -luaUserData.CreateUserDataFromType = clrLuaUserData.CreateUserDataFromType -luaUserData.HasMember = clrLuaUserData.HasMember - -luaUserData.RegisterType = function(typeName) - local success, result = pcall(clrLuaUserData.RegisterType, typeName) - - if not success then - error(result, 2) - end - - luaUserData.Descriptors[typeName] = result - - return result -end - -luaUserData.RegisterTypeBarotrauma = function(typeName) - typeName = "Barotrauma." .. typeName - local success, result = pcall(luaUserData.RegisterType, typeName) - - if not success then - error(result, 2) - end - - return result -end - -luaUserData.AddCallMetaTable = function (userdata) - if userdata == nil then - error("Attempted to add a call metatable to a nil value.", 2) - end - - if not LuaUserData.HasMember(userdata, ".ctor") then - error("Attempted to add a call metatable to a userdata that does not have a constructor.", 2) - end - - debug.setmetatable(userdata, { - __call = function(obj, ...) - if userdata == nil then - error("userdata was nil.", 2) - end - - local success, result = pcall(userdata.__new, ...) - - - if not success then - error(result, 2) - end - - return result - end - }) -end - -luaUserData.CreateStatic = function(typeName) - if type(typeName) ~= "string" then - error("Expected a string for typeName, got " .. type(typeName) .. ".", 2) - end - - local success, result = pcall(clrLuaUserData.CreateStatic, typeName) - - if not success then - error(result, 2) - end - - if result == nil then - return - end - - if LuaUserData.HasMember(result, ".ctor") then - luaUserData.AddCallMetaTable(result) - end - - return result -end \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/Lua/PostSetup.lua b/Barotrauma/BarotraumaShared/Lua/PostSetup.lua index f54417259..d18dbc685 100644 --- a/Barotrauma/BarotraumaShared/Lua/PostSetup.lua +++ b/Barotrauma/BarotraumaShared/Lua/PostSetup.lua @@ -1,75 +1,13 @@ -if CSActive then - return +if not CSActive then + LuaUserDataIUUD = LuaUserData.RegisterType("Barotrauma.LuaSafeUserData") + LuaUserData = LuaUserData.CreateStatic("Barotrauma.LuaSafeUserData"); + + for k, v in pairs(debug) do + if k ~= "getmetatable" and k ~= "setmetatable" and k ~= "traceback" then + debug[k] = nil + end + end end -local function IsAllowed(typeName) - if string.startsWith(typeName, "Barotrauma.Lua") or string.startsWith(typeName, "Barotrauma.Cs") or string.startsWith(typeName, "Barotrauma.LuaCs") then - return false - end - - if string.startsWith(typeName, "System.Collections") then return true end - - if string.startsWith(typeName, "Microsoft.Xna") then return true end - - if string.startsWith(typeName, "Barotrauma.IO") then return false end - - if string.startsWith(typeName, "Barotrauma.ToolBox") then return false end - if string.startsWith(typeName, "Barotrauma.SaveUtil") then return false end - - if string.startsWith(typeName, "Barotrauma.") then return true end - - return false -end - -local function CanBeReRegistered(typeName) - if string.startsWith(typeName, "Barotrauma.Lua") or string.startsWith(typeName, "Barotrauma.Cs") or string.startsWith(typeName, "Barotrauma.LuaCs") then - return false - end - - return true -end - -local originalRegisterType = LuaUserData.RegisterType -LuaUserData.RegisterType = function (typeName) - if not (CanBeReRegistered(typeName) and LuaUserData.IsRegistered(typeName)) and not IsAllowed(typeName) then - error("Couldn't register type " .. typeName .. ".", 2) - end - - local success, result = pcall(originalRegisterType, typeName) - - if not success then - error(result, 2) - end - - return result -end - -local originalRegisterGenericType = LuaUserData.RegisterType -LuaUserData.RegisterGenericType = function (typeName, ...) - if not (CanBeReRegistered(typeName) and LuaUserData.IsRegistered(typeName)) and not IsAllowed(typeName) then - error("Couldn't register generic type " .. typeName .. ".", 2) - end - - local success, result = pcall(originalRegisterGenericType, typeName, ...) - - if not success then - error(result, 2) - end - - return result -end - -local originalCreateStatic = LuaUserData.CreateStatic -LuaUserData.CreateStatic = function (typeName, addCallMethod) - if not (CanBeReRegistered(typeName) and LuaUserData.IsRegistered(typeName)) and not IsAllowed(typeName) then - error("Couldn't create static type " .. typeName .. ".", 2) - end - - local success, result = pcall(originalCreateStatic, typeName, addCallMethod) - - if not success then - error(result, 2) - end - - return result -end \ No newline at end of file +Descriptors = LuaUserData.__new() +LuaUserDataIUUD = nil \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaSafeUserData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaSafeUserData.cs new file mode 100644 index 000000000..a7ba597fa --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaSafeUserData.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reflection; +using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Interop; + +namespace Barotrauma +{ + partial class LuaSafeUserData + { + public IUserDataDescriptor this[string index] + { + get => LuaUserData.Descriptors.GetValueOrDefault(index); + } + + private static bool CanBeRegistered(string typeName) + { + if (typeName.StartsWith("Barotrauma.Lua", StringComparison.Ordinal) || + typeName.StartsWith("Barotrauma.Cs", StringComparison.Ordinal) || + typeName.StartsWith("Barotrauma.LuaCs", StringComparison.Ordinal)) + { + return false; + } + + if (typeName == "System.Single") { return true; } + + if (typeName.StartsWith("System.Collections", StringComparison.Ordinal)) + return true; + + if (typeName.StartsWith("Microsoft.Xna", StringComparison.Ordinal)) + return true; + + if (typeName.StartsWith("Barotrauma.IO", StringComparison.Ordinal)) + return false; + + if (typeName.StartsWith("Barotrauma.ToolBox", StringComparison.Ordinal)) + return false; + + if (typeName.StartsWith("Barotrauma.SaveUtil", StringComparison.Ordinal)) + return false; + + if (typeName.StartsWith("Barotrauma.", StringComparison.Ordinal)) + return true; + + return false; + } + + private static bool CanBeReRegistered(string typeName) + { + if (typeName.StartsWith("Barotrauma.Lua", StringComparison.Ordinal) || + typeName.StartsWith("Barotrauma.Cs", StringComparison.Ordinal) || + typeName.StartsWith("Barotrauma.LuaCs", StringComparison.Ordinal)) + { + return false; + } + + return true; + } + + private static bool IsAllowed(string typeName) + { + if (!CanBeReRegistered(typeName) && LuaUserData.IsRegistered(typeName)) + { + return false; + } + + if (!CanBeRegistered(typeName)) + { + return false; + } + + return true; + } + + private static void CheckAllowed(string typeName) + { + if (!IsAllowed(typeName)) + { + throw new ScriptRuntimeException($"Type {typeName} can't be registered"); + } + } + + public static Type GetType(string typeName) + { + CheckAllowed(typeName); + + return LuaUserData.GetType(typeName); + } + + public static IUserDataDescriptor RegisterType(string typeName) + { + CheckAllowed(typeName); + + return LuaUserData.RegisterType(typeName); + } + + public static IUserDataDescriptor RegisterTypeBarotrauma(string typeName) + { + return RegisterType($"Barotrauma.{typeName}"); + } + + public static void RegisterExtensionType(string typeName) + { + CheckAllowed(typeName); + LuaUserData.RegisterExtensionType(typeName); + } + + public static bool IsRegistered(string typeName) + { + return LuaUserData.IsRegistered(typeName); + } + + public static void UnregisterType(string typeName, bool deleteHistory = false) + { + LuaUserData.UnregisterType(typeName, deleteHistory); + } + public static IUserDataDescriptor RegisterGenericType(string typeName, params string[] typeNameArguements) + { + CheckAllowed(typeName); + return LuaUserData.RegisterGenericType(typeName, typeNameArguements); + } + + public static void UnregisterGenericType(string typeName, params string[] typeNameArguements) + { + LuaUserData.UnregisterGenericType(typeName, typeNameArguements); + } + + public static bool IsTargetType(object obj, string typeName) + { + return LuaUserData.IsTargetType(obj, typeName); + } + + public static string TypeOf(object obj) + { + return LuaUserData.TypeOf(obj); + } + + public static object CreateStatic(string typeName) + { + CheckAllowed(typeName); + return LuaUserData.CreateStatic(typeName); + } + + public static object CreateEnumTable(string typeName) + { + return LuaUserData.CreateEnumTable(typeName); + } + + public static void MakeFieldAccessible(IUserDataDescriptor IUUD, string fieldName) + { + LuaUserData.MakeFieldAccessible(IUUD, fieldName); + } + + public static void MakeMethodAccessible(IUserDataDescriptor IUUD, string methodName, string[] parameters = null) + { + LuaUserData.MakeMethodAccessible(IUUD, methodName, parameters); + } + + public static void MakePropertyAccessible(IUserDataDescriptor IUUD, string propertyName) + { + LuaUserData.MakePropertyAccessible(IUUD, propertyName); + } + + public static void AddMethod(IUserDataDescriptor IUUD, string methodName, object function) + { + LuaUserData.AddMethod(IUUD, methodName, function); + } + + public static void AddField(IUserDataDescriptor IUUD, string fieldName, DynValue value) + { + LuaUserData.AddField(IUUD, fieldName, value); + } + + public static void RemoveMember(IUserDataDescriptor IUUD, string memberName) + { + LuaUserData.RemoveMember(IUUD, memberName); + } + + public static bool HasMember(object obj, string memberName) + { + return LuaUserData.HasMember(obj, memberName); + } + + public static DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor desiredTypeDescriptor) + { + return LuaUserData.CreateUserDataFromDescriptor(scriptObject, desiredTypeDescriptor); + } + + public static DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType) + { + return LuaUserData.CreateUserDataFromType(scriptObject, desiredType); + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs index a1e53d8eb..fd336066f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs @@ -1,14 +1,24 @@ -using System; +using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Interop; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Reflection; -using MoonSharp.Interpreter; -using MoonSharp.Interpreter.Interop; namespace Barotrauma { partial class LuaUserData { + public static ReadOnlyDictionary Descriptors => new ReadOnlyDictionary(descriptors); + private static ConcurrentDictionary descriptors = new ConcurrentDictionary(); + + public IUserDataDescriptor this[string index] + { + get => Descriptors.GetValueOrDefault(index); + } + public static Type GetType(string typeName) => LuaCsSetup.GetType(typeName); public static IUserDataDescriptor RegisterType(string typeName) @@ -20,7 +30,15 @@ namespace Barotrauma throw new ScriptRuntimeException($"tried to register a type that doesn't exist: {typeName}."); } - return UserData.RegisterType(type); + var descriptor = UserData.RegisterType(type); + descriptors.TryAdd(typeName, descriptor); + + return descriptor; + } + + public static IUserDataDescriptor RegisterTypeBarotrauma(string typeName) + { + return RegisterType($"Barotrauma.{typeName}"); } public static void RegisterExtensionType(string typeName) @@ -102,7 +120,9 @@ namespace Barotrauma MethodInfo method = typeof(UserData).GetMethod(nameof(UserData.CreateStatic), 1, new Type[0]); MethodInfo generic = method.MakeGenericMethod(type); - return generic.Invoke(null, null); + var result = generic.Invoke(null, null); + AddCallMetaTable(result); + return result; } public static object CreateEnumTable(string typeName) @@ -359,5 +379,47 @@ namespace Barotrauma descriptor ??= new StandardUserDataDescriptor(desiredType, InteropAccessMode.Default); return CreateUserDataFromDescriptor(scriptObject, descriptor); } + + public static void AddCallMetaTable(object userdata) + { + if (userdata == null) { return; } + + // not sure how to implement this in C# + var function = GameMain.LuaCs.Lua.LoadString(""" + local userdata = ... + if userdata == nil then + error("Attempted to add a call metatable to a nil value.", 2) + end + + if not LuaUserData.HasMember(userdata, ".ctor") then + return + end + + debug.setmetatable(userdata, { + __call = function(obj, ...) + if userdata == nil then + error("userdata was nil.", 2) + end + + local success, result = pcall(userdata.__new, ...) + + + if not success then + error(result, 2) + end + + return result + end + }) + """); + + GameMain.LuaCs.Lua.Call(function, userdata); + } + + + public static void Clear() + { + descriptors.Clear(); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index d51193a50..fb2ef00f1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -347,6 +347,8 @@ namespace Barotrauma DebugServer.Detach(Lua); } + LuaUserData.Clear(); + Game?.Stop(); Hook?.Clear(); @@ -416,7 +418,7 @@ namespace Barotrauma UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); - UserData.RegisterType(); + var uuid = UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); @@ -433,6 +435,7 @@ namespace Barotrauma Lua.Globals["Logger"] = UserData.CreateStatic(); Lua.Globals["LuaUserData"] = UserData.CreateStatic(); + Lua.Globals["LuaUserDataIUUD"] = uuid; Lua.Globals["Game"] = Game; Lua.Globals["Hook"] = Hook; Lua.Globals["ModStore"] = ModStore; From a546615f698f01d6d962b92017a7831bc090ed47 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 1 Feb 2026 09:18:11 -0300 Subject: [PATCH 003/288] Oops accidentally broke this check --- .../SharedSource/LuaCs/Lua/LuaClasses/LuaSafeUserData.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaSafeUserData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaSafeUserData.cs index a7ba597fa..1eaf09563 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaSafeUserData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaSafeUserData.cs @@ -66,7 +66,7 @@ namespace Barotrauma return false; } - if (!CanBeRegistered(typeName)) + if (!CanBeRegistered(typeName) && !LuaUserData.IsRegistered(typeName)) { return false; } From 9e957a75b005f96006475d4562b3c43deaf4de34 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Mon, 2 Feb 2026 21:30:06 -0300 Subject: [PATCH 004/288] Update MoonSharp --- .../LuaCs/Lua/LuaClasses/LuaSafeUserData.cs | 2 + .../LuaCs/Lua/LuaClasses/LuaUserData.cs | 38 +------------------ Libraries/moonsharp | 2 +- 3 files changed, 5 insertions(+), 37 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaSafeUserData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaSafeUserData.cs index 1eaf09563..e4d91d5c1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaSafeUserData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaSafeUserData.cs @@ -183,6 +183,8 @@ namespace Barotrauma return LuaUserData.HasMember(obj, memberName); } + public static void AddCallMetaTable(object userdata) { } + public static DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor desiredTypeDescriptor) { return LuaUserData.CreateUserDataFromDescriptor(scriptObject, desiredTypeDescriptor); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs index fd336066f..959ee31a9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs @@ -121,7 +121,7 @@ namespace Barotrauma MethodInfo method = typeof(UserData).GetMethod(nameof(UserData.CreateStatic), 1, new Type[0]); MethodInfo generic = method.MakeGenericMethod(type); var result = generic.Invoke(null, null); - AddCallMetaTable(result); + return result; } @@ -380,41 +380,7 @@ namespace Barotrauma return CreateUserDataFromDescriptor(scriptObject, descriptor); } - public static void AddCallMetaTable(object userdata) - { - if (userdata == null) { return; } - - // not sure how to implement this in C# - var function = GameMain.LuaCs.Lua.LoadString(""" - local userdata = ... - if userdata == nil then - error("Attempted to add a call metatable to a nil value.", 2) - end - - if not LuaUserData.HasMember(userdata, ".ctor") then - return - end - - debug.setmetatable(userdata, { - __call = function(obj, ...) - if userdata == nil then - error("userdata was nil.", 2) - end - - local success, result = pcall(userdata.__new, ...) - - - if not success then - error(result, 2) - end - - return result - end - }) - """); - - GameMain.LuaCs.Lua.Call(function, userdata); - } + public static void AddCallMetaTable(object userdata) { } public static void Clear() diff --git a/Libraries/moonsharp b/Libraries/moonsharp index f67e9ee5a..e3c2270e8 160000 --- a/Libraries/moonsharp +++ b/Libraries/moonsharp @@ -1 +1 @@ -Subproject commit f67e9ee5a315ad0c1ba60199488df37f5ef09cf2 +Subproject commit e3c2270e8277de98b0ec2b42b42909e6e6c8afd9 From 01cc1d331b81f2e1196d377e0dea7c192aac93cd Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 18 Sep 2024 20:54:56 -0400 Subject: [PATCH 005/288] -- Squash: - In progress implementation of services model. --- .../LuaCs/Configuration/DisplayableData.cs | 16 + .../IConfigDisplayDefinitions.cs | 6 + .../LuaCs/Configuration/IDisplayableConfig.cs | 66 ++ .../LuaCs/Data/DataInterfaceDefinitions.cs | 21 + .../ClientSource/LuaCs/Data/IConfigInfo.cs | 5 + .../LuaCs/Data/IResourceInfoDeclarations.cs | 15 + .../ClientSource/LuaCs/Data/IStylesInfo.cs | 6 + .../LuaCs/Services/IClientLoggerService.cs | 7 + .../LuaCs/Services/IStylesService.cs | 51 ++ .../LuaCs/Services/LoggerService.cs | 50 ++ .../LuaCs/Services/PackageService.cs | 43 ++ .../Processing/IClientParserDefinitions.cs | 9 + .../LuaCs/Services/StylesService.cs | 138 ++++ .../LuaCs/Services/UIStyleProcessor.cs | 93 +++ .../LuaCs/Services/PackageService.cs | 31 + Barotrauma/BarotraumaShared/Luatrauma.props | 2 + .../LuaCs/Configuration/IConfigBase.cs | 21 + .../LuaCs/Configuration/IConfigEntry.cs | 12 + .../LuaCs/Configuration/IConfigList.cs | 8 + .../LuaCs/Configuration/IConfigRangeEntry.cs | 13 + .../LuaCs/Data/DataInterfaceDefinitions.cs | 81 +++ .../LuaCs/Data/EPlatformsTargets.cs | 27 + .../LuaCs/Data/IBaseInfoDefinitions.cs | 77 +++ .../SharedSource/LuaCs/Data/IConfigInfo.cs | 26 + .../LuaCs/Data/IConfigProfileInfo.cs | 6 + .../LuaCs/Data/ILocalizationInfo.cs | 6 + .../SharedSource/LuaCs/Data/IModConfigInfo.cs | 14 + .../LuaCs/Data/IPackageDependencyInfo.cs | 31 + .../LuaCs/Data/IResourceInfoDeclarations.cs | 55 ++ .../SharedSource/LuaCs/Data/IRunConfig.cs | 18 + .../SharedSource/LuaCs/LuaCsLogger.cs | 2 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 8 +- .../LuaCs/Networking/INetCallback.cs | 15 + .../SharedSource/LuaCs/Networking/INetVar.cs | 25 + .../LuaCs/Networking/NetInterfaceCompat.cs | 151 +++++ .../LuaCs/Plugins/CsPackageManager.cs | 11 +- .../MemoryFileAssemblyContextLoader.cs | 7 +- .../SharedSource/LuaCs/Plugins/RunConfig.cs | 7 +- .../{Plugins => Services}/AssemblyManager.cs | 153 +---- .../Services/IAssemblyManagementService.cs | 189 ++++++ .../LuaCs/Services/IConfigService.cs | 50 ++ .../LuaCs/Services/IEventService.cs | 6 + .../LuaCs/Services/IHookManagementService.cs | 6 + .../LuaCs/Services/ILegacyConfigService.cs | 8 + .../LuaCs/Services/ILocalizationService.cs | 21 + .../LuaCs/Services/ILoggerService.cs | 25 + .../LuaCs/Services/ILuaScriptService.cs | 83 +++ .../LuaCs/Services/INetworkingService.cs | 23 + .../Services/IPackageManagementService.cs | 21 + .../LuaCs/Services/IPackageService.cs | 36 + .../Services/IPluginManagementService.cs | 7 + .../LuaCs/Services/IPluginService.cs | 50 ++ .../SharedSource/LuaCs/Services/IService.cs | 15 + .../LuaCs/Services/IServicesProvider.cs | 110 +++ .../LuaCs/Services/IStorageService.cs | 42 ++ .../LuaCs/Services/LoggerService.cs | 149 +++++ .../LuaCs/Services/LuaScriptService.cs | 6 + .../Services/PackageManagementService.cs | 71 ++ .../LuaCs/Services/PackageService.cs | 627 ++++++++++++++++++ .../IConverterServiceDefinitions.cs | 51 ++ .../LuaCs/Services/Safe/ILuaConfigService.cs | 6 + .../LuaCs/Services/Safe/ILuaDataService.cs | 39 ++ .../LuaCs/Services/Safe/ILuaEventService.cs | 6 + .../Services/Safe/ILuaNetworkingService.cs | 6 + .../Safe/ILuaPackageManagementService.cs | 6 + .../LuaCs/Services/Safe/ILuaPackageService.cs | 6 + .../LuaCs/Services/Safe/ILuaService.cs | 6 + .../LuaCs/Services/ServicesProvider.cs | 231 +++++++ 68 files changed, 3083 insertions(+), 152 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/DisplayableData.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IConfigDisplayDefinitions.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayableConfig.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IStylesInfo.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IClientLoggerService.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/LoggerService.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/IClientParserDefinitions.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStyleProcessor.cs create mode 100644 Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigBase.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigList.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigRangeEntry.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ILocalizationInfo.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependencyInfo.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IRunConfig.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetCallback.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Plugins => Services}/AssemblyManager.cs (80%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IAssemblyManagementService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IConfigService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IEventService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IHookManagementService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILegacyConfigService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILocalizationService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILoggerService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILuaScriptService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/INetworkingService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageManagementService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginManagementService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IServicesProvider.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IStorageService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaDataService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaNetworkingService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageManagementService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/DisplayableData.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/DisplayableData.cs new file mode 100644 index 000000000..64649068e --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/DisplayableData.cs @@ -0,0 +1,16 @@ +using Microsoft.Xna.Framework; + +namespace Barotrauma.LuaCs.Configuration; + +public class DisplayableData : IDisplayableData +{ + public string Name { get; private set; } + public string ModName { get; private set; } + public string DisplayName { get; private set; } + public string DisplayModName { get; private set; } + public string DisplayCategory { get; private set; } + public string Tooltip { get; private set; } + public string ImageIcon { get; private set; } + public Point IconResolution { get; private set; } + public bool ShowWhenNotLoaded { get; private set; } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IConfigDisplayDefinitions.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IConfigDisplayDefinitions.cs new file mode 100644 index 000000000..3c5fbc5f7 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IConfigDisplayDefinitions.cs @@ -0,0 +1,6 @@ +using Barotrauma.LuaCs.Configuration; +using Barotrauma.LuaCs.Data; + +namespace Barotrauma.LuaCs.Configuration; + +public partial interface IConfigBase : IDisplayableData, IDisplayableInitialize { } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayableConfig.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayableConfig.cs new file mode 100644 index 000000000..69449ed6b --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayableConfig.cs @@ -0,0 +1,66 @@ +using System.Numerics; +using Microsoft.Xna.Framework; + +namespace Barotrauma.LuaCs.Configuration; + +/// +/// Contains the Display Data for use with Menus. +/// +public interface IDisplayableData +{ + /// + /// Internal name of the instance. + /// + string Name { get; } + /// + /// Internal mod name of the instance. ContentPackage name will be used by default. + /// + string ModName { get; } + /// + /// The name to display in GUIs and Menus. + /// + string DisplayName { get; } + /// + /// The mod name to display in GUIs and Menus. + /// + string DisplayModName { get; } + /// + /// Category this instance falls under. Used by menus when filtering by category. + /// + string DisplayCategory { get; } + /// + /// The tooltip shown on hover. + /// + string Tooltip { get; } + /// + /// The fully qualified filepath to the image icon for this config. + /// + string ImageIcon { get; } + /// + /// Required if ImageIcon is set. X,Y resolution of the image. + /// + Point IconResolution { get; } + /// + /// Whether to show the entry in the menu when not loaded. + /// + bool ShowWhenNotLoaded { get; } +} + +public interface IDisplayableInitialize +{ + void Initialize(IDisplayableData values); + + // copy this as needed + /*public void Initialize(IDisplayableData values) + { + this.Name = values.Name; + this.ModName = values.ModName; + this.DisplayName = values.DisplayName; + this.DisplayModName = values.DisplayModName; + this.DisplayCategory = values.DisplayCategory; + this.Tooltip = values.Tooltip; + this.ImageIcon = values.ImageIcon; + this.IconResolution = values.IconResolution; + this.ShowWhenNotLoaded = values.ShowWhenNotLoaded; + }*/ +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs new file mode 100644 index 000000000..d46048703 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs @@ -0,0 +1,21 @@ +using System.Collections.Immutable; +using System.Globalization; + +namespace Barotrauma.LuaCs.Data; + +public partial record ModConfigInfo : IModConfigInfo +{ + public ImmutableArray StylesResourceInfos { get; init; } +} + +public record StylesResourceInfo : IStylesResourceInfo +{ + public Platform SupportedPlatforms { get; init; } + public Target SupportedTargets { get; init; } + public int LoadPriority { get; init; } + public ImmutableArray FilePaths { get; init; } + public bool Optional { get; init; } + public ImmutableArray SupportedCultures { get; init; } + public string InternalName { get; init; } + public ImmutableArray Dependencies { get; init; } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs new file mode 100644 index 000000000..45d563fa7 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs @@ -0,0 +1,5 @@ +using Barotrauma.LuaCs.Configuration; + +namespace Barotrauma.LuaCs.Data; + +public partial interface IConfigInfo : IDisplayableData { } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs new file mode 100644 index 000000000..14974ac8c --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs @@ -0,0 +1,15 @@ +using System.Collections.Immutable; + +namespace Barotrauma.LuaCs.Data; + +public partial interface IModConfigInfo : IStylesResourcesInfo { } + +public interface IStylesResourceInfo : IResourceInfo, IResourceCultureInfo, ILoadableResourceInfo, IPackageDependenciesInfo { } + +public interface IStylesResourcesInfo +{ + /// + /// Collection of loadable styles data. + /// + ImmutableArray StylesResourceInfos { get; } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IStylesInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IStylesInfo.cs new file mode 100644 index 000000000..b3e2a456b --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IStylesInfo.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Data; + +public interface IStylesInfo +{ + +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IClientLoggerService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IClientLoggerService.cs new file mode 100644 index 000000000..21cc88352 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IClientLoggerService.cs @@ -0,0 +1,7 @@ +namespace Barotrauma.LuaCs.Services; + +public interface IClientLoggerService : IService +{ + void AddToGUIUpdateList(); + void ShowErrorOverlay(string message, float time = 5f, float duration = 1.5f); +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs new file mode 100644 index 000000000..cd0b38829 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs @@ -0,0 +1,51 @@ +namespace Barotrauma.LuaCs.Services; + +// TODO: Rework interface to support resource infos. +/// +/// Loads XML Style assets from the given content package. +/// +public interface IStylesService : IService +{ + /// + /// Tries to load the styles file for the given contentpackage and path into a new UIStylesProcessor instance. + /// + /// + /// + /// + bool TryLoadStylesFile(ContentPackage package, ContentPath path); + /// + /// Unloads all styles assets and UIStyleProcessor instances. + /// + void UnloadAllStyles(); + + /// + /// Tries to the get the font asset by xml asset name, returns null on failure. + /// + /// XML Name of the asset. + /// The asset or null if none are found. + GUIFont GetFont(string fontName); + /// + /// Tries to the get the sprite asset by xml asset name, returns null on failure. + /// + /// XML Name of the asset. + /// The asset or null if none are found. + GUISprite GetSprite(string spriteName); + /// + /// Tries to the get the sprite sheet asset by xml asset name, returns null on failure. + /// + /// XML Name of the asset. + /// The asset or null if none are found. + GUISpriteSheet GetSpriteSheet(string spriteSheetName); + /// + /// Tries to the get the cursor asset by xml asset name, returns null on failure. + /// + /// XML Name of the asset. + /// The asset or null if none are found. + GUICursor GetCursor(string cursorName); + /// + /// Tries to the get the color asset by xml asset name, returns null on failure. + /// + /// XML Name of the asset. + /// The asset or null if none are found. + GUIColor GetColor(string colorName); +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/LoggerService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/LoggerService.cs new file mode 100644 index 000000000..aee2efabb --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/LoggerService.cs @@ -0,0 +1,50 @@ +using Microsoft.Xna.Framework; + +namespace Barotrauma.LuaCs.Services; + +public partial class LoggerService : ILoggerService, IClientLoggerService +{ + private GUIFrame _overlayFrame; + private GUITextBlock _textBlock; + private double _showTimer = 0; + + + private void CreateOverlay(string message) + { + _overlayFrame = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.03f), null), null, new Color(50, 50, 50, 100)) + { + CanBeFocused = false + }; + + GUILayoutGroup layout = + new GUILayoutGroup( + new RectTransform(new Vector2(0.8f, 0.8f), _overlayFrame.RectTransform, Anchor.CenterLeft), false, + Anchor.Center); + + _textBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0f), layout.RectTransform), message); + _overlayFrame.RectTransform.MinSize = new Point((int)(_textBlock.TextSize.X * 1.2), 0); + + layout.Recalculate(); + } + + public void AddToGUIUpdateList() + { + if (_overlayFrame != null && Timing.TotalTime <= _showTimer) + { + _overlayFrame.AddToGUIUpdateList(); + } + } + + public void ShowErrorOverlay(string message, float time = 5f, float duration = 1.5f) + { + if (Timing.TotalTime <= _showTimer) + { + return; + } + + CreateOverlay(message); + + _overlayFrame.Flash(Color.Red, duration, true); + _showTimer = Timing.TotalTime + time; + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs new file mode 100644 index 000000000..a079ef1ec --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Services.Processing; + +namespace Barotrauma.LuaCs.Services; + +public partial class PackageService : IStylesResourcesInfo +{ + private readonly Lazy _stylesService; + + public PackageService( + Lazy converterService, + Lazy legacyConfigService, + Lazy luaScriptService, + Lazy localizationService, + Lazy pluginService, + Lazy stylesService, + Lazy configService, + IPackageManagementService packageManagementService, + IStorageService storageService, + ILoggerService loggerService) + { + _modConfigConverterService = converterService; + _legacyConfigService = legacyConfigService; + _luaScriptService = luaScriptService; + _localizationService = localizationService; + _pluginService = pluginService; + _stylesService = stylesService; + _configService = configService; + _packageManagementService = packageManagementService; + _storageService = storageService; + _loggerService = loggerService; + } + + public ImmutableArray StylesResourceInfos => ModConfigInfo?.StylesResourceInfos ?? ImmutableArray.Empty; + + public void LoadStyles([NotNull]IStylesResourcesInfo stylesInfo) + { + throw new NotImplementedException(); + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/IClientParserDefinitions.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/IClientParserDefinitions.cs new file mode 100644 index 000000000..8c81c7fb5 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/IClientParserDefinitions.cs @@ -0,0 +1,9 @@ +using System.Xml.Linq; +using Barotrauma.LuaCs.Data; + +namespace Barotrauma.LuaCs.Services.Processing; + +#region XmlToResourceParsers +public interface IXmlStylesToResConverterService : IXmlResourceConverterService { } + +#endregion diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs new file mode 100644 index 000000000..f859dcd09 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.LuaCs.Services; + +public class StylesService : IStylesService +{ + private readonly Dictionary _loadedProcessors = new(); + private readonly IStorageService _storageService; + private readonly ILoggerService _loggerService; + + public StylesService(IStorageService storageService, ILoggerService loggerService) + { + _storageService = storageService; + _loggerService = loggerService; + } + + public bool TryLoadStylesFile(ContentPackage package, ContentPath path) + { + //check if file already in dict + if (_loadedProcessors.ContainsKey(path.FullPath)) + { + return true; + } + //check if file exists + if (_storageService.FileExists(path.FullPath)) + { + try + { + var styleProcessor = new UIStyleProcessor(package, path); + styleProcessor.LoadFile(); + _loadedProcessors.Add(path.FullPath, styleProcessor); + } + catch (InvalidDataException exception) + { + _loggerService.LogError($"XmlAssetService.TryLoadStylesFile failed for ContentPackage {package.Name}: Exception: {exception.Message}"); + return false; + } + + return true; + } + + return false; + } + + public void UnloadAllStyles() + { + if (NoProcessorsLoaded) + return; + + foreach (var processor in _loadedProcessors) + { + processor.Value.UnloadFile(); + } + _loadedProcessors.Clear(); + } + + public GUIFont GetFont(string fontName) + { + if (NoProcessorsLoaded) + return null; + foreach (var processor in _loadedProcessors.Values) + { + if (processor.Fonts.TryGetValue(fontName, out var asset)) + return asset; + } + + return null; + } + + public GUISprite GetSprite(string spriteName) + { + if (NoProcessorsLoaded) + return null; + foreach (var processor in _loadedProcessors.Values) + { + if (processor.Sprites.TryGetValue(spriteName, out var asset)) + return asset; + } + + return null; + } + + public GUISpriteSheet GetSpriteSheet(string spriteSheetName) + { + if (NoProcessorsLoaded) + return null; + foreach (var processor in _loadedProcessors.Values) + { + if (processor.SpriteSheets.TryGetValue(spriteSheetName, out var asset)) + return asset; + } + + return null; + } + + public GUICursor GetCursor(string cursorName) + { + if (NoProcessorsLoaded) + return null; + foreach (var processor in _loadedProcessors.Values) + { + if (processor.Cursors.TryGetValue(cursorName, out var asset)) + return asset; + } + + return null; + } + + public GUIColor GetColor(string colorName) + { + if (NoProcessorsLoaded) + return null; + foreach (var processor in _loadedProcessors.Values) + { + if (processor.Colors.TryGetValue(colorName, out var asset)) + return asset; + } + + return null; + } + + private bool NoProcessorsLoaded => _loadedProcessors.Count < 1; + + public void Dispose() + { + UnloadAllStyles(); + GC.SuppressFinalize(this); + } + + public void Reset() + { + UnloadAllStyles(); + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStyleProcessor.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStyleProcessor.cs new file mode 100644 index 000000000..b90276e7a --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStyleProcessor.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml.Linq; +using Barotrauma.Extensions; + +namespace Barotrauma.LuaCs.Services; + +public class UIStyleProcessor : HashlessFile +{ + private readonly UIStyleFile _fake; + public readonly Dictionary Fonts = new(); + public readonly Dictionary Sprites = new(); + public readonly Dictionary SpriteSheets = new(); + public readonly Dictionary Cursors = new(); + public readonly Dictionary Colors = new(); + + public UIStyleProcessor(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) + { + _fake = new UIStyleFile(contentPackage, path); + } + + public override void LoadFile() + { + var element = XMLExtensions.TryLoadXml(path: Path)?.Root?.FromPackage(ContentPackage); + if (element is null) + throw new InvalidDataException($"UIStyleProcessor: Failed to load UI style file: {Path}"); + + var styleElement = element.Name.LocalName.ToLowerInvariant() == "style" ? element : element.GetChildElement("style"); + if (styleElement is null) + throw new InvalidDataException($"UIStyleProcessor: no 'style' XmlElement found in file: {Path}"); + + var childElements = styleElement.GetChildElements("Font"); + if (childElements is not null) + AddToList(Fonts, childElements, _fake); + + childElements = styleElement.GetChildElements("Sprite"); + if (childElements is not null) + AddToList(Sprites, childElements, _fake); + + childElements = styleElement.GetChildElements("Spritesheet"); + if (childElements is not null) + AddToList(SpriteSheets, childElements, _fake); + + childElements = styleElement.GetChildElements("Cursor"); + if (childElements is not null) + AddToList(Cursors, childElements, _fake); + + childElements = styleElement.GetChildElements("Color"); + if (childElements is not null) + AddToList(Colors, childElements, _fake); + + + void AddToList(Dictionary dict, IEnumerable ele, UIStyleFile file) where T1 : GUISelector where T2 : GUIPrefab + { + foreach (ContentXElement prefabElement in ele) + { + string name = prefabElement.GetAttributeString("name", string.Empty); + if (name != string.Empty) + { + var prefab = (T2)Activator.CreateInstance(typeof(T2), new object[]{ prefabElement, file })!; + if (!dict.ContainsKey(name)) + dict[name] = (T1)Activator.CreateInstance(typeof(T1), new object[] { name })!; + dict[name].Prefabs.Add(prefab, false); + } + } + } + } + + public override void UnloadFile() + { + Fonts.Values.ForEach(p => p.Prefabs.RemoveByFile(_fake)); + Sprites.Values.ForEach(p => p.Prefabs.RemoveByFile(_fake)); + SpriteSheets.Values.ForEach(p => p.Prefabs.RemoveByFile(_fake)); + Cursors.Values.ForEach(p => p.Prefabs.RemoveByFile(_fake)); + Colors.Values.ForEach(p => p.Prefabs.RemoveByFile(_fake)); + + Fonts.Clear(); + Sprites.Clear(); + SpriteSheets.Clear(); + Cursors.Clear(); + Colors.Clear(); + } + + public override void Sort() + { + Fonts.Values.ForEach(p => p.Prefabs.Sort()); + Sprites.Values.ForEach(p => p.Prefabs.Sort()); + SpriteSheets.Values.ForEach(p => p.Prefabs.Sort()); + Cursors.Values.ForEach(p => p.Prefabs.Sort()); + Colors.Values.ForEach(p => p.Prefabs.Sort()); + } +} diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs new file mode 100644 index 000000000..4b97459b1 --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs @@ -0,0 +1,31 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Barotrauma.LuaCs.Services.Processing; + +// ReSharper disable once CheckNamespace +namespace Barotrauma.LuaCs.Services; + +public partial class PackageService +{ + public PackageService( + Lazy converterService, + Lazy legacyConfigService, + Lazy luaScriptService, + Lazy localizationService, + Lazy pluginService, + Lazy configService, + IPackageManagementService packageManagementService, + IStorageService storageService, + ILoggerService loggerService) + { + _modConfigConverterService = converterService; + _legacyConfigService = legacyConfigService; + _luaScriptService = luaScriptService; + _localizationService = localizationService; + _pluginService = pluginService; + _configService = configService; + _packageManagementService = packageManagementService; + _storageService = storageService; + _loggerService = loggerService; + } +} diff --git a/Barotrauma/BarotraumaShared/Luatrauma.props b/Barotrauma/BarotraumaShared/Luatrauma.props index cf9a48490..62c907748 100644 --- a/Barotrauma/BarotraumaShared/Luatrauma.props +++ b/Barotrauma/BarotraumaShared/Luatrauma.props @@ -5,6 +5,8 @@ + + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigBase.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigBase.cs new file mode 100644 index 000000000..37e076506 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigBase.cs @@ -0,0 +1,21 @@ +using System; +using Barotrauma.Networking; + +namespace Barotrauma.LuaCs.Configuration; + +public partial interface IConfigBase : IVarId +{ + bool IsInitialized { get; } + string GetValue(); + bool TrySetValue(string value); + bool IsAssignable(string value); + Type GetValueType(); + void Initialize(IVarId id, string defaultValue); +} + +public interface IVarId +{ + Guid InstanceId { get; } + string InternalName { get; } + ContentPackage OwnerPackage { get; } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs new file mode 100644 index 000000000..30ba129c6 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs @@ -0,0 +1,12 @@ +using System; +using Barotrauma.LuaCs.Networking; + +namespace Barotrauma.LuaCs.Configuration; + +public interface IConfigEntry : IConfigBase, INetVar where T : IConvertible, IEquatable +{ + T Value { get; } + bool TrySetValue(T value); + bool IsAssignable(T value); + void Initialize(IVarId id, T defaultValue); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigList.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigList.cs new file mode 100644 index 000000000..226ddbe08 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigList.cs @@ -0,0 +1,8 @@ +using Barotrauma.LuaCs.Networking; + +namespace Barotrauma.LuaCs.Configuration; + +public interface IConfigList : IConfigBase, INetVar +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigRangeEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigRangeEntry.cs new file mode 100644 index 000000000..571bb1d3f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigRangeEntry.cs @@ -0,0 +1,13 @@ +using System; + +namespace Barotrauma.LuaCs.Configuration; + +public interface IConfigRangeEntry : IConfigEntry where T : IConvertible, IEquatable +{ + T MinValue { get; } + T MaxValue { get; } + + int GetStepCount(); + float GetRangeMin(); + float GetRangeMax(); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs new file mode 100644 index 000000000..7362f990f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; +using System.Runtime.InteropServices; + +namespace Barotrauma.LuaCs.Data; + +#region ModConfigurationInfo + +public partial record ModConfigInfo : IModConfigInfo +{ + public ContentPackage Package { get; init; } + public string PackageName { get; init; } + public TargetRunMode RunModes { get; init; } + + public ImmutableArray SupportedCultures { get; init; } + public ImmutableArray Assemblies { get; init; } + public ImmutableArray Localizations { get; init; } + public ImmutableArray LuaScripts { get; init; } + public ImmutableArray Configs { get; init; } + public ImmutableArray ConfigProfiles { get; init; } +} + +#endregion + +#region DataContracts + +public record AssemblyResourceInfo : IAssemblyResourceInfo +{ + public ContentPackage OwnerPackage { get; init; } + public string FriendlyName { get; init; } + public bool IsScript { get; init; } + public string InternalName { get; init; } + public bool LazyLoad { get; init; } + public Platform SupportedPlatforms { get; init; } + public Target SupportedTargets { get; init; } + public int LoadPriority { get; init; } + public ImmutableArray FilePaths { get; init; } + public ImmutableArray SupportedCultures { get; init; } + public ImmutableArray Dependencies { get; init; } + public bool Optional { get; init; } +} + +public record DependencyInfo : IPackageDependencyInfo +{ + public ContentPackage OwnerPackage { get; init; } + public string FolderPath { get; init; } + public string PackageName { get; init; } + public ulong SteamWorkshopId { get; init; } + public ContentPackage DependencyPackage { get; init; } +} + +public record LocalizationResourceInfo : ILocalizationResourceInfo +{ + public ContentPackage OwnerPackage { get; init; } + public CultureInfo TargetCulture { get; init; } + public Platform SupportedPlatforms { get; init; } + public Target SupportedTargets { get; init; } + public int LoadPriority { get; init; } + public ImmutableArray FilePaths { get; init; } + public ImmutableArray SupportedCultures { get; init; } + public ImmutableArray Dependencies { get; init; } + public bool Optional { get; init; } +} + +public readonly struct LuaScriptResourceInfo : ILuaResourceInfo +{ + public ContentPackage OwnerPackage { get; init; } + public Platform SupportedPlatforms { get; init; } + public Target SupportedTargets { get; init; } + public int LoadPriority { get; init; } + public ImmutableArray FilePaths { get; init; } + public ImmutableArray SupportedCultures { get; init; } + public ImmutableArray Dependencies { get; init; } + public bool Optional { get; init; } + public string InternalName { get; init; } + public bool LazyLoad { get; init; } +} + +#endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs new file mode 100644 index 000000000..42d702b7f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs @@ -0,0 +1,27 @@ +using System; + +namespace Barotrauma.LuaCs.Data; + +[Flags] +public enum Platform +{ + Linux=0x1, + OSX=0x2, + Windows=0x4 +} + +[Flags] +public enum Target +{ + Client=0x1, + Server=0x2 +} + +[Flags] +public enum TargetRunMode +{ + ClientEnabled = 0x1, + ClientAlways = 0x2, + ServerEnabled = 0x4, + ServerAlways = 0x8 +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs new file mode 100644 index 000000000..e970aa9ce --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.ComponentModel.DataAnnotations; +using System.Globalization; + +namespace Barotrauma.LuaCs.Data; + +public interface IPlatformInfo +{ + /// + /// Platforms that these localization files should be loaded for. + /// + [Required] + Platform SupportedPlatforms { get; } + + /// + /// Targets that these localization files should be loaded for. + /// + [Required] + Target SupportedTargets { get; } +} + + +/// +/// Which package does the following data belong to? +/// +public interface IPackageInfo +{ + ContentPackage OwnerPackage { get; } +} + + +/// +/// ResourceInfos contain metadata about a resource. +/// +public interface IResourceInfo : IPlatformInfo +{ + /// + /// [Optional] + /// Allows you to specify the loading order for all assets of the same type (ie. styles, assemblies, etc.). + /// + int LoadPriority { get; } + + /// + /// Resource absolute file paths. + /// + [Required] + ImmutableArray FilePaths { get; } + + /// + /// Marks this resource as optional (ie. Cross-CP content). Setting this to true will allow the dependency system to + /// try and order the loading but not fail if it runs into circular dependency issues. + /// + bool Optional { get; } +} + +/// +/// Information about supported cultures. It is intended to be ignored if the array is ImmutableArray.Empty . +/// +public interface IResourceCultureInfo +{ + /// + /// List of supported cultures by this resource. + /// + ImmutableArray SupportedCultures { get; } +} + + +public interface ILoadableResourceInfo +{ + /// + /// [UNIQUE] The name that will be used when trying to reference this resource for execution or loading. + /// + [Required] + public string InternalName { get; } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs new file mode 100644 index 000000000..c56cb5aa1 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs @@ -0,0 +1,26 @@ +using System; +using Barotrauma.Networking; + +namespace Barotrauma.LuaCs.Data; + +// TODO: Finish +public partial interface IConfigInfo +{ + string Name { get; } + string PackageName { get; } + ConfigDataType Type { get; } + string DefaultValue { get; } + ClientPermissions RequiredPermissions { get; } +} + +public enum ConfigDataType +{ + Boolean, Int32, Int64, Single, Double, String, + Color, Vector2, Vector3, List, + RangeInt32, RangeSingle, ControlInput +} + +public enum NetSync +{ + None, TwoWay, ServerAuthority, ClientOneWay +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs new file mode 100644 index 000000000..7be1db56c --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Data; + +public interface IConfigProfileInfo +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ILocalizationInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ILocalizationInfo.cs new file mode 100644 index 000000000..c05887e5a --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ILocalizationInfo.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Data; + +public interface ILocalizationInfo +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs new file mode 100644 index 000000000..8a324f870 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs @@ -0,0 +1,14 @@ +using System.Collections.Immutable; + +namespace Barotrauma.LuaCs.Data; + +public partial interface IModConfigInfo : IResourceCultureInfo, IAssembliesResourcesInfo, + ILocalizationsResourcesInfo, ILuaScriptsResourcesInfo, IConfigsResourcesInfo, + IConfigProfilesResourcesInfo +{ + // package info + ContentPackage Package { get; } + string PackageName { get; } + // configuration + TargetRunMode RunModes { get; } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependencyInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependencyInfo.cs new file mode 100644 index 000000000..ef96428f1 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependencyInfo.cs @@ -0,0 +1,31 @@ +using System.Collections.Immutable; + +namespace Barotrauma.LuaCs.Data; + +public interface IPackageDependencyInfo : IPackageInfo +{ + /// + /// Root folder of the content package. + /// + public string FolderPath { get; } + /// + /// Name of the package. + /// + public string PackageName { get; } + /// + /// Steam ID of the package. + /// + public ulong SteamWorkshopId { get; } + /// + /// The dependency package, if found in the ALL Packages List. + /// + public ContentPackage DependencyPackage { get; } +} + +public interface IPackageDependenciesInfo +{ + /// + /// List of required packages. + /// + ImmutableArray Dependencies { get; } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs new file mode 100644 index 000000000..8b6f28e9b --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; + +namespace Barotrauma.LuaCs.Data; + +public interface IConfigResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { } +public interface IConfigProfileResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { } +public interface ILocalizationResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { } +/// +/// Represents loadable Lua files. +/// +public interface ILuaResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, ILoadableResourceInfo, IPackageInfo { } +public interface IAssemblyResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, ILoadableResourceInfo, IPackageInfo +{ + /// + /// The friendly name of the assembly. Script files belonging to the same assembly should all have the same name. + /// Legacy scripts will all be given the sanitized name of the Content Package they belong to. + /// + public string FriendlyName { get; } + /// + /// Is this entry referring to a script file collection. + /// + public bool IsScript { get; } +} + + +#region Collections + +public interface IAssembliesResourcesInfo +{ + ImmutableArray Assemblies { get; } +} + +public interface ILocalizationsResourcesInfo +{ + ImmutableArray Localizations { get; } +} + +public interface ILuaScriptsResourcesInfo +{ + ImmutableArray LuaScripts { get; } +} + +public interface IConfigsResourcesInfo +{ + ImmutableArray Configs { get; } +} + +public interface IConfigProfilesResourcesInfo +{ + ImmutableArray ConfigProfiles { get; } +} + +#endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IRunConfig.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IRunConfig.cs new file mode 100644 index 000000000..a0da6263b --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IRunConfig.cs @@ -0,0 +1,18 @@ +namespace Barotrauma.LuaCs.Data; + +/// +/// Legacy data contract for the old run configuration system. Should be deprecated +/// once no longer needed. +/// +public interface IRunConfig +{ + bool UseNonPublicizedAssemblies { get; } + bool AutoGenerated { get; } + bool UseInternalAssemblyName { get; } + string Client { get; } + string Server { get; } + + bool IsForced(); + bool IsStandard(); + bool IsForcedOrStandard(); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsLogger.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsLogger.cs index a8816c595..e854a8e04 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsLogger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsLogger.cs @@ -19,7 +19,7 @@ namespace Barotrauma #if SERVER private const string LogPrefix = "SV"; - private const int NetMaxLength = 1024; + private const int NetMaxLength = 1024; // character limit of vanilla Barotrauma's chat system. private const int NetMaxMessages = 60; // This is used so its possible to call logging functions inside the serverLog diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index fb2ef00f1..4a479c11f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -11,6 +11,7 @@ using MoonSharp.VsCodeDebugger; using System.Reflection; using System.Runtime.Loader; using System.Xml.Linq; +using Barotrauma.LuaCs.Services; using Barotrauma.Networking; namespace Barotrauma @@ -99,7 +100,7 @@ namespace Barotrauma public CsPackageManager PluginPackageManager => _pluginPackageManager ??= new CsPackageManager(AssemblyManager, this); public LuaCsModStore ModStore { get; private set; } - private LuaRequire require { get; set; } + private LuaRequire Require { get; set; } public LuaCsSetupConfig Config { get; private set; } public MoonSharpVsCodeDebugServer DebugServer { get; private set; } public bool IsInitialized { get; private set; } @@ -320,6 +321,7 @@ namespace Barotrauma #endif } + public void Stop() { PluginPackageManager.UnloadPlugins(); @@ -395,7 +397,7 @@ namespace Barotrauma Lua.Options.CheckThreadAccess = false; Script.GlobalOptions.ShouldPCallCatchException = (Exception ex) => { return true; }; - require = new LuaRequire(Lua); + Require = new LuaRequire(Lua); Game = new LuaGame(); Networking = new LuaCsNetworking(); @@ -428,7 +430,7 @@ namespace Barotrauma Lua.Globals["dofile"] = (Func)DoFile; Lua.Globals["loadfile"] = (Func)LoadFile; - Lua.Globals["require"] = (Func)require.Require; + Lua.Globals["require"] = (Func)Require.Require; Lua.Globals["dostring"] = (Func)Lua.DoString; Lua.Globals["load"] = (Func)Lua.LoadString; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetCallback.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetCallback.cs new file mode 100644 index 000000000..f498da372 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetCallback.cs @@ -0,0 +1,15 @@ +using System; + +namespace Barotrauma.LuaCs.Networking; + +public partial interface INetCallback +{ + public ushort CallbackId { get; } +} + +#if SERVER +public partial interface INetCallback +{ + +} +#endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs new file mode 100644 index 000000000..b4a7d5885 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs @@ -0,0 +1,25 @@ +using Barotrauma.LuaCs.Configuration; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Networking; +using Barotrauma.Networking; + +namespace Barotrauma.LuaCs.Networking; + +public interface INetVar : IVarId +{ + /// + /// Synchronized network id, uninitialized if value is zero/0. Used by Networking service. + /// + ushort NetId { get; } + /// + /// Synchronization type + /// + NetSync SyncType { get; } + /// + /// Permissions needed by clients to send net-events or receive net messages. + /// + ClientPermissions WritePermissions { get; } + void ReadNetMessage(INetReadMessage message); + void WriteNetMessage(INetWriteMessage message); + void Initialize(ushort netId, NetSync syncMode, ClientPermissions writePermissions); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs new file mode 100644 index 000000000..c68b318ae --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs @@ -0,0 +1,151 @@ +using Barotrauma.Networking; +using Microsoft.Xna.Framework; + +namespace Barotrauma.LuaCs.Networking; + +#region Wrapper-IWriteMessage + +/// +/// Literally just exists because Barotrauma.IWriteMessage is internal only. +/// +public interface INetWriteMessage +{ + internal IWriteMessage Message { get; } + internal void SetMessage(IWriteMessage msg); + + void WriteBoolean(bool val) => Message.WriteBoolean(val); + + void WritePadBits() => Message.WritePadBits(); + + void WriteByte(byte val) => Message.WriteByte(val); + + void WriteInt16(short val) => Message.WriteInt16(val); + + void WriteUInt16(ushort val) => Message.WriteUInt16(val); + + void WriteInt32(int val) => Message.WriteInt32(val); + + void WriteUInt32(uint val) => Message.WriteUInt32(val); + + void WriteInt64(long val) => Message.WriteInt64(val); + + void WriteUInt64(ulong val) => Message.WriteUInt64(val); + + void WriteSingle(float val) => Message.WriteSingle(val); + + void WriteDouble(double val) => Message.WriteDouble(val); + + void WriteColorR8G8B8(Color val) => Message.WriteColorR8G8B8(val); + + void WriteColorR8G8B8A8(Color val) => Message.WriteColorR8G8B8A8(val); + + void WriteVariableUInt32(uint val) => Message.WriteVariableUInt32(val); + + void WriteString(string val) => Message.WriteString(val); + + void WriteIdentifier(Identifier val) => Message.WriteIdentifier(val); + + void WriteRangedInteger(int val, int min, int max) => Message.WriteRangedInteger(val, min, max); + + void WriteRangedSingle(float val, float min, float max, int bitCount) => + Message.WriteRangedSingle(val, min, max, bitCount); + + void WriteBytes(byte[] val, int startIndex, int length) => Message.WriteBytes(val, startIndex, length); + + byte[] PrepareForSending(bool compressPastThreshold, out bool isCompressed, out int outLength) => + Message.PrepareForSending(compressPastThreshold, out isCompressed, out outLength); + + int BitPosition + { + get => Message.BitPosition; + set => Message.BitPosition = value; + } + + int BytePosition => Message.BytePosition; + + byte[] Buffer => Message.Buffer; + + int LengthBits + { + get => Message.LengthBits; + set => Message.LengthBits = value; + } + + int LengthBytes => Message.LengthBytes; +} + +#endregion + +#region Wrapper-IReadMessage + +/// +/// Literally just exists because Barotrauma.IReadMessage is internal only. +/// +public interface INetReadMessage +{ + internal IReadMessage Message { get; } + internal void SetMessage(IReadMessage msg); + + bool ReadBoolean() => Message.ReadBoolean(); + void ReadPadBits() => Message.ReadPadBits(); + byte ReadByte() => Message.ReadByte(); + byte PeekByte() => Message.PeekByte(); + ushort ReadUInt16() => Message.ReadUInt16(); + short ReadInt16() => Message.ReadInt16(); + uint ReadUInt32() => Message.ReadUInt32(); + int ReadInt32() => Message.ReadInt32(); + ulong ReadUInt64() => Message.ReadUInt64(); + long ReadInt64() => Message.ReadInt64(); + float ReadSingle() => Message.ReadSingle(); + double ReadDouble() => Message.ReadDouble(); + uint ReadVariableUInt32() => Message.ReadVariableUInt32(); + string ReadString() => Message.ReadString(); + Identifier ReadIdentifier() => Message.ReadIdentifier(); + Color ReadColorR8G8B8() => Message.ReadColorR8G8B8(); + Color ReadColorR8G8B8A8() => Message.ReadColorR8G8B8A8(); + int ReadRangedInteger(int min, int max) => Message.ReadRangedInteger(min, max); + float ReadRangedSingle(float min, float max, int bitCount) => Message.ReadRangedSingle(min, max, bitCount); + byte[] ReadBytes(int numberOfBytes) => Message.ReadBytes(numberOfBytes); + int BitPosition + { + get => Message.BitPosition; + set => Message.BitPosition = value; + } + int BytePosition => Message.BytePosition; + byte[] Buffer => Message.Buffer; + int LengthBits + { + get => Message.LengthBits; + set => Message.LengthBits = value; + } + int LengthBytes => Message.LengthBytes; +} + +#endregion + +#region HelperImplementations + +public class NetWriteMessage : INetWriteMessage +{ + private IWriteMessage Message { get; set; } + + IWriteMessage INetWriteMessage.Message => Message; + + void INetWriteMessage.SetMessage(IWriteMessage msg) + { + Message = msg; + } +} + +public class NetReadMessage : INetReadMessage +{ + private IReadMessage Message { get; set; } + IReadMessage INetReadMessage.Message => Message; + + void INetReadMessage.SetMessage(IReadMessage msg) + { + Message = msg; + } +} + +#endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/CsPackageManager.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/CsPackageManager.cs index 8ba1f8921..a23d70f7b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/CsPackageManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/CsPackageManager.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading; +using Barotrauma.LuaCs.Services; using Barotrauma.Steam; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -149,23 +150,23 @@ public sealed class CsPackageManager : IDisposable #endregion /// - /// Whether or not assemblies have been loaded. + /// Whether assemblies have been loaded. /// public bool AssembliesLoaded { get; private set; } /// - /// Whether or not loaded plugins had their preloader run. + /// Whether loaded plugins had their preloader run. /// public bool PluginsPreInit { get; private set; } /// - /// Whether or not plugins' types have been instantiated. + /// Whether plugins' types have been instantiated. /// public bool PluginsInitialized { get; private set; } /// - /// Whether or not plugins are fully loaded. + /// Whether plugins are fully loaded. /// public bool PluginsLoaded { get; private set; } @@ -966,7 +967,7 @@ public sealed class CsPackageManager : IDisposable /// Packages with errors or cyclic dependencies. Element is error message. Null if empty. /// Optional: Allows for a custom checks to be performed on each package. /// Returns a bool indicating if the package is ready to load. - /// Whether or not the process produces a usable list. + /// Whether the process produces a usable list. private static bool OrderAndFilterPackagesByDependencies( Dictionary> packages, out IEnumerable readyToLoad, diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/MemoryFileAssemblyContextLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/MemoryFileAssemblyContextLoader.cs index dd61c0108..e01db5f4c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/MemoryFileAssemblyContextLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/MemoryFileAssemblyContextLoader.cs @@ -7,13 +7,14 @@ using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Loader; +using Barotrauma.LuaCs.Services; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; // ReSharper disable ConditionIsAlwaysTrueOrFalse [assembly: InternalsVisibleTo("CompiledAssembly")] -namespace Barotrauma; +namespace Barotrauma.LuaCs; /// /// AssemblyLoadContext to compile from syntax trees in memory and to load from disk/file. Provides dependency resolution. @@ -31,11 +32,11 @@ public class MemoryFileAssemblyContextLoader : AssemblyLoadContext // internal private readonly Dictionary _dependencyResolvers = new(); // path-folder, resolver protected bool IsResolving; //this is to avoid circular dependency lookup. - private AssemblyManager _assemblyManager; + private IAssemblyManagementService _assemblyManager; public bool IsTemplateMode { get; set; } public bool IsDisposed { get; private set; } - public MemoryFileAssemblyContextLoader(AssemblyManager assemblyManager) : base(isCollectible: true) + public MemoryFileAssemblyContextLoader(IAssemblyManagementService assemblyManager) : base(isCollectible: true) { this._assemblyManager = assemblyManager; this.IsDisposed = false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/RunConfig.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/RunConfig.cs index 64bc66006..0e46032c1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/RunConfig.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/RunConfig.cs @@ -1,25 +1,26 @@ using System; using System.ComponentModel; using System.Xml.Serialization; +using Barotrauma.LuaCs.Data; namespace Barotrauma; [Serializable] -public sealed class RunConfig +public sealed class RunConfig : IRunConfig { /// /// How should scripts be run on the server. /// [XmlElement(ElementName = "Server")] [DefaultValue("Standard")] - public string Server; + public string Server { get; set; } /// /// How should scripts be run on the client. /// [XmlElement(ElementName = "Client")] [DefaultValue("Standard")] - public string Client; + public string Client { get; set; } /// /// List of dependencies by either Steam Workshop ID or by Partial Inclusive Name (ie. "ModDep" will match a mod named "A ModDependency"). diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/AssemblyManager.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/AssemblyManager.cs similarity index 80% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/AssemblyManager.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/AssemblyManager.cs index c7f582395..9d1b7c38f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/AssemblyManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/AssemblyManager.cs @@ -14,7 +14,7 @@ using Microsoft.CodeAnalysis.CSharp; // ReSharper disable EventNeverSubscribedTo.Global // ReSharper disable InconsistentNaming -namespace Barotrauma; +namespace Barotrauma.LuaCs.Services; /*** * Note: This class was written to be thread-safe in order to allow parallelization in loading in the future if the need @@ -25,36 +25,14 @@ namespace Barotrauma; /// Provides functionality for the loading, unloading and management of plugins implementing IAssemblyPlugin. /// All plugins are loaded into their own AssemblyLoadContext along with their dependencies. /// -public class AssemblyManager +public class AssemblyManager : IAssemblyManagementService { #region ExternalAPI - - /// - /// Called when an assembly is loaded. - /// + public event Action OnAssemblyLoaded; - - /// - /// Called when an assembly is marked for unloading, before unloading begins. You should use this to cleanup - /// any references that you have to this assembly. - /// public event Action OnAssemblyUnloading; - - /// - /// Called whenever an exception is thrown. First arg is a formatted message, Second arg is the Exception. - /// public event Action OnException; - - /// - /// For unloading issue debugging. Called whenever MemoryFileAssemblyContextLoader [load context] is unloaded. - /// public event Action OnACLUnload; - - - /// - /// [DEBUG ONLY] - /// Returns a list of the current unloading ACLs. - /// public ImmutableList> StillUnloadingACLs { get @@ -70,12 +48,6 @@ public class AssemblyManager } } } - - - // ReSharper disable once MemberCanBePrivate.Global - /// - /// Checks if there are any AssemblyLoadContexts still in the process of unloading. - /// public bool IsCurrentlyUnloading { get @@ -95,21 +67,6 @@ public class AssemblyManager } } } - - // Old API compatibility - public IEnumerable GetSubTypesInLoadedAssemblies() - { - return GetSubTypesInLoadedAssemblies(false); - } - - - /// - /// Allows iteration over all non-interface types in all loaded assemblies in the AsmMgr that are assignable to the given type (IsAssignableFrom). - /// Warning: care should be used when using this method in hot paths as performance may be affected. - /// - /// The type to compare against - /// Forces caches to clear and for the lists of types to be rebuilt. - /// An Enumerator for matching types. public IEnumerable GetSubTypesInLoadedAssemblies(bool rebuildList) { Type targetType = typeof(T); @@ -165,14 +122,6 @@ public class AssemblyManager OpsLockLoaded.ExitReadLock(); } } - - /// - /// Tries to get types assignable to type from the ACL given the Guid. - /// - /// - /// - /// - /// public bool TryGetSubTypesFromACL(Guid id, out IEnumerable types) { Type targetType = typeof(T); @@ -188,13 +137,6 @@ public class AssemblyManager types = null; return false; } - - /// - /// Tries to get types from the ACL given the Guid. - /// - /// - /// - /// public bool TryGetSubTypesFromACL(Guid id, out IEnumerable types) { if (TryGetACL(id, out var acl)) @@ -206,14 +148,6 @@ public class AssemblyManager types = null; return false; } - - - /// - /// Allows iteration over all types, including interfaces, in all loaded assemblies in the AsmMgr who's names match the string. - /// Note: Will return the by-reference equivalent type if the type name is prefixed with "out " or "ref ". - /// - /// The string name of the type to search for. - /// An Enumerator for matching types. List will be empty if bad params are supplied. public IEnumerable GetTypesByName(string typeName) { List types = new(); @@ -299,12 +233,6 @@ public class AssemblyManager } } } - - /// - /// Allows iteration over all types (including interfaces) in all loaded assemblies managed by the AsmMgr. - /// Warning: High usage may result in performance issues. - /// - /// An Enumerator for iteration. public IEnumerable GetAllTypesInLoadedAssemblies() { OpsLockLoaded.EnterReadLock(); @@ -325,13 +253,6 @@ public class AssemblyManager OpsLockLoaded.ExitReadLock(); } } - - /// - /// Returns a list of all loaded ACLs. - /// WARNING: References to these ACLs outside of the AssemblyManager should be kept in a WeakReference in order - /// to avoid causing issues with unloading/disposal. - /// - /// public IEnumerable GetAllLoadedACLs() { OpsLockLoaded.EnterReadLock(); @@ -358,36 +279,14 @@ public class AssemblyManager #region InternalAPI - /// - /// [Unsafe] Warning: only for use in nested threading functions. Requires care to manage access. - /// Does not make any guarantees about the state of the ACL after the list has been returned. - /// - /// [MethodImpl(MethodImplOptions.Synchronized | MethodImplOptions.NoInlining)] - internal ImmutableList UnsafeGetAllLoadedACLs() + ImmutableList IAssemblyManagementService.UnsafeGetAllLoadedACLs() { if (LoadedACLs.IsEmpty) return ImmutableList.Empty; return LoadedACLs.Select(kvp => kvp.Value).ToImmutableList(); } - - /// - /// Used by content package and plugin management to stop unloading of a given ACL until all plugins have gracefully closed. - /// public event System.Func IsReadyToUnloadACL; - - /// - /// Compiles an assembly from supplied references and syntax trees into the specified AssemblyContextLoader. - /// A new ACL will be created if the Guid supplied is Guid.Empty. - /// - /// - /// - /// - /// - /// A non-unique name for later reference. Optional, set to null if unused. - /// The guid of the assembly - /// - /// public AssemblyLoadingSuccessState LoadAssemblyFromMemory([NotNull] string compiledAssemblyName, [NotNull] IEnumerable syntaxTree, IEnumerable externalMetadataReferences, @@ -440,14 +339,6 @@ public class AssemblyManager return state; } - - /// - /// Switches the ACL with the given Guid to Template Mode, which disables assembly name resolution for any assemblies loaded in it. - /// These ACLs are intended to be used to host Assemblies for information only and not for code execution. - /// WARNING: This process is irreversible. - /// - /// Guid of the ACL. - /// Whether or not an ACL was found with the given ID. public bool SetACLToTemplateMode(Guid guid) { if (!TryGetACL(guid, out var acl)) @@ -455,16 +346,6 @@ public class AssemblyManager acl.Acl.IsTemplateMode = true; return true; } - - /// - /// Tries to load all assemblies at the supplied file paths list into the ACl with the given Guid. - /// If the supplied Guid is Empty, then a new ACl will be created and the Guid will be assigned to it. - /// - /// List of assemblies to try and load. - /// A non-unique name for later reference. Optional. - /// Guid of the ACL or Empty if none specified. Guid of ACL will be assigned to this var. - /// Operation success messages. - /// public AssemblyLoadingSuccessState LoadAssembliesFromLocations([NotNull] IEnumerable filePaths, string friendlyName, ref Guid id) { @@ -507,9 +388,7 @@ public class AssemblyManager return AssemblyLoadingSuccessState.ACLLoadFailure; } - - - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Synchronized)] + [MethodImpl(MethodImplOptions.NoInlining)] public bool TryBeginDispose() { OpsLockLoaded.EnterWriteLock(); @@ -563,8 +442,6 @@ public class AssemblyManager OpsLockLoaded.ExitWriteLock(); } } - - [MethodImpl(MethodImplOptions.NoInlining)] public bool FinalizeDispose() { @@ -606,15 +483,7 @@ public class AssemblyManager return isUnloaded; } - - /// - /// Tries to retrieve the LoadedACL with the given ID or null if none is found. - /// WARNING: External references to this ACL with long lifespans should be kept in a WeakReference - /// to avoid causing unloading/disposal issues. - /// - /// GUID of the ACL. - /// The found ACL or null if none was found. - /// Whether or not an ACL was found. + [MethodImpl(MethodImplOptions.NoInlining)] public bool TryGetACL(Guid id, out LoadedACL acl) { @@ -865,6 +734,16 @@ public class AssemblyManager } #endregion + + public void Dispose() + { + TryBeginDispose(); + } + + public void Reset() + { + TryBeginDispose(); + } } public static class AssemblyExtensions diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IAssemblyManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IAssemblyManagementService.cs new file mode 100644 index 000000000..1ba46fa9a --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IAssemblyManagementService.cs @@ -0,0 +1,189 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +// ReSharper disable InconsistentNaming + +namespace Barotrauma.LuaCs.Services; + + +public interface IAssemblyManagementService : IService +{ + #region Public API + + /// + /// Called when an assembly is loaded. + /// + public event Action OnAssemblyLoaded; + + /// + /// Called when an assembly is marked for unloading, before unloading begins. You should use this to cleanup + /// any references that you have to this assembly. + /// + public event Action OnAssemblyUnloading; + + /// + /// Called whenever an exception is thrown. First arg is a formatted message, Second arg is the Exception. + /// + public event Action OnException; + + /// + /// For unloading issue debugging. Called whenever MemoryFileAssemblyContextLoader [load context] is unloaded. + /// + // ReSharper disable once InconsistentNaming + public event Action OnACLUnload; + + + /// + /// [DEBUG ONLY] + /// Returns a list of the current unloading ACLs. + /// + // ReSharper disable once InconsistentNaming + public ImmutableList> StillUnloadingACLs { get; } + + // ReSharper disable once MemberCanBePrivate.Global + /// + /// Checks if there are any AssemblyLoadContexts still in the process of unloading. + /// + public bool IsCurrentlyUnloading { get; } + + /// + /// Allows iteration over all non-interface types in all loaded assemblies in the AsmMgr that are assignable to the given type (IsAssignableFrom). + /// Warning: care should be used when using this method in hot paths as performance may be affected. + /// + /// The type to compare against + /// Forces caches to clear and for the lists of types to be rebuilt. + /// An Enumerator for matching types. + public IEnumerable GetSubTypesInLoadedAssemblies(bool rebuildList); + + /// + /// Tries to get types assignable to type from the ACL given the Guid. + /// + /// + /// + /// + /// Operation success. + public bool TryGetSubTypesFromACL(Guid id, out IEnumerable types); + + /// + /// Tries to get types from the ACL given the Guid. + /// + /// + /// + /// + public bool TryGetSubTypesFromACL(Guid id, out IEnumerable types); + + /// + /// Allows iteration over all types, including interfaces, in all loaded assemblies in the AsmMgr who's names match the string. + /// Note: Will return the by-reference equivalent type if the type name is prefixed with "out " or "ref ". + /// + /// The string name of the type to search for. + /// An Enumerator for matching types. List will be empty if bad params are supplied. + public IEnumerable GetTypesByName(string typeName); + + /// + /// Allows iteration over all types (including interfaces) in all loaded assemblies managed by the AsmMgr. + /// Warning: High usage may result in performance issues. + /// + /// An Enumerator for iteration. + public IEnumerable GetAllTypesInLoadedAssemblies(); + + /// + /// Returns a list of all loaded ACLs. + /// WARNING: References to these ACLs outside the AssemblyManager should be kept in a WeakReference in order + /// to avoid causing issues with unloading/disposal. + /// + /// + public IEnumerable GetAllLoadedACLs(); + + #endregion + + #region InternalAPI + /*** Notes: Internal API uses the 'public' modifier because of the common and recommended use of publicized APIs + * by third-party add-ins. + */ + + /// + /// [Unsafe] Warning: only for use in nested threading functions. Requires care to manage access. + /// Does not make any guarantees about the state of the ACL after the list has been returned. + /// + /// + public ImmutableList UnsafeGetAllLoadedACLs(); + + /// + /// Used by content package and plugin management to stop unloading of a given ACL until all plugins have gracefully closed. + /// + public event System.Func IsReadyToUnloadACL; + + /// + /// Compiles an assembly from supplied references and syntax trees into the specified AssemblyContextLoader. + /// A new ACL will be created if the Guid supplied is Guid.Empty. + /// + /// + /// + /// + /// + /// A non-unique name for later reference. Optional, set to null if unused. + /// The guid of the assembly + /// + /// + public AssemblyLoadingSuccessState LoadAssemblyFromMemory([NotNull] string compiledAssemblyName, + [NotNull] IEnumerable syntaxTree, + IEnumerable externalMetadataReferences, + [NotNull] CSharpCompilationOptions compilationOptions, + string friendlyName, + ref Guid id, + IEnumerable externFileAssemblyRefs = null); + + /// + /// Switches the ACL with the given Guid to Template Mode, which disables assembly name resolution for any assemblies loaded in it. + /// These ACLs are intended to be used to host Assemblies for information only and not for code execution. + /// WARNING: This process is irreversible. + /// + /// Guid of the ACL. + /// Whether an ACL was found with the given ID. + public bool SetACLToTemplateMode(Guid guid); + + + /// + /// Tries to load all assemblies at the supplied file paths list into the ACl with the given Guid. + /// If the supplied Guid is Empty, then a new ACl will be created and the Guid will be assigned to it. + /// + /// List of assemblies to try and load. + /// A non-unique name for later reference. Optional. + /// Guid of the ACL or Empty if none specified. Guid of ACL will be assigned to this var. + /// Operation success messages. + /// + public AssemblyLoadingSuccessState LoadAssembliesFromLocations([NotNull] IEnumerable filePaths, + string friendlyName, ref Guid id); + + + /// + /// Tries to begin the disposal process of ACLs. + /// + /// Returns whether the unloading process could be initiated. + public bool TryBeginDispose(); + + + /// + /// Returns whether unloading is completed and updates the styate of the unloading cache. + /// + /// + public bool FinalizeDispose(); + + /// + /// Tries to retrieve the LoadedACL with the given ID or null if none is found. + /// WARNING: External references to this ACL with long lifespans should be kept in a WeakReference + /// to avoid causing unloading/disposal issues. + /// + /// GUID of the ACL. + /// The found ACL or null if none was found. + /// Whether an ACL was found. + public bool TryGetACL(Guid id, out AssemblyManager.LoadedACL acl); + + #endregion +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IConfigService.cs new file mode 100644 index 000000000..ac6deedd5 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IConfigService.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Barotrauma.LuaCs.Configuration; +using Barotrauma.LuaCs.Data; +using Barotrauma.Networking; + +namespace Barotrauma.LuaCs.Services; + +public interface IConfigService : IService +{ + /* + * Resource Files. + */ + bool TryAddConfigs(ImmutableArray configResources); + bool TryAddConfigsProfiles(ImmutableArray configProfileResources); + void RemoveConfigs(ImmutableArray configResources); + void RemoveConfigsProfiles(ImmutableArray configProfilesResources); + + + /* + * Already processed + */ + bool TryAddConfigs(ImmutableArray configs); + bool TryAddConfigsProfiles(ImmutableArray configProfiles); + void RemoveConfigs(ImmutableArray configs); + void RemoveConfigsProfiles(ImmutableArray configProfiles); + + /* + * Immediate mode, does not have displayable functionality + */ + IConfigEntry AddConfigEntry(ContentPackage package, string name, + T defaultValue, + NetSync syncMode = NetSync.None, + ClientPermissions permissions = ClientPermissions.None, + Func valueChangePredicate = null, + Action> onValueChanged = null) where T : IConvertible, IEquatable; + + IConfigList AddConfigList(ContentPackage package, string name, + int defaultIndex, IReadOnlyList values, + NetSync syncMode = NetSync.None, + ClientPermissions permissions = ClientPermissions.None, + Func valueChangePredicate = null, + Action onValueChanged = null); + + IReadOnlyDictionary GetConfigsForPackage(ContentPackage package); + IReadOnlyDictionary GetConfigsForPackage(string packageName); + IReadOnlyDictionary<(ContentPackage, string), IConfigBase> GetAllConfigs(); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IEventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IEventService.cs new file mode 100644 index 000000000..3c0caef4e --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IEventService.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Services; + +public interface IEventService +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IHookManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IHookManagementService.cs new file mode 100644 index 000000000..5428ed704 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IHookManagementService.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Services; + +public interface IHookManagementService : IService +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILegacyConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILegacyConfigService.cs new file mode 100644 index 000000000..a93559084 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILegacyConfigService.cs @@ -0,0 +1,8 @@ +using Barotrauma.LuaCs.Data; + +namespace Barotrauma.LuaCs.Services; + +public interface ILegacyConfigService : IService +{ + bool TryBuildModConfigFromLegacy(ContentPackage package, out IModConfigInfo configInfo); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILocalizationService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILocalizationService.cs new file mode 100644 index 000000000..045d3e986 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILocalizationService.cs @@ -0,0 +1,21 @@ +using System; +using System.Globalization; +using System.Collections.Generic; +using System.Collections.Immutable; +using Barotrauma.LuaCs.Data; + +namespace Barotrauma.LuaCs.Services; + +public interface ILocalizationService : IService +{ + IReadOnlyCollection GetLoadedLocales(); + void Remove(ImmutableArray localizations); + bool TrySetCurrentCulture(CultureInfo culture); + bool TrySetCurrentCulture(string cultureName); + bool TryLoadLocalizations(ImmutableArray localizationResources); + string GetLocalizedString(string key, string fallback); + string GetLocalizedString(string key, CultureInfo targetCulture); + bool TryRegisterLocalizationResolver(CultureInfo targetCulture, Func factoryResolver); + bool ReplaceSymbols(string text, string symbolExpr); + bool IsCurrentCultureSupported(IResourceCultureInfo culturesInfo); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILoggerService.cs new file mode 100644 index 000000000..eb457b7d3 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILoggerService.cs @@ -0,0 +1,25 @@ +using System; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; + +namespace Barotrauma.LuaCs.Services; + +/// +/// Provides console and debug logging services +/// +public interface ILoggerService : IService +{ + void HandleException(Exception exception, string prefix = null); + void LogError(string message); + void LogWarning(string message); + void LogMessage(string message, Color? serverColor = null, Color? clientColor = null); + void Log(string message, Color? color = null, ServerLog.MessageType messageType = ServerLog.MessageType.ServerMessage); + + #region DebugBuilds + + void LogDebug(string message, Color? color = null); + void LogDebugWarning(string message); + void LogDebugError(string message); + + #endregion +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILuaScriptService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILuaScriptService.cs new file mode 100644 index 000000000..6d4a8e673 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILuaScriptService.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Reflection; +using Barotrauma.LuaCs.Data; +using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Interop; + +namespace Barotrauma.LuaCs.Services; + +public interface ILuaScriptService : IService +{ + #region Script_File_Collector + + /// + /// Adds the script files to the runner but does not execute them. + /// + /// + /// + bool TryAddScriptFiles(ImmutableArray luaResource); + /// + /// Removes the specific resources from the script runner. Important: Does not stop the + /// execution of any code related to the files nor guarantee cleanup of resources! + /// + /// + void RemoveScriptFiles(ImmutableArray luaResource); + + /// + /// Executes loaded script files on the management service. + /// + /// + /// + /// + bool TryExecuteScripts(bool pauseExecutionOnScriptError = false, bool verboseLogging = false); + ImmutableArray GetScriptResources(); + + #endregion +} + +public interface ILuaScriptManagementService : IService +{ + #region Script_File_Execution + + bool TryExecuteLoadedScripts(ContentPackage package, bool pauseExecutionOnError = false, bool verboseLogging = false); + bool TryExecuteLoadedScripts(ImmutableArray scripts, bool pauseExecutionOnError = false, bool verboseLogging = false); + bool TryExecuteLoadedScripts(bool pauseExecutionOnError = false, bool verboseLogging = false); + + #endregion + + #region Type_Registration + + IUserDataDescriptor RegisterType(Type type); + IUserDataDescriptor RegisterType(string typeName); + IUserDataDescriptor RegisterGenericType(Type type); + IUserDataDescriptor RegisterGenericType(string typeName, params string[] typeNameArgs); + void UnregisterType(Type type); + void UnregisterType(string typeName); + void UnregisterAllTypes(); + + #endregion + + #region Type_Checks_&Utilities + + bool IsRegistered(Type type); + bool IsTargetType(object obj, string typeName); + string TypeOf(object obj); + object CreateStatic(string typeName); + object CreateEnumTable(string typeName); + FieldInfo FindFieldRecursively(Type type, string fieldName); + void MakeFieldAccessible(IUserDataDescriptor descriptor, string fieldName); + MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null); + void MakeMethodAccessible(IUserDataDescriptor descriptor, string methodName, string[] parameters = null); + PropertyInfo FindPropertyRecursively(Type type, string propertyName); + void MakePropertyAccessible(IUserDataDescriptor descriptor, string propertyName); + void AddMethod(IUserDataDescriptor descriptor, string methodName, object function); + void AddField(IUserDataDescriptor descriptor, string fieldName, DynValue value); + void RemoveMember(IUserDataDescriptor descriptor, string memberName); + bool HasMember(object obj, string memberName); + DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor descriptor); + DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType); + + #endregion +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/INetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/INetworkingService.cs new file mode 100644 index 000000000..3b2c7269b --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/INetworkingService.cs @@ -0,0 +1,23 @@ +using System; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Networking; +using Barotrauma.Networking; + +namespace Barotrauma.LuaCs.Services; + +public interface INetworkingService : IService +{ + bool IsActive { get; } + bool IsSynchronized { get; } + bool TryRegisterVar(INetVar var, NetSync mode, ClientPermissions permissions); + void UnregisterVar(Guid varId); + bool SendEvent(Guid varId); + void SendMessageGlobal(string id, string message); + void Synchronize(); + + #region LegacyAPI + + bool RestrictMessageSize { get; set; } + + #endregion +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageManagementService.cs new file mode 100644 index 000000000..3ef92394a --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageManagementService.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; +using Barotrauma.LuaCs.Data; + +namespace Barotrauma.LuaCs.Services; + +public interface IPackageManagementService : IService +{ + void AddPackages(ref ReadOnlySpan<(ContentPackage, bool)> packages, + bool executeImmediately = false, + bool errorOnFailures = false, + bool errorOnExistingPackageFound = false); + void LoadPackages(bool onlyUnloadedPackages = true, bool rescanPackages = false); + void UnloadPackages(bool errorOnFailures = true); + bool IsPackageLoaded(ContentPackage package); + bool CheckDependencyLoaded(IPackageDependencyInfo info); + bool CheckDependenciesLoaded(IEnumerable infos, out IReadOnlyList missingPackages); + bool CheckEnvironmentSupported(IPlatformInfo platform); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageService.cs new file mode 100644 index 000000000..1411cb313 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageService.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Barotrauma.LuaCs.Data; + +namespace Barotrauma.LuaCs.Services; + +public interface IPackageService : IService, + // These allow us the pass the IContentPackageService to anything that needs the data without having to directly reference the member + IResourceCultureInfo, IAssembliesResourcesInfo, ILocalizationsResourcesInfo, ILuaScriptsResourcesInfo +{ + ContentPackage Package { get; } + IModConfigInfo ModConfigInfo { get; } + /// + /// Try to load the XML config and resources information from the given package. + /// + /// + /// Whether the package was parsed without errors and any information was found. Will return false for purely vanilla packages. + bool TryLoadResourcesInfo([NotNull]ContentPackage package); + /// + /// Tries to load all assemblies and instance plugins for the given resources list, regardless whether they're marked as optional and/or lazy load. + /// Will sort by load priority unless overriden/bypassed. + /// + /// + /// + /// Whether loading is successful. Returns true on an empty list. + void LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false); + void LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo); + void AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo); +#if CLIENT + void LoadStyles([NotNull]IStylesResourcesInfo stylesInfo); +#endif + void LoadConfig([NotNull]IConfigsResourcesInfo configsResourcesInfo, [NotNull]IConfigProfilesResourcesInfo configProfilesResourcesInfo); +} + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginManagementService.cs new file mode 100644 index 000000000..b5c505c83 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginManagementService.cs @@ -0,0 +1,7 @@ +namespace Barotrauma.LuaCs.Services; + +public interface IPluginManagementService : IService +{ + bool IsAssemblyLoadedGlobal(string friendlyName); + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginService.cs new file mode 100644 index 000000000..cbdd9ba74 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginService.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Reflection; +using Barotrauma.LuaCs.Data; + +namespace Barotrauma.LuaCs.Services; + +public interface IPluginService : IService +{ + bool IsAssemblyLoaded(string friendlyName); + /// + /// Loads the assemblies for the given information + /// + /// + /// + /// + /// + /// + bool TryLoadAndInstanceTypes(IEnumerable assemblyResourcesInfo, bool injectServices, out ImmutableArray typeInstances) where T : class, IAssemblyPlugin; + ImmutableArray GetLoadedPluginTypesInPackage() where T : class, IAssemblyPlugin; + /// + /// Advances the loading/execution state of the plugin. IMPORTANT: You cannot set the execution state of plugins + /// to 'Disposed'. You must instead call the 'DisposePlugins' method. + /// + /// + /// + bool AdvancePluginStates(PluginRunState newState); + + /// + /// Disposes of all running plugins hosted by the service and releases their references to allow unloading. + /// + /// Success of the operation. Returns false if any plugin threw errors during disposal. + bool DisposePlugins(); + + /// + /// Gets the current plugin execution state. + /// + /// + PluginRunState GetPluginRunState(); +} + +public enum PluginRunState +{ + Instanced=0, + PreInitialization=1, + Initialized=2, + LoadingCompleted=3, + Disposed=4 +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IService.cs new file mode 100644 index 000000000..bd7595143 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IService.cs @@ -0,0 +1,15 @@ +using System; + +namespace Barotrauma.LuaCs.Services; + +/// +/// Base interface inherited by all services +/// +public interface IService : IDisposable +{ + /// + /// Returns the service to its original state (post-instantiation). + /// Allows a service instance to be reused without disposing of the instance. + /// + void Reset(); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IServicesProvider.cs new file mode 100644 index 000000000..0304b7da4 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IServicesProvider.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using LightInject; + +namespace Barotrauma.LuaCs.Services; + +/// +/// Provides instancing and management of IServices. +/// +public interface IServicesProvider +{ + #region Type_Registration + + /// + /// Registers a type as a service for a given interface. + /// + /// + /// + /// + /// + void RegisterServiceType(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface, new(); + + /// + /// Registers a type as a service for a given interface that can be requested by name. + /// + /// + /// + /// + /// + /// + void RegisterServiceType(string name, ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface, new(); + + /// + /// Called whenever a new service type for a given interface is implemented. + /// Args[0]: Interface type + /// Args[1]: Implementing type + /// + event System.Action OnServiceRegistered; + + /// + /// Runs compilation of registered services. + /// + public void Compile(); + + #endregion + + #region Services_Instancing_Injection + + /// + /// Injects services into the properties of already instanced objects. + /// + /// + /// + void InjectServices(T inst) where T : class; + + /// + /// Tries to get a service for the given interface, returns success/failure. + /// + /// + /// + /// + /// + bool TryGetService(out IService service) where TSvcInterface : class, IService; + + /// + /// Tries to get a service for the given name and interface, returns success/failure. + /// + /// + /// + /// + /// + /// + bool TryGetService(string name, out IService service) where TSvcInterface : class, IService; + + /// + /// Called whenever a new service is created/instanced. + /// Args[0]: The interface type of the service. + /// Args[1]: The instance of the service. + /// + event System.Action OnServiceInstanced; + + #endregion + + #region ActiveServices + + /// + /// Returns all services for the given interface. + /// + /// + /// + ImmutableArray GetAllServices() where TSvc : class, IService; + + #endregion + + // Notes: Left public due to the common use of Publicizers + #region Internal_Use + + /// + /// Notes: Internal use only if hosted by LuaCsForBarotrauma. Disposes of all services and resets DI container. Warning: unable to dispose of services held by other objects. + /// + void DisposeAndReset(); + + #endregion +} + +public enum ServiceLifetime +{ + Transient, Singleton, PerThread, Invalid, Custom +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IStorageService.cs new file mode 100644 index 000000000..608a5417c --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IStorageService.cs @@ -0,0 +1,42 @@ +using System.Collections.Immutable; +using System.Xml.Linq; + +namespace Barotrauma.LuaCs.Services; + +public interface IStorageService : IService +{ + #region LocalGameData + + bool TryLoadLocalXml(ContentPackage package, string localFilePath, out XDocument document); + bool TryLoadLocalBinary(ContentPackage package, string localFilePath, out byte[] bytes); + bool TryLoadLocalText(ContentPackage package, string localFilePath, out string text); + bool FileExistsInLocalData(ContentPackage package, string localFilePath); + + #endregion + + #region ContentPackageData + bool TryLoadPackageXml(ContentPackage package, string localFilePath, out XDocument document); + bool TryLoadPackageBinary(ContentPackage package, string localFilePath, out byte[] bytes); + bool TryLoadPackageText(ContentPackage package, string localFilePath, out string text); + + ImmutableArray TryLoadPackageXmlFiles(ContentPackage package, ImmutableArray localFilePath, out ImmutableArray document); + ImmutableArray TryLoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePath, out ImmutableArray bytes); + ImmutableArray TryLoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePath, out ImmutableArray text); + + bool FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively, out ImmutableArray localFilePaths); + bool FileExistsInPackage(ContentPackage package, string localFilePath); + + #endregion + + #region AbsolutePaths + + bool TryLoadXml(string filePath, out XDocument document); + bool TrySaveXml(string filePath, in XDocument document); + bool TryLoadBinary(string filePath, out byte[] bytes); + bool TrySaveBinary(string filePath, in byte[] bytes); + bool TryLoadText(string filePath, out string text); + bool TrySaveText(string filePath, string text); + bool FileExists(string filePath); + + #endregion +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs new file mode 100644 index 000000000..25fb27542 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs @@ -0,0 +1,149 @@ +using System; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; +using MoonSharp.Interpreter; + +namespace Barotrauma.LuaCs.Services; + +public partial class LoggerService : ILoggerService +{ + public bool HideUserNames = true; + +#if SERVER + private const string LogPrefix = "SV"; + private const int NetMaxLength = 1024; // character limit of vanilla Barotrauma's chat system. + private const int NetMaxMessages = 60; + + // This is used so it's possible to call logging functions inside the serverLog + // hook without creating an infinite loop + private bool _lockLog = false; +#else + private const string LogPrefix = "CL"; +#endif + + public void HandleException(Exception exception, string prefix = null) + { + string errorString = ""; + switch (exception) + { + case NetRuntimeException netRuntimeException: + if (netRuntimeException.DecoratedMessage == null) + { + errorString = $"{prefix ?? ""}{netRuntimeException.ToString()}"; + } + else + { + // FIXME: netRuntimeException.ToString() doesn't print the InnerException's stack trace... + errorString = $"{prefix ?? ""}{netRuntimeException.DecoratedMessage}: {netRuntimeException}"; + } + break; + case InterpreterException interpreterException: + if (interpreterException.DecoratedMessage == null) + { + errorString = $"{prefix ?? ""}{interpreterException.ToString()}"; + } + else + { + errorString = $"{prefix ?? ""}{interpreterException.DecoratedMessage}"; + } + break; + default: + string s = exception.StackTrace != null ? exception.ToString() : $"{exception}\n{Environment.StackTrace}"; + errorString = $"{prefix ?? ""}{s}"; + break; + } + + LogError(prefix + Environment.UserName + " " + errorString); + } + + public void LogError(string message) + { + if (HideUserNames && !Environment.UserName.IsNullOrEmpty()) + { + message = message.Replace(Environment.UserName, "USERNAME"); + } + + Log($"{message}", Color.Red, ServerLog.MessageType.Error); + } + + public void LogWarning(string message) + { + throw new NotImplementedException(); + } + + public void LogMessage(string message, Color? serverColor = null, Color? clientColor = null) + { + serverColor ??= Color.MediumPurple; + clientColor ??= Color.Purple; + +#if SERVER + Log(message, serverColor); +#else + Log(message, clientColor); +#endif + } + + public void Log(string message, Color? color = null, ServerLog.MessageType messageType = ServerLog.MessageType.ServerMessage) + { + DebugConsole.NewMessage(message, color); + +#if SERVER + void BroadcastMessage(string m) + { + foreach (var client in GameMain.Server.ConnectedClients) + { + ChatMessage consoleMessage = ChatMessage.Create("", m, ChatMessageType.Console, null, textColor: color); + GameMain.Server.SendDirectChatMessage(consoleMessage, client); + + if (!GameMain.Server.ServerSettings.SaveServerLogs || !client.HasPermission(ClientPermissions.ServerLog)) + { + continue; + } + + ChatMessage logMessage = ChatMessage.Create(messageType.ToString(), "[LuaCs] " + m, ChatMessageType.ServerLog, null); + GameMain.Server.SendDirectChatMessage(logMessage, client); + } + } + + if (GameMain.Server != null) + { + if (GameMain.Server.ServerSettings.SaveServerLogs) + { + string logMessage = "[LuaCs] " + message; + GameMain.Server.ServerSettings.ServerLog.WriteLine(logMessage, messageType, false); + + if (!_lockLog) + { + _lockLog = true; + GameMain.LuaCs?.Hook?.Call("serverLog", logMessage, messageType); + _lockLog = false; + } + } + + for (int i = 0; i < message.Length; i += NetMaxLength) + { + string subStr = message.Substring(i, Math.Min(1024, message.Length - i)); + BroadcastMessage(subStr); + } + } +#endif + } + + public void LogDebug(string message, Color? color = null) + { + throw new NotImplementedException(); + } + + public void LogDebugWarning(string message) + { + throw new NotImplementedException(); + } + + public void LogDebugError(string message) + { + throw new NotImplementedException(); + } + + public void Dispose() { } + public void Reset() { } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs new file mode 100644 index 000000000..b1c30f23e --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Services; + +public class LuaScriptService +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs new file mode 100644 index 000000000..2f5d5eb6f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using Barotrauma.LuaCs.Data; + +namespace Barotrauma.LuaCs.Services; + +public class PackageManagementService : IPackageManagementService, IPluginManagementService +{ + private readonly Func _contentPackageServiceFactory; + private readonly Lazy _assemblyManagementService; + + public PackageManagementService( + Func getPackageService, + Lazy assemblyManagementService) + { + this._contentPackageServiceFactory = getPackageService; + this._assemblyManagementService = assemblyManagementService; + } + + + public void Dispose() + { + // TODO release managed resources here + } + + public void Reset() + { + throw new NotImplementedException(); + } + + public bool IsAssemblyLoadedGlobal(string friendlyName) + { + throw new NotImplementedException(); + } + + public void AddPackages(ref ReadOnlySpan<(ContentPackage, bool)> packages, bool executeImmediately = false, bool errorOnFailures = false, + bool errorOnExistingPackageFound = false) + { + throw new NotImplementedException(); + } + + public void LoadPackages(bool onlyUnloadedPackages = true, bool rescanPackages = false) + { + throw new NotImplementedException(); + } + + public void UnloadPackages(bool errorOnFailures = true) + { + throw new NotImplementedException(); + } + + public bool IsPackageLoaded(ContentPackage package) + { + throw new NotImplementedException(); + } + + public bool CheckDependencyLoaded(IPackageDependencyInfo info) + { + throw new NotImplementedException(); + } + + public bool CheckDependenciesLoaded(IEnumerable infos, out IReadOnlyList missingPackages) + { + throw new NotImplementedException(); + } + + public bool CheckEnvironmentSupported(IPlatformInfo platform) + { + throw new NotImplementedException(); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs new file mode 100644 index 000000000..97e1f0cfe --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs @@ -0,0 +1,627 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using Barotrauma.Extensions; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Services.Processing; + +namespace Barotrauma.LuaCs.Services; + +public partial class PackageService : IPackageService +{ + private readonly ReaderWriterLockSlim _operationsUsageLock = new(); + // only stops race conditions for pointer access + + + // mod config / package scanners/parsers + private readonly Lazy _modConfigConverterService; + private readonly Lazy _legacyConfigService; + private readonly Lazy _luaScriptService; + private readonly Lazy _localizationService; + private readonly Lazy _pluginService; + private readonly Lazy _configService; + private readonly IPackageManagementService _packageManagementService; + private readonly IStorageService _storageService; + private readonly ILoggerService _loggerService; + + // .ctor in server source and client source + + // state monitors + private int _configsLoaded, _localizationsLoaded, _luaScriptsLoaded, _pluginsLoaded, _isDisposed; + private int _loadingOperationsRunning; + + public bool ConfigsLoaded + { + get => GetThreadSafeBool(ref _configsLoaded); + private set => SetThreadSafeBool(ref _configsLoaded, value); + } + public bool LocalizationsLoaded + { + get => GetThreadSafeBool(ref _localizationsLoaded); + private set => SetThreadSafeBool(ref _localizationsLoaded, value); + } + public bool LuaScriptsLoaded + { + get => GetThreadSafeBool(ref _luaScriptsLoaded); + private set => SetThreadSafeBool(ref _luaScriptsLoaded, value); + } + public bool PluginsLoaded + { + get => GetThreadSafeBool(ref _pluginsLoaded); + private set => SetThreadSafeBool(ref _pluginsLoaded, value); + } + public bool IsDisposed + { + get => GetThreadSafeBool(ref _isDisposed); + private set => SetThreadSafeBool(ref _isDisposed, value); + } + + private bool LoadingOperationsRunning + { + get => Interlocked.CompareExchange(ref _loadingOperationsRunning, 0, 0) > 0; + set // we use the set as our inc/decr + { + if (value) + { + Interlocked.Add(ref _loadingOperationsRunning, 1); + } + else + { + Interlocked.Add(ref _loadingOperationsRunning, -1); + } + } + } + + #region Member: ContentPackage + + private readonly ReaderWriterLockSlim _packageAccessLock = new(); + private ContentPackage _package; + public ContentPackage Package + { + get + { + _packageAccessLock.EnterReadLock(); + try + { + return _package; + } + finally + { + _packageAccessLock.ExitReadLock(); + } + } + private set + { + _packageAccessLock.EnterWriteLock(); + try + { + _package = value; + } + finally + { + _packageAccessLock.ExitWriteLock(); + } + } + } + + #endregion + + #region DataContracts + + #region Member: ModConfigInfo + + private readonly ReaderWriterLockSlim _modConfigUsageLock = new(); + private IModConfigInfo _modConfigInfo; + public IModConfigInfo ModConfigInfo + { + get + { + _modConfigUsageLock.EnterReadLock(); + try + { + return _modConfigInfo; + } + finally + { + _modConfigUsageLock.ExitReadLock(); + } + } + private set + { + _modConfigUsageLock.EnterWriteLock(); + try + { + _modConfigInfo = value; + } + finally + { + _modConfigUsageLock.ExitWriteLock(); + } + } + } + + #endregion + + public ImmutableArray SupportedCultures => ModConfigInfo?.SupportedCultures ?? ImmutableArray.Empty; + public ImmutableArray Assemblies => ModConfigInfo?.Assemblies ?? ImmutableArray.Empty; + public ImmutableArray Localizations => ModConfigInfo?.Localizations ?? ImmutableArray.Empty; + public ImmutableArray LuaScripts => ModConfigInfo?.LuaScripts ?? ImmutableArray.Empty; + public ImmutableArray Configs => ModConfigInfo?.Configs ?? ImmutableArray.Empty; + public ImmutableArray ConfigProfiles => ModConfigInfo?.ConfigProfiles ?? ImmutableArray.Empty; + + #endregion + + #region PublicAPI + + public bool TryLoadResourcesInfo(ContentPackage package) + { + _operationsUsageLock.EnterWriteLock(); + LoadingOperationsRunning = true; + try + { + if (IsDisposed) + { + throw new ObjectDisposedException($"This package service instance is disposed!"); + } + + // try loading the ModConfig.xml. If it fails, use the Legacy loader to try and construct one from the package structure. + if (_storageService.TryLoadPackageXml(package, "ModConfig.xml", out var configXml) + && configXml.Root is not null) + { + if (_modConfigConverterService.Value.TryParseResource(configXml.Root, out IModConfigInfo configInfo)) + { + ModConfigInfo = configInfo; + } + else + { + _loggerService.LogError( + $"Failed to parse ModConfig.xml for package {package.Name}, package mod content not loaded."); + return false; + } + } + else if (_legacyConfigService.Value.TryBuildModConfigFromLegacy(package, out var legacyConfig)) + { + ModConfigInfo = legacyConfig; + } + else + { + // vanilla mod or broken + return false; + } + + return true; + } + finally + { + LoadingOperationsRunning = false; + _operationsUsageLock.ExitWriteLock(); + } + } + + public void LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false) + { + _operationsUsageLock.EnterReadLock(); + LoadingOperationsRunning = true; + try + { + if (IsDisposed) + { + throw new ObjectDisposedException($"This package service instance is disposed!"); + } + + SanitationChecksCore(assembliesInfo, "assemblies", nameof(LoadPlugins)); + SanitationChecksEnumerable(assembliesInfo.Assemblies, "assemblies", nameof(LoadPlugins)); + +#if DEBUG + assembliesInfo.Assemblies.ForEach(ari => + { + if (!this.Assemblies.Contains(ari)) + { + throw new ArgumentException( + $"Package Service: tried to load the assembly resource {ari.InternalName} for package {this.Package.Name} but it is not in the list for this package."); + } + }); +#endif + + // Order these assemblies by internal dependencies + ImmutableArray resources; + if (ignoreDependencySorting) + { + resources = assembliesInfo.Assemblies; + } + else // sort by load order + { + resources = assembliesInfo.Assemblies + .OrderByDescending(a => a.LoadPriority) + .ToImmutableArray(); + } + + // Try loading them, throw on failure. + if (!_pluginService.Value.TryLoadAndInstanceTypes(resources, true, out var instancedTypes)) + { + throw new TypeLoadException($"PackageService: unable to load assemblies for package {this.Package.Name}! Aborting loading!"); + } + + PluginsLoaded = true; + } + finally + { + LoadingOperationsRunning = false; + _operationsUsageLock.ExitReadLock(); + } + } + + public void LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo) + { + _operationsUsageLock.EnterReadLock(); + LoadingOperationsRunning = true; + try + { + if (IsDisposed) + { + throw new ObjectDisposedException($"This package service instance is disposed!"); + } + + SanitationChecksCore(localizationsInfo, "localizations", nameof(LoadLocalizations)); + SanitationChecksEnumerable(localizationsInfo.Localizations, "localizations", nameof(LoadLocalizations)); + +#if DEBUG + localizationsInfo.Localizations.ForEach(ri => + { + if (!this.Localizations.Contains(ri)) + { + throw new ArgumentException( + $"Package Service: tried to load the localization resource for package {this.Package.Name} but it is not in the list for this package."); + } + }); +#endif + + if (!_localizationService.Value.TryLoadLocalizations(localizationsInfo.Localizations)) + { + throw new FileLoadException($"Package Service: unable to load localizations for package {this.Package.Name}! Aborting!"); + } + + LocalizationsLoaded = true; + } + finally + { + LoadingOperationsRunning = false; + _operationsUsageLock.ExitReadLock(); + } + } + + public void AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo) + { + _operationsUsageLock.EnterReadLock(); + LoadingOperationsRunning = true; + try + { + if (IsDisposed) + { + throw new ObjectDisposedException($"This package service instance is disposed!"); + } + + SanitationChecksCore(luaScriptsInfo, "luaScripts", nameof(AddLuaScripts)); + SanitationChecksEnumerable(luaScriptsInfo.LuaScripts, "luaScripts", nameof(AddLuaScripts)); + +#if DEBUG + luaScriptsInfo.LuaScripts.ForEach(ri => + { + if (!this.LuaScripts.Contains(ri)) + { + throw new ArgumentException( + $"Package Service: tried to load the lua script resource for package {this.Package.Name} but it is not in the list for this package."); + } + }); +#endif + + if (!_luaScriptService.Value.TryAddScriptFiles(luaScriptsInfo.LuaScripts)) + { + throw new ArgumentException( + $"Package Service: unable to add lua files for package {this.Package.Name}! Aborting!"); + } + + LuaScriptsLoaded = true; + } + finally + { + LoadingOperationsRunning = false; + _operationsUsageLock.ExitReadLock(); + } + } + + public void LoadConfig( + [NotNull]IConfigsResourcesInfo configsResourcesInfo, + [NotNull]IConfigProfilesResourcesInfo configProfilesResourcesInfo) + { + _operationsUsageLock.EnterReadLock(); + LoadingOperationsRunning = true; + try + { + if (IsDisposed) + { + throw new ObjectDisposedException($"This package service instance is disposed!"); + } + + SanitationChecksCore(configsResourcesInfo, "config", nameof(LoadConfig)); + SanitationChecksCore(configProfilesResourcesInfo, "config profiles", nameof(LoadConfig)); + SanitationChecksEnumerable(configsResourcesInfo.Configs, "config", nameof(LoadConfig)); + SanitationChecksEnumerable(configProfilesResourcesInfo.ConfigProfiles, "config profiles", nameof(LoadConfig)); + +#if DEBUG + configsResourcesInfo.Configs.ForEach(ri => + { + if (!this.Configs.Contains(ri)) + { + throw new ArgumentException( + $"Package Service: tried to load the configs resource for package {this.Package.Name} but it is not in the list for this package."); + } + }); + + configProfilesResourcesInfo.ConfigProfiles.ForEach(ri => + { + if (!this.ConfigProfiles.Contains(ri)) + { + throw new ArgumentException( + $"Package Service: tried to load the localization resource for package {this.Package.Name} but it is not in the list for this package."); + } + }); +#endif + + if (!_configService.Value.TryAddConfigs(configsResourcesInfo.Configs)) + { + throw new ArgumentException( + $"Package Service: unable to add configs for package {this.Package.Name}! Aborting!"); + } + + if (!_configService.Value.TryAddConfigsProfiles(configProfilesResourcesInfo.ConfigProfiles)) + { + throw new ArgumentException( + $"Package Service: unable to add configs profiles for package {this.Package.Name}! Aborting!"); + } + ConfigsLoaded = true; + } + finally + { + LoadingOperationsRunning = false; + _operationsUsageLock.ExitReadLock(); + } + } + + public void Dispose() + { + /* + * Notes: we need to unload this package from services in the order that the services are dependent on each other. + * Unloading Order: Lua Scripts > Assemblies > Config Profiles > Configs > Styles > Localizations + */ + _operationsUsageLock.EnterWriteLock(); + try + { + if (this.Package is null) + { + _loggerService.LogError( + $"Package Service: cannot Dispose of service as ContentPackage and info is not set!"); + return; + } + + if (this.ModConfigInfo is null) + { + _loggerService.LogError($"Package Service: cannot Dispose of service as ModConfigInfo is not loaded!"); + return; + } + + /* + * To be graceful, we want to ensure that any async calls and other threads are allowed to be processed before we begin + * disposal to reduce friction with other thread operations, so we release the lock and periodically check it + * to see of other threads have finished operations before cleaning everything up. + */ + + IsDisposed = true; // set stop flag, callers should handle exception cases + Interlocked.MemoryBarrier(); //ensure cache states + + DateTime timeoutLimit = DateTime.Now.AddSeconds(10); + while (LoadingOperationsRunning) + { + _operationsUsageLock.ExitWriteLock(); + Thread.Sleep(1); + _operationsUsageLock.EnterWriteLock(); + if (timeoutLimit < DateTime.Now) + { + _loggerService.LogError($"Package Service: Dispose() time out reached while waiting for other operations. Continuing."); + break; + } + } + + GC.SuppressFinalize(this); + + _luaScriptService.Value.RemoveScriptFiles(this.LuaScripts); + _pluginService.Value.DisposePlugins(); + _configService.Value.RemoveConfigsProfiles(this.ConfigProfiles); + _configService.Value.RemoveConfigs(this.Configs); +#if CLIENT + _stylesService.Value.UnloadAllStyles(); +#endif + _localizationService.Value.Remove(this.Localizations); + + ModConfigInfo = null; + Package = null; + } + catch + { + _loggerService.LogError($"Package Service: exception while running Dispose()."); + throw; + } + finally + { + _operationsUsageLock.ExitWriteLock(); + } + } + + public void Reset() + { + _operationsUsageLock.EnterWriteLock(); + try + { + if (this.Package is null) + { + _loggerService.LogError( + $"Package Service: cannot Dispose of service as ContentPackage and info is not set!"); + return; + } + + if (this.ModConfigInfo is null) + { + _loggerService.LogError($"Package Service: cannot Dispose of service as ModConfigInfo is not loaded!"); + return; + } + + Interlocked.MemoryBarrier(); //ensure cache states + + DateTime timeoutLimit = DateTime.Now.AddSeconds(10); + while (LoadingOperationsRunning) + { + _operationsUsageLock.ExitWriteLock(); + Thread.Sleep(1); + _operationsUsageLock.EnterWriteLock(); + if (timeoutLimit < DateTime.Now) + { + _loggerService.LogError($"Package Service: Dispose() time out reached while waiting for other operations. Continuing."); + break; + } + } + + if (LuaScriptsLoaded) + { + _luaScriptService.Value.RemoveScriptFiles(this.LuaScripts); + LuaScriptsLoaded = false; + } + + if (PluginsLoaded) + { + _pluginService.Value.DisposePlugins(); + PluginsLoaded = false; + } + + if (ConfigsLoaded) + { + _configService.Value.RemoveConfigsProfiles(this.ConfigProfiles); + _configService.Value.RemoveConfigs(this.Configs); + ConfigsLoaded = false; + } + + if (LocalizationsLoaded) + { + _localizationService.Value.Remove(this.Localizations); + LocalizationsLoaded = false; + } + } + finally + { + _operationsUsageLock.ExitWriteLock(); + } + } + + #endregion + + #region INTERNAL + + private void SanitationChecksCore(object o, string resTypeInfoName, string callerName) + { + if (o is null) + { + _loggerService.LogError($"Package Service: {resTypeInfoName} resources list is null!"); + throw new NullReferenceException($"Package Service: {resTypeInfoName} resources list is null!"); + } + + if (this.Package is null) + { + _loggerService.LogError($"Package Service: package not set at {callerName}()!"); + throw new NullReferenceException($"Package Service: package not set at {callerName}()!"); + } + } + + private void SanitationChecksEnumerable(ImmutableArray resourceInfos, string resTypeInfoName, string callerName) where T : IResourceInfo, IResourceCultureInfo, IPackageInfo, IPackageDependenciesInfo + { + // Check if list is empty. Nothing more to do. + if (resourceInfos.IsDefaultOrEmpty) + return; + + // Check if all resources in the list are registered to this package, throw if not. + foreach (var resourceInfo in resourceInfos) + { + // ownership checks + if (resourceInfo.OwnerPackage is null) + { + throw new ArgumentException($"Package Service: {resTypeInfoName} info for resource does not have a package name set! Run by {this.Package.Name}."); + } + + if (resourceInfo.OwnerPackage != this.Package) + { + throw new ArgumentException( + $"Package Service: {resTypeInfoName} info does not belong to this package! Owned by {resourceInfo.OwnerPackage.Name} but is run by {this.Package.Name}."); + } + + // Check if external dependencies are loaded and if current environment is supported, throw if not + if (resourceInfo.Dependencies.IsDefaultOrEmpty) + continue; + + bool resourceMissing = false; + + resourceInfo.Dependencies.ForEach(pdi => + { + // for clarification: assemblies passed to the function should always be loaded. + // optional assemblies should be filtered out before the list is sent. + // left this as a reminder :) + /*if (pdi.Optional) + return;*/ + if (!_packageManagementService.CheckDependencyLoaded(pdi)) + { + resourceMissing = true; + _loggerService.LogError( + $"Package Service: the following dependency for package {resourceInfo.OwnerPackage.Name} is not loaded: {pdi.DependencyPackage?.Name ?? (pdi.PackageName.IsNullOrWhiteSpace() ? pdi.SteamWorkshopId.ToString() : pdi.PackageName)}"); + } + }); + + if (!resourceMissing) + { + throw new FileLoadException($"Package Service: dependencies for package {resourceInfo.OwnerPackage.Name} are not loaded."); + } + + // check runtime platform + if (!_packageManagementService.CheckEnvironmentSupported(resourceInfo)) + { + throw new PlatformNotSupportedException($"Package service: the {resTypeInfoName} from {resourceInfo.OwnerPackage.Name} is not supported on this platform."); + } + + // check local culture + if (!_localizationService.Value.IsCurrentCultureSupported(resourceInfo)) + { + throw new PlatformNotSupportedException($"Package service: the {resTypeInfoName} from {resourceInfo.OwnerPackage.Name} is not supported in this culture."); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool GetThreadSafeBool(ref int var) => Interlocked.CompareExchange(ref var, 1, 1) == 1; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void SetThreadSafeBool(ref int var, bool value) + { + if (value) + { + Interlocked.CompareExchange(ref var, 1, 0); + } + else + { + Interlocked.CompareExchange(ref var, 0, 1); + } + } + + #endregion +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs new file mode 100644 index 000000000..dbb150ff0 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Xml.Linq; +using Barotrauma.LuaCs.Data; + +namespace Barotrauma.LuaCs.Services.Processing; + +#region TypeDef + +// ReSharper disable once TypeParameterCanBeVariant +public interface IConverterService : IService +{ + bool TryParseResource(TSrc src, out TOut resources); + bool TryParseResources(IEnumerable sources, out List resources); +} + +public interface IXmlResourceConverterService : IConverterService { } +public interface IResourceToXmlConverterService : IConverterService { } + +#endregion + +/// +/// Parses Xml to produce loading metadata info for linked loadable files. +/// +#region XmlToResourceInfoParsers + +public interface IXmlAssemblyResConverter : IXmlResourceConverterService { } +public interface IXmlConfigResConverterService : IXmlResourceConverterService { } +public interface IXmlLocalizationResConverterService : IXmlResourceConverterService { } + +#endregion + +/// +/// Parses Xml to produce ready-to-use info/data without any additional file/data loading. +/// +#region XmlToInfoParsers +public interface IXmlDependencyConverterService : IXmlResourceConverterService { } +public interface IXmlModConfigConverterService : IXmlResourceConverterService { } +/// +/// Parses legacy packages that make use of the RunConfig.xml structure to produce a ModConfig. +/// +public interface IXmlLegacyModConfigConverterService : IXmlResourceConverterService { } + +#endregion + + +#region ResToInfoParsers +public interface ILocalizationResToInfoParser : IConverterService { } +public interface IConfigResConverterService : IConverterService { } +public interface IConfigProfileResConverterService : IConverterService { } + +#endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs new file mode 100644 index 000000000..2f4004e3b --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Services.Safe; + +public interface ILuaConfigService : ILuaService +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaDataService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaDataService.cs new file mode 100644 index 000000000..04f4893bc --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaDataService.cs @@ -0,0 +1,39 @@ +using MoonSharp.Interpreter; + +namespace Barotrauma.LuaCs.Services.Safe; + +/// +/// Service for providing stateful functions and in-memory storage for lua functions +/// +public interface ILuaDataService : ILuaService +{ + /// + /// Returns stored table for the given object if it exists. + /// + /// + /// + /// The table data or null if none exists. + Table GetObjectTable(object obj, string tableName); + + /// + /// Returns stored table data under the given name if it exists. + /// + /// + /// The table data or null if none exists. + Table GetTable(string tableName); + + /// + /// Returns stored table data for the given object or creates a new table if one doesn't exist. + /// + /// + /// + /// + Table GetOrCreateObjectTable(object obj, string tableName); + + /// + /// Returns stored table data or creates a new table if one doesn't exist. + /// + /// + /// + Table GetOrCreateTable(string tableName); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs new file mode 100644 index 000000000..07f38202c --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Services.Safe; + +public interface ILuaEventService : ILuaService +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaNetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaNetworkingService.cs new file mode 100644 index 000000000..5df591472 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaNetworkingService.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Services.Safe; + +public interface ILuaNetworkingService : ILuaService +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageManagementService.cs new file mode 100644 index 000000000..391d680a5 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageManagementService.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Services.Safe; + +public interface ILuaPackageManagementService : ILuaService +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageService.cs new file mode 100644 index 000000000..891224015 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageService.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Services.Safe; + +public interface ILuaPackageService : ILuaService +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaService.cs new file mode 100644 index 000000000..0c6093319 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaService.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Services.Safe; + +public interface ILuaService +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs new file mode 100644 index 000000000..b06b74940 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections.Immutable; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading; +using LightInject; + +namespace Barotrauma.LuaCs.Services; + + +public class ServicesProvider : IServicesProvider +{ + private ServiceContainer _serviceContainerInst; + private ServiceContainer ServiceContainer + { + get + { + // ReSharper disable once ConvertIfStatementToNullCoalescingExpression + if (_serviceContainerInst is null) + _serviceContainerInst = new ServiceContainer(); + return _serviceContainerInst; + } + } + + private readonly ReaderWriterLockSlim _serviceLock = new(); + + public void RegisterServiceType(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface, new() + { + if (lifetimeInstance is null) + { + switch (lifetime) + { + case ServiceLifetime.Singleton: + lifetimeInstance = new PerContainerLifetime(); + break; + case ServiceLifetime.PerThread: + lifetimeInstance = new PerThreadLifetime(); + break; + // treat these as transient + case ServiceLifetime.Transient: + case ServiceLifetime.Invalid: + case ServiceLifetime.Custom: // lifetime should not be null here + default: + lifetimeInstance = new PerRequestLifeTime(); + break; + } + } + + try + { + _serviceLock.EnterReadLock(); + ServiceContainer.Register(lifetimeInstance); + ServiceContainer.Compile(); + OnServiceRegistered?.Invoke(typeof(TSvcInterface), typeof(TService)); + } + finally + { + _serviceLock.ExitReadLock(); + } + } + + public void RegisterServiceType(string name, ServiceLifetime lifetime, + ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface, new() + { + if (name.IsNullOrWhiteSpace()) + { + throw new ArgumentNullException($"Tried to register a service of type {typeof(TService).Name} but the name provided is null or empty." ); + } + + if (lifetimeInstance is null) + { + switch (lifetime) + { + case ServiceLifetime.Singleton: + lifetimeInstance = new PerContainerLifetime(); + break; + case ServiceLifetime.PerThread: + lifetimeInstance = new PerThreadLifetime(); + break; + // treat these as transient + case ServiceLifetime.Transient: + case ServiceLifetime.Invalid: + case ServiceLifetime.Custom: // lifetime should not be null here + default: + lifetimeInstance = new PerRequestLifeTime(); + break; + } + } + + try + { + _serviceLock.EnterReadLock(); + ServiceContainer.Register(name, lifetimeInstance); + ServiceContainer.Compile(); + OnServiceRegistered?.Invoke(typeof(TSvcInterface), typeof(TService)); + } + finally + { + _serviceLock.ExitReadLock(); + } + } + + public void Compile() + { + try + { + _serviceLock.EnterReadLock(); + ServiceContainer?.Compile(); + } + finally + { + _serviceLock.ExitReadLock(); + } + } + + public event Action OnServiceRegistered; + + public void InjectServices(T inst) where T : class + { + try + { + _serviceLock.EnterReadLock(); + ServiceContainer.InjectProperties(inst); + } + finally + { + _serviceLock.ExitReadLock(); + } + } + + public bool TryGetService(out IService service) where TSvcInterface : class, IService + { + try + { + _serviceLock.EnterReadLock(); + service = ServiceContainer.TryGetInstance(); + return service is not null; + } + catch + { + service = null; + return false; + } + finally + { + _serviceLock.ExitReadLock(); + } + } + + public bool TryGetService(string name, out IService service) where TSvcInterface : class, IService + { + try + { + _serviceLock.EnterReadLock(); + service = ServiceContainer.TryGetInstance(name); + return service is not null; + } + catch + { + service = null; + return false; + } + finally + { + _serviceLock.ExitReadLock(); + } + } + + public event Action OnServiceInstanced; + + public ImmutableArray GetAllServices() where TSvc : class, IService + { + try + { + _serviceLock.EnterReadLock(); + return ServiceContainer.GetAllInstances().ToImmutableArray(); + } + finally + { + _serviceLock.ExitReadLock(); + } + } + + [MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.NoInlining)] + public void DisposeAndReset() + { + // Plugins should never be allowed to execute this. + if (Assembly.GetCallingAssembly() != Assembly.GetExecutingAssembly()) + { + throw new MethodAccessException( + $"Assembly {Assembly.GetCallingAssembly().FullName} attempted to call DisposeAllServices()."); + } + + try + { + _serviceLock.EnterWriteLock(); + _serviceContainerInst?.Dispose(); + _serviceContainerInst = new ServiceContainer(); + } + finally + { + _serviceLock.ExitWriteLock(); + } + } +} + +public class PerThreadLifetime : ILifetime +{ + private readonly ThreadLocal _instance = new(); + + public object GetInstance(Func createInstance, Scope scope) + { + if (_instance.Value is null) + { + var inst = createInstance.Invoke(); + // IDisposable dispatch + if (inst is IDisposable disposable) + { + if (scope is null) + { + throw new InvalidOperationException("Attempt disposable object without a valid scope."); + } + scope.TrackInstance(disposable); + } + + _instance.Value = inst; + } + + return _instance.Value; + } +} From 6880e5e9ee7cdb57fdb182d555679db2ececd472 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 4 Nov 2024 02:33:31 -0500 Subject: [PATCH 006/288] [Milestone] AssemblyLoader completed. Details: - Assembly Mgmt Service for loading now a separate interface, not intended for normal use. - Assembly Loader work; implemented custom dictionary key and table. - Assembly loading work. - EventService completed. - Moved assembly extensions to ModUtils.cs - Work to event service. NetworkService work - Added ImpromptuInterfaces package. - Networking Service work to support NetVars - Event Service - Added assemblies references package for script compilation. Updated Roslyn version for compatibility. - Package Loading work. Swap Harmony to HarmonyX - More refactor conversion to FluentResults. - Updated StylesService to return Results. - Refactor of PackageService partially complete. - Made IService.Reset() required to return a Result. - Moved plugin/assembly related code to their own folder (same namespace). - Updated interfaces to reflect the use of Result. - Partial refactor, incomplete. - Added 'FluentResults' so we can stop using cursed Exception-based flow control in loading code. - Added 'OneOf' nuget package: https://github.com/mcintyre321/OneOf for the implementation of the Optional pattern and complex discrete return types instead of cursed enums (see current AssemblyManager.cs). - Reapplied old branch changes. --- .../LuaCs/Configuration/DisplayableData.cs | 22 +- .../LuaCs/Configuration/IConfigControl.cs | 12 + ...IDisplayableConfig.cs => IDisplayables.cs} | 19 +- .../LuaCs/Data/DataInterfaceDefinitions.cs | 1 + .../LuaCs/Data/IResourceInfoDeclarations.cs | 2 +- .../Compatibility/ILuaCsNetworking.cs | 8 + .../LuaCs/Services/IClientLoggerService.cs | 2 +- .../LuaCs/Services/IConfigService.cs | 35 + .../LuaCs/Services/IStylesService.cs | 6 +- .../LuaCs/Services/NetworkingService.cs | 135 ++++ .../LuaCs/Services/PackageService.cs | 9 +- .../LuaCs/Services/StylesService.cs | 53 +- .../ClientSource/Networking/GameClient.cs | 2 +- .../BarotraumaClient/LinuxClient.csproj | 1 + Barotrauma/BarotraumaClient/MacClient.csproj | 1 + .../BarotraumaClient/WindowsClient.csproj | 1 + .../BarotraumaServer/LinuxServer.csproj | 1 + Barotrauma/BarotraumaServer/MacServer.csproj | 1 + .../BarotraumaServer/ServerSource/GameMain.cs | 4 +- .../LuaCs/Services/NetworkingService.cs | 176 +++++ .../LuaCs/Services/PackageService.cs | 6 +- .../BarotraumaServer/WindowsServer.csproj | 1 + Barotrauma/BarotraumaShared/Luatrauma.props | 9 +- .../SharedSource/Items/Item.cs | 6 +- .../LuaCs/Configuration/IConfigBase.cs | 5 +- .../LuaCs/Configuration/IConfigEntry.cs | 2 + .../LuaCs/Data/DataInterfaceDefinitions.cs | 99 ++- .../LuaCs/Data/EPlatformsTargets.cs | 3 +- .../LuaCs/Data/IBaseInfoDefinitions.cs | 18 +- .../SharedSource/LuaCs/Data/IConfigInfo.cs | 29 +- .../SharedSource/LuaCs/Data/IDataInfo.cs | 20 + .../LuaCs/Data/ILocalizationInfo.cs | 12 +- .../LuaCs/Data/IPackageDependencyInfo.cs | 21 +- .../LuaCs/Data/IResourceInfoDeclarations.cs | 4 +- .../SharedSource/LuaCs/IEvents.cs | 212 +++++ .../LuaCs/Lua/LuaClasses/LuaGame.cs | 7 +- .../SharedSource/LuaCs/LuaCsHook.cs | 2 +- .../SharedSource/LuaCs/LuaCsModStore.cs | 114 --- .../LuaCs/LuaCsPerformanceCounter.cs | 93 ++- .../SharedSource/LuaCs/LuaCsSetup.cs | 486 +++++------- .../SharedSource/LuaCs/LuaCsUtility.cs | 6 +- .../SharedSource/LuaCs/ModUtils.cs | 731 +++++++++++------- .../SharedSource/LuaCs/Networking/INetVar.cs | 14 +- .../LuaCs/Networking/NetInterfaceCompat.cs | 18 +- .../LuaCs/Plugins/IAssemblyPlugin.cs | 22 - .../LuaCs/Services/AssemblyManager.cs | 47 +- .../Services/Compatibility/ILuaCsHook.cs | 15 + .../Services/Compatibility/ILuaCsLogger.cs | 6 + .../Compatibility/ILuaCsNetworking.cs | 6 + .../Services/Compatibility/ILuaCsShim.cs | 6 + .../Services/Compatibility/ILuaCsUtility.cs | 6 + .../LuaCs/Services/EventService.cs | 376 +++++++++ .../Services/IAssemblyManagementService.cs | 189 ----- .../LuaCs/Services/IConfigService.cs | 50 -- .../LuaCs/Services/IHookManagementService.cs | 6 - .../LuaCs/Services/ILegacyConfigService.cs | 8 - .../LuaCs/Services/ILocalizationService.cs | 21 - .../LuaCs/Services/INetworkingService.cs | 23 - .../Services/IPackageManagementService.cs | 21 - .../Services/IPluginManagementService.cs | 7 - .../SharedSource/LuaCs/Services/IService.cs | 15 - .../LuaCs/Services/IStorageService.cs | 42 - .../LuaCs/Services/LoggerService.cs | 7 +- .../LuaCs/Services/LuaScriptService.cs | 176 ++++- .../LuaCs/Services/NetworkingService.cs | 128 +++ .../Services/PackageManagementService.cs | 434 ++++++++++- .../LuaCs/Services/PackageService.cs | 419 +++++----- .../LuaCs/Services/PluginManagementService.cs | 52 ++ .../{IEventService.cs => PluginService.cs} | 2 +- .../IConverterServiceDefinitions.cs | 7 +- .../Processing/IModConfigParserService.cs | 9 + .../LuaCs/Services/Safe/ILuaConfigService.cs | 4 +- .../LuaCs/Services/Safe/ILuaEventService.cs | 30 +- .../LuaCs/Services/ServicesProvider.cs | 10 +- .../_Interfaces/IAssemblyManagementService.cs | 46 ++ .../Services/_Interfaces/IConfigService.cs | 86 +++ .../Services/_Interfaces/IEventService.cs | 45 ++ .../_Interfaces/ILocalizationService.cs | 32 + .../{ => _Interfaces}/ILoggerService.cs | 6 +- .../{ => _Interfaces}/ILuaScriptService.cs | 16 +- .../_Interfaces/INetworkingService.cs | 25 + .../_Interfaces/IPackageManagementService.cs | 91 +++ .../{ => _Interfaces}/IPackageService.cs | 18 +- .../_Interfaces/IPluginManagementService.cs | 53 ++ .../{ => _Interfaces}/IPluginService.cs | 10 +- .../LuaCs/Services/_Interfaces/IService.cs | 29 + .../{ => _Interfaces}/IServicesProvider.cs | 12 +- .../Services/_Interfaces/IStorageService.cs | 42 + .../LuaCs/{Plugins => _Plugins}/ACsMod.cs | 0 .../{Plugins => _Plugins}/ApplicationMode.cs | 0 .../LuaCs/_Plugins/AssemblyLoader.cs | 428 ++++++++++ .../AssemblyLoadingSuccessState.cs | 0 .../{Plugins => _Plugins}/CsPackageManager.cs | 6 +- .../LuaCs/_Plugins/IAssemblyLoaderService.cs | 106 +++ .../LuaCs/_Plugins/IAssemblyPlugin.cs | 6 + .../MemoryFileAssemblyContextLoader.cs | 2 +- .../LuaCs/{Plugins => _Plugins}/RunConfig.cs | 0 97 files changed, 4100 insertions(+), 1512 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IConfigControl.cs rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/{IDisplayableConfig.cs => IDisplayables.cs} (83%) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs create mode 100644 Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IDataInfo.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsModStore.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/IAssemblyPlugin.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsLogger.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsShim.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsUtility.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IAssemblyManagementService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IConfigService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IHookManagementService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILegacyConfigService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILocalizationService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/INetworkingService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageManagementService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginManagementService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IStorageService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/{IEventService.cs => PluginService.cs} (61%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IModConfigParserService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IAssemblyManagementService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IEventService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILocalizationService.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/{ => _Interfaces}/ILoggerService.cs (85%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/{ => _Interfaces}/ILuaScriptService.cs (79%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/{ => _Interfaces}/IPackageService.cs (58%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/{ => _Interfaces}/IPluginService.cs (74%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IService.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/{ => _Interfaces}/IServicesProvider.cs (86%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Plugins => _Plugins}/ACsMod.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Plugins => _Plugins}/ApplicationMode.cs (100%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Plugins => _Plugins}/AssemblyLoadingSuccessState.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Plugins => _Plugins}/CsPackageManager.cs (99%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyPlugin.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Plugins => _Plugins}/MemoryFileAssemblyContextLoader.cs (99%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Plugins => _Plugins}/RunConfig.cs (100%) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/DisplayableData.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/DisplayableData.cs index 64649068e..59df05404 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/DisplayableData.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/DisplayableData.cs @@ -2,15 +2,17 @@ using Microsoft.Xna.Framework; namespace Barotrauma.LuaCs.Configuration; -public class DisplayableData : IDisplayableData +public record DisplayableData : IDisplayableData { - public string Name { get; private set; } - public string ModName { get; private set; } - public string DisplayName { get; private set; } - public string DisplayModName { get; private set; } - public string DisplayCategory { get; private set; } - public string Tooltip { get; private set; } - public string ImageIcon { get; private set; } - public Point IconResolution { get; private set; } - public bool ShowWhenNotLoaded { get; private set; } + public string InternalName { get; init; } + public ContentPackage OwnerPackage { get; init; } + public string FallbackPackageName { get; init; } + public string DisplayName { get; init; } + public string DisplayModName { get; init; } + public string DisplayCategory { get; init; } + public string Tooltip { get; init; } + public string ImageIcon { get; init; } + public Point IconResolution { get; init; } + public bool ShowWhenNotLoaded { get; init; } + public string Description { get; init; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IConfigControl.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IConfigControl.cs new file mode 100644 index 000000000..4e3afbd93 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IConfigControl.cs @@ -0,0 +1,12 @@ +using System; + +namespace Barotrauma.LuaCs.Configuration; + +public interface IConfigControl : IConfigBase +{ + event Action OnDown; + KeyOrMouse Value { get; } + bool IsAssignable(KeyOrMouse value); + bool TrySetValue(KeyOrMouse value); + bool IsDown(); +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayableConfig.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayables.cs similarity index 83% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayableConfig.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayables.cs index 69449ed6b..0422fb91c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayableConfig.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayables.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Barotrauma.LuaCs.Data; using Microsoft.Xna.Framework; namespace Barotrauma.LuaCs.Configuration; @@ -6,16 +7,8 @@ namespace Barotrauma.LuaCs.Configuration; /// /// Contains the Display Data for use with Menus. /// -public interface IDisplayableData +public interface IDisplayableData : IDataInfo { - /// - /// Internal name of the instance. - /// - string Name { get; } - /// - /// Internal mod name of the instance. ContentPackage name will be used by default. - /// - string ModName { get; } /// /// The name to display in GUIs and Menus. /// @@ -44,6 +37,10 @@ public interface IDisplayableData /// Whether to show the entry in the menu when not loaded. /// bool ShowWhenNotLoaded { get; } + /// + /// What does this setting do? + /// + string Description { get; } } public interface IDisplayableInitialize @@ -53,8 +50,8 @@ public interface IDisplayableInitialize // copy this as needed /*public void Initialize(IDisplayableData values) { - this.Name = values.Name; - this.ModName = values.ModName; + this.InternalName = values.InternalName; + this.OwnerPackage = values.OwnerPackage; this.DisplayName = values.DisplayName; this.DisplayModName = values.DisplayModName; this.DisplayCategory = values.DisplayCategory; diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs index d46048703..d7be1b0cf 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs @@ -17,5 +17,6 @@ public record StylesResourceInfo : IStylesResourceInfo public bool Optional { get; init; } public ImmutableArray SupportedCultures { get; init; } public string InternalName { get; init; } + public ContentPackage OwnerPackage { get; init; } public ImmutableArray Dependencies { get; init; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs index 14974ac8c..bd9e60424 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs @@ -4,7 +4,7 @@ namespace Barotrauma.LuaCs.Data; public partial interface IModConfigInfo : IStylesResourcesInfo { } -public interface IStylesResourceInfo : IResourceInfo, IResourceCultureInfo, ILoadableResourceInfo, IPackageDependenciesInfo { } +public interface IStylesResourceInfo : IResourceInfo, IResourceCultureInfo, IDataInfo, IPackageDependenciesInfo { } public interface IStylesResourcesInfo { diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs new file mode 100644 index 000000000..570debbaf --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs @@ -0,0 +1,8 @@ +using Barotrauma.Networking; + +namespace Barotrauma.LuaCs.Services.Compatibility; + +internal partial interface ILuaCsNetworking : ILuaCsShim +{ + +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IClientLoggerService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IClientLoggerService.cs index 21cc88352..54b5445cf 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IClientLoggerService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IClientLoggerService.cs @@ -1,6 +1,6 @@ namespace Barotrauma.LuaCs.Services; -public interface IClientLoggerService : IService +public interface IClientLoggerService : IReusableService { void AddToGUIUpdateList(); void ShowErrorOverlay(string message, float time = 5f, float duration = 1.5f); diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs new file mode 100644 index 000000000..6d817456c --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using Barotrauma.LuaCs.Configuration; +using Barotrauma.LuaCs.Networking; +using Barotrauma.Networking; + +namespace Barotrauma.LuaCs.Services; + +public partial interface IConfigService +{ + /* + * Immediate mode + */ + FluentResults.Result> AddConfigEntry(IDisplayableData data, + T defaultValue, + NetSync syncMode = NetSync.None, + ClientPermissions permissions = ClientPermissions.None, + Func valueChangePredicate = null, + Action> onValueChanged = null) where T : IConvertible, IEquatable; + + FluentResults.Result AddConfigList(IDisplayableData data, + int defaultIndex, IReadOnlyList values, + NetSync syncMode = NetSync.None, + ClientPermissions permissions = ClientPermissions.None, + Func valueChangePredicate = null, + Action onValueChanged = null); + + FluentResults.Result> AddConfigRangeEntry(IDisplayableData data, + T defaultValue, T minValue, T maxValue, + Func, int> getStepCount, + NetSync syncMode = NetSync.None, + ClientPermissions permissions = ClientPermissions.None, + Func valueChangePredicate = null, + Action> onValueChanged = null) where T : IConvertible, IEquatable; +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs index cd0b38829..05cde12b4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs @@ -4,7 +4,7 @@ /// /// Loads XML Style assets from the given content package. /// -public interface IStylesService : IService +public interface IStylesService : IReusableService { /// /// Tries to load the styles file for the given contentpackage and path into a new UIStylesProcessor instance. @@ -12,11 +12,11 @@ public interface IStylesService : IService /// /// /// - bool TryLoadStylesFile(ContentPackage package, ContentPath path); + FluentResults.Result LoadStylesFile(ContentPackage package, ContentPath path); /// /// Unloads all styles assets and UIStyleProcessor instances. /// - void UnloadAllStyles(); + FluentResults.Result UnloadAllStyles(); /// /// Tries to the get the font asset by xml asset name, returns null on failure. diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs new file mode 100644 index 000000000..8d9c0f0c0 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs @@ -0,0 +1,135 @@ +using Barotrauma.LuaCs.Services; +using Barotrauma.Networking; +using System; +using System.Collections.Generic; + +namespace Barotrauma.LuaCs.Networking; + +partial class NetworkingService +{ + private Dictionary> receiveQueue = new Dictionary>(); + + public void SendSyncMessage() + { + if (GameMain.Client == null) { return; } + + WriteOnlyMessage message = new WriteOnlyMessage(); + message.WriteByte((byte)ClientPacketHeader.LUA_NET_MESSAGE); + message.WriteByte((byte)LuaCsClientToServer.RequestAllIds); + GameMain.Client.ClientPeer.Send(message, DeliveryMethod.Reliable); + } + + public void NetMessageReceived(IReadMessage netMessage, ServerPacketHeader header, Client client = null) + { + if (header != ServerPacketHeader.LUA_NET_MESSAGE) + { + return; + } + + LuaCsServerToClient luaCsHeader = (LuaCsServerToClient)netMessage.ReadByte(); + + switch (luaCsHeader) + { + case LuaCsServerToClient.NetMessageString: + HandleNetMessageString(netMessage); + break; + + case LuaCsServerToClient.NetMessageId: + HandleNetMessageId(netMessage); + break; + + case LuaCsServerToClient.ReceiveIds: + ReadIds(netMessage); + break; + } + } + + public INetWriteMessage Start(Guid netId) + { + var message = new WriteOnlyMessage(); + + message.WriteByte((byte)ClientPacketHeader.LUA_NET_MESSAGE); + + if (idToPacket.ContainsKey(netId)) + { + message.WriteByte((byte)LuaCsClientToServer.NetMessageId); + message.WriteUInt16(idToPacket[netId]); + } + else + { + message.WriteByte((byte)LuaCsClientToServer.NetMessageString); + message.WriteBytes(netId.ToByteArray(), 0, 16); + } + + return message.ToNetWriteMessage(); + } + + public void RequestId(Guid netId) + { + if (idToPacket.ContainsKey(netId)) { return; } + + if (GameMain.Client == null) { return; } + + WriteOnlyMessage message = new WriteOnlyMessage(); + message.WriteByte((byte)ClientPacketHeader.LUA_NET_MESSAGE); + message.WriteByte((byte)LuaCsClientToServer.RequestSingleId); + + message.WriteBytes(netId.ToByteArray(), 0, 16); + + Send(message, DeliveryMethod.Reliable); + } + + public void Send(IWriteMessage netMessage, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable) + { + GameMain.Client.ClientPeer.Send(netMessage, deliveryMethod); + } + + private void HandleNetMessageId(IReadMessage netMessage, Client client = null) + { + ushort id = netMessage.ReadUInt16(); + + if (packetToId.ContainsKey(id)) + { + HandleNetMessage(netMessage, packetToId[id], client); + } + else + { + if (!receiveQueue.ContainsKey(id)) { receiveQueue[id] = new Queue(); } + receiveQueue[id].Enqueue(netMessage); + + if (GameSettings.CurrentConfig.VerboseLogging) + { + LuaCsLogger.LogMessage($"Received NetMessage with unknown id {id} from server, storing in queue in case we receive the id later."); + } + } + } + + private void ReadIds(IReadMessage netMessage) + { + ushort size = netMessage.ReadUInt16(); + + for (int i = 0; i < size; i++) + { + ushort packetId = netMessage.ReadUInt16(); + Guid netId = new Guid(netMessage.ReadBytes(16)); + + packetToId[packetId] = netId; + idToPacket[netId] = packetId; + + if (!receiveQueue.ContainsKey(packetId)) + { + continue; + } + + // We could have received messages before receiving the sync message, so we need to process them now + + while (receiveQueue[packetId].TryDequeue(out var queueMessage)) + { + if (netReceives.ContainsKey(netId)) + { + netReceives[netId](queueMessage); + } + } + } + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs index a079ef1ec..d1ceb600c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs @@ -9,10 +9,10 @@ namespace Barotrauma.LuaCs.Services; public partial class PackageService : IStylesResourcesInfo { private readonly Lazy _stylesService; + public IStylesService Styles => _stylesService.Value; public PackageService( - Lazy converterService, - Lazy legacyConfigService, + Lazy configParserService, Lazy luaScriptService, Lazy localizationService, Lazy pluginService, @@ -22,8 +22,7 @@ public partial class PackageService : IStylesResourcesInfo IStorageService storageService, ILoggerService loggerService) { - _modConfigConverterService = converterService; - _legacyConfigService = legacyConfigService; + _configParserService = configParserService; _luaScriptService = luaScriptService; _localizationService = localizationService; _pluginService = pluginService; @@ -36,7 +35,7 @@ public partial class PackageService : IStylesResourcesInfo public ImmutableArray StylesResourceInfos => ModConfigInfo?.StylesResourceInfos ?? ImmutableArray.Empty; - public void LoadStyles([NotNull]IStylesResourcesInfo stylesInfo) + public FluentResults.Result LoadStyles([NotNull]IStylesResourcesInfo stylesInfo) { throw new NotImplementedException(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs index f859dcd09..16c384157 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; +using FluentResults; +using FluentResults.LuaCs; namespace Barotrauma.LuaCs.Services; @@ -18,44 +20,53 @@ public class StylesService : IStylesService _loggerService = loggerService; } - public bool TryLoadStylesFile(ContentPackage package, ContentPath path) + public FluentResults.Result LoadStylesFile(ContentPackage package, ContentPath path) { //check if file already in dict if (_loadedProcessors.ContainsKey(path.FullPath)) { - return true; + return FluentResults.Result.Ok(); } //check if file exists - if (_storageService.FileExists(path.FullPath)) + if (_storageService.FileExists(path.FullPath) is {} result + && result.IsFailed | (result.IsSuccess & result.Value == false)) { - try - { - var styleProcessor = new UIStyleProcessor(package, path); - styleProcessor.LoadFile(); - _loadedProcessors.Add(path.FullPath, styleProcessor); - } - catch (InvalidDataException exception) - { - _loggerService.LogError($"XmlAssetService.TryLoadStylesFile failed for ContentPackage {package.Name}: Exception: {exception.Message}"); - return false; - } - - return true; + return FluentResults.Result.Fail(result.Errors) + .WithError(new Error($"{nameof(StylesService)}.{nameof(LoadStylesFile)} file does not exist!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, package)); } - return false; + try + { + var styleProcessor = new UIStyleProcessor(package, path); + styleProcessor.LoadFile(); + _loadedProcessors.Add(path.FullPath, styleProcessor); + } + catch (InvalidDataException exception) + { + return FluentResults.Result.Fail(new Error($"{nameof(StylesService)}.{nameof(LoadStylesFile)} failed for ContentPackage {package.Name}: Exception: {exception.Message}") + .WithMetadata(MetadataType.ExceptionDetails, exception.Message) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, package) + .WithMetadata(MetadataType.StackTrace, exception.StackTrace)); + } + + return FluentResults.Result.Ok(); } - public void UnloadAllStyles() + public FluentResults.Result UnloadAllStyles() { if (NoProcessorsLoaded) - return; + return FluentResults.Result.Fail(new Error($"{nameof(StylesService)}.{nameof(UnloadAllStyles)}: No processors have been loaded.") + .WithMetadata(MetadataType.ExceptionObject, this)); foreach (var processor in _loadedProcessors) { processor.Value.UnloadFile(); } _loadedProcessors.Clear(); + return FluentResults.Result.Ok(); } public GUIFont GetFont(string fontName) @@ -131,8 +142,8 @@ public class StylesService : IStylesService GC.SuppressFinalize(this); } - public void Reset() + public FluentResults.Result Reset() { - UnloadAllStyles(); + return UnloadAllStyles(); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index c416d7a10..33b4a8aec 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -603,7 +603,7 @@ namespace Barotrauma.Networking { ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte(); - GameMain.LuaCs.Networking.NetMessageReceived(inc, header); + GameMain.LuaCs.NetworkingService.NetMessageReceived(inc, header); if (roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize && header is not ( diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index a74a96392..371d9d527 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -14,6 +14,7 @@ Debug;Release;Unstable true ;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 + latest diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 67009978a..39d3507cb 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -15,6 +15,7 @@ true ;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 false + latest diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 43f2ab06c..37458baff 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -15,6 +15,7 @@ true app.manifest ;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 + latest diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 18a89a450..e2dfeb5b2 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -14,6 +14,7 @@ Debug;Release;Unstable true ;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 + latest diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index fe5683a68..55ea36236 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -14,6 +14,7 @@ Debug;Release;Unstable true ;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 + latest diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs index 0f09fa7e5..b95af9c57 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs @@ -368,7 +368,7 @@ namespace Barotrauma performanceCounterTimer.Stop(); if (GameMain.LuaCs.PerformanceCounter.EnablePerformanceCounter) { - GameMain.LuaCs.PerformanceCounter.UpdateElapsedTime = (double)performanceCounterTimer.ElapsedTicks / Stopwatch.Frequency; + GameMain.LuaCs.PerformanceCounter.AddElapsedTicks(new SimplePerformanceData("Update", performanceCounterTimer.ElapsedTicks)); } performanceCounterTimer.Reset(); @@ -452,7 +452,7 @@ namespace Barotrauma public void Exit() { ShouldRun = false; - GameMain.LuaCs.Stop(); + GameMain.LuaCs.Dispose(); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs new file mode 100644 index 000000000..5cdcff59a --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs @@ -0,0 +1,176 @@ +using Barotrauma.LuaCs.Services; +using Barotrauma.Networking; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Barotrauma.LuaCs.Networking; + +partial class NetworkingService +{ + private const int MaxRegisterPerClient = 1000; + + private Dictionary clientRegisterCount = new Dictionary(); + + private ushort currentId = 0; + + public INetWriteMessage Start(Guid netId) + { + var message = new WriteOnlyMessage(); + + message.WriteByte((byte)ServerPacketHeader.LUA_NET_MESSAGE); + + if (idToPacket.ContainsKey(netId)) + { + message.WriteByte((byte)LuaCsServerToClient.NetMessageId); + message.WriteUInt16(idToPacket[netId]); + } + else + { + message.WriteByte((byte)LuaCsServerToClient.NetMessageString); + message.WriteBytes(netId.ToByteArray(), 0, 16); + } + + return message.ToNetWriteMessage(); + } + + public void NetMessageReceived(IReadMessage netMessage, ClientPacketHeader header, Client client = null) + { + if (header != ClientPacketHeader.LUA_NET_MESSAGE) + { + return; + } + + LuaCsClientToServer luaCsHeader = (LuaCsClientToServer)netMessage.ReadByte(); + + switch (luaCsHeader) + { + case LuaCsClientToServer.NetMessageString: + HandleNetMessageString(netMessage, client); + break; + + case LuaCsClientToServer.NetMessageId: + HandleNetMessageId(netMessage, client); + break; + + case LuaCsClientToServer.RequestAllIds: + WriteAllIds(client); + break; + + case LuaCsClientToServer.RequestSingleId: + RequestIdSingle(netMessage, client); + break; + } + } + + private void HandleNetMessageId(IReadMessage netMessage, Client client = null) + { + ushort id = netMessage.ReadUInt16(); + + if (packetToId.ContainsKey(id)) + { + Guid netId = packetToId[id]; + + HandleNetMessage(netMessage, netId, client); + } + else + { + if (GameSettings.CurrentConfig.VerboseLogging) + { + LuaCsLogger.LogError($"Received NetMessage for unknown id {id} from {GameServer.ClientLogName(client)}."); + } + } + } + + private ushort RegisterId(Guid netId) + { + if (idToPacket.ContainsKey(netId)) + { + return idToPacket[netId]; + } + + if (currentId >= ushort.MaxValue) + { + LuaCsLogger.LogError($"Tried to register more than {ushort.MaxValue} network ids!"); + return 0; + } + + currentId++; + + packetToId[currentId] = netId; + idToPacket[netId] = currentId; + + WriteIdToAll(currentId, netId); + + return currentId; + } + + private void RequestIdSingle(IReadMessage netMessage, Client client) + { + Guid netId = new Guid(netMessage.ReadBytes(16)); + + if (!idToPacket.ContainsKey(netId) && client.AccountId.TryUnwrap(out AccountId id)) + { + if (!clientRegisterCount.ContainsKey(id.StringRepresentation)) + { + clientRegisterCount[id.StringRepresentation] = 0; + } + + clientRegisterCount[id.StringRepresentation]++; + + if (clientRegisterCount[id.StringRepresentation] > MaxRegisterPerClient) + { + LuaCsLogger.Log($"{GameServer.ClientLogName(client)} Tried to register more than {MaxRegisterPerClient} Ids!"); + return; + } + } + + RegisterId(netId); + } + + private void WriteIdToAll(ushort packet, Guid netId) + { + WriteOnlyMessage message = new WriteOnlyMessage(); + message.WriteByte((byte)ServerPacketHeader.LUA_NET_MESSAGE); + message.WriteByte((byte)LuaCsServerToClient.ReceiveIds); + + message.WriteUInt16(1); + message.WriteUInt16(packet); + message.WriteBytes(netId.ToByteArray(), 0, 16); + + Send(message, null, DeliveryMethod.Reliable); + } + + private void WriteAllIds(Client client) + { + WriteOnlyMessage message = new WriteOnlyMessage(); + message.WriteByte((byte)ServerPacketHeader.LUA_NET_MESSAGE); + message.WriteByte((byte)LuaCsServerToClient.ReceiveIds); + + message.WriteUInt16((ushort)packetToId.Count()); + foreach ((ushort packet, Guid netId) in packetToId) + { + message.WriteUInt16(packet); + message.WriteBytes(netId.ToByteArray(), 0, 16); + } + + Send(message, client.Connection, DeliveryMethod.Reliable); + } + + public void ClientWriteLobby(Client client) => GameMain.Server.ClientWriteLobby(client); + + public void Send(IWriteMessage netMessage, NetworkConnection connection = null, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable) + { + if (connection == null) + { + foreach (NetworkConnection conn in Client.ClientList.Select(c => c.Connection)) + { + GameMain.Server.ServerPeer.Send(netMessage, conn, deliveryMethod); + } + } + else + { + GameMain.Server.ServerPeer.Send(netMessage, connection, deliveryMethod); + } + } +} diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs index 4b97459b1..e4d92ec2f 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs @@ -8,8 +8,7 @@ namespace Barotrauma.LuaCs.Services; public partial class PackageService { public PackageService( - Lazy converterService, - Lazy legacyConfigService, + Lazy configParserService, Lazy luaScriptService, Lazy localizationService, Lazy pluginService, @@ -18,8 +17,7 @@ public partial class PackageService IStorageService storageService, ILoggerService loggerService) { - _modConfigConverterService = converterService; - _legacyConfigService = legacyConfigService; + _configParserService = configParserService; _luaScriptService = luaScriptService; _localizationService = localizationService; _pluginService = pluginService; diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index 857c49007..fa071c43c 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -14,6 +14,7 @@ Debug;Release;Unstable true ;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 + latest diff --git a/Barotrauma/BarotraumaShared/Luatrauma.props b/Barotrauma/BarotraumaShared/Luatrauma.props index 62c907748..1adac0339 100644 --- a/Barotrauma/BarotraumaShared/Luatrauma.props +++ b/Barotrauma/BarotraumaShared/Luatrauma.props @@ -1,12 +1,15 @@ - - - + + + + + + diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 83b869ba4..e61097a03 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -1411,7 +1411,7 @@ namespace Barotrauma if (Components.Any(ic => ic is Wire) && Components.All(ic => ic is Wire || ic is Holdable)) { isWire = true; } if (HasTag(Barotrauma.Tags.LogicItem)) { isLogic = true; } - GameMain.LuaCs.Hook.Call("item.created", this); + GameMain.LuaCs.Hook.Call("item.created", this); ApplyStatusEffects(ActionType.OnSpawn, 1.0f); @@ -4622,7 +4622,7 @@ namespace Barotrauma body = null; } - GameMain.LuaCs.Hook.Call("item.removed", this); + GameMain.LuaCs.Hook.Call("item.removed", this); } public override void Remove() @@ -4708,7 +4708,7 @@ namespace Barotrauma RemoveProjSpecific(); - GameMain.LuaCs.Hook.Call("item.removed", this); + GameMain.LuaCs.Hook.Call("item.removed", this); } private void RemoveFromLists() diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigBase.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigBase.cs index 37e076506..87334e592 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigBase.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigBase.cs @@ -1,4 +1,5 @@ using System; +using Barotrauma.LuaCs.Data; using Barotrauma.Networking; namespace Barotrauma.LuaCs.Configuration; @@ -13,9 +14,7 @@ public partial interface IConfigBase : IVarId void Initialize(IVarId id, string defaultValue); } -public interface IVarId +public interface IVarId : IDataInfo { Guid InstanceId { get; } - string InternalName { get; } - ContentPackage OwnerPackage { get; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs index 30ba129c6..8f5325eca 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs @@ -9,4 +9,6 @@ public interface IConfigEntry : IConfigBase, INetVar where T : IConvertible, bool TrySetValue(T value); bool IsAssignable(T value); void Initialize(IVarId id, T defaultValue); + + } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs index 7362f990f..f620bbbce 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; +using System.Linq; using System.Runtime.InteropServices; +using System.Text; namespace Barotrauma.LuaCs.Data; @@ -29,6 +31,7 @@ public partial record ModConfigInfo : IModConfigInfo public record AssemblyResourceInfo : IAssemblyResourceInfo { public ContentPackage OwnerPackage { get; init; } + public string FallbackPackageName { get; init; } public string FriendlyName { get; init; } public bool IsScript { get; init; } public string InternalName { get; init; } @@ -44,16 +47,109 @@ public record AssemblyResourceInfo : IAssemblyResourceInfo public record DependencyInfo : IPackageDependencyInfo { + public string InternalName { get; init; } public ContentPackage OwnerPackage { get; init; } public string FolderPath { get; init; } - public string PackageName { get; init; } + public string FallbackPackageName { get; init; } public ulong SteamWorkshopId { get; init; } public ContentPackage DependencyPackage { get; init; } + public bool IsMissing { get; init; } + public bool IsWorkshopInstallation { get; init; } + + public virtual bool Equals(DependencyInfo other) => Equals(this, other); + + public override int GetHashCode() + { + if (DependencyPackage is not null) + return DependencyPackage.GetHashCode(); + if (SteamWorkshopId != 0) + return SteamWorkshopId.GetHashCode(); + if (!FallbackPackageName.IsNullOrWhiteSpace() && !FolderPath.IsNullOrWhiteSpace()) + return string.Concat(FallbackPackageName, FolderPath).GetHashCode(); + if (!InternalName.IsNullOrWhiteSpace() && !FolderPath.IsNullOrWhiteSpace()) + return string.Concat(InternalName, FolderPath).GetHashCode(); + + return base.GetHashCode(); + } + + bool IEqualityComparer.Equals(IPackageDependencyInfo x, IPackageDependencyInfo y) => DependencyInfo.Equals(x, y); + + public static bool operator ==(IPackageDependencyInfo x, DependencyInfo y) => y?.Equals(x) ?? false; + public static bool operator !=(IPackageDependencyInfo x, DependencyInfo y) => y?.Equals(x) ?? false; + public static bool Equals(IPackageDependencyInfo x, IPackageDependencyInfo y) + { + if (x is null) + return false; + if (y is null) + return false; + if (x == y) + return true; + + if (x.DependencyPackage is not null && y.DependencyPackage is not null) + return y.DependencyPackage == x.DependencyPackage; + + if (!x.FolderPath.IsNullOrWhiteSpace() + && !y.FolderPath.IsNullOrWhiteSpace() + && y.FolderPath == x.FolderPath) + return true; + + if (!x.FolderPath.IsNullOrWhiteSpace() != !y.FolderPath.IsNullOrWhiteSpace()) + return false; + + if (!x.FallbackPackageName.IsNullOrWhiteSpace() + && !y.FallbackPackageName.IsNullOrWhiteSpace() + && y.FallbackPackageName == x.FallbackPackageName) + return true; + + if (x.SteamWorkshopId != 0 && y.SteamWorkshopId == x.SteamWorkshopId) + return true; + + return false; + } + + /// + /// Returns the hash code unique for the package reference. + /// + /// + /// + /// The hash should only be collision-free when referring to different packages. + public int GetHashCode(IPackageDependencyInfo obj) + { + int hashCode = Seed; + hashCode = ApplyHashString(hashCode, obj.FallbackPackageName); + hashCode = ApplyHashString(hashCode, obj.InternalName); + if (obj.SteamWorkshopId > 0) + hashCode ^= (int)obj.SteamWorkshopId; + + + int ApplyHashString(int currentValue, string str) + { + try + { + if (str is null || str.Length < 1) + return currentValue; + byte[] b = Encoding.UTF8.GetBytes(str); + for (int i = 0; i < Math.Min(24, b.Length-1); i++) + currentValue ^= b[i]; + return currentValue; + } + catch + { + return currentValue; + } + } + + return hashCode; + } + + private static readonly int Seed = new Random().Next(436457, int.MaxValue-900); } public record LocalizationResourceInfo : ILocalizationResourceInfo { + public string InternalName { get; init; } public ContentPackage OwnerPackage { get; init; } + public string FallbackPackageName { get; init; } public CultureInfo TargetCulture { get; init; } public Platform SupportedPlatforms { get; init; } public Target SupportedTargets { get; init; } @@ -67,6 +163,7 @@ public record LocalizationResourceInfo : ILocalizationResourceInfo public readonly struct LuaScriptResourceInfo : ILuaResourceInfo { public ContentPackage OwnerPackage { get; init; } + public string FallbackPackageName { get; init; } public Platform SupportedPlatforms { get; init; } public Target SupportedTargets { get; init; } public int LoadPriority { get; init; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs index 42d702b7f..bdf4188dc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs @@ -1,4 +1,5 @@ using System; +// ReSharper disable InconsistentNaming namespace Barotrauma.LuaCs.Data; @@ -6,7 +7,7 @@ namespace Barotrauma.LuaCs.Data; public enum Platform { Linux=0x1, - OSX=0x2, + MacOS=0x2, Windows=0x4 } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs index e970aa9ce..4b269abe4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs @@ -21,14 +21,10 @@ public interface IPlatformInfo Target SupportedTargets { get; } } - /// -/// Which package does the following data belong to? +/// All info we should have on a package for a given resource. /// -public interface IPackageInfo -{ - ContentPackage OwnerPackage { get; } -} +public interface IPackageInfo : IDataInfo { } /// @@ -65,13 +61,3 @@ public interface IResourceCultureInfo /// ImmutableArray SupportedCultures { get; } } - - -public interface ILoadableResourceInfo -{ - /// - /// [UNIQUE] The name that will be used when trying to reference this resource for execution or loading. - /// - [Required] - public string InternalName { get; } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs index c56cb5aa1..fa1c487b7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs @@ -1,26 +1,23 @@ using System; +using Barotrauma.LuaCs.Networking; using Barotrauma.Networking; namespace Barotrauma.LuaCs.Data; // TODO: Finish -public partial interface IConfigInfo +public partial interface IConfigInfo : IDataInfo { - string Name { get; } - string PackageName { get; } - ConfigDataType Type { get; } + /// + /// Specifies the data type this should be initialized to (ie. string, int, vector, etc.) + /// Custom types can be registered by mods. + /// + string DataType { get; } string DefaultValue { get; } + string StoredValue { get; } ClientPermissions RequiredPermissions { get; } -} - -public enum ConfigDataType -{ - Boolean, Int32, Int64, Single, Double, String, - Color, Vector2, Vector3, List, - RangeInt32, RangeSingle, ControlInput -} - -public enum NetSync -{ - None, TwoWay, ServerAuthority, ClientOneWay + /// + /// Whether a value can be changed at runtime. + /// + bool IsReadOnly { get; } + NetSync NetSync { get; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IDataInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IDataInfo.cs new file mode 100644 index 000000000..330b08ac4 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IDataInfo.cs @@ -0,0 +1,20 @@ +namespace Barotrauma.LuaCs.Data; + +/// +/// Serves as a compound-key to refer to all resources and information that comes from a specific source. +/// +public interface IDataInfo +{ + /// + /// Package-Unique name to be used internally for all representations of, and references to, this information. + /// + string InternalName { get; } + /// + /// The package this information belongs to. + /// + ContentPackage OwnerPackage { get; } + /// + /// Used in place of the package data when the OwnerPackage is missing. + /// + string FallbackPackageName { get; } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ILocalizationInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ILocalizationInfo.cs index c05887e5a..b74b1f275 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ILocalizationInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ILocalizationInfo.cs @@ -1,6 +1,12 @@ -namespace Barotrauma.LuaCs.Data; +using System.Collections.Generic; +using System.Globalization; -public interface ILocalizationInfo +namespace Barotrauma.LuaCs.Data; + +public interface ILocalizationInfo : IDataInfo { - + string Symbol { get; } + IReadOnlyDictionary LocalizedValues { get; } + RawLString GetLocalizedString(CultureInfo locale); + RawLString GetLocalizedString(string cultureCode); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependencyInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependencyInfo.cs index ef96428f1..7da431927 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependencyInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependencyInfo.cs @@ -1,18 +1,17 @@ -using System.Collections.Immutable; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; namespace Barotrauma.LuaCs.Data; -public interface IPackageDependencyInfo : IPackageInfo +public interface IPackageDependencyInfo : IPackageInfo, + IEqualityComparer { /// /// Root folder of the content package. /// public string FolderPath { get; } /// - /// Name of the package. - /// - public string PackageName { get; } - /// /// Steam ID of the package. /// public ulong SteamWorkshopId { get; } @@ -20,6 +19,16 @@ public interface IPackageDependencyInfo : IPackageInfo /// The dependency package, if found in the ALL Packages List. /// public ContentPackage DependencyPackage { get; } + + /// + /// This dependency was not found. + /// + public bool IsMissing { get; } + + /// + /// Whether the package is installed from the workshop. False means installation is from local mods. + /// + public bool IsWorkshopInstallation { get; } } public interface IPackageDependenciesInfo diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs index 8b6f28e9b..c92fc1ec0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs @@ -10,8 +10,8 @@ public interface ILocalizationResourceInfo : IResourceInfo, IResourceCultureInfo /// /// Represents loadable Lua files. /// -public interface ILuaResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, ILoadableResourceInfo, IPackageInfo { } -public interface IAssemblyResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, ILoadableResourceInfo, IPackageInfo +public interface ILuaResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { } +public interface IAssemblyResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { /// /// The friendly name of the assembly. Script files belonging to the same assembly should all have the same name. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs new file mode 100644 index 000000000..b82eef22f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Barotrauma.LuaCs.Services; +using Barotrauma.Networking; +using Dynamitey; +using ImpromptuInterface; + +namespace Barotrauma.LuaCs.Events; + +/* + * The following is a collection of interfaces that types can implement to be registered events. + * Note: Internally-marked interfaces should be consumed using a publicizer. This is due to the Barotrauma source + * types being internal by default. +*/ + +public interface IEvent +{ + bool IsLuaRunner() => false; +} + +public interface IEvent : IEvent where T : IEvent +{ + static virtual T GetLuaRunner(IDictionary luaFunc) + { + // throw error if not overriden since we don't have 'static abstract'. + // Implementers must provide the runner. + throw new NotImplementedException(); + } +} + +#region GameEvents + +/// +/// Called as soon as round begins to load before any loading takes place. +/// +public interface IEventRoundStarting : IEvent +{ + void OnRoundStarting(); + static IEventRoundStarting IEvent.GetLuaRunner(IDictionary luaFunc) => new + { + IsLuaRunner = Return.Arguments(() => true), + OnRoundStarting = ReturnVoid.Arguments(() => luaFunc[nameof(OnRoundStarting)]()) + }.ActLike(); +} + +/// +/// Called when a round has started and fully loaded. +/// +public interface IEventRoundStarted : IEvent +{ + void OnRoundStart(); + static IEventRoundStarted IEvent.GetLuaRunner(IDictionary luaFunc) => new + { + IsLuaRunner = Return.Arguments(() => true), + OnRoundStart = ReturnVoid.Arguments(() => luaFunc[nameof(OnRoundStart)]()) + }.ActLike(); +} + +/// +/// Called on game loop normal update. +/// +public interface IEventUpdate : IEvent +{ + void OnUpdate(float fixedDeltaTime); + static IEventUpdate IEvent.GetLuaRunner(IDictionary luaFunc) => new + { + IsLuaRunner = Return.Arguments(() => true), + OnUpdate = ReturnVoid.Arguments((fixedDeltaTime) => luaFunc[nameof(OnUpdate)](fixedDeltaTime)) + }.ActLike(); +} + +/// +/// Called on game loop draw update. +/// +public interface IEventDrawUpdate : IEvent +{ + void OnDrawUpdate(float deltaTime); + static IEventDrawUpdate IEvent.GetLuaRunner(IDictionary luaFunc) => new + { + IsLuaRunner = Return.Arguments(() => true), + OnDrawUpdate = ReturnVoid.Arguments((deltaTime) => luaFunc[nameof(OnDrawUpdate)](deltaTime)) + }.ActLike(); +} + +#endregion + +#region Networking + + +#region Networking-Server +#if SERVER +/// +/// Called when a client connects to the server and has loaded into the lobby. +/// +interface IEventClientConnected : IEvent +{ + /// + /// Called when a client connects to the server. + /// + /// The connecting client. + void OnClientConnected(Client client); + static IEventClientConnected IEvent.GetLuaRunner(IDictionary luaFunc) => new + { + IsLuaRunner = Return.Arguments(() => true), + OnClientConnected = ReturnVoid.Arguments((client) => luaFunc[nameof(OnClientConnected)](client)) + }.ActLike(); +} +#endif +#endregion + +#region Networking-Client +#if CLIENT +/// +/// Called when the client has connected to the server and loaded to the lobby. +/// +public interface IEventServerConnected : IEvent +{ + void OnServerConnected(); + static IEventServerConnected IEvent.GetLuaRunner(IDictionary luaFunc) => new + { + IsLuaRunner = Return.Arguments(() => true), + OnServerConnected = ReturnVoid.Arguments(() => luaFunc[nameof(OnServerConnected)]()) + }.ActLike(); +} +#endif +#endregion + +#endregion + +#region Assembly_PluginEvents + +/// +/// Called on plugin normal, use this for basic/core loading that does not rely on any other modded content. +/// +public interface IEventPluginInitialize : IEvent +{ + void Initialize(); + static IEventPluginInitialize IEvent.GetLuaRunner(IDictionary luaFunc) => new + { + IsLuaRunner = Return.Arguments(() => true), + OnInitialize = ReturnVoid.Arguments(() => luaFunc[nameof(Initialize)]()) + }.ActLike(); +} + +/// +/// Called once all plugins have been loaded. if you have integrations with any other mod, put that code here. +/// +public interface IEventPluginLoadCompleted : IEvent +{ + void OnLoadCompleted(); + static IEventPluginLoadCompleted IEvent.GetLuaRunner(IDictionary luaFunc) => new + { + IsLuaRunner = Return.Arguments(() => true), + OnLoadCompleted = ReturnVoid.Arguments(() => luaFunc[nameof(OnLoadCompleted)]()) + }.ActLike(); +} + +/// +/// Called before Barotrauma initializes plugins. Use if you want to patch another plugin's behaviour 'unofficially'. +/// WARNING: This method is called before Initialize()! +/// +public interface IEventPluginPreInitialize : IEvent +{ + void PreInitPatching(); + static IEventPluginPreInitialize IEvent.GetLuaRunner(IDictionary luaFunc) => new + { + IsLuaRunner = Return.Arguments(() => true), + OnPreInitialize = ReturnVoid.Arguments(() => luaFunc[nameof(PreInitPatching)]()) + }.ActLike(); +} + +/// +/// Called whenever a new assembly is loaded. +/// +public interface IEventAssemblyLoaded : IEvent +{ + void OnAssemblyLoaded(Assembly assembly); + static IEventAssemblyLoaded IEvent.GetLuaRunner(IDictionary luaFunc) => new + { + IsLuaRunner = Return.Arguments(() => true), + OnAssemblyLoaded = ReturnVoid.Arguments((ass) => luaFunc[nameof(OnAssemblyLoaded)](ass)) + }.ActLike(); +} + +/// +/// Called whenever an is instanced. +/// +public interface IEventAssemblyContextCreated : IEvent +{ + void OnAssemblyCreated(IAssemblyLoaderService loaderService); + static IEventAssemblyContextCreated IEvent.GetLuaRunner(IDictionary luaFunc) => new + { + IsLuaRunner = Return.Arguments(() => true), + OnAssemblyContextCreated = ReturnVoid.Arguments((loader) => luaFunc[nameof(OnAssemblyCreated)](loader)) + }.ActLike(); +} + +/// +/// Called whenever an begins unloading. +/// +public interface IEventAssemblyContextUnloading : IEvent +{ + void OnAssemblyUnloading(WeakReference loaderService); + static IEventAssemblyContextUnloading IEvent.GetLuaRunner(IDictionary luaFunc) => new + { + IsLuaRunner = Return.Arguments(() => true), + OnAssemblyUnloading = ReturnVoid.Arguments>((loader) => luaFunc[nameof(OnAssemblyUnloading)](loader)) + }.ActLike(); +} + +#endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaGame.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaGame.cs index fc515c8bd..5b6dba8d1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaGame.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaGame.cs @@ -463,7 +463,12 @@ namespace Barotrauma public List Commands => DebugConsole.Commands; - public void AssignOnExecute(string names, object onExecute) => DebugConsole.AssignOnExecute(names, (string[] a) => { GameMain.LuaCs.CallLuaFunction(onExecute, new object[] { a }); }); + public void AssignOnExecute(string names, object onExecute) => DebugConsole.AssignOnExecute(names, + (string[] a) => + { + //GameMain.LuaCs.CallLuaFunction(onExecute, new object[] { a }); + throw new NotImplementedException(); + }); public void SaveGame(string path) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsHook.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsHook.cs index cce9de419..11de83634 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsHook.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsHook.cs @@ -817,7 +817,7 @@ namespace Barotrauma if (luaCs.PerformanceCounter.EnablePerformanceCounter) { performanceMeasurement.Stop(); - luaCs.PerformanceCounter.SetHookElapsedTicks(name, key, performanceMeasurement.ElapsedTicks); + //luaCs.PerformanceCounter.SetHookElapsedTicks(name, key, performanceMeasurement.ElapsedTicks); TODO performanceMeasurement.Reset(); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsModStore.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsModStore.cs deleted file mode 100644 index cdd77e17d..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsModStore.cs +++ /dev/null @@ -1,114 +0,0 @@ -using MoonSharp.Interpreter; -using MoonSharp.Interpreter.Interop; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace Barotrauma -{ - partial class LuaCsSetup - { - public class LuaCsModStore - { - public abstract class ModStore - { - protected Dictionary store; - - public TStore Set(string name, TStore value) => store[name] = value; - public TStore Get(string name) => store[name]; - - public ModStore(Dictionary store) => this.store = store; - - public abstract bool Equals(T value); - } - public class LuaModStore : ModStore - { - public string Name; - - public LuaModStore(Dictionary store) : base(store) { } - public override bool Equals(string value) => Name == value; - } - public class CsModStore : ModStore - { - public ACsMod Mod; - - public CsModStore(Dictionary store) : base(store) { } - public override bool Equals(ACsMod value) => Mod == value; - } - - private HashSet luaModInterface; - private HashSet csModInterface; - - public LuaCsModStore() - { - luaModInterface = new HashSet(); - csModInterface = new HashSet(); - } - - public void Initialize() - { - UserData.RegisterType(); - UserData.RegisterType(); - var msType = UserData.RegisterType(); - var msDesc = (StandardUserDataDescriptor)msType; - - typeof(StandardUserDataDescriptor).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).ToList().ForEach(m => - { - if ( - m.Name.Contains("Register") - ) - { - msDesc.AddMember(m.Name, new MethodMemberDescriptor(m, InteropAccessMode.Default)); - } - }); - } - public void Clear() - { - luaModInterface.Clear(); - csModInterface.Clear(); - } - - protected LuaModStore Register(string modName) - { - if (luaModInterface.Any(i => i.Equals(modName))) - { - LuaCsLogger.HandleException(new ArgumentException($"'{modName}' entry already registered"), LuaCsMessageOrigin.LuaMod); - return null; - } - - var newHandle = new LuaModStore(new Dictionary()); - if (luaModInterface.Add(newHandle)) return newHandle; - else return null; - } - [MoonSharpHidden] - public CsModStore Register(ACsMod mod) - { - if (csModInterface.Any(i => i.Equals(mod))) - { - LuaCsLogger.HandleException(new ArgumentException($"'{mod.GetType().FullName}' entry already registered"), LuaCsMessageOrigin.CSharpMod); - return null; - } - - var newHandle = new CsModStore(new Dictionary()); - if (csModInterface.Add(newHandle)) return newHandle; - else return null; - } - - public CsModStore GetCsStore(string modName) { - var result = csModInterface.Where(i => i.Mod.GetType().FullName == modName).FirstOrDefault(); - if (result != null) - { - if (!result.Mod.IsDisposed) return result; - else - { - csModInterface.Remove(result); - return null; - } - } - else return null; - } - protected LuaModStore GetLuaStore(string modName) => luaModInterface.Where(i => i.Name == modName).FirstOrDefault(); - } - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsPerformanceCounter.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsPerformanceCounter.cs index 77c18a887..b59ae4dac 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsPerformanceCounter.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsPerformanceCounter.cs @@ -1,36 +1,81 @@ +using Barotrauma.LuaCs.Services; using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; namespace Barotrauma { - public class LuaCsPerformanceCounter + public interface IPerformanceData { - public bool EnablePerformanceCounter = false; + public string Identifier { get; } + public long ElapsedTicks { get; } + } - public double UpdateElapsedTime; - public Dictionary> HookElapsedTime = new Dictionary>(); + public class SimplePerformanceData : IPerformanceData + { + public string Identifier { get; } + public long ElapsedTicks { get; } - public static float MemoryUsage + public SimplePerformanceData(string identifier, long elapsedTicks) { - get - { - Process proc = Process.GetCurrentProcess(); - float memory = MathF.Round(proc.PrivateMemorySize64 / (1024 * 1024), 2); - proc.Dispose(); - - return memory; - } - } - - public void SetHookElapsedTicks(string eventName, string hookName, long ticks) - { - if (!HookElapsedTime.ContainsKey(eventName)) - { - HookElapsedTime[eventName] = new Dictionary(); - } - - HookElapsedTime[eventName][hookName] = (double)ticks / Stopwatch.Frequency; + Identifier = identifier; + ElapsedTicks = elapsedTicks; } } -} \ No newline at end of file + + public class PerformanceCounterService : IReusableService + { + public bool EnablePerformanceCounter { get; set; } = false; + + private Dictionary> _data = new Dictionary>(); + + public void AddElapsedTicks(IPerformanceData data) + { + if (!EnablePerformanceCounter) { return; } + + if (!_data.ContainsKey(data.Identifier)) + { + _data.Add(data.Identifier, new List()); + } + + _data[data.Identifier].Add(data); + + Trim(data.Identifier, 100); + } + + public T GetLatestSnapshot(string identifier) where T : class, IPerformanceData + { + if (!_data.ContainsKey(identifier)) { return default; } + + return (T)_data[identifier].Last(); + } + + public T[] GetSnapshot(string identifier, int length) where T : class, IPerformanceData, new() + { + if (!_data.ContainsKey(identifier)) { return new T[] { }; } + + length = Math.Min(length, _data[identifier].Count); + + return _data[identifier].GetRange(_data[identifier].Count - length, length).Cast().ToArray(); + } + + public void Trim(string identifier, int maxSize) + { + if (!_data.ContainsKey(identifier)) { return; } + + if (_data[identifier].Count > maxSize) + { + _data[identifier].RemoveRange(0, _data[identifier].Count - maxSize); + } + } + + public FluentResults.Result Reset() + { + _data = new Dictionary>(); + return FluentResults.Result.Ok(); + } + + public void Dispose() { } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 4a479c11f..56a8536f7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -1,17 +1,9 @@ using System; using System.IO; -using MoonSharp.Interpreter; -using MoonSharp.Interpreter.Interop; -using System.Runtime.CompilerServices; -using System.Linq; -using System.Threading; -using LuaCsCompatPatchFunc = Barotrauma.LuaCsPatch; -using System.Diagnostics; -using MoonSharp.VsCodeDebugger; -using System.Reflection; -using System.Runtime.Loader; -using System.Xml.Linq; +using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs.Services.Compatibility; +using Barotrauma.LuaCs.Services.Processing; using Barotrauma.Networking; namespace Barotrauma @@ -42,23 +34,98 @@ namespace Barotrauma internal delegate void LuaCsErrorHandler(Exception ex, LuaCsMessageOrigin origin); internal delegate void LuaCsExceptionHandler(Exception ex, LuaCsMessageOrigin origin); - partial class LuaCsSetup + partial class LuaCsSetup : IDisposable { - public const string LuaSetupFile = "Lua/LuaSetup.lua"; - public const string VersionFile = "luacsversion.txt"; -#if WINDOWS - public static ContentPackageId LuaForBarotraumaId = new SteamWorkshopId(2559634234); -#elif LINUX - public static ContentPackageId LuaForBarotraumaId = new SteamWorkshopId(2970628943); -#elif OSX - public static ContentPackageId LuaForBarotraumaId = new SteamWorkshopId(2970890020); -#endif + public LuaCsSetup() + { + // load services + _servicesProvider = new ServicesProvider(); + RegisterServices(); + + // load manifest + if (!_servicesProvider.TryGetService(out IModConfigParserService modConfigSvc)) + throw new NullReferenceException("LuaCsSetup: Failed to get mod config parser service!"); // we should crash here + var luaConfig = modConfigSvc.BuildConfigFromManifest(Directory.GetCurrentDirectory() + LuaCsConfigFile); + if (!luaConfig.IsSuccess) + { + Logger.LogResults(luaConfig.ToResult()); + throw new FileLoadException("LuaCsSetup: Failed to load config file!"); + } + + // load resources + RegisterLocalizations(); + RegisterConfigs(); - public static ContentPackageId CsForBarotraumaId = new SteamWorkshopId(2795927223); + LuaForBarotraumaId = new SteamWorkshopId(LuaForBarotraumaSteamId.Value); + + return; + //--- + + void RegisterServices() + { + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + // TODO: IConfigService + // TODO: INetworkingService + // TODO: [Resource Converter/Parser Services] + // TODO: ILocalizationService + // TODO: IEventService + _servicesProvider.Compile(); + } + void RegisterLocalizations() + { + LocalizationService.LoadLocalizations(luaConfig.Value.Localizations); + } - private const string configFileName = "LuaCsSetupConfig.xml"; + void RegisterConfigs() + { + if (ConfigService.AddConfigs(luaConfig.Value.Configs) is { IsSuccess: false } res1) + { + Logger.LogResults(res1); + throw new Exception("LuaCsSetup: Failed to load config!"); + } + + if (ConfigService.AddConfigsProfiles(luaConfig.Value.ConfigProfiles) is { IsSuccess: false } res2) + { + Logger.LogResults(res2); + throw new Exception("LuaCsSetup: Failed to load config profiles!"); + } + IsCsEnabled = GetOrThrowForConfig(luaConfig.Value.PackageName, "IsCsEnabled"); + TreatForcedModsAsNormal = GetOrThrowForConfig(luaConfig.Value.PackageName, "TreatForcedModsAsNormal"); + PreferToUseWorkshopLuaSetup = GetOrThrowForConfig(luaConfig.Value.PackageName, "PreferToUseWorkshopLuaSetup"); + DisableErrorGUIOverlay = GetOrThrowForConfig(luaConfig.Value.PackageName, "DisableErrorGUIOverlay"); + EnableThreadedLoading = GetOrThrowForConfig(luaConfig.Value.PackageName, "EnableThreadedLoading"); + HideUserNamesInLogs = GetOrThrowForConfig(luaConfig.Value.PackageName, "HideUserNamesInLogs"); + LuaForBarotraumaSteamId = GetOrThrowForConfig(luaConfig.Value.PackageName, "LuaForBarotraumaSteamId"); + + return; + //--- + + IConfigEntry GetOrThrowForConfig(string packName, string internalName) where T : IConvertible, IEquatable + { + var cfgRes = ConfigService.GetConfig>(packName, internalName); + if (cfgRes.IsSuccess) + { + return cfgRes.Value; + } + Logger.LogResults(cfgRes.ToResult()); + throw new Exception($"LuaCsSetup: Failed to load config for {internalName}!"); + } + } + + + } + + #region CONST_DEF + + public const string LuaCsConfigFile = "LuaCsConfig.xml"; + #if SERVER public const bool IsServer = true; public const bool IsClient = false; @@ -67,6 +134,87 @@ namespace Barotrauma public const bool IsClient = true; #endif + #endregion + + #region Services_ConfigVars + + /* + * === Singleton Services + */ + + private readonly IServicesProvider _servicesProvider; + + public PerformanceCounterService PerformanceCounter => _servicesProvider.TryGetService(out var svc) + ? svc : throw new NullReferenceException("Performance counter service not found!"); + public ILoggerService Logger => _servicesProvider.TryGetService(out var svc) + ? svc : throw new NullReferenceException("Logger service not found!"); + public IConfigService ConfigService => _servicesProvider.TryGetService(out var svc) + ? svc : throw new NullReferenceException("Config Manager service not found!"); + public IPackageManagementService PackageManagementService => _servicesProvider.TryGetService(out var svc) + ? svc : throw new NullReferenceException("Package Manager service not found!"); + public IPluginManagementService PluginManagementService => _servicesProvider.TryGetService(out var svc) + ? svc : throw new NullReferenceException("Plugin Manager service not found!"); + public ILuaScriptManagementService LuaScriptService => _servicesProvider.TryGetService(out var svc) + ? svc : throw new NullReferenceException("Lua Script Manager service not found!"); + public ILocalizationService LocalizationService => _servicesProvider.TryGetService(out var svc) + ? svc : throw new NullReferenceException("Localization Manager service not found!"); + public INetworkingService NetworkingService => _servicesProvider.TryGetService(out var svc) + ? svc : throw new NullReferenceException("Networking Manager service not found!"); + public IEventService EventService => _servicesProvider.TryGetService(out var svc) + ? svc : throw new NullReferenceException("Networking Manager service not found!"); + + /* + * === Config Vars + */ + + /// + /// Whether C# plugin code is enabled. + /// + public IConfigEntry IsCsEnabled { get; private set; } + + /// + /// Whether mods marked as 'forced' or 'always load' should only be loaded if they're in the enabled mods list. + /// + public IConfigEntry TreatForcedModsAsNormal { get; private set; } + + /// + /// Whether the lua script runner from Workshop package should be used over the in-built version. + /// + public IConfigEntry PreferToUseWorkshopLuaSetup { get; private set; } + + /// + /// Whether the popup error GUI should be hidden/suppressed. + /// + public IConfigEntry DisableErrorGUIOverlay { get; private set; } + + /// + /// [Experimental] Whether multithreading should be used for loading. + /// + public IConfigEntry EnableThreadedLoading { get; private set; } + + /// + /// Whether usernames are anonymized or show in logs. + /// + public IConfigEntry HideUserNamesInLogs { get; private set; } + + private IConfigEntry LuaForBarotraumaSteamId { get; set; } + + #endregion + + #region LegacyRedirects + + public ILuaCsHook Hook => this.EventService; + + + #endregion + + /// + /// Whether mod content is loaded and being executed. + /// + public bool IsModContentRunning { get; private set; } + + public readonly ContentPackageId LuaForBarotraumaId; + public static bool IsRunningInsideWorkshop { get @@ -79,10 +227,7 @@ namespace Barotrauma } } - private static int executionNumber = 0; - - - public Script Lua { get; private set; } + /*public Script Lua { get; private set; } public LuaScriptLoader LuaScriptLoader { get; private set; } public LuaGame Game { get; private set; } @@ -90,7 +235,6 @@ namespace Barotrauma public LuaCsTimer Timer { get; private set; } public LuaCsNetworking Networking { get; private set; } public LuaCsSteam Steam { get; private set; } - public LuaCsPerformanceCounter PerformanceCounter { get; private set; } // must be available at anytime private static AssemblyManager _assemblyManager; @@ -98,12 +242,10 @@ namespace Barotrauma private CsPackageManager _pluginPackageManager; public CsPackageManager PluginPackageManager => _pluginPackageManager ??= new CsPackageManager(AssemblyManager, this); - - public LuaCsModStore ModStore { get; private set; } private LuaRequire Require { get; set; } public LuaCsSetupConfig Config { get; private set; } public MoonSharpVsCodeDebugServer DebugServer { get; private set; } - public bool IsInitialized { get; private set; } + public bool IsInitialized { get; private set; }*/ private bool ShouldRunCs { @@ -112,64 +254,21 @@ namespace Barotrauma #if SERVER if (GetPackage(CsForBarotraumaId, false, false) != null && GameMain.Server.ServerPeer is LidgrenServerPeer) { return true; } #endif - - return Config.EnableCsScripting; + return IsCsEnabled.Value; } } - public LuaCsSetup() - { - Script.GlobalOptions.Platform = new LuaPlatformAccessor(); - - Hook = new LuaCsHook(this); - ModStore = new LuaCsModStore(); - - Game = new LuaGame(); - Networking = new LuaCsNetworking(); - DebugServer = new MoonSharpVsCodeDebugServer(); - - ReadSettings(); - } + [Obsolete("Use AssemblyManager::GetTypesByName()")] public static Type GetType(string typeName, bool throwOnError = false, bool ignoreCase = false) { - return AssemblyManager.GetTypesByName(typeName).FirstOrDefault((Type)null); + throw new NotImplementedException(); + //return AssemblyManager.GetTypesByName(typeName).FirstOrDefault((Type)null); } - - public void ToggleDebugger(int port = 41912) - { - if (!GameMain.LuaCs.DebugServer.IsStarted) - { - DebugServer.Start(); - AttachDebugger(); - - LuaCsLogger.Log($"Lua Debug Server started on port {port}."); - } - else - { - DetachDebugger(); - DebugServer.Stop(); - - LuaCsLogger.Log($"Lua Debug Server stopped."); - } - } - - public void AttachDebugger() - { - DebugServer.AttachToScript(Lua, "Script", s => - { - if (s.Name.StartsWith("LocalMods") || s.Name.StartsWith("Lua")) - { - return Environment.CurrentDirectory + "/" + s.Name; - } - return s.Name; - }); - } - - public void DetachDebugger() => DebugServer.Detach(Lua); - - public void ReadSettings() + + // Old config ref + /*public void ReadSettings() { Config = new LuaCsSetupConfig(); @@ -205,7 +304,7 @@ namespace Barotrauma document.Root.SetAttributeValue("DisableErrorGUIOverlay", Config.DisableErrorGUIOverlay); document.Root.SetAttributeValue("HideUserNames", Config.HideUserNames); document.Save(configFileName); - } + }*/ public static ContentPackage GetPackage(ContentPackageId id, bool fallbackToAll = true, bool useBackup = false) { @@ -250,7 +349,8 @@ namespace Barotrauma return null; } - private DynValue DoFile(string file, Table globalContext = null, string codeStringFriendly = null) + // Old code ref + /*private DynValue DoFile(string file, Table globalContext = null, string codeStringFriendly = null) { if (!LuaCsFile.CanReadFromPath(file)) { @@ -324,51 +424,7 @@ namespace Barotrauma public void Stop() { - PluginPackageManager.UnloadPlugins(); - - // unregister types - foreach (Type type in AssemblyManager.GetAllLoadedACLs().SelectMany( - acl => acl.AssembliesTypes.Select(kvp => kvp.Value))) - { - UserData.UnregisterType(type, true); - } - if (Lua?.Globals is not null) - { - Lua.Globals.Remove("CsPackageManager"); - Lua.Globals.Remove("AssemblyManager"); - } - - if (Thread.CurrentThread == GameMain.MainThread) - { - Hook?.Call("stop"); - } - - if (Lua != null && DebugServer.IsStarted) - { - DebugServer.Detach(Lua); - } - - LuaUserData.Clear(); - - Game?.Stop(); - - Hook?.Clear(); - ModStore.Clear(); - LuaScriptLoader = null; - Lua = null; - - // we can only unload assemblies after clearing ModStore/references. - PluginPackageManager.Dispose(); -#pragma warning disable CS0618 - ACsMod.LoadedMods.Clear(); -#pragma warning restore CS0618 - - Game = new LuaGame(); - Networking = new LuaCsNetworking(); - Timer = new LuaCsTimer(); - Steam = new LuaCsSteam(); - PerformanceCounter = new LuaCsPerformanceCounter(); IsInitialized = false; } @@ -382,170 +438,22 @@ namespace Barotrauma IsInitialized = true; - LuaCsLogger.LogMessage("Lua! Version " + AssemblyInfo.GitRevision); + Logger.Log($"Initializing LuaCs, git revision = {AssemblyInfo.GitRevision}"); + }*/ + + public void Update() + { + throw new NotImplementedException(); + } - bool csActive = ShouldRunCs || forceEnableCs; + public void Reset() + { + throw new NotImplementedException(); + } - LuaScriptLoader = new LuaScriptLoader(); - LuaScriptLoader.ModulePaths = new string[] { }; - - RegisterLuaConverters(); - - Lua = new Script(CoreModules.Preset_SoftSandbox | CoreModules.Debug | CoreModules.IO | CoreModules.OS_System); - Lua.Options.DebugPrint = (o) => { LuaCsLogger.LogMessage(o); }; - Lua.Options.ScriptLoader = LuaScriptLoader; - Lua.Options.CheckThreadAccess = false; - Script.GlobalOptions.ShouldPCallCatchException = (Exception ex) => { return true; }; - - Require = new LuaRequire(Lua); - - Game = new LuaGame(); - Networking = new LuaCsNetworking(); - Timer = new LuaCsTimer(); - Steam = new LuaCsSteam(); - PerformanceCounter = new LuaCsPerformanceCounter(); - Hook.Initialize(); - ModStore.Initialize(); - Networking.Initialize(); - - UserData.RegisterType(); - UserData.RegisterType(); - UserData.RegisterType(); - UserData.RegisterType(); - UserData.RegisterType(); - UserData.RegisterType(); - UserData.RegisterType(); - UserData.RegisterType(); - UserData.RegisterType(); - UserData.RegisterType(); - UserData.RegisterType(); - UserData.RegisterType(); - var uuid = UserData.RegisterType(); - UserData.RegisterType(); - UserData.RegisterType(); - - Lua.Globals["printerror"] = (DynValue o) => { LuaCsLogger.LogError(o.ToString(), LuaCsMessageOrigin.LuaMod); }; - - Lua.Globals["setmodulepaths"] = (Action)SetModulePaths; - - Lua.Globals["dofile"] = (Func)DoFile; - Lua.Globals["loadfile"] = (Func)LoadFile; - Lua.Globals["require"] = (Func)Require.Require; - - Lua.Globals["dostring"] = (Func)Lua.DoString; - Lua.Globals["load"] = (Func)Lua.LoadString; - - Lua.Globals["Logger"] = UserData.CreateStatic(); - Lua.Globals["LuaUserData"] = UserData.CreateStatic(); - Lua.Globals["LuaUserDataIUUD"] = uuid; - Lua.Globals["Game"] = Game; - Lua.Globals["Hook"] = Hook; - Lua.Globals["ModStore"] = ModStore; - Lua.Globals["Timer"] = Timer; - Lua.Globals["File"] = UserData.CreateStatic(); - Lua.Globals["Networking"] = Networking; - Lua.Globals["Steam"] = Steam; - Lua.Globals["PerformanceCounter"] = PerformanceCounter; - Lua.Globals["LuaCsConfig"] = new LuaCsSetupConfig(Config); - - Lua.Globals["ExecutionNumber"] = executionNumber; - Lua.Globals["CSActive"] = csActive; - - Lua.Globals["SERVER"] = IsServer; - Lua.Globals["CLIENT"] = IsClient; - - if (DebugServer.IsStarted) - { - AttachDebugger(); - } - - if (csActive) - { - LuaCsLogger.LogMessage("Cs! Version " + AssemblyInfo.GitRevision); - - UserData.RegisterType(); - UserData.RegisterType(); - UserData.RegisterType(); - - Lua.Globals["PluginPackageManager"] = PluginPackageManager; - Lua.Globals["AssemblyManager"] = AssemblyManager; - - try - { - Stopwatch taskTimer = new(); - taskTimer.Start(); - ModStore.Clear(); - - var state = PluginPackageManager.LoadAssemblyPackages(); - if (state is AssemblyLoadingSuccessState.Success or AssemblyLoadingSuccessState.AlreadyLoaded) - { - if(!PluginPackageManager.PluginsInitialized) - PluginPackageManager.InstantiatePlugins(true); - if(!PluginPackageManager.PluginsPreInit) - PluginPackageManager.RunPluginsPreInit(); // this is intended to be called at startup in the future - if(!PluginPackageManager.PluginsLoaded) - PluginPackageManager.RunPluginsInit(); - state = AssemblyLoadingSuccessState.Success; - taskTimer.Stop(); - ModUtils.Logging.PrintMessage($"{nameof(LuaCsSetup)}: Completed assembly loading. Total time {taskTimer.ElapsedMilliseconds}ms."); - } - else - { - PluginPackageManager.Dispose(); // cleanup if there's an error - } - - if(state is not AssemblyLoadingSuccessState.Success) - { - ModUtils.Logging.PrintError($"{nameof(LuaCsSetup)}: Error while loading Cs-Assembly Mods | Err: {state}"); - taskTimer.Stop(); - } - } - catch (Exception e) - { - ModUtils.Logging.PrintError($"{nameof(LuaCsSetup)}::{nameof(Initialize)}() | Error while loading assemblies! Details: {e.Message} | {e.StackTrace}"); - } - } - - - ContentPackage luaPackage = GetPackage(LuaForBarotraumaId); - - void RunLocal() - { - LuaCsLogger.LogMessage("Using LuaSetup.lua from the Barotrauma Lua/ folder."); - string luaPath = LuaSetupFile; - CallLuaFunction(Lua.LoadFile(luaPath), Path.GetDirectoryName(Path.GetFullPath(luaPath))); - } - - void RunWorkshop() - { - LuaCsLogger.LogMessage("Using LuaSetup.lua from the content package."); - string luaPath = Path.Combine(Path.GetDirectoryName(luaPackage.Path), "Binary/Lua/LuaSetup.lua"); - CallLuaFunction(Lua.LoadFile(luaPath), Path.GetDirectoryName(Path.GetFullPath(luaPath))); - } - - void RunNone() - { - LuaCsLogger.LogError("LuaSetup.lua not found! Lua/LuaSetup.lua, no Lua scripts will be executed or work.", LuaCsMessageOrigin.LuaMod); - } - - if (Config.PreferToUseWorkshopLuaSetup) - { - if (luaPackage != null) { RunWorkshop(); } - else if (File.Exists(LuaSetupFile)) { RunLocal(); } - else { RunNone(); } - } - else - { - if (File.Exists(LuaSetupFile)) { RunLocal(); } - else if (luaPackage != null) { RunWorkshop(); } - else { RunNone(); } - } - -#if SERVER - GameMain.Server.ServerSettings.LoadClientPermissions(); -#endif - - executionNumber++; + public void Dispose() + { + // TODO release managed resources here } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsUtility.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsUtility.cs index 3354449e3..6ed7c87de 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsUtility.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsUtility.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Net; using System.Reflection; using System.Xml.Linq; +using Barotrauma.LuaCs; namespace Barotrauma { @@ -262,7 +263,8 @@ namespace Barotrauma private static Type[] LoadDocTypes(XElement typesElem) { - var result = new List(); + throw new NotImplementedException(); + /*var result = new List(); var loadedTypes = LuaCsSetup.AssemblyManager .GetAllTypesInLoadedAssemblies() .ToImmutableHashSet(); @@ -279,7 +281,7 @@ namespace Barotrauma result.AddRange(typesFound); } - return result.ToArray(); + return result.ToArray();*/ } private static IEnumerable SaveDocTypes(IEnumerable types) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs index 089cea715..bc181f36e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs @@ -1,341 +1,534 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; +using System.Threading; using System.Xml.Serialization; using Barotrauma; using Barotrauma.Items.Components; +using Barotrauma.LuaCs.Data; using Barotrauma.Networking; using Microsoft.CodeAnalysis; using Microsoft.Xna.Framework; +using OneOf; +using Platform = Barotrauma.LuaCs.Data.Platform; -namespace Barotrauma; - -public static class ModUtils +namespace Barotrauma.LuaCs { - #region LOGGING - public static class Logging + public static class ModUtils { - public static void PrintMessage(string s) + public static class Environment { -#if SERVER - LuaCsLogger.LogMessage($"[Server] {s}"); + internal static void SetCurrentThreadAsMain() => MainThreadId = Thread.CurrentThread.ManagedThreadId; + public static int MainThreadId { get; private set; } = Int32.MinValue; + public static bool IsMainThread + { + get + { + if (MainThreadId == Int32.MinValue) + throw new ArgumentNullException("MainThread ID not set."); + return Thread.CurrentThread.ManagedThreadId == MainThreadId; + } + } + + public static readonly Platform CurrentPlatform = +#if WINDOWS + Platform.Windows; +#elif MACOS + Platform.MacOS; +#elif LINUX + Platform.Linux; #else - LuaCsLogger.LogMessage($"[Client] {s}"); + Platform.Linux; #endif - } - public static void PrintWarning(string s) - { -#if SERVER - LuaCsLogger.Log($"[Server] {s}", Color.Yellow); + public static readonly Target CurrentTarget = +#if CLIENT + Target.Client; +#elif SERVER + Target.Server; #else - LuaCsLogger.Log($"[Client] {s}", Color.Yellow); + Target.Server; #endif + } - public static void PrintError(string s) + #region LOGGING + + public static class Logging { + public static void PrintMessage(string s) + { #if SERVER - LuaCsLogger.LogError($"[Server] {s}"); + LuaCsLogger.LogMessage($"[Server] {s}"); #else - LuaCsLogger.LogError($"[Client] {s}"); + LuaCsLogger.LogMessage($"[Client] {s}"); #endif - } - } - - #endregion - - #region FILE_IO - - // ReSharper disable once InconsistentNaming - public static class IO - { - public static IEnumerable FindAllFilesInDirectory(string folder, string pattern, - SearchOption option) - { - try - { - return Directory.GetFiles(folder, pattern, option); } - catch (DirectoryNotFoundException e) + + public static void PrintWarning(string s) { - return new string[] { }; +#if SERVER + LuaCsLogger.Log($"[Server] {s}", Color.Yellow); +#else + LuaCsLogger.Log($"[Client] {s}", Color.Yellow); +#endif + } + + public static void PrintError(string s) + { +#if SERVER + LuaCsLogger.LogError($"[Server] {s}"); +#else + LuaCsLogger.LogError($"[Client] {s}"); +#endif } } - public static string PrepareFilePathString(string filePath) => - PrepareFilePathString(Path.GetDirectoryName(filePath)!, Path.GetFileName(filePath)); + #endregion - public static string PrepareFilePathString(string path, string fileName) => - Path.Combine(SanitizePath(path), SanitizeFileName(fileName)); + #region FILE_IO - public static string SanitizeFileName(string fileName) + // ReSharper disable once InconsistentNaming + public static class IO { - foreach (char c in Barotrauma.IO.Path.GetInvalidFileNameCharsCrossPlatform()) - fileName = fileName.Replace(c, '_'); - return fileName; - } - - /// - /// Gets the sanitized path for the top-level directory for a given content package. - /// - /// - /// - public static string GetContentPackageDir(ContentPackage package) - { - return SanitizePath(Path.GetFullPath(package.Dir)); - } - - public static string SanitizePath(string path) - { - foreach (char c in Path.GetInvalidPathChars()) - path = path.Replace(c.ToString(), "_"); - return path.CleanUpPath(); - } - - public static IOActionResultState GetOrCreateFileText(string filePath, out string fileText, Func fileDataFactory = null, bool createFile = true) - { - fileText = null; - string fp = Path.GetFullPath(SanitizePath(filePath)); - - IOActionResultState ioActionResultState = IOActionResultState.Success; - if (createFile) - { - ioActionResultState = CreateFilePath(SanitizePath(filePath), out fp, fileDataFactory); - } - else if (!File.Exists(fp)) - { - return IOActionResultState.FileNotFound; - } - - if (ioActionResultState == IOActionResultState.Success) + public static IEnumerable FindAllFilesInDirectory(string folder, string pattern, + SearchOption option) { try { - fileText = File.ReadAllText(fp!); - return IOActionResultState.Success; + return Directory.GetFiles(folder, pattern, option); } - catch (ArgumentNullException ane) + catch (DirectoryNotFoundException e) { - ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: An argument is null. path: {fp ?? "null"} | Exception Details: {ane.Message}"); - return IOActionResultState.FilePathNull; - } - catch (ArgumentException ae) - { - ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: An argument is invalid. path: {fp ?? "null"} | Exception Details: {ae.Message}"); - return IOActionResultState.FilePathInvalid; - } - catch (DirectoryNotFoundException dnfe) - { - ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: Cannot find directory. path: {fp ?? "null"} | Exception Details: {dnfe.Message}"); - return IOActionResultState.DirectoryMissing; - } - catch (PathTooLongException ptle) - { - ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: path length is over 200 characters. path: {fp ?? "null"} | Exception Details: {ptle.Message}"); - return IOActionResultState.PathTooLong; - } - catch (NotSupportedException nse) - { - ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: Operation not supported on your platform/environment (permissions?). path: {fp ?? "null"} | Exception Details: {nse.Message}"); - return IOActionResultState.InvalidOperation; - } - catch (IOException ioe) - { - ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: IO tasks failed (Operation not supported). path: {fp ?? "null"} | Exception Details: {ioe.Message}"); - return IOActionResultState.IOFailure; - } - catch (Exception e) - { - ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: Unknown/Other Exception. path: {fp ?? "null"} | ExceptionMessage: {e.Message}"); - return IOActionResultState.UnknownError; + return new string[] { }; } } - return ioActionResultState; - } + public static string PrepareFilePathString(string filePath) => + PrepareFilePathString(Path.GetDirectoryName(filePath)!, Path.GetFileName(filePath)); - public static IOActionResultState CreateFilePath(string filePath, out string formattedFilePath, Func fileDataFactory = null) - { - string file = Path.GetFileName(filePath); - string path = Path.GetDirectoryName(filePath)!; + public static string PrepareFilePathString(string path, string fileName) => + Path.Combine(SanitizePath(path), SanitizeFileName(fileName)); - formattedFilePath = IO.PrepareFilePathString(path, file); - try + public static string SanitizeFileName(string fileName) { - if (!Directory.Exists(path)) - Directory.CreateDirectory(path); - if (!File.Exists(formattedFilePath)) - File.WriteAllText(formattedFilePath, fileDataFactory is null ? "" : fileDataFactory.Invoke()); - return IOActionResultState.Success; - } - catch (ArgumentNullException ane) - { - ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: An argument is null. path: {formattedFilePath ?? "null"} | Exception Details: {ane.Message}"); - return IOActionResultState.FilePathNull; - } - catch (ArgumentException ae) - { - ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: An argument is invalid. path: {formattedFilePath ?? "null"} | Exception Details: {ae.Message}"); - return IOActionResultState.FilePathInvalid; - } - catch (DirectoryNotFoundException dnfe) - { - ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: Cannot find directory. path: {path ?? "null"} | Exception Details: {dnfe.Message}"); - return IOActionResultState.DirectoryMissing; - } - catch (PathTooLongException ptle) - { - ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: path length is over 200 characters. path: {formattedFilePath ?? "null"} | Exception Details: {ptle.Message}"); - return IOActionResultState.PathTooLong; - } - catch (NotSupportedException nse) - { - ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: Operation not supported on your platform/environment (permissions?). path: {formattedFilePath ?? "null"} | Exception Details: {nse.Message}"); - return IOActionResultState.InvalidOperation; - } - catch (IOException ioe) - { - ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: IO tasks failed (Operation not supported). path: {formattedFilePath ?? "null"} | Exception Details: {ioe.Message}"); - return IOActionResultState.IOFailure; - } - catch (Exception e) - { - ModUtils.Logging.PrintError($"ModUtils::CreateFilePath() | Exception: Unknown/Other Exception. path: {path ?? "null"} | Exception Details: {e.Message}"); - return IOActionResultState.UnknownError; - } - } - - public static IOActionResultState WriteFileText(string filePath, string fileText) - { - IOActionResultState ioActionResultState = CreateFilePath(filePath, out var fp); - if (ioActionResultState == IOActionResultState.Success) - { - try - { - File.WriteAllText(fp!, fileText); - return IOActionResultState.Success; - } - catch (ArgumentNullException ane) - { - ModUtils.Logging.PrintError($"ModUtils::WriteFileText() | Exception: An argument is null. path: {fp ?? "null"} | Exception Details: {ane.Message}"); - return IOActionResultState.FilePathNull; - } - catch (ArgumentException ae) - { - ModUtils.Logging.PrintError($"ModUtils::WriteFileText() | Exception: An argument is invalid. path: {fp ?? "null"} | Exception Details: {ae.Message}"); - return IOActionResultState.FilePathInvalid; - } - catch (DirectoryNotFoundException dnfe) - { - ModUtils.Logging.PrintError($"ModUtils::WriteFileText() | Exception: Cannot find directory. path: {fp ?? "null"} | Exception Details: {dnfe.Message}"); - return IOActionResultState.DirectoryMissing; - } - catch (PathTooLongException ptle) - { - ModUtils.Logging.PrintError($"ModUtils::WriteFileText() | Exception: path length is over 200 characters. path: {fp ?? "null"} | Exception Details: {ptle.Message}"); - return IOActionResultState.PathTooLong; - } - catch (NotSupportedException nse) - { - ModUtils.Logging.PrintError($"ModUtils::WriteFileText() | Exception: Operation not supported on your platform/environment (permissions?). path: {fp ?? "null"} | Exception Details: {nse.Message}"); - return IOActionResultState.InvalidOperation; - } - catch (IOException ioe) - { - ModUtils.Logging.PrintError($"ModUtils::WriteFileText() | Exception: IO tasks failed (Operation not supported). path: {fp ?? "null"} | Exception Details: {ioe.Message}"); - return IOActionResultState.IOFailure; - } - catch (Exception e) - { - ModUtils.Logging.PrintError($"ModUtils::WriteFileText() | Exception: Unknown/Other Exception. path: {fp ?? "null"} | ExceptionMessage: {e.Message}"); - return IOActionResultState.UnknownError; - } + foreach (char c in Barotrauma.IO.Path.GetInvalidFileNameCharsCrossPlatform()) + fileName = fileName.Replace(c, '_'); + return fileName; } - return ioActionResultState; - } + /// + /// Gets the sanitized path for the top-level directory for a given content package. + /// + /// + /// + public static string GetContentPackageDir(ContentPackage package) + { + return SanitizePath(Path.GetFullPath(package.Dir)); + } - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static bool LoadOrCreateTypeXml(out T instance, - string filepath, Func typeFactory = null, bool createFile = true) where T : class, new() - { - instance = null; - filepath = filepath.CleanUpPath(); - if (IOActionResultState.Success == GetOrCreateFileText( - filepath, out string fileText, typeFactory is not null ? () => + public static string SanitizePath(string path) + { + foreach (char c in Path.GetInvalidPathChars()) + path = path.Replace(c.ToString(), "_"); + return path.CleanUpPath(); + } + + public static IOActionResultState GetOrCreateFileText(string filePath, out string fileText, + Func fileDataFactory = null, bool createFile = true) + { + fileText = null; + string fp = Path.GetFullPath(SanitizePath(filePath)); + + IOActionResultState ioActionResultState = IOActionResultState.Success; + if (createFile) + { + ioActionResultState = CreateFilePath(SanitizePath(filePath), out fp, fileDataFactory); + } + else if (!File.Exists(fp)) + { + return IOActionResultState.FileNotFound; + } + + if (ioActionResultState == IOActionResultState.Success) + { + try { - using StringWriter sw = new StringWriter(); - T t = typeFactory?.Invoke(); - if (t is not null) - { - XmlSerializer s = new XmlSerializer(typeof(T)); - s.Serialize(sw, t); - return sw.ToString(); - } - return ""; - } : null, createFile)) + fileText = File.ReadAllText(fp!); + return IOActionResultState.Success; + } + catch (ArgumentNullException ane) + { + ModUtils.Logging.PrintError( + $"ModUtils::CreateFilePath() | Exception: An argument is null. path: {fp ?? "null"} | Exception Details: {ane.Message}"); + return IOActionResultState.FilePathNull; + } + catch (ArgumentException ae) + { + ModUtils.Logging.PrintError( + $"ModUtils::CreateFilePath() | Exception: An argument is invalid. path: {fp ?? "null"} | Exception Details: {ae.Message}"); + return IOActionResultState.FilePathInvalid; + } + catch (DirectoryNotFoundException dnfe) + { + ModUtils.Logging.PrintError( + $"ModUtils::CreateFilePath() | Exception: Cannot find directory. path: {fp ?? "null"} | Exception Details: {dnfe.Message}"); + return IOActionResultState.DirectoryMissing; + } + catch (PathTooLongException ptle) + { + ModUtils.Logging.PrintError( + $"ModUtils::CreateFilePath() | Exception: path length is over 200 characters. path: {fp ?? "null"} | Exception Details: {ptle.Message}"); + return IOActionResultState.PathTooLong; + } + catch (NotSupportedException nse) + { + ModUtils.Logging.PrintError( + $"ModUtils::CreateFilePath() | Exception: Operation not supported on your platform/environment (permissions?). path: {fp ?? "null"} | Exception Details: {nse.Message}"); + return IOActionResultState.InvalidOperation; + } + catch (IOException ioe) + { + ModUtils.Logging.PrintError( + $"ModUtils::CreateFilePath() | Exception: IO tasks failed (Operation not supported). path: {fp ?? "null"} | Exception Details: {ioe.Message}"); + return IOActionResultState.IOFailure; + } + catch (Exception e) + { + ModUtils.Logging.PrintError( + $"ModUtils::CreateFilePath() | Exception: Unknown/Other Exception. path: {fp ?? "null"} | ExceptionMessage: {e.Message}"); + return IOActionResultState.UnknownError; + } + } + + return ioActionResultState; + } + + public static IOActionResultState CreateFilePath(string filePath, out string formattedFilePath, + Func fileDataFactory = null) { - XmlSerializer s = new XmlSerializer(typeof(T)); + string file = Path.GetFileName(filePath); + string path = Path.GetDirectoryName(filePath)!; + + formattedFilePath = IO.PrepareFilePathString(path, file); try { - using TextReader tr = new StringReader(fileText); - instance = (T)s.Deserialize(tr); - return true; + if (!Directory.Exists(path)) + Directory.CreateDirectory(path); + if (!File.Exists(formattedFilePath)) + File.WriteAllText(formattedFilePath, fileDataFactory is null ? "" : fileDataFactory.Invoke()); + return IOActionResultState.Success; } - catch(InvalidOperationException ioe) + catch (ArgumentNullException ane) { - ModUtils.Logging.PrintError($"Error while parsing type data for {typeof(T)}."); - #if DEBUG - ModUtils.Logging.PrintError($"Exception: {ioe.Message}. Details: {ioe.InnerException?.Message}"); - #endif - instance = null; - return false; + ModUtils.Logging.PrintError( + $"ModUtils::CreateFilePath() | Exception: An argument is null. path: {formattedFilePath ?? "null"} | Exception Details: {ane.Message}"); + return IOActionResultState.FilePathNull; + } + catch (ArgumentException ae) + { + ModUtils.Logging.PrintError( + $"ModUtils::CreateFilePath() | Exception: An argument is invalid. path: {formattedFilePath ?? "null"} | Exception Details: {ae.Message}"); + return IOActionResultState.FilePathInvalid; + } + catch (DirectoryNotFoundException dnfe) + { + ModUtils.Logging.PrintError( + $"ModUtils::CreateFilePath() | Exception: Cannot find directory. path: {path ?? "null"} | Exception Details: {dnfe.Message}"); + return IOActionResultState.DirectoryMissing; + } + catch (PathTooLongException ptle) + { + ModUtils.Logging.PrintError( + $"ModUtils::CreateFilePath() | Exception: path length is over 200 characters. path: {formattedFilePath ?? "null"} | Exception Details: {ptle.Message}"); + return IOActionResultState.PathTooLong; + } + catch (NotSupportedException nse) + { + ModUtils.Logging.PrintError( + $"ModUtils::CreateFilePath() | Exception: Operation not supported on your platform/environment (permissions?). path: {formattedFilePath ?? "null"} | Exception Details: {nse.Message}"); + return IOActionResultState.InvalidOperation; + } + catch (IOException ioe) + { + ModUtils.Logging.PrintError( + $"ModUtils::CreateFilePath() | Exception: IO tasks failed (Operation not supported). path: {formattedFilePath ?? "null"} | Exception Details: {ioe.Message}"); + return IOActionResultState.IOFailure; + } + catch (Exception e) + { + ModUtils.Logging.PrintError( + $"ModUtils::CreateFilePath() | Exception: Unknown/Other Exception. path: {path ?? "null"} | Exception Details: {e.Message}"); + return IOActionResultState.UnknownError; } } - return false; + public static IOActionResultState WriteFileText(string filePath, string fileText) + { + IOActionResultState ioActionResultState = CreateFilePath(filePath, out var fp); + if (ioActionResultState == IOActionResultState.Success) + { + try + { + File.WriteAllText(fp!, fileText); + return IOActionResultState.Success; + } + catch (ArgumentNullException ane) + { + ModUtils.Logging.PrintError( + $"ModUtils::WriteFileText() | Exception: An argument is null. path: {fp ?? "null"} | Exception Details: {ane.Message}"); + return IOActionResultState.FilePathNull; + } + catch (ArgumentException ae) + { + ModUtils.Logging.PrintError( + $"ModUtils::WriteFileText() | Exception: An argument is invalid. path: {fp ?? "null"} | Exception Details: {ae.Message}"); + return IOActionResultState.FilePathInvalid; + } + catch (DirectoryNotFoundException dnfe) + { + ModUtils.Logging.PrintError( + $"ModUtils::WriteFileText() | Exception: Cannot find directory. path: {fp ?? "null"} | Exception Details: {dnfe.Message}"); + return IOActionResultState.DirectoryMissing; + } + catch (PathTooLongException ptle) + { + ModUtils.Logging.PrintError( + $"ModUtils::WriteFileText() | Exception: path length is over 200 characters. path: {fp ?? "null"} | Exception Details: {ptle.Message}"); + return IOActionResultState.PathTooLong; + } + catch (NotSupportedException nse) + { + ModUtils.Logging.PrintError( + $"ModUtils::WriteFileText() | Exception: Operation not supported on your platform/environment (permissions?). path: {fp ?? "null"} | Exception Details: {nse.Message}"); + return IOActionResultState.InvalidOperation; + } + catch (IOException ioe) + { + ModUtils.Logging.PrintError( + $"ModUtils::WriteFileText() | Exception: IO tasks failed (Operation not supported). path: {fp ?? "null"} | Exception Details: {ioe.Message}"); + return IOActionResultState.IOFailure; + } + catch (Exception e) + { + ModUtils.Logging.PrintError( + $"ModUtils::WriteFileText() | Exception: Unknown/Other Exception. path: {fp ?? "null"} | ExceptionMessage: {e.Message}"); + return IOActionResultState.UnknownError; + } + } + + return ioActionResultState; + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static bool LoadOrCreateTypeXml(out T instance, + string filepath, Func typeFactory = null, bool createFile = true) where T : class, new() + { + instance = null; + filepath = filepath.CleanUpPath(); + if (IOActionResultState.Success == GetOrCreateFileText( + filepath, out string fileText, typeFactory is not null + ? () => + { + using StringWriter sw = new StringWriter(); + T t = typeFactory?.Invoke(); + if (t is not null) + { + XmlSerializer s = new XmlSerializer(typeof(T)); + s.Serialize(sw, t); + return sw.ToString(); + } + + return ""; + } + : null, createFile)) + { + XmlSerializer s = new XmlSerializer(typeof(T)); + try + { + using TextReader tr = new StringReader(fileText); + instance = (T)s.Deserialize(tr); + return true; + } + catch (InvalidOperationException ioe) + { + ModUtils.Logging.PrintError($"Error while parsing type data for {typeof(T)}."); +#if DEBUG + ModUtils.Logging.PrintError( + $"Exception: {ioe.Message}. Details: {ioe.InnerException?.Message}"); +#endif + instance = null; + return false; + } + } + + return false; + } + + public enum IOActionResultState + { + Success, + FileNotFound, + FilePathNull, + FilePathInvalid, + DirectoryMissing, + PathTooLong, + InvalidOperation, + IOFailure, + UnknownError + } } - public enum IOActionResultState + #endregion + + #region GAME + + public static class Game { - Success, FileNotFound, FilePathNull, FilePathInvalid, DirectoryMissing, PathTooLong, InvalidOperation, IOFailure, UnknownError + /// + /// Returns whether or not there is a round running. + /// + /// + public static bool IsRoundInProgress() + { +#if CLIENT + if (Screen.Selected is not null + && Screen.Selected.IsEditor) + return false; +#endif + return GameMain.GameSession is not null && Level.Loaded is not null; + } + } + + #endregion + + #region THREADING + + public static class Threading + { + /// + /// Gets the boolean value of an integer with thread-safety via Interlocked. + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool GetBool(ref int var) => Interlocked.CompareExchange(ref var, 1, 1) > 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetBool(ref int var, bool value) + { + if (value) + { + Interlocked.CompareExchange(ref var, 1, 0); + } + else + { + Interlocked.CompareExchange(ref var, 0, 1); + } + } + + /// + /// Gets if the integer is under 1 (is zero/false) and, if so, sets the value to one/true. + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CheckClearAndSetBool(ref int var) + { + return Interlocked.CompareExchange(ref var, 1, 0) < 1; + } + + /// + /// Gets if the integer is over 0 (is one/true) and, if so, sets the value to zero/false. + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CheckSetAndClearBool(ref int var) + { + return Interlocked.CompareExchange(ref var, 0, 1) > 0; + } + } + + #endregion + + #region UTILITIES_CORE + + public static V TryGetOrSet(this IDictionary dict, K key, Func valueFactory) where K : IEquatable + { + if (dict.TryGetValue(key, out var dictValue)) return dictValue; + if (valueFactory is not null) + dict.Add(key, valueFactory()); + else + return default; + return dict[key]; + } + + #endregion } - #endregion - - #region GAME - - public static class Game + public static class AssemblyExtensions { /// - /// Returns whether or not there is a round running. + /// Gets all types in the given assembly. Handles invalid type scenarios. /// - /// - public static bool IsRoundInProgress() + /// The assembly to scan + /// An enumerable collection of types. + public static IEnumerable GetSafeTypes(this Assembly assembly) { -#if CLIENT - if (Screen.Selected is not null - && Screen.Selected.IsEditor) - return false; -#endif - return GameMain.GameSession is not null && Level.Loaded is not null; + // Based on https://github.com/Qkrisi/ktanemodkit/blob/master/Assets/Scripts/ReflectionHelper.cs#L53-L67 + + try + { + return assembly.GetTypes(); + } + catch (ReflectionTypeLoadException re) + { + try + { + return re.Types.Where(x => x != null)!; + } + catch (InvalidOperationException) + { + return new List(); + } + } + catch (Exception) + { + return new List(); + } } - } - - #endregion } + +#region ExceptionData + +namespace FluentResults.LuaCs +{ + public static class MetadataType + { + public static string ExceptionDetails = nameof(ExceptionDetails); + public static string ExceptionObject = nameof(ExceptionObject); + public static string RootObject = nameof(RootObject); + public static string Sources = nameof(Sources); + public static string StackTrace = nameof(StackTrace); + } +} + +#endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs index b4a7d5885..38c962046 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs @@ -1,4 +1,5 @@ -using Barotrauma.LuaCs.Configuration; +using System; +using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Networking; using Barotrauma.Networking; @@ -7,10 +8,6 @@ namespace Barotrauma.LuaCs.Networking; public interface INetVar : IVarId { - /// - /// Synchronized network id, uninitialized if value is zero/0. Used by Networking service. - /// - ushort NetId { get; } /// /// Synchronization type /// @@ -19,7 +16,12 @@ public interface INetVar : IVarId /// Permissions needed by clients to send net-events or receive net messages. /// ClientPermissions WritePermissions { get; } + void ReadNetMessage(INetReadMessage message); void WriteNetMessage(INetWriteMessage message); - void Initialize(ushort netId, NetSync syncMode, ClientPermissions writePermissions); +} + +public enum NetSync +{ + None, TwoWay, ServerAuthority, ClientOneWay } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs index c68b318ae..8799208df 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs @@ -11,7 +11,7 @@ namespace Barotrauma.LuaCs.Networking; public interface INetWriteMessage { internal IWriteMessage Message { get; } - internal void SetMessage(IWriteMessage msg); + internal INetWriteMessage SetMessage(IWriteMessage msg); void WriteBoolean(bool val) => Message.WriteBoolean(val); @@ -84,7 +84,7 @@ public interface INetWriteMessage public interface INetReadMessage { internal IReadMessage Message { get; } - internal void SetMessage(IReadMessage msg); + internal INetReadMessage SetMessage(IReadMessage msg); bool ReadBoolean() => Message.ReadBoolean(); void ReadPadBits() => Message.ReadPadBits(); @@ -131,20 +131,30 @@ public class NetWriteMessage : INetWriteMessage IWriteMessage INetWriteMessage.Message => Message; - void INetWriteMessage.SetMessage(IWriteMessage msg) + INetWriteMessage INetWriteMessage.SetMessage(IWriteMessage msg) { Message = msg; + return this; } } +internal static class NetHelperExtensions +{ + internal static INetWriteMessage ToNetWriteMessage(this IWriteMessage msg) => + ((INetWriteMessage)new NetWriteMessage()).SetMessage(msg); + internal static INetReadMessage ToNetReadMessage(this IReadMessage msg) => + ((INetReadMessage)new NetReadMessage()).SetMessage(msg); +} + public class NetReadMessage : INetReadMessage { private IReadMessage Message { get; set; } IReadMessage INetReadMessage.Message => Message; - void INetReadMessage.SetMessage(IReadMessage msg) + INetReadMessage INetReadMessage.SetMessage(IReadMessage msg) { Message = msg; + return this; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/IAssemblyPlugin.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/IAssemblyPlugin.cs deleted file mode 100644 index 5a450ba74..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/IAssemblyPlugin.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace Barotrauma; - -public interface IAssemblyPlugin : IDisposable -{ - /// - /// Called on plugin normal, use this for basic/core loading that does not rely on any other modded content. - /// - void Initialize(); - - /// - /// Called once all plugins have been loaded. if you have integrations with any other mod, put that code here. - /// - void OnLoadCompleted(); - - - /// - /// Called before Barotrauma initializes vanilla content. WARNING: This method may be called before Initialize()! - /// - void PreInitPatching(); -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/AssemblyManager.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/AssemblyManager.cs index 9d1b7c38f..e2017fbee 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/AssemblyManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/AssemblyManager.cs @@ -8,6 +8,8 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Loader; using System.Threading; +using FluentResults; +using FluentResults.LuaCs; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -25,7 +27,8 @@ namespace Barotrauma.LuaCs.Services; /// Provides functionality for the loading, unloading and management of plugins implementing IAssemblyPlugin. /// All plugins are loaded into their own AssemblyLoadContext along with their dependencies. /// -public class AssemblyManager : IAssemblyManagementService +[Obsolete] +public class AssemblyManager : IAssemblyManagementService, IPluginManagementService { #region ExternalAPI @@ -275,6 +278,11 @@ public class AssemblyManager : IAssemblyManagementService } } + public bool IsAssemblyLoadedGlobal(string friendlyName) + { + throw new NotImplementedException(); + } + #endregion #region InternalAPI @@ -740,41 +748,12 @@ public class AssemblyManager : IAssemblyManagementService TryBeginDispose(); } - public void Reset() + public FluentResults.Result Reset() { - TryBeginDispose(); + return TryBeginDispose() ? FluentResults.Result.Ok() + : FluentResults.Result.Fail(new Error($"{nameof(AssemblyManager)}: failed to Reset service.") + .WithMetadata(MetadataType.ExceptionObject, this)); } } -public static class AssemblyExtensions -{ - /// - /// Gets all types in the given assembly. Handles invalid type scenarios. - /// - /// The assembly to scan - /// An enumerable collection of types. - public static IEnumerable GetSafeTypes(this Assembly assembly) - { - // Based on https://github.com/Qkrisi/ktanemodkit/blob/master/Assets/Scripts/ReflectionHelper.cs#L53-L67 - try - { - return assembly.GetTypes(); - } - catch (ReflectionTypeLoadException re) - { - try - { - return re.Types.Where(x => x != null)!; - } - catch (InvalidOperationException) - { - return new List(); - } - } - catch (Exception) - { - return new List(); - } - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs new file mode 100644 index 000000000..3eb723a2e --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs @@ -0,0 +1,15 @@ +using System; + +namespace Barotrauma.LuaCs.Services.Compatibility; + +public interface ILuaCsHook : ILuaCsShim +{ + [Obsolete("ACsMod is deprecated. Use ILuaEventService.Add() instead.")] + void Add(string eventName, string identifier, LuaCsFunc callback, ACsMod mod = null); + [Obsolete("ACsMod is deprecated. Use ILuaEventService.Add() instead.")] + void Add(string eventName, LuaCsFunc callback, ACsMod mod = null); + bool Exists(string eventName, string identifier); + [Obsolete("Only Lua subscribers will receive events from call. Use ILuaEventService.Add() instead.")] + T Call(string eventName, params object[] args); + object Call(string eventName, params object[] args); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsLogger.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsLogger.cs new file mode 100644 index 000000000..30c82860e --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsLogger.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Services.Compatibility; + +public interface ILuaCsLogger : ILuaCsShim +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs new file mode 100644 index 000000000..fe78085bd --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Services.Compatibility; + +internal partial interface ILuaCsNetworking : ILuaCsShim +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsShim.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsShim.cs new file mode 100644 index 000000000..661b26360 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsShim.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Services.Compatibility; + +public interface ILuaCsShim +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsUtility.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsUtility.cs new file mode 100644 index 000000000..b5db0066c --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsUtility.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Services.Compatibility; + +public interface ILuaCsUtility : ILuaCsShim +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs new file mode 100644 index 000000000..59a1e983f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs @@ -0,0 +1,376 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.Specialized; +using System.Dynamic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading; +using Barotrauma.Extensions; +using Barotrauma.LuaCs.Events; +using Barotrauma.LuaCs.Services.Compatibility; +using Barotrauma.LuaCs.Services.Safe; +using Dynamitey; +using FluentResults; +using FluentResults.LuaCs; +using HarmonyLib; +using ImpromptuInterface; +using OneOf; + +namespace Barotrauma.LuaCs.Services; + +public class EventService : IEventService, IEventAssemblyContextUnloading +{ + private readonly record struct TypeStringKey : IEqualityComparer, IEquatable + { + public Type Type { get; init; } + public string TypeName { get; init; } + public readonly int HashCode; + + public TypeStringKey(Type type) + { + Type = type ?? throw new ArgumentNullException(nameof(type)); + TypeName = type.Name; + HashCode = TypeName.GetHashCode(); + } + + public TypeStringKey(string typeName) + { + Type = null; + TypeName = typeName ?? throw new ArgumentNullException(nameof(typeName)); + HashCode = TypeName.GetHashCode(); + } + + public bool Equals(TypeStringKey x, TypeStringKey y) + { + if (x.Type is not null && y.Type is not null) + return x.Type == y.Type; + return x.TypeName == y.TypeName; + } + + public int GetHashCode(TypeStringKey obj) + { + return obj.HashCode; + } + + public static implicit operator TypeStringKey(Type type) => new(type); + public static implicit operator TypeStringKey(string typeName) => new(typeName); + } + /// + /// Contains subscriber delegates by event and identifier. + /// Structure:
+ /// - Key: Type or String, TypeName == String Equality.
+ /// - Value: Dictionary
+ /// ---- Key: Either string identifier or subscriber instance pointer
+ /// ---- Value: Subscriber delegate
+ ///
+ private readonly Dictionary, IEvent>> _subscriptions = new(); + private readonly Dictionary _eventTypeNameAliases = new(); + private readonly Lazy _pluginManagementService; + private readonly Dictionary>> _luaSubscriptionFactories = new(); + /// + /// A collection of factories to produce subscribers from a single lua function handle. For legacy Add() API. + /// + private readonly Dictionary> _luaLegacySubscriptionFactories = new(); + /// + /// A collection of lua event subscribers from Add() that had neither a valid event name nor an event alias pointing to one. + /// Only actionable via Call(). + /// + private readonly Dictionary> _luaOrphanSubscribers = new(); + + public EventService(Lazy pluginManagementService) + { + _pluginManagementService = pluginManagementService ?? throw new ArgumentNullException(nameof(pluginManagementService)); + } + + public bool IsDisposed { get; private set; } = false; + + #region Compatibility + + [Obsolete("ACsMod is deprecated. Use ILuaEventService.Add() instead.")] + void ILuaCsHook.Add(string eventName, string identifier, LuaCsFunc callback, ACsMod mod = null) + { + Add(eventName, identifier, callback); + } + [Obsolete("ACsMod is deprecated. Use ILuaEventService.Add() instead.")] + void ILuaCsHook.Add(string eventName, LuaCsFunc callback, ACsMod mod = null) + { + Add(eventName, callback); + } + + public bool Exists(string eventName, string identifier) + { + ((IService)this).CheckDisposed(); + if (_subscriptions.ContainsKey(eventName) && _subscriptions[eventName].ContainsKey(identifier)) + return true; + if (_luaOrphanSubscribers.ContainsKey(eventName)) + return true; + return false; + } + + [Obsolete("Part of the legacy events API, only works for Lua-only custom events.")] + public T Call(string eventName, params object[] args) + { + ((IService)this).CheckDisposed(); + if (!_luaOrphanSubscribers.TryGetValue(eventName, out var dict)) + return default; + T returnValue = default; + foreach (var sub in dict.Values) + { + try + { + var r = sub(args); + if (r != default) + returnValue = (T)r; + } + catch + { + continue; + } + } + return returnValue; + } + + [Obsolete("Part of the legacy events API, only works for Lua-only custom events.")] + public object Call(string eventName, params object[] args) => Call(eventName, args); + + #endregion + + public void Add(string eventName, string identifier, LuaCsFunc callback) + { + var eventKey = eventName; + if (_eventTypeNameAliases.TryGetValue(eventName, out var aliasType)) + eventKey = aliasType; + if (_luaLegacySubscriptionFactories.TryGetValue(eventKey, out var factory)) + { + factory(identifier, callback); + return; + } + _luaOrphanSubscribers.TryGetOrSet(eventName, () => new Dictionary()) + .Add(identifier.IsNullOrWhiteSpace() ? string.Empty : identifier, callback); + } + + public void Add(string eventName, LuaCsFunc callback) + { + Add(eventName, string.Empty, callback); + } + + public void Remove(string eventName, string identifier) + { + if (_luaOrphanSubscribers.TryGetValue(eventName, out var dict)) + dict.Remove(identifier); + if (_subscriptions.TryGetValue(eventName, out var dict2)) + dict2.Remove(identifier); + } + + public void PublishLuaEvent(string interfaceName, LuaCsFunc runner) + { + ((IService)this).CheckDisposed(); + if (interfaceName.IsNullOrWhiteSpace()) + return; + if (!_subscriptions.TryGetValue(interfaceName, out var dict)) + return; + + var type = _subscriptions + .Select(x => x.Key) + .FirstOrNull(x => x.Type?.Name == interfaceName)?.Type; + + var errors = new Queue(); + foreach (var eventSub in dict.Values) + { + try + { + runner(type is null ? eventSub : Convert.ChangeType(eventSub, type)); // cast if possible + } + catch + { + continue; + } + } + } + + public FluentResults.Result RegisterSafeEvent() where T : IEvent + { + ((IService)this).CheckDisposed(); + var type = typeof(T); + if (_luaSubscriptionFactories.ContainsKey(type)) + return FluentResults.Result.Ok().WithReason(new Success($"The event {type.Name} is already registered.")); + try + { + _luaSubscriptionFactories.Add(type, (ident, funcDict) => + { + var runner = T.GetLuaRunner(funcDict); + var dict = _subscriptions.TryGetOrSet(type, () => new Dictionary, IEvent>()); + if (!ident.IsNullOrWhiteSpace()) + dict[ident] = runner; + else + dict[runner] = runner; + }); + return FluentResults.Result.Ok(); + } + catch (NullReferenceException e) + { + return FluentResults.Result.Fail(new Error($"The lua runner for {type.Name} is not registered.") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, type)); + } + } + + public FluentResults.Result UnregisterSafeEvent() where T : IEvent + { + ((IService)this).CheckDisposed(); + _luaSubscriptionFactories.Remove(typeof(T)); + if (!_subscriptions.TryGetValue(typeof(T), out var dict)) + return FluentResults.Result.Ok(); + dict.Values.Where(value => value.IsLuaRunner()).ToImmutableArray().ForEach(Unsubscribe); + return FluentResults.Result.Ok(); + } + + // lua subscribe + public void Subscribe(string interfaceName, string identifier, IDictionary callbacks) + { + ((IService)this).CheckDisposed(); + if (_luaSubscriptionFactories.TryGetValue(interfaceName, out var subFactory)) + subFactory(identifier, callbacks); + } + + public FluentResults.Result SetLegacyLuaRunnerFactory(Func runnerFactory) where T : IEvent + { + var type = typeof(T); + if (!_luaSubscriptionFactories.TryGetValue(type, out var dict)) + return FluentResults.Result.Fail(new Error($"Tried to add legacy lua factory for an event not registered for lua subscriptions.")); + + _luaLegacySubscriptionFactories[type] = (ident, func) => + { + var runner = runnerFactory(func); + _subscriptions.TryGetOrSet(type, () => new Dictionary, IEvent>())[ident] = runner; + }; + return FluentResults.Result.Ok(); + } + + public void RemoveLegacyLuaRunnerFactory() where T : IEvent + { + _luaLegacySubscriptionFactories.Remove(typeof(T)); + } + + public void SetAliasToEvent(string alias) where T : IEvent + { + if (alias.IsNullOrWhiteSpace()) + return; + _eventTypeNameAliases[alias] = typeof(T).Name; + } + + public void RemoveEventAlias(string alias) + { + _eventTypeNameAliases.Remove(alias); + } + + public void RemoveAllEventAliases() where T : IEvent + { + foreach (var keys in _eventTypeNameAliases + .Where(kvp => kvp.Value.IsNullOrWhiteSpace() || kvp.Value == typeof(T).Name) + .Select(kvp => kvp.Key).ToImmutableArray()) + { + _eventTypeNameAliases.Remove(keys); + } + } + + public FluentResults.Result Subscribe(T subscriber) where T : IEvent + { + ((IService)this).CheckDisposed(); + var eventType = typeof(T); + var dict = _subscriptions.TryGetOrSet(eventType, () => new Dictionary, IEvent>()); + if (dict.ContainsKey(OneOf.FromT1(subscriber))) + { + return FluentResults.Result.Fail( + new Error($"The subscriber for {eventType.Name} is already registered to the event.") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, subscriber)); + } + dict[subscriber] = subscriber; + return FluentResults.Result.Ok(); + } + + public void Unsubscribe(T subscriber) where T : IEvent + { + ((IService)this).CheckDisposed(); + if (!_subscriptions.TryGetValue(typeof(T), out var dict)) + return; + dict.Remove(OneOf.FromT1(subscriber)); + } + + public void ClearAllEventSubscribers() where T : IEvent => _subscriptions.Remove(typeof(T)); + public void ClearAllSubscribers() => _subscriptions.Clear(); + + public FluentResults.Result PublishEvent(Action action) where T : IEvent + { + ((IService)this).CheckDisposed(); + var eventType = typeof(T); + if (!_subscriptions.TryGetValue(eventType, out var dict)) + { + return FluentResults.Result.Fail(new Error($"The event {eventType.Name} is not registered.") + .WithMetadata(MetadataType.ExceptionObject, this)); + } + + var errors = new Queue(); + foreach (var eventSub in dict.Values) + { + try + { + action((T)eventSub); + } + catch (Exception e) + { + errors.Enqueue(new Error($"Error while executing runner for {eventType.Name} on type {eventSub.GetType().Name}.") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, eventSub) + .WithMetadata(MetadataType.ExceptionDetails, e.Message) + .WithMetadata(MetadataType.StackTrace, e.StackTrace)); + } + } + + var result = errors.Count > 0 ? FluentResults.Result.Fail($"Errors while executing event type {eventType.Name}") : FluentResults.Result.Ok(); + while (errors.Count > 0) + result = result.WithError(errors.Dequeue()); + return result; + } + + public void Dispose() + { + IsDisposed = true; + _subscriptions.Clear(); + _luaSubscriptionFactories.Clear(); + _eventTypeNameAliases.Clear(); + GC.SuppressFinalize(this); + } + + public FluentResults.Result Reset() + { + ((IService)this).CheckDisposed(); + _subscriptions.Clear(); + _luaSubscriptionFactories.Clear(); + _eventTypeNameAliases.Clear(); + return FluentResults.Result.Ok(); + } + + public void OnAssemblyUnloading(WeakReference loaderService) + { + if (!loaderService.TryGetTarget(out var loader)) + return; + foreach (var assembly in loader.Assemblies) + { + var types = assembly.GetSafeTypes() + .Where(t => typeof(IEvent).IsAssignableFrom(t)) + .ToImmutableArray(); + if (!types.Any()) + continue; + foreach (var type in types) + { + _subscriptions.Remove(type); + _luaSubscriptionFactories.Remove(type); + } + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IAssemblyManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IAssemblyManagementService.cs deleted file mode 100644 index 1ba46fa9a..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IAssemblyManagementService.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using System.Runtime.CompilerServices; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -// ReSharper disable InconsistentNaming - -namespace Barotrauma.LuaCs.Services; - - -public interface IAssemblyManagementService : IService -{ - #region Public API - - /// - /// Called when an assembly is loaded. - /// - public event Action OnAssemblyLoaded; - - /// - /// Called when an assembly is marked for unloading, before unloading begins. You should use this to cleanup - /// any references that you have to this assembly. - /// - public event Action OnAssemblyUnloading; - - /// - /// Called whenever an exception is thrown. First arg is a formatted message, Second arg is the Exception. - /// - public event Action OnException; - - /// - /// For unloading issue debugging. Called whenever MemoryFileAssemblyContextLoader [load context] is unloaded. - /// - // ReSharper disable once InconsistentNaming - public event Action OnACLUnload; - - - /// - /// [DEBUG ONLY] - /// Returns a list of the current unloading ACLs. - /// - // ReSharper disable once InconsistentNaming - public ImmutableList> StillUnloadingACLs { get; } - - // ReSharper disable once MemberCanBePrivate.Global - /// - /// Checks if there are any AssemblyLoadContexts still in the process of unloading. - /// - public bool IsCurrentlyUnloading { get; } - - /// - /// Allows iteration over all non-interface types in all loaded assemblies in the AsmMgr that are assignable to the given type (IsAssignableFrom). - /// Warning: care should be used when using this method in hot paths as performance may be affected. - /// - /// The type to compare against - /// Forces caches to clear and for the lists of types to be rebuilt. - /// An Enumerator for matching types. - public IEnumerable GetSubTypesInLoadedAssemblies(bool rebuildList); - - /// - /// Tries to get types assignable to type from the ACL given the Guid. - /// - /// - /// - /// - /// Operation success. - public bool TryGetSubTypesFromACL(Guid id, out IEnumerable types); - - /// - /// Tries to get types from the ACL given the Guid. - /// - /// - /// - /// - public bool TryGetSubTypesFromACL(Guid id, out IEnumerable types); - - /// - /// Allows iteration over all types, including interfaces, in all loaded assemblies in the AsmMgr who's names match the string. - /// Note: Will return the by-reference equivalent type if the type name is prefixed with "out " or "ref ". - /// - /// The string name of the type to search for. - /// An Enumerator for matching types. List will be empty if bad params are supplied. - public IEnumerable GetTypesByName(string typeName); - - /// - /// Allows iteration over all types (including interfaces) in all loaded assemblies managed by the AsmMgr. - /// Warning: High usage may result in performance issues. - /// - /// An Enumerator for iteration. - public IEnumerable GetAllTypesInLoadedAssemblies(); - - /// - /// Returns a list of all loaded ACLs. - /// WARNING: References to these ACLs outside the AssemblyManager should be kept in a WeakReference in order - /// to avoid causing issues with unloading/disposal. - /// - /// - public IEnumerable GetAllLoadedACLs(); - - #endregion - - #region InternalAPI - /*** Notes: Internal API uses the 'public' modifier because of the common and recommended use of publicized APIs - * by third-party add-ins. - */ - - /// - /// [Unsafe] Warning: only for use in nested threading functions. Requires care to manage access. - /// Does not make any guarantees about the state of the ACL after the list has been returned. - /// - /// - public ImmutableList UnsafeGetAllLoadedACLs(); - - /// - /// Used by content package and plugin management to stop unloading of a given ACL until all plugins have gracefully closed. - /// - public event System.Func IsReadyToUnloadACL; - - /// - /// Compiles an assembly from supplied references and syntax trees into the specified AssemblyContextLoader. - /// A new ACL will be created if the Guid supplied is Guid.Empty. - /// - /// - /// - /// - /// - /// A non-unique name for later reference. Optional, set to null if unused. - /// The guid of the assembly - /// - /// - public AssemblyLoadingSuccessState LoadAssemblyFromMemory([NotNull] string compiledAssemblyName, - [NotNull] IEnumerable syntaxTree, - IEnumerable externalMetadataReferences, - [NotNull] CSharpCompilationOptions compilationOptions, - string friendlyName, - ref Guid id, - IEnumerable externFileAssemblyRefs = null); - - /// - /// Switches the ACL with the given Guid to Template Mode, which disables assembly name resolution for any assemblies loaded in it. - /// These ACLs are intended to be used to host Assemblies for information only and not for code execution. - /// WARNING: This process is irreversible. - /// - /// Guid of the ACL. - /// Whether an ACL was found with the given ID. - public bool SetACLToTemplateMode(Guid guid); - - - /// - /// Tries to load all assemblies at the supplied file paths list into the ACl with the given Guid. - /// If the supplied Guid is Empty, then a new ACl will be created and the Guid will be assigned to it. - /// - /// List of assemblies to try and load. - /// A non-unique name for later reference. Optional. - /// Guid of the ACL or Empty if none specified. Guid of ACL will be assigned to this var. - /// Operation success messages. - /// - public AssemblyLoadingSuccessState LoadAssembliesFromLocations([NotNull] IEnumerable filePaths, - string friendlyName, ref Guid id); - - - /// - /// Tries to begin the disposal process of ACLs. - /// - /// Returns whether the unloading process could be initiated. - public bool TryBeginDispose(); - - - /// - /// Returns whether unloading is completed and updates the styate of the unloading cache. - /// - /// - public bool FinalizeDispose(); - - /// - /// Tries to retrieve the LoadedACL with the given ID or null if none is found. - /// WARNING: External references to this ACL with long lifespans should be kept in a WeakReference - /// to avoid causing unloading/disposal issues. - /// - /// GUID of the ACL. - /// The found ACL or null if none was found. - /// Whether an ACL was found. - public bool TryGetACL(Guid id, out AssemblyManager.LoadedACL acl); - - #endregion -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IConfigService.cs deleted file mode 100644 index ac6deedd5..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IConfigService.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using Barotrauma.LuaCs.Configuration; -using Barotrauma.LuaCs.Data; -using Barotrauma.Networking; - -namespace Barotrauma.LuaCs.Services; - -public interface IConfigService : IService -{ - /* - * Resource Files. - */ - bool TryAddConfigs(ImmutableArray configResources); - bool TryAddConfigsProfiles(ImmutableArray configProfileResources); - void RemoveConfigs(ImmutableArray configResources); - void RemoveConfigsProfiles(ImmutableArray configProfilesResources); - - - /* - * Already processed - */ - bool TryAddConfigs(ImmutableArray configs); - bool TryAddConfigsProfiles(ImmutableArray configProfiles); - void RemoveConfigs(ImmutableArray configs); - void RemoveConfigsProfiles(ImmutableArray configProfiles); - - /* - * Immediate mode, does not have displayable functionality - */ - IConfigEntry AddConfigEntry(ContentPackage package, string name, - T defaultValue, - NetSync syncMode = NetSync.None, - ClientPermissions permissions = ClientPermissions.None, - Func valueChangePredicate = null, - Action> onValueChanged = null) where T : IConvertible, IEquatable; - - IConfigList AddConfigList(ContentPackage package, string name, - int defaultIndex, IReadOnlyList values, - NetSync syncMode = NetSync.None, - ClientPermissions permissions = ClientPermissions.None, - Func valueChangePredicate = null, - Action onValueChanged = null); - - IReadOnlyDictionary GetConfigsForPackage(ContentPackage package); - IReadOnlyDictionary GetConfigsForPackage(string packageName); - IReadOnlyDictionary<(ContentPackage, string), IConfigBase> GetAllConfigs(); -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IHookManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IHookManagementService.cs deleted file mode 100644 index 5428ed704..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IHookManagementService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Barotrauma.LuaCs.Services; - -public interface IHookManagementService : IService -{ - -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILegacyConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILegacyConfigService.cs deleted file mode 100644 index a93559084..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILegacyConfigService.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Barotrauma.LuaCs.Data; - -namespace Barotrauma.LuaCs.Services; - -public interface ILegacyConfigService : IService -{ - bool TryBuildModConfigFromLegacy(ContentPackage package, out IModConfigInfo configInfo); -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILocalizationService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILocalizationService.cs deleted file mode 100644 index 045d3e986..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILocalizationService.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Globalization; -using System.Collections.Generic; -using System.Collections.Immutable; -using Barotrauma.LuaCs.Data; - -namespace Barotrauma.LuaCs.Services; - -public interface ILocalizationService : IService -{ - IReadOnlyCollection GetLoadedLocales(); - void Remove(ImmutableArray localizations); - bool TrySetCurrentCulture(CultureInfo culture); - bool TrySetCurrentCulture(string cultureName); - bool TryLoadLocalizations(ImmutableArray localizationResources); - string GetLocalizedString(string key, string fallback); - string GetLocalizedString(string key, CultureInfo targetCulture); - bool TryRegisterLocalizationResolver(CultureInfo targetCulture, Func factoryResolver); - bool ReplaceSymbols(string text, string symbolExpr); - bool IsCurrentCultureSupported(IResourceCultureInfo culturesInfo); -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/INetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/INetworkingService.cs deleted file mode 100644 index 3b2c7269b..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/INetworkingService.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Networking; -using Barotrauma.Networking; - -namespace Barotrauma.LuaCs.Services; - -public interface INetworkingService : IService -{ - bool IsActive { get; } - bool IsSynchronized { get; } - bool TryRegisterVar(INetVar var, NetSync mode, ClientPermissions permissions); - void UnregisterVar(Guid varId); - bool SendEvent(Guid varId); - void SendMessageGlobal(string id, string message); - void Synchronize(); - - #region LegacyAPI - - bool RestrictMessageSize { get; set; } - - #endregion -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageManagementService.cs deleted file mode 100644 index 3ef92394a..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageManagementService.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Globalization; -using Barotrauma.LuaCs.Data; - -namespace Barotrauma.LuaCs.Services; - -public interface IPackageManagementService : IService -{ - void AddPackages(ref ReadOnlySpan<(ContentPackage, bool)> packages, - bool executeImmediately = false, - bool errorOnFailures = false, - bool errorOnExistingPackageFound = false); - void LoadPackages(bool onlyUnloadedPackages = true, bool rescanPackages = false); - void UnloadPackages(bool errorOnFailures = true); - bool IsPackageLoaded(ContentPackage package); - bool CheckDependencyLoaded(IPackageDependencyInfo info); - bool CheckDependenciesLoaded(IEnumerable infos, out IReadOnlyList missingPackages); - bool CheckEnvironmentSupported(IPlatformInfo platform); -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginManagementService.cs deleted file mode 100644 index b5c505c83..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginManagementService.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Barotrauma.LuaCs.Services; - -public interface IPluginManagementService : IService -{ - bool IsAssemblyLoadedGlobal(string friendlyName); - -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IService.cs deleted file mode 100644 index bd7595143..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IService.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Barotrauma.LuaCs.Services; - -/// -/// Base interface inherited by all services -/// -public interface IService : IDisposable -{ - /// - /// Returns the service to its original state (post-instantiation). - /// Allows a service instance to be reused without disposing of the instance. - /// - void Reset(); -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IStorageService.cs deleted file mode 100644 index 608a5417c..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IStorageService.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections.Immutable; -using System.Xml.Linq; - -namespace Barotrauma.LuaCs.Services; - -public interface IStorageService : IService -{ - #region LocalGameData - - bool TryLoadLocalXml(ContentPackage package, string localFilePath, out XDocument document); - bool TryLoadLocalBinary(ContentPackage package, string localFilePath, out byte[] bytes); - bool TryLoadLocalText(ContentPackage package, string localFilePath, out string text); - bool FileExistsInLocalData(ContentPackage package, string localFilePath); - - #endregion - - #region ContentPackageData - bool TryLoadPackageXml(ContentPackage package, string localFilePath, out XDocument document); - bool TryLoadPackageBinary(ContentPackage package, string localFilePath, out byte[] bytes); - bool TryLoadPackageText(ContentPackage package, string localFilePath, out string text); - - ImmutableArray TryLoadPackageXmlFiles(ContentPackage package, ImmutableArray localFilePath, out ImmutableArray document); - ImmutableArray TryLoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePath, out ImmutableArray bytes); - ImmutableArray TryLoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePath, out ImmutableArray text); - - bool FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively, out ImmutableArray localFilePaths); - bool FileExistsInPackage(ContentPackage package, string localFilePath); - - #endregion - - #region AbsolutePaths - - bool TryLoadXml(string filePath, out XDocument document); - bool TrySaveXml(string filePath, in XDocument document); - bool TryLoadBinary(string filePath, out byte[] bytes); - bool TrySaveBinary(string filePath, in byte[] bytes); - bool TryLoadText(string filePath, out string text); - bool TrySaveText(string filePath, string text); - bool FileExists(string filePath); - - #endregion -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs index 25fb27542..a551aca9f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs @@ -129,6 +129,11 @@ public partial class LoggerService : ILoggerService #endif } + public void LogResults(FluentResults.Result result) + { + throw new NotImplementedException(); + } + public void LogDebug(string message, Color? color = null) { throw new NotImplementedException(); @@ -145,5 +150,5 @@ public partial class LoggerService : ILoggerService } public void Dispose() { } - public void Reset() { } + public FluentResults.Result Reset() => FluentResults.Result.Ok(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs index b1c30f23e..eef266305 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs @@ -1,6 +1,176 @@ -namespace Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs.Data; +using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Interop; +using System; +using System.Collections.Immutable; +using System.Reflection; -public class LuaScriptService +namespace Barotrauma.LuaCs.Services; + +public class LuaScriptService : ILuaScriptService, ILuaScriptManagementService { - + public void AddField(IUserDataDescriptor descriptor, string fieldName, DynValue value) + { + throw new NotImplementedException(); + } + + public void AddMethod(IUserDataDescriptor descriptor, string methodName, object function) + { + throw new NotImplementedException(); + } + + public FluentResults.Result AddScriptFiles(ImmutableArray luaResource) + { + throw new System.NotImplementedException(); + } + + public object CreateEnumTable(string typeName) + { + throw new NotImplementedException(); + } + + public object CreateStatic(string typeName) + { + throw new NotImplementedException(); + } + + public DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor descriptor) + { + throw new NotImplementedException(); + } + + public DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + throw new System.NotImplementedException(); + } + + public FluentResults.Result ExecuteLoadedScripts(ContentPackage package, bool pauseExecutionOnError = false, bool verboseLogging = false) + { + throw new NotImplementedException(); + } + + public FluentResults.Result ExecuteLoadedScripts(ImmutableArray scripts, bool pauseExecutionOnError = false, bool verboseLogging = false) + { + throw new NotImplementedException(); + } + + public FluentResults.Result ExecuteLoadedScripts(bool pauseExecutionOnError = false, bool verboseLogging = false) + { + throw new NotImplementedException(); + } + + public FluentResults.Result ExecuteScripts(bool pauseExecutionOnScriptError = false, bool verboseLogging = false) + { + throw new System.NotImplementedException(); + } + + public FieldInfo FindFieldRecursively(Type type, string fieldName) + { + throw new NotImplementedException(); + } + + public MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null) + { + throw new NotImplementedException(); + } + + public PropertyInfo FindPropertyRecursively(Type type, string propertyName) + { + throw new NotImplementedException(); + } + + public ImmutableArray GetScriptResources() + { + throw new System.NotImplementedException(); + } + + public bool HasMember(object obj, string memberName) + { + throw new NotImplementedException(); + } + + public bool IsRegistered(Type type) + { + throw new NotImplementedException(); + } + + public bool IsTargetType(object obj, string typeName) + { + throw new NotImplementedException(); + } + + public void MakeFieldAccessible(IUserDataDescriptor descriptor, string fieldName) + { + throw new NotImplementedException(); + } + + public void MakeMethodAccessible(IUserDataDescriptor descriptor, string methodName, string[] parameters = null) + { + throw new NotImplementedException(); + } + + public void MakePropertyAccessible(IUserDataDescriptor descriptor, string propertyName) + { + throw new NotImplementedException(); + } + + public IUserDataDescriptor RegisterGenericType(Type type) + { + throw new NotImplementedException(); + } + + public IUserDataDescriptor RegisterGenericType(string typeName, params string[] typeNameArgs) + { + throw new NotImplementedException(); + } + + public IUserDataDescriptor RegisterType(Type type) + { + throw new NotImplementedException(); + } + + public IUserDataDescriptor RegisterType(string typeName) + { + throw new NotImplementedException(); + } + + public void RemoveMember(IUserDataDescriptor descriptor, string memberName) + { + throw new NotImplementedException(); + } + + public void RemoveScriptFiles(ImmutableArray luaResource) + { + throw new System.NotImplementedException(); + } + + public FluentResults.Result Reset() + { + throw new System.NotImplementedException(); + } + + public string TypeOf(object obj) + { + throw new NotImplementedException(); + } + + public void UnregisterAllTypes() + { + throw new NotImplementedException(); + } + + public void UnregisterType(Type type) + { + throw new NotImplementedException(); + } + + public void UnregisterType(string typeName) + { + throw new NotImplementedException(); + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs new file mode 100644 index 000000000..971661eec --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs @@ -0,0 +1,128 @@ +using Barotrauma.LuaCs.Services; +using Barotrauma.Networking; +using System; +using System.Collections.Generic; + +namespace Barotrauma.LuaCs.Networking; + +internal partial class NetworkingService : INetworkingService +{ + private enum LuaCsClientToServer + { + NetMessageId, + NetMessageString, + RequestSingleId, + RequestAllIds, + } + + private enum LuaCsServerToClient + { + NetMessageId, + NetMessageString, + ReceiveIds + } + + private Dictionary netVars = new Dictionary(); + private Dictionary netReceives = new Dictionary(); + private Dictionary packetToId = new Dictionary(); + private Dictionary idToPacket = new Dictionary(); + + public bool IsActive + { + get + { + return GameMain.NetworkMember != null; // ehh? + } + } + public bool IsSynchronized { get; private set; } + public bool IsDisposed { get; private set; } + + public void Initialize() + { +#if SERVER + IsSynchronized = true; +#elif CLIENT + SendSyncMessage(); +#endif + } + + public void RegisterNetVar(INetVar netVar) + { + netVars[netVar.InstanceId] = netVar; + + netReceives[netVar.InstanceId] = (IReadMessage netMessage) => + { + INetReadMessage internalMind = new NetReadMessage(); + internalMind.SetMessage(netMessage); + netVar.ReadNetMessage(internalMind); + }; + } + + public void SendNetVar(INetVar netVar) + { + if (netVars.ContainsKey(netVar.InstanceId)) + { + INetWriteMessage message = Start(netVar.InstanceId); + netVar.WriteNetMessage(message); + Send(message.Message); + } + } + + public void Receive(Guid netId, NetMessageReceived callback) + { +#if SERVER + RegisterId(netId); +#elif CLIENT + RequestId(netId); +#endif + netReceives[netId] = callback; + } + + private void HandleNetMessage(IReadMessage netMessage, Guid netId, Client client = null) + { + if (netReceives.ContainsKey(netId)) + { + try + { + netReceives[netId](netMessage); + } + catch (Exception e) + { + LuaCsLogger.LogError($"Exception thrown inside NetMessageReceive({netId})", LuaCsMessageOrigin.CSharpMod); + LuaCsLogger.HandleException(e, LuaCsMessageOrigin.CSharpMod); + } + } + else + { + if (GameSettings.CurrentConfig.VerboseLogging) + { +#if SERVER + LuaCsLogger.LogError($"Received NetMessage for unknown netid {netId} from {GameServer.ClientLogName(client)}."); +#else + LuaCsLogger.LogError($"Received NetMessage for unknown netid {netId} from server."); +#endif + } + } + } + + private void HandleNetMessageString(IReadMessage netMessage, Client client = null) + { + Guid guid = new Guid(netMessage.ReadBytes(16)); + + HandleNetMessage(netMessage, guid, client); + } + + public FluentResults.Result Reset() + { + IsSynchronized = false; + netReceives = new Dictionary(); + packetToId = new Dictionary(); + idToPacket = new Dictionary(); + return FluentResults.Result.Ok(); + } + + public void Dispose() + { + IsDisposed = true; + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index 2f5d5eb6f..8fda053e1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -1,14 +1,41 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Barotrauma.Extensions; using Barotrauma.LuaCs.Data; +using Barotrauma.Steam; +using FluentResults; +using FluentResults.LuaCs; +using QuikGraph; namespace Barotrauma.LuaCs.Services; -public class PackageManagementService : IPackageManagementService, IPluginManagementService +public class PackageManagementService : IPackageManagementService { private readonly Func _contentPackageServiceFactory; private readonly Lazy _assemblyManagementService; + private readonly ConcurrentDictionary _contentPackages = new(); + private readonly ConcurrentQueue _queuedPackages = new(); + private readonly ConcurrentDictionary _packageDependencyInfos = new(); + /// + /// ConcurrentDictionary handles access/read synchronization. This is to ensure that we are not trying to + /// access the collection during a load/unload/modify operation. + /// + private readonly ReaderWriterLockSlim _contentPackagesModificationsLock = new(); + /// + /// This lock ensures that we are not adding new entries to the queue between when we read the contents and + /// empty the buffer. + /// + private readonly ReaderWriterLockSlim _packageQueueProcessingLock = new(); + public PackageManagementService( Func getPackageService, Lazy assemblyManagementService) @@ -17,55 +44,418 @@ public class PackageManagementService : IPackageManagementService, IPluginManage this._assemblyManagementService = assemblyManagementService; } + #region STATE_RESET public void Dispose() { // TODO release managed resources here } - public void Reset() + public FluentResults.Result Reset() { throw new NotImplementedException(); } - public bool IsAssemblyLoadedGlobal(string friendlyName) + #endregion + + public void QueuePackages(ImmutableArray packages) + { + _packageQueueProcessingLock.EnterReadLock(); + try + { + foreach (LoadablePackage package in packages) + _queuedPackages.Enqueue(package); + } + finally + { + _packageQueueProcessingLock.ExitReadLock(); + } + } + + public FluentResults.Result ParseQueuedPackages(bool loadParallel = true, bool reportFailOnDuplicates = false) + { + if (!ModUtils.Environment.IsMainThread) + throw new InvalidOperationException($"{nameof(ParseQueuedPackages)}: This method can only be called on the main thread."); + + ImmutableArray packagesToProcess = ImmutableArray.Empty; + + _packageQueueProcessingLock.EnterWriteLock(); + try + { + Interlocked.MemoryBarrier(); + if (_queuedPackages.IsEmpty) + return FluentResults.Result.Ok().WithSuccess($"{nameof(ParseQueuedPackages)}: The Queue is empty."); + packagesToProcess = _queuedPackages.Where(p => p.Package is not null) + .Distinct().ToImmutableArray(); + _queuedPackages.Clear(); + } + finally + { + _packageQueueProcessingLock.ExitWriteLock(); + } + + FluentResults.Result[] loadResults = new FluentResults.Result[packagesToProcess.Length]; + FluentResults.Result res = new FluentResults.Result(); + + // Load ModConfigInfo + _contentPackagesModificationsLock.EnterWriteLock(); + try + { + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); + + Interlocked.MemoryBarrier(); + if (loadParallel) + { + Parallel.For(0, loadResults.Length, new ParallelOptions() + { + /* + * This is an IO-bound operation. The purpose of parallelism here is to allow loaded package + * data to be processed while another package is waiting on the storage device for its info. + */ + MaxDegreeOfParallelism = 2 + },i => + { + loadResults[i] = LoadPackageInfo(packagesToProcess[i]); + }); + } + else + { + for (int i = 0; i < loadResults.Length; i++) + { + loadResults[i] = LoadPackageInfo(packagesToProcess[i]); + } + } + + stopwatch.Stop(); + + res.WithSuccess(new Success( + $"Completed parsing of {loadResults.Length} packages in {stopwatch.ElapsedMilliseconds} milliseconds.")); + + for (int i = 0; i < loadResults.Length; i++) + { + res = loadResults[i].IsSuccess + ? res.WithSuccesses(loadResults[i].Successes) + : res.WithErrors(loadResults[i].Errors); + } + + return res; + } + catch (AggregateException ae) + { + return FluentResults.Result.Fail(new Error($"{nameof(ParseQueuedPackages)}: Failed to load packages! AE.") + .WithMetadata(MetadataType.ExceptionDetails, ae.InnerException?.Message ?? ae.Message) + .WithMetadata(MetadataType.StackTrace, ae.StackTrace) + .WithMetadata(MetadataType.ExceptionObject, this)); + } + catch (ArgumentNullException ane) + { + return FluentResults.Result.Fail( + new Error($"{nameof(ParseQueuedPackages)}: Failed to load packages! ANE.") + .WithMetadata(MetadataType.ExceptionDetails, ane.InnerException?.Message ?? ane.Message) + .WithMetadata(MetadataType.StackTrace, ane.StackTrace) + .WithMetadata(MetadataType.ExceptionObject, this)); + } + finally + { + _contentPackagesModificationsLock.ExitWriteLock(); + } + + + /* + * Helper functions + */ + + // register in the list so we can check against it. + FluentResults.Result LoadPackageInfo(LoadablePackage package) + { + try + { + if (package.Package == null) + { + return FluentResults.Result.Fail( + new Error($"{nameof(LoadPackageInfo)}: Package is null!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, package)); + } + + if (_contentPackages.TryGetValue(package.Package, out var packageService)) + { + if (reportFailOnDuplicates) + { + return FluentResults.Result.Fail(new Error($"The package {package.Package?.Name} is already loaded.") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, package.Package)); + } + + return FluentResults.Result.Ok(); + } + + packageService = _contentPackageServiceFactory.Invoke(); + _contentPackages[package.Package] = packageService; + return packageService.LoadResourcesInfo(package); + } + catch (NullReferenceException nre) + { + return FluentResults.Result.Fail(new Error($"{nameof(LoadPackageInfo)}: NRE while loading package {package.Package?.Name}!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.StackTrace, nre.StackTrace ?? "StackTrace not available") + .WithMetadata(MetadataType.ExceptionDetails, nre.InnerException?.Message ?? nre.Message) + .WithMetadata(MetadataType.RootObject, package)); + } + } + } + + public FluentResults.Result LoadPackageConfigsResourcesGroup(bool loadParallel = true) { throw new NotImplementedException(); } - public void AddPackages(ref ReadOnlySpan<(ContentPackage, bool)> packages, bool executeImmediately = false, bool errorOnFailures = false, - bool errorOnExistingPackageFound = false) + public FluentResults.Result LoadAllPackageResources(bool loadParallel = true, bool safeResourcesOnly = true) { throw new NotImplementedException(); } - public void LoadPackages(bool onlyUnloadedPackages = true, bool rescanPackages = false) + public FluentResults.Result UnloadPackages() { + if (!ModUtils.Environment.IsMainThread) + { + return FluentResults.Result.Fail( + new ExceptionalError(new InvalidOperationException($"{nameof(UnloadPackages)}: This method can only be called on the main thread.")) + .WithMetadata(MetadataType.ExceptionObject, this)); + } + + var res = new FluentResults.Result(); + _contentPackagesModificationsLock.EnterWriteLock(); + try + { + // TODO: Finish him + } + finally + { + _contentPackagesModificationsLock.ExitWriteLock(); + } + throw new NotImplementedException(); } - public void UnloadPackages(bool errorOnFailures = true) - { - throw new NotImplementedException(); - } + public bool IsPackageLoaded(ContentPackage package) => package is not null && _contentPackages.ContainsKey(package); - public bool IsPackageLoaded(ContentPackage package) - { - throw new NotImplementedException(); - } + public bool CheckDependencyLoaded(IPackageDependencyInfo info) => + info is not null && IsPackageLoaded(info.DependencyPackage); - public bool CheckDependencyLoaded(IPackageDependencyInfo info) + public bool CheckDependenciesLoaded([NotNull]IEnumerable infos, out ImmutableArray missingPackages) { - throw new NotImplementedException(); + var missing = ImmutableArray.CreateBuilder(); + missing.AddRange(infos + .Where(i => i.DependencyPackage is not null) + .DistinctBy(i => i.DependencyPackage) + .Where(i => !CheckDependencyLoaded(i))); + missingPackages = missing.MoveToImmutable(); + return missingPackages.Length == 0; } - - public bool CheckDependenciesLoaded(IEnumerable infos, out IReadOnlyList missingPackages) - { - throw new NotImplementedException(); - } - + public bool CheckEnvironmentSupported(IPlatformInfo platform) { + return (platform.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 + && (platform.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0; + } + + public Result GetPackageDependencyInfoRecord(ContentPackage package, bool addIfMissing = false) + { + if (package is null) + { + return new FluentResults.Result() + .WithError(new Error($"{nameof(GetPackageDependencyInfoRecord)}: Package is null!") + .WithMetadata(MetadataType.ExceptionObject, this)); + } + + if (_packageDependencyInfos.TryGetValue(package, out var result)) + { + return new FluentResults.Result() + .WithValue(result); + } + + if (addIfMissing) + { + return AddDependencyRecord(package, package.Name, package.Path, + package.TryExtractSteamWorkshopId(out var id) ? id.Value : 0, + false); + } + + return FluentResults.Result.Fail(new Error($"Could not find package {package.Name}!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, package)); + } + + public Result GetPackageDependencyInfoRecord(ulong steamWorkshopId, string packageName, string folderPath = null, + bool addIfMissing = false) + { + if (packageName.IsNullOrWhiteSpace() || folderPath.IsNullOrWhiteSpace()) + { + return new FluentResults.Result() + .WithError(new Error($"{nameof(GetPackageDependencyInfoRecord)}: folder path and/or package name are null!") + .WithMetadata(MetadataType.ExceptionObject, this)); + } + + if (_packageDependencyInfos.TryGetValue((packageName,steamWorkshopId,folderPath), out var result)) + { + return new FluentResults.Result() + .WithValue(result); + } + + // TODO: Finish this throw new NotImplementedException(); } + + public Result GetPackageDependencyInfoRecord(string folderPath) + { + throw new NotImplementedException(); + } + + + public IPackageDependencyInfo CreateOrphanPackageDependencyInfoRecord( + string packageName, + string packagePath, + ulong steamWorkshopId) + { + return new DependencyInfo() + { + DependencyPackage = null, + FallbackPackageName = packageName, + FolderPath = packagePath.IsNullOrWhiteSpace() ? null : System.IO.Path.GetFullPath(packagePath), + SteamWorkshopId = steamWorkshopId, + IsMissing = true, + IsWorkshopInstallation = false + }; + } + + private Result AddDependencyRecord( + ContentPackage package, + string packageName, + string folderPath, + ulong steamWorkshopId, + bool isMissing) + { + // TODO: Redo + try + { + var dependencyInfo = new DependencyInfo() + { + DependencyPackage = package, + FallbackPackageName = packageName, + FolderPath = System.IO.Path.GetFullPath(folderPath), + SteamWorkshopId = steamWorkshopId, + IsMissing = isMissing, + IsWorkshopInstallation = steamWorkshopId != 0 + }; + if (package is not null) + { + _packageDependencyInfos.AddOrUpdate(package, pack => dependencyInfo, + (pack, dep) => dependencyInfo); + } + return new FluentResults.Result() + .WithValue(dependencyInfo) + .WithSuccess($"New value created."); + } + catch (Exception ex) + { + return new FluentResults.Result() + .WithError(new ExceptionalError(ex) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.ExceptionDetails, ex.Message) + .WithMetadata(MetadataType.RootObject, package) + .WithMetadata(MetadataType.StackTrace, ex.StackTrace ?? "StackTrace not available")); + } + } + + private readonly record struct DependencyEntryKey : IEqualityComparer, IEquatable + { + public ContentPackage Package { get; init; } + public string FolderPath { get; init; } + public string PackageName { get; init; } + public ulong SteamWorkshopId { get; init; } + + public DependencyEntryKey(ContentPackage package) + { + Package = package ?? throw new ArgumentNullException(nameof(package), $"{nameof(DependencyEntryKey)}.ctor: Package cannot be null!"); + PackageName = package.Name; + SteamWorkshopId = package.TryExtractSteamWorkshopId(out var id) ? id.Value : (ulong)0; + FolderPath = package.Path; + } + + public DependencyEntryKey(string packageName, string folderPath, ulong steamWorkshopId) + { + PackageName = packageName; + SteamWorkshopId = steamWorkshopId; + FolderPath = folderPath; + Package = null; + } + + public DependencyEntryKey(string packageName, ulong steamWorkshopId) + { + PackageName = packageName; + SteamWorkshopId = steamWorkshopId; + FolderPath = null; + Package = null; + } + + public bool Equals(DependencyEntryKey other) + { + return Equals(this, other); + } + + public override int GetHashCode() + { + return GetHashCode(this); + } + + public bool Equals(DependencyEntryKey x, DependencyEntryKey y) + { + if (x == y) + return true; + + if (x.Package is not null && y.Package is not null && x.Package == Package) + return true; + + // folder should be a unique key if not unset. + if (!x.FolderPath.IsNullOrWhiteSpace() && !y.FolderPath.IsNullOrWhiteSpace() && + x.FolderPath == FolderPath) + return true; + + if (!x.PackageName.IsNullOrWhiteSpace() && !y.PackageName.IsNullOrWhiteSpace() + && x.SteamWorkshopId != 0 && y.SteamWorkshopId != 0) + return x.PackageName == y.PackageName && x.SteamWorkshopId == y.SteamWorkshopId; + + if (!x.PackageName.IsNullOrWhiteSpace() && !y.PackageName.IsNullOrWhiteSpace() && x.PackageName == PackageName) + return true; + + if (x.SteamWorkshopId != 0 && y.SteamWorkshopId != 0 && + x.SteamWorkshopId == y.SteamWorkshopId) + return true; + + return false; + } + + public int GetHashCode(DependencyEntryKey obj) + { + if (!obj.PackageName.IsNullOrWhiteSpace()) + return obj.PackageName.GetHashCode(); + if (obj.SteamWorkshopId != 0) + return obj.SteamWorkshopId.GetHashCode(); + if (obj.Package is not null) + return obj.Package.GetHashCode(); + // We don't want to check the FolderPath because we want to resolve dependencies using packages + // that might be local instead in the workshop folder. + return 2342568; // random const value: collisions are fine as we want to call Equals() + } + + public static implicit operator DependencyEntryKey(ContentPackage package) => new(package); + public static implicit operator DependencyEntryKey((string packageName, ulong steamWorkshopId) tuple1) => + new (tuple1.packageName, tuple1.steamWorkshopId); + public static implicit operator DependencyEntryKey((string packageName, ulong steamWorkshopId, string folderPath) tuple1) => + new (tuple1.packageName, tuple1.folderPath, tuple1.steamWorkshopId); + } + + } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs index 97e1f0cfe..219685d5b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs @@ -10,6 +10,9 @@ using System.Threading; using Barotrauma.Extensions; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Services.Processing; +using FluentResults; +using FluentResults.LuaCs; +using OneOf; namespace Barotrauma.LuaCs.Services; @@ -20,8 +23,7 @@ public partial class PackageService : IPackageService // mod config / package scanners/parsers - private readonly Lazy _modConfigConverterService; - private readonly Lazy _legacyConfigService; + private readonly Lazy _configParserService; private readonly Lazy _luaScriptService; private readonly Lazy _localizationService; private readonly Lazy _pluginService; @@ -35,31 +37,32 @@ public partial class PackageService : IPackageService // state monitors private int _configsLoaded, _localizationsLoaded, _luaScriptsLoaded, _pluginsLoaded, _isDisposed; private int _loadingOperationsRunning; + private int _isEnabledInModList; public bool ConfigsLoaded { - get => GetThreadSafeBool(ref _configsLoaded); - private set => SetThreadSafeBool(ref _configsLoaded, value); + get => ModUtils.Threading.GetBool(ref _configsLoaded); + private set => ModUtils.Threading.SetBool(ref _configsLoaded, value); } public bool LocalizationsLoaded { - get => GetThreadSafeBool(ref _localizationsLoaded); - private set => SetThreadSafeBool(ref _localizationsLoaded, value); + get => ModUtils.Threading.GetBool(ref _localizationsLoaded); + private set => ModUtils.Threading.SetBool(ref _localizationsLoaded, value); } public bool LuaScriptsLoaded { - get => GetThreadSafeBool(ref _luaScriptsLoaded); - private set => SetThreadSafeBool(ref _luaScriptsLoaded, value); + get => ModUtils.Threading.GetBool(ref _luaScriptsLoaded); + private set => ModUtils.Threading.SetBool(ref _luaScriptsLoaded, value); } public bool PluginsLoaded { - get => GetThreadSafeBool(ref _pluginsLoaded); - private set => SetThreadSafeBool(ref _pluginsLoaded, value); + get => ModUtils.Threading.GetBool(ref _pluginsLoaded); + private set => ModUtils.Threading.SetBool(ref _pluginsLoaded, value); } public bool IsDisposed { - get => GetThreadSafeBool(ref _isDisposed); - private set => SetThreadSafeBool(ref _isDisposed, value); + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); } private bool LoadingOperationsRunning @@ -146,6 +149,12 @@ public partial class PackageService : IPackageService } } + public bool IsEnabledInModList + { + get => ModUtils.Threading.GetBool(ref _isEnabledInModList); + private set => ModUtils.Threading.SetBool(ref _isEnabledInModList, value); + } + #endregion public ImmutableArray SupportedCultures => ModConfigInfo?.SupportedCultures ?? ImmutableArray.Empty; @@ -159,43 +168,48 @@ public partial class PackageService : IPackageService #region PublicAPI - public bool TryLoadResourcesInfo(ContentPackage package) + public FluentResults.Result LoadResourcesInfo(LoadablePackage cpackage) { + if (cpackage.Package == null) + { + return FluentResults.Result.Fail(new Error($"{nameof(LoadResourcesInfo)}: Package is null!") + .WithMetadata(MetadataType.ExceptionObject,this) + .WithMetadata(MetadataType.RootObject, cpackage)); + } + ContentPackage package = cpackage.Package; + _operationsUsageLock.EnterWriteLock(); LoadingOperationsRunning = true; try { if (IsDisposed) { - throw new ObjectDisposedException($"This package service instance is disposed!"); + return FluentResults.Result.Fail( + new Error("Service is disposed.") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, package)); } - // try loading the ModConfig.xml. If it fails, use the Legacy loader to try and construct one from the package structure. - if (_storageService.TryLoadPackageXml(package, "ModConfig.xml", out var configXml) - && configXml.Root is not null) + var res = _configParserService.Value.BuildConfigForPackage(package); + + if (res.IsFailed) { - if (_modConfigConverterService.Value.TryParseResource(configXml.Root, out IModConfigInfo configInfo)) - { - ModConfigInfo = configInfo; - } - else - { - _loggerService.LogError( - $"Failed to parse ModConfig.xml for package {package.Name}, package mod content not loaded."); - return false; - } - } - else if (_legacyConfigService.Value.TryBuildModConfigFromLegacy(package, out var legacyConfig)) - { - ModConfigInfo = legacyConfig; - } - else - { - // vanilla mod or broken - return false; + return FluentResults.Result.Fail(res.Errors) + .WithError(new Error("PackageService failed to load ModConfigInfo") + .WithMetadata(MetadataType.ExceptionObject, _configParserService) + .WithMetadata(MetadataType.RootObject, package)); } - return true; + this.ModConfigInfo = res.Value; + this.IsEnabledInModList = cpackage.IsEnabled; + return FluentResults.Result.Ok(); + } + catch (Exception e) + { + return FluentResults.Result.Fail(new Error(e.Message) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, package) + .WithMetadata(MetadataType.StackTrace, e.StackTrace)); } finally { @@ -204,31 +218,19 @@ public partial class PackageService : IPackageService } } - public void LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false) + public FluentResults.Result LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false) { _operationsUsageLock.EnterReadLock(); LoadingOperationsRunning = true; try { - if (IsDisposed) + if (CheckResourceSanitation(OneOf + .FromT0(assembliesInfo)) is { IsFailed: true } failed) { - throw new ObjectDisposedException($"This package service instance is disposed!"); + return failed; } - SanitationChecksCore(assembliesInfo, "assemblies", nameof(LoadPlugins)); - SanitationChecksEnumerable(assembliesInfo.Assemblies, "assemblies", nameof(LoadPlugins)); - -#if DEBUG - assembliesInfo.Assemblies.ForEach(ari => - { - if (!this.Assemblies.Contains(ari)) - { - throw new ArgumentException( - $"Package Service: tried to load the assembly resource {ari.InternalName} for package {this.Package.Name} but it is not in the list for this package."); - } - }); -#endif - // Order these assemblies by internal dependencies ImmutableArray resources; if (ignoreDependencySorting) @@ -243,12 +245,15 @@ public partial class PackageService : IPackageService } // Try loading them, throw on failure. - if (!_pluginService.Value.TryLoadAndInstanceTypes(resources, true, out var instancedTypes)) + if (_pluginService.Value.LoadAndInstanceTypes(resources, true, out var instancedTypes) is { IsFailed: true} failed2) { - throw new TypeLoadException($"PackageService: unable to load assemblies for package {this.Package.Name}! Aborting loading!"); + return failed2.WithError(new Error($"{nameof(LoadPlugins)}: Failed to load plugins for {this.Package.Name}") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, assembliesInfo)); } PluginsLoaded = true; + return FluentResults.Result.Ok(); } finally { @@ -257,37 +262,28 @@ public partial class PackageService : IPackageService } } - public void LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo) + public FluentResults.Result LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo) { _operationsUsageLock.EnterReadLock(); LoadingOperationsRunning = true; try { - if (IsDisposed) + if (CheckResourceSanitation(OneOf + .FromT1(localizationsInfo)) is { IsFailed: true } failed) { - throw new ObjectDisposedException($"This package service instance is disposed!"); + return failed; } - SanitationChecksCore(localizationsInfo, "localizations", nameof(LoadLocalizations)); - SanitationChecksEnumerable(localizationsInfo.Localizations, "localizations", nameof(LoadLocalizations)); - -#if DEBUG - localizationsInfo.Localizations.ForEach(ri => + if (_localizationService.Value.LoadLocalizations(localizationsInfo.Localizations) is { IsFailed: true} failed2) { - if (!this.Localizations.Contains(ri)) - { - throw new ArgumentException( - $"Package Service: tried to load the localization resource for package {this.Package.Name} but it is not in the list for this package."); - } - }); -#endif - - if (!_localizationService.Value.TryLoadLocalizations(localizationsInfo.Localizations)) - { - throw new FileLoadException($"Package Service: unable to load localizations for package {this.Package.Name}! Aborting!"); + return failed2.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load localizations") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, localizationsInfo)); } LocalizationsLoaded = true; + return FluentResults.Result.Ok(); } finally { @@ -296,38 +292,28 @@ public partial class PackageService : IPackageService } } - public void AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo) + public FluentResults.Result AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo) { _operationsUsageLock.EnterReadLock(); LoadingOperationsRunning = true; try { - if (IsDisposed) + if (CheckResourceSanitation(OneOf + .FromT4(luaScriptsInfo)) is { IsFailed: true } failed) { - throw new ObjectDisposedException($"This package service instance is disposed!"); + return failed; } - SanitationChecksCore(luaScriptsInfo, "luaScripts", nameof(AddLuaScripts)); - SanitationChecksEnumerable(luaScriptsInfo.LuaScripts, "luaScripts", nameof(AddLuaScripts)); - -#if DEBUG - luaScriptsInfo.LuaScripts.ForEach(ri => + if (_luaScriptService.Value.AddScriptFiles(luaScriptsInfo.LuaScripts) is { IsFailed: true} failed2) { - if (!this.LuaScripts.Contains(ri)) - { - throw new ArgumentException( - $"Package Service: tried to load the lua script resource for package {this.Package.Name} but it is not in the list for this package."); - } - }); -#endif - - if (!_luaScriptService.Value.TryAddScriptFiles(luaScriptsInfo.LuaScripts)) - { - throw new ArgumentException( - $"Package Service: unable to add lua files for package {this.Package.Name}! Aborting!"); + return failed2.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load lua scripts.") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, luaScriptsInfo)); } LuaScriptsLoaded = true; + return FluentResults.Result.Ok(); } finally { @@ -336,7 +322,7 @@ public partial class PackageService : IPackageService } } - public void LoadConfig( + public FluentResults.Result LoadConfig( [NotNull]IConfigsResourcesInfo configsResourcesInfo, [NotNull]IConfigProfilesResourcesInfo configProfilesResourcesInfo) { @@ -344,48 +330,38 @@ public partial class PackageService : IPackageService LoadingOperationsRunning = true; try { - if (IsDisposed) + // register configs + if (CheckResourceSanitation(OneOf + .FromT2(configsResourcesInfo)) is { IsFailed: true } failed) { - throw new ObjectDisposedException($"This package service instance is disposed!"); + return failed; } - SanitationChecksCore(configsResourcesInfo, "config", nameof(LoadConfig)); - SanitationChecksCore(configProfilesResourcesInfo, "config profiles", nameof(LoadConfig)); - SanitationChecksEnumerable(configsResourcesInfo.Configs, "config", nameof(LoadConfig)); - SanitationChecksEnumerable(configProfilesResourcesInfo.ConfigProfiles, "config profiles", nameof(LoadConfig)); - -#if DEBUG - configsResourcesInfo.Configs.ForEach(ri => + if (_configService.Value.AddConfigs(configsResourcesInfo.Configs) is { IsFailed: true} failed2) { - if (!this.Configs.Contains(ri)) - { - throw new ArgumentException( - $"Package Service: tried to load the configs resource for package {this.Package.Name} but it is not in the list for this package."); - } - }); - - configProfilesResourcesInfo.ConfigProfiles.ForEach(ri => - { - if (!this.ConfigProfiles.Contains(ri)) - { - throw new ArgumentException( - $"Package Service: tried to load the localization resource for package {this.Package.Name} but it is not in the list for this package."); - } - }); -#endif - - if (!_configService.Value.TryAddConfigs(configsResourcesInfo.Configs)) - { - throw new ArgumentException( - $"Package Service: unable to add configs for package {this.Package.Name}! Aborting!"); + return failed2.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load configs.") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, configsResourcesInfo)); } - if (!_configService.Value.TryAddConfigsProfiles(configProfilesResourcesInfo.ConfigProfiles)) + // register config profiles + if (CheckResourceSanitation(OneOf + .FromT3(configProfilesResourcesInfo)) is { IsFailed: true } failed3) { - throw new ArgumentException( - $"Package Service: unable to add configs profiles for package {this.Package.Name}! Aborting!"); + return failed3; } + + if (_configService.Value.AddConfigsProfiles(configProfilesResourcesInfo.ConfigProfiles) is { IsFailed: true} failed4) + { + return failed4.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load config profiles.") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, configProfilesResourcesInfo)); + } + ConfigsLoaded = true; + return FluentResults.Result.Ok(); } finally { @@ -463,22 +439,24 @@ public partial class PackageService : IPackageService } } - public void Reset() + public FluentResults.Result Reset() { _operationsUsageLock.EnterWriteLock(); + try { if (this.Package is null) { - _loggerService.LogError( - $"Package Service: cannot Dispose of service as ContentPackage and info is not set!"); - return; + return FluentResults.Result.Fail(new Error($"Package Service: cannot Dispose of service as ContentPackage and info is not set!") + .WithMetadata(MetadataType.ExceptionDetails, nameof(Reset)) + .WithMetadata(MetadataType.ExceptionObject, this)); } if (this.ModConfigInfo is null) { - _loggerService.LogError($"Package Service: cannot Dispose of service as ModConfigInfo is not loaded!"); - return; + return FluentResults.Result.Fail(new Error($"Package Service: cannot Dispose of service as ModConfigInfo is not set!") + .WithMetadata(MetadataType.ExceptionDetails, nameof(Reset)) + .WithMetadata(MetadataType.ExceptionObject, this)); } Interlocked.MemoryBarrier(); //ensure cache states @@ -491,7 +469,7 @@ public partial class PackageService : IPackageService _operationsUsageLock.EnterWriteLock(); if (timeoutLimit < DateTime.Now) { - _loggerService.LogError($"Package Service: Dispose() time out reached while waiting for other operations. Continuing."); + _loggerService.LogError($"Package Service: Dispose() grace time-out reached while waiting for other operations. Continuing."); break; } } @@ -520,6 +498,7 @@ public partial class PackageService : IPackageService _localizationService.Value.Remove(this.Localizations); LocalizationsLoaded = false; } + return FluentResults.Result.Ok(); } finally { @@ -531,96 +510,176 @@ public partial class PackageService : IPackageService #region INTERNAL - private void SanitationChecksCore(object o, string resTypeInfoName, string callerName) + /// + /// [Thread Unsafe] Performs sanitation and null checks on resources and returns the results. + /// NOTE: Requires that resource locks be set by the caller. + /// + /// + /// + private FluentResults.Result CheckResourceSanitation( + OneOf.OneOf resourcesInfos) { - if (o is null) + // execute checks based on known types + return resourcesInfos.Match( + ass => ChecksDispatcher(ass, nameof(ass.Assemblies), nameof(LoadPlugins), + ass.Assemblies, this.Assemblies), + loc => ChecksDispatcher(loc, nameof(loc.Localizations), nameof(LoadLocalizations), + loc.Localizations, this.Localizations), + cfg => ChecksDispatcher(cfg, nameof(cfg.Configs), nameof(LoadConfig), + cfg.Configs, this.Configs), + cfp => ChecksDispatcher(cfp, nameof(cfp.ConfigProfiles), nameof(LoadConfig), + cfp.ConfigProfiles, this.ConfigProfiles), + lua => ChecksDispatcher(lua, nameof(lua.LuaScripts), nameof(AddLuaScripts), + lua.LuaScripts, this.LuaScripts)); + + + /* + * Helper functions + */ + FluentResults.Result ChecksDispatcher(object obj, string resName, string callerName, + ImmutableArray resList, ImmutableArray compareList) + where T : class, IPackageInfo, IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo { - _loggerService.LogError($"Package Service: {resTypeInfoName} resources list is null!"); - throw new NullReferenceException($"Package Service: {resTypeInfoName} resources list is null!"); + string errMsg = $"{callerName}: Failed to load {resName}."; + if (DisposeCheck(obj) is { IsFailed: true } failed) + return failed; + if (SanitationChecksCore(obj, resName, callerName) is { IsFailed: true } failed1) + return failed1.WithError(new Error(errMsg)); + if (SanitationChecksEnumerable(resList, resName, callerName) is { IsFailed: true } failed2) + return failed2.WithError(new Error(errMsg)); + if (DebugCheck(resList, compareList, resName) is {IsFailed: true} failed3) + return failed3.WithError(new Error(errMsg)); + return FluentResults.Result.Ok(); + } + + FluentResults.Result DisposeCheck(object obj) + { + if (IsDisposed) + { + return FluentResults.Result.Fail(new Error($"{nameof(PackageService)}: Tried to load resources when disposed.") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, obj)); + } + return FluentResults.Result.Ok(); + } + + FluentResults.Result DebugCheck(ImmutableArray resList, ImmutableArray compareList, string resName) + where T : class, IPackageInfo + { +#if DEBUG + Stack errors = new(); + resList.ForEach(res => + { + if (!compareList.Contains(res)) + { + errors.Push(new Error($"Failed to load {resName} for: {this.Package.Name}") + .WithMetadata(MetadataType.ExceptionDetails, $"Tries to load {resName} resource {res.InternalName} but it is not from this package!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, res)); + } + }); + if (errors.Count > 0) + { + return FluentResults.Result.Fail(errors).WithError( + new Error($"{nameof(LoadPlugins)}: errors in {resName} resources.") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, this.Package)); + } +#endif + return FluentResults.Result.Ok(); + } + } + + private FluentResults.Result SanitationChecksCore(object obj, string resTypeInfoName, string callerName) + { + Error e = null; + + if (obj is null) + { + e = new Error($"{nameof(SanitationChecksCore)}: null checks failed!") + .WithMetadata(MetadataType.ExceptionDetails, "Object is null!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.Sources, new List() { resTypeInfoName, callerName }); } if (this.Package is null) { - _loggerService.LogError($"Package Service: package not set at {callerName}()!"); - throw new NullReferenceException($"Package Service: package not set at {callerName}()!"); + e = (e ?? new Error($"{nameof(SanitationChecksCore)}: null checks failed!")) + .WithMetadata(MetadataType.ExceptionDetails, "The Package is null!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.Sources, new List() { resTypeInfoName, callerName }); } + + return e is null ? FluentResults.Result.Ok() : FluentResults.Result.Fail(e); } - private void SanitationChecksEnumerable(ImmutableArray resourceInfos, string resTypeInfoName, string callerName) where T : IResourceInfo, IResourceCultureInfo, IPackageInfo, IPackageDependenciesInfo + private FluentResults.Result SanitationChecksEnumerable(ImmutableArray resourceInfos, string resTypeInfoName, string callerName) where T : IResourceInfo, IResourceCultureInfo, IPackageInfo, IPackageDependenciesInfo { // Check if list is empty. Nothing more to do. if (resourceInfos.IsDefaultOrEmpty) - return; + return FluentResults.Result.Ok(); + Stack errors = new(); + // Check if all resources in the list are registered to this package, throw if not. foreach (var resourceInfo in resourceInfos) { // ownership checks if (resourceInfo.OwnerPackage is null) - { - throw new ArgumentException($"Package Service: {resTypeInfoName} info for resource does not have a package name set! Run by {this.Package.Name}."); + { + errors.Push(new Error($"Error for resource: {resTypeInfoName}. OwnerPackage is null!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, resourceInfo)); + continue; } if (resourceInfo.OwnerPackage != this.Package) { - throw new ArgumentException( - $"Package Service: {resTypeInfoName} info does not belong to this package! Owned by {resourceInfo.OwnerPackage.Name} but is run by {this.Package.Name}."); + errors.Push(new Error($"Error for resource: {resTypeInfoName}. $\"OwnerPackage {{resourceInfo.OwnerPackage?.Name}} is not the same as this package: {{this.Package}}") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, resourceInfo)); + continue; } - // Check if external dependencies are loaded and if current environment is supported, throw if not if (resourceInfo.Dependencies.IsDefaultOrEmpty) continue; - - bool resourceMissing = false; - - resourceInfo.Dependencies.ForEach(pdi => + + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator + foreach (var pdi in resourceInfo.Dependencies) { - // for clarification: assemblies passed to the function should always be loaded. - // optional assemblies should be filtered out before the list is sent. + // for clarification: all resources passed to the function should always be loaded. + // unneeded optional resources should be filtered out before the list is sent. // left this as a reminder :) /*if (pdi.Optional) return;*/ if (!_packageManagementService.CheckDependencyLoaded(pdi)) { - resourceMissing = true; - _loggerService.LogError( - $"Package Service: the following dependency for package {resourceInfo.OwnerPackage.Name} is not loaded: {pdi.DependencyPackage?.Name ?? (pdi.PackageName.IsNullOrWhiteSpace() ? pdi.SteamWorkshopId.ToString() : pdi.PackageName)}"); + errors.Push(new Error($"Dependency missing for resource: {resourceInfo.OwnerPackage.Name}") + .WithMetadata(MetadataType.ExceptionDetails, $"Missing dependency: {pdi.DependencyPackage?.Name ?? (pdi.FallbackPackageName.IsNullOrWhiteSpace() ? pdi.SteamWorkshopId.ToString() : pdi.FallbackPackageName)}") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, resourceInfo)); } - }); - - if (!resourceMissing) - { - throw new FileLoadException($"Package Service: dependencies for package {resourceInfo.OwnerPackage.Name} are not loaded."); } // check runtime platform if (!_packageManagementService.CheckEnvironmentSupported(resourceInfo)) { - throw new PlatformNotSupportedException($"Package service: the {resTypeInfoName} from {resourceInfo.OwnerPackage.Name} is not supported on this platform."); + errors.Push(new Error($"The resource {resourceInfo.OwnerPackage?.Name} does not support the current platform!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, resourceInfo)); } // check local culture if (!_localizationService.Value.IsCurrentCultureSupported(resourceInfo)) { - throw new PlatformNotSupportedException($"Package service: the {resTypeInfoName} from {resourceInfo.OwnerPackage.Name} is not supported in this culture."); + errors.Push(new Error($"The resource {resourceInfo.OwnerPackage?.Name} does not support the current culture/region!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, resourceInfo)); } } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool GetThreadSafeBool(ref int var) => Interlocked.CompareExchange(ref var, 1, 1) == 1; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void SetThreadSafeBool(ref int var, bool value) - { - if (value) - { - Interlocked.CompareExchange(ref var, 1, 0); - } - else - { - Interlocked.CompareExchange(ref var, 0, 1); - } + return errors.Count > 0 ? FluentResults.Result.Fail(errors) : FluentResults.Result.Ok(); } #endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs new file mode 100644 index 000000000..64f36d63f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -0,0 +1,52 @@ +using System.Collections.Immutable; +using Barotrauma.LuaCs.Data; +using FluentResults; +using Microsoft.CodeAnalysis; + +namespace Barotrauma.LuaCs.Services; + +public class PluginManagementService : IPluginManagementService +{ + + + public void Dispose() + { + throw new System.NotImplementedException(); + } + + public FluentResults.Result Reset() + { + throw new System.NotImplementedException(); + } + + public bool IsAssemblyLoadedGlobal(string friendlyName) + { + throw new System.NotImplementedException(); + } + + public Result> GetTypes(ContentPackage package = null, string namespacePrefix = null, bool includeInterfaces = false, + bool includeAbstractTypes = false, bool includeDefaultContext = true, bool includeExplicitAssembliesOnly = false) + { + throw new System.NotImplementedException(); + } + + public ImmutableArray GetStandardMetadataReferences() + { + throw new System.NotImplementedException(); + } + + public ImmutableArray GetPluginMetadataReferences() + { + throw new System.NotImplementedException(); + } + + public Result> GetCachedAssembliesForPackage(ContentPackage package) + { + throw new System.NotImplementedException(); + } + + public Result> LoadAssemblyResources(ImmutableArray resource) + { + throw new System.NotImplementedException(); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IEventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginService.cs similarity index 61% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IEventService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginService.cs index 3c0caef4e..098fa1954 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IEventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginService.cs @@ -1,6 +1,6 @@ namespace Barotrauma.LuaCs.Services; -public interface IEventService +public class PluginService { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs index dbb150ff0..0b8c8ad70 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs @@ -1,16 +1,17 @@ using System.Collections.Generic; using System.Xml.Linq; using Barotrauma.LuaCs.Data; +using FluentResults; namespace Barotrauma.LuaCs.Services.Processing; #region TypeDef // ReSharper disable once TypeParameterCanBeVariant -public interface IConverterService : IService +public interface IConverterService : IReusableService { - bool TryParseResource(TSrc src, out TOut resources); - bool TryParseResources(IEnumerable sources, out List resources); + Result TryParseResource(TSrc src); + Result TryParseResources(IEnumerable sources); } public interface IXmlResourceConverterService : IConverterService { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IModConfigParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IModConfigParserService.cs new file mode 100644 index 000000000..e90f4d598 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IModConfigParserService.cs @@ -0,0 +1,9 @@ +using Barotrauma.LuaCs.Data; + +namespace Barotrauma.LuaCs.Services.Processing; + +public interface IModConfigParserService : IReusableService +{ + FluentResults.Result BuildConfigForPackage(ContentPackage package); + FluentResults.Result BuildConfigFromManifest(string manifestPath); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs index 2f4004e3b..781827ced 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs @@ -1,4 +1,6 @@ -namespace Barotrauma.LuaCs.Services.Safe; +using Barotrauma.LuaCs.Configuration; + +namespace Barotrauma.LuaCs.Services.Safe; public interface ILuaConfigService : ILuaService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs index 07f38202c..9a2f0d02b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs @@ -1,6 +1,30 @@ -namespace Barotrauma.LuaCs.Services.Safe; +using System; +using System.Collections.Generic; +using Barotrauma.LuaCs.Events; +using Barotrauma.LuaCs.Services.Compatibility; -public interface ILuaEventService : ILuaService +namespace Barotrauma.LuaCs.Services.Safe; + +public interface ILuaSafeEventService : ILuaService, ILuaCsHook { - + void Subscribe(string interfaceName, string identifier, IDictionary callbacks); + /// + /// Removes a subscriber from an event that subscribed under the given identifier. + /// + /// + /// + void Remove(string eventName, string identifier); + /// + /// Send an event to all subscribers to an interface. + /// + /// Name of the interface (must be registered with Lua). + /// Execution runner, the subscriber is provided as the first argument in the lua runner. + /// + void PublishLuaEvent(string interfaceName, LuaCsFunc runner); +} + +public interface ILuaEventService : ILuaSafeEventService +{ + public FluentResults.Result RegisterSafeEvent() where T : IEvent; + public FluentResults.Result UnregisterSafeEvent() where T : IEvent; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs index b06b74940..49d45cb75 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs @@ -24,7 +24,7 @@ public class ServicesProvider : IServicesProvider private readonly ReaderWriterLockSlim _serviceLock = new(); - public void RegisterServiceType(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface, new() + public void RegisterServiceType(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface { if (lifetimeInstance is null) { @@ -50,7 +50,6 @@ public class ServicesProvider : IServicesProvider { _serviceLock.EnterReadLock(); ServiceContainer.Register(lifetimeInstance); - ServiceContainer.Compile(); OnServiceRegistered?.Invoke(typeof(TSvcInterface), typeof(TService)); } finally @@ -60,7 +59,7 @@ public class ServicesProvider : IServicesProvider } public void RegisterServiceType(string name, ServiceLifetime lifetime, - ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface, new() + ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface { if (name.IsNullOrWhiteSpace()) { @@ -91,7 +90,6 @@ public class ServicesProvider : IServicesProvider { _serviceLock.EnterReadLock(); ServiceContainer.Register(name, lifetimeInstance); - ServiceContainer.Compile(); OnServiceRegistered?.Invoke(typeof(TSvcInterface), typeof(TService)); } finally @@ -128,7 +126,7 @@ public class ServicesProvider : IServicesProvider } } - public bool TryGetService(out IService service) where TSvcInterface : class, IService + public bool TryGetService(out TSvcInterface service) where TSvcInterface : class, IService { try { @@ -147,7 +145,7 @@ public class ServicesProvider : IServicesProvider } } - public bool TryGetService(string name, out IService service) where TSvcInterface : class, IService + public bool TryGetService(string name, out TSvcInterface service) where TSvcInterface : class, IService { try { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IAssemblyManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IAssemblyManagementService.cs new file mode 100644 index 000000000..b347a301f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IAssemblyManagementService.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +// ReSharper disable InconsistentNaming + +namespace Barotrauma.LuaCs.Services; + +public interface IAssemblyManagementService : IReusableService +{ + /// + /// Searches for an assembly given it's fully qualified name, while excluding the contexts with the given Guids, if supplied. + /// + /// The fully-qualified assembly name. + /// Guids of excluded contexts. + /// On Success: The assembly.
On Failure: nothing.
+ FluentResults.Result GetLoadedAssembly(string assemblyName, in Guid[] excludedContexts); + /// + /// Searches for an assembly given it's fully qualified name, while excluding the contexts with the given Guids, if supplied. + /// + /// The assembly info. + /// Guids of excluded contexts. + /// On Success: The assembly.
On Failure: nothing.
+ FluentResults.Result GetLoadedAssembly(AssemblyName assemblyName, in Guid[] excludedContexts); + + /// + /// Gets the assembly collection for the BCL and base game assemblies. + /// + /// collection, if any are found. Returns an empty collection otherwise. + ImmutableArray GetDefaultMetadataReferences(); + + /// + /// Gets the assembly collection for all add-in assemblies loaded. + /// + /// collection, if any are found. Returns an empty collection otherwise. + ImmutableArray GetAddInContextsMetadataReferences(); + + /// + /// + /// + ImmutableArray AssemblyLoaderServices { get; } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs new file mode 100644 index 000000000..b40a1da69 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Barotrauma.LuaCs.Configuration; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Networking; +using Barotrauma.LuaCs.Services.Safe; +using Barotrauma.Networking; + +namespace Barotrauma.LuaCs.Services; + +public partial interface IConfigService : IReusableService, ILuaConfigService +{ + /* + * Resource Files. + */ + FluentResults.Result AddConfigs(ImmutableArray configResources); + FluentResults.Result AddConfigsProfiles(ImmutableArray configProfileResources); + FluentResults.Result RemoveConfigs(ImmutableArray configResources); + FluentResults.Result RemoveConfigsProfiles(ImmutableArray configProfilesResources); + + + /* + * From resources + */ + FluentResults.Result AddConfigs(ImmutableArray configs); + FluentResults.Result AddConfigsProfiles(ImmutableArray configProfiles); + FluentResults.Result RemoveConfigs(ImmutableArray configs); + FluentResults.Result RemoveConfigsProfiles(ImmutableArray configProfiles); + + /* + * Immediate mode + */ + FluentResults.Result> AddConfigEntry(ContentPackage package, string name, + T defaultValue, + NetSync syncMode = NetSync.None, + ClientPermissions permissions = ClientPermissions.None, + Func valueChangePredicate = null, + Action> onValueChanged = null) where T : IConvertible, IEquatable; + + FluentResults.Result AddConfigList(ContentPackage package, string name, + int defaultIndex, IReadOnlyList values, + NetSync syncMode = NetSync.None, + ClientPermissions permissions = ClientPermissions.None, + Func valueChangePredicate = null, + Action onValueChanged = null); + + FluentResults.Result> AddConfigRangeEntry(ContentPackage package, string name, + T defaultValue, T minValue, T maxValue, + Func, int> getStepCount, + NetSync syncMode = NetSync.None, + ClientPermissions permissions = ClientPermissions.None, + Func valueChangePredicate = null, + Action> onValueChanged = null) where T : IConvertible, IEquatable; + + FluentResults.Result> AddConfigEntry(string packageName, string name, + T defaultValue, + NetSync syncMode = NetSync.None, + ClientPermissions permissions = ClientPermissions.None, + Func valueChangePredicate = null, + Action> onValueChanged = null) where T : IConvertible, IEquatable; + + FluentResults.Result AddConfigList(string packageName, string name, + int defaultIndex, IReadOnlyList values, + NetSync syncMode = NetSync.None, + ClientPermissions permissions = ClientPermissions.None, + Func valueChangePredicate = null, + Action onValueChanged = null); + + FluentResults.Result> AddConfigRangeEntry(string packageName, string name, + T defaultValue, T minValue, T maxValue, + Func, int> getStepCount, + NetSync syncMode = NetSync.None, + ClientPermissions permissions = ClientPermissions.None, + Func valueChangePredicate = null, + Action> onValueChanged = null) where T : IConvertible, IEquatable; + + FluentResults.Result> GetConfigsForPackage(ContentPackage package); + FluentResults.Result> GetConfigsForPackage(string packageName); + IReadOnlyDictionary<(ContentPackage, string), IConfigBase> GetAllConfigs(); + FluentResults.Result GetConfig(ContentPackage package, string name); + FluentResults.Result GetConfig(string packageName, string name); + FluentResults.Result GetConfig(ContentPackage package, string name) where T : IConfigBase; + FluentResults.Result GetConfig(string packageName, string name) where T : IConfigBase; +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IEventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IEventService.cs new file mode 100644 index 000000000..08d612944 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IEventService.cs @@ -0,0 +1,45 @@ +using System; +using System.Reflection; +using Barotrauma.LuaCs.Events; +using Barotrauma.LuaCs.Services.Compatibility; +using Barotrauma.LuaCs.Services.Safe; + +namespace Barotrauma.LuaCs.Services; + +public interface IEventService : IReusableService, ILuaEventService +{ + FluentResults.Result SetLegacyLuaRunnerFactory(Func runnerFactory) where T : IEvent; + void RemoveLegacyLuaRunnerFactory() where T : IEvent; + void SetAliasToEvent(string alias) where T : IEvent; + void RemoveEventAlias(string alias); + void RemoveAllEventAliases() where T : IEvent; + /// + /// + /// + /// + /// + /// + FluentResults.Result Subscribe(T subscriber) where T : IEvent; + /// + /// + /// + /// + /// + void Unsubscribe(T subscriber) where T : IEvent; + /// + /// Clears all subscribers for a given event type and removes any registration to the type. + /// + /// The event type. + void ClearAllEventSubscribers() where T : IEvent; + /// + /// Clears all subscribers lists. + /// + void ClearAllSubscribers(); + /// + /// Invokes all alive subscribers of the given event using the provided invocation factory. + /// + /// + /// + /// + FluentResults.Result PublishEvent(Action action) where T : IEvent; +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILocalizationService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILocalizationService.cs new file mode 100644 index 000000000..26575f720 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILocalizationService.cs @@ -0,0 +1,32 @@ +using System; +using System.Globalization; +using System.Collections.Generic; +using System.Collections.Immutable; +using Barotrauma.LuaCs.Data; + +namespace Barotrauma.LuaCs.Services; + +public interface ILocalizationService : IReusableService +{ + IReadOnlyCollection GetLoadedLocales(); + void Remove(ImmutableArray localizations); + FluentResults.Result SetCurrentCulture(CultureInfo culture); + FluentResults.Result SetCurrentCulture(string cultureName); + FluentResults.Result LoadLocalizations(ImmutableArray localizationResources); + + /// + /// Tries to get a localized string without a fallback. Returns success/failure and associated data. + /// + /// Neutral localization key. + /// + FluentResults.Result GetLocalizedString(string key); + FluentResults.Result GetLocalizedString(string key, CultureInfo targetCulture); + string GetLocalizedString(string key, string fallback); + string GetLocalizedString(string key, string fallback, CultureInfo targetCulture); + FluentResults.Result GetLocalizedStringForPackage(ContentPackage package, string key); + FluentResults.Result GetLocalizedStringForPackage(ContentPackage package, string key, CultureInfo targetCulture); + string GetLocalizedStringForPackage(ContentPackage package, string key, string fallback); + string GetLocalizedStringForPackage(ContentPackage package, string key, string fallback, CultureInfo targetCulture); + FluentResults.Result RegisterLocalizationResolver(CultureInfo targetCulture, Func factoryResolver); + bool IsCurrentCultureSupported(IResourceCultureInfo culturesInfo); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILoggerService.cs similarity index 85% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILoggerService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILoggerService.cs index eb457b7d3..e1b0805e5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILoggerService.cs @@ -1,5 +1,6 @@ using System; using Barotrauma.Networking; +using FluentResults; using Microsoft.Xna.Framework; namespace Barotrauma.LuaCs.Services; @@ -7,14 +8,15 @@ namespace Barotrauma.LuaCs.Services; /// /// Provides console and debug logging services /// -public interface ILoggerService : IService +public interface ILoggerService : IReusableService { void HandleException(Exception exception, string prefix = null); void LogError(string message); void LogWarning(string message); void LogMessage(string message, Color? serverColor = null, Color? clientColor = null); void Log(string message, Color? color = null, ServerLog.MessageType messageType = ServerLog.MessageType.ServerMessage); - + void LogResults(FluentResults.Result result); + #region DebugBuilds void LogDebug(string message, Color? color = null); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILuaScriptService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptService.cs similarity index 79% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILuaScriptService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptService.cs index 6d4a8e673..99f9fa516 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ILuaScriptService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptService.cs @@ -8,7 +8,7 @@ using MoonSharp.Interpreter.Interop; namespace Barotrauma.LuaCs.Services; -public interface ILuaScriptService : IService +public interface ILuaScriptService : IReusableService { #region Script_File_Collector @@ -17,7 +17,8 @@ public interface ILuaScriptService : IService /// /// /// - bool TryAddScriptFiles(ImmutableArray luaResource); + FluentResults.Result AddScriptFiles(ImmutableArray luaResource); + /// /// Removes the specific resources from the script runner. Important: Does not stop the /// execution of any code related to the files nor guarantee cleanup of resources! @@ -31,19 +32,20 @@ public interface ILuaScriptService : IService /// /// /// - bool TryExecuteScripts(bool pauseExecutionOnScriptError = false, bool verboseLogging = false); + FluentResults.Result ExecuteScripts(bool pauseExecutionOnScriptError = false, bool verboseLogging = false); + ImmutableArray GetScriptResources(); #endregion } -public interface ILuaScriptManagementService : IService +public interface ILuaScriptManagementService : IReusableService { #region Script_File_Execution - bool TryExecuteLoadedScripts(ContentPackage package, bool pauseExecutionOnError = false, bool verboseLogging = false); - bool TryExecuteLoadedScripts(ImmutableArray scripts, bool pauseExecutionOnError = false, bool verboseLogging = false); - bool TryExecuteLoadedScripts(bool pauseExecutionOnError = false, bool verboseLogging = false); + FluentResults.Result ExecuteLoadedScripts(ContentPackage package, bool pauseExecutionOnError = false, bool verboseLogging = false); + FluentResults.Result ExecuteLoadedScripts(ImmutableArray scripts, bool pauseExecutionOnError = false, bool verboseLogging = false); + FluentResults.Result ExecuteLoadedScripts(bool pauseExecutionOnError = false, bool verboseLogging = false); #endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs new file mode 100644 index 000000000..e5c94a1d3 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs @@ -0,0 +1,25 @@ +using System; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Networking; +using Barotrauma.LuaCs.Services.Compatibility; +using Barotrauma.Networking; + +namespace Barotrauma.LuaCs.Services; + +internal delegate void NetMessageReceived(IReadMessage netMessage); + +internal interface INetworkingService : IReusableService, ILuaCsNetworking +{ + bool IsActive { get; } + bool IsSynchronized { get; } + + public INetWriteMessage Start(Guid netId); + public void Receive(Guid netId, NetMessageReceived action); +#if SERVER + public void Send(IWriteMessage netMessage, NetworkConnection connection = null, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable); +#elif CLIENT + public void Send(IWriteMessage netMessage, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable); +#endif + public void RegisterNetVar(INetVar netVar); + public void SendNetVar(INetVar netVar); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs new file mode 100644 index 000000000..397a871b4 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using Barotrauma.Extensions; +using Barotrauma.LuaCs.Data; + +namespace Barotrauma.LuaCs.Services; + +public interface IPackageManagementService : IReusableService +{ + /// + /// Adds packages to the queue of loadable packages without initializing them. + /// + /// + void QueuePackages(ImmutableArray packages); + + /// + /// Generates the ModConfigInfo for all queued packages and adds them to the store. + /// + /// Use multithreaded loading. + /// Whether duplicate packages should be reported as errors. + /// Failure/Success records for each package. + FluentResults.Result ParseQueuedPackages(bool loadParallel = true, bool reportFailOnDuplicates = false); + /// + /// Loads only the localizations, configs, and config profiles for stored packages. + /// + /// + /// + FluentResults.Result LoadPackageConfigsResourcesGroup(bool loadParallel = true); + /// + /// Loads all resources for stored packages. + /// + /// Use multithreaded loading. + /// Only load safe scripting resources, such as Lua. C# plugins disabled. + /// + FluentResults.Result LoadAllPackageResources(bool loadParallel = true, bool safeResourcesOnly = true); + FluentResults.Result UnloadPackages(); + bool IsPackageLoaded(ContentPackage package); + bool CheckDependencyLoaded(IPackageDependencyInfo info); + bool CheckDependenciesLoaded([NotNull]IEnumerable infos, out ImmutableArray missingPackages); + bool CheckEnvironmentSupported(IPlatformInfo platform); + /// + /// Tries to get the package dependency record to refer to that specific package if it exists, optionally create it. + /// + /// ContentPackage reference + /// Register a new IPackageDependencyInfo reference. + /// + FluentResults.Result GetPackageDependencyInfoRecord(ContentPackage package, + bool addIfMissing = false); + /// + /// Tries to get the package dependency record to refer to that specific package if it exists, optionally create it. + /// + /// The Steam Workshop ID, if available, if not enter zero ('0'). + /// The name of the package. + /// The folder path, as formatted in [ContentPackage.Path]. + /// Register a new IPackageDependencyInfo reference. + /// + FluentResults.Result GetPackageDependencyInfoRecord(ulong steamWorkshopId, + string packageName, string folderPath = null, bool addIfMissing = false); + /// + /// Tries to get the package dependency record to refer to that specific package if it exists. + /// Note: This overload does not allow the registration of a new dependency. + /// + /// The folder path, as formatted in [ContentPackage.Path]. + /// + FluentResults.Result GetPackageDependencyInfoRecord(string folderPath); + + IPackageDependencyInfo CreateOrphanPackageDependencyInfoRecord(string packageName, + string packagePath, ulong steamWorkshopId); +} + +public readonly record struct LoadablePackage +{ + public ContentPackage Package { get; } + public bool IsEnabled { get; } + + public LoadablePackage(ContentPackage package, bool isEnabled) + { + Package = package; + IsEnabled = isEnabled; + } + + public static ImmutableArray FromEnumerable(IEnumerable packages, bool isEnabled) + { + var builder = ImmutableArray.CreateBuilder(); + packages.ForEach(p => builder.Add(new LoadablePackage(p, isEnabled))); + return builder.ToImmutable(); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageService.cs similarity index 58% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageService.cs index 1411cb313..3f1189250 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPackageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageService.cs @@ -3,21 +3,23 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using Barotrauma.LuaCs.Data; +using FluentResults; namespace Barotrauma.LuaCs.Services; -public interface IPackageService : IService, +public interface IPackageService : IReusableService, // These allow us the pass the IContentPackageService to anything that needs the data without having to directly reference the member IResourceCultureInfo, IAssembliesResourcesInfo, ILocalizationsResourcesInfo, ILuaScriptsResourcesInfo { ContentPackage Package { get; } IModConfigInfo ModConfigInfo { get; } + bool IsEnabledInModList { get; } /// /// Try to load the XML config and resources information from the given package. /// /// - /// Whether the package was parsed without errors and any information was found. Will return false for purely vanilla packages. - bool TryLoadResourcesInfo([NotNull]ContentPackage package); + /// Whether the package was parsed without errors. + FluentResults.Result LoadResourcesInfo([NotNull]LoadablePackage package); /// /// Tries to load all assemblies and instance plugins for the given resources list, regardless whether they're marked as optional and/or lazy load. /// Will sort by load priority unless overriden/bypassed. @@ -25,12 +27,12 @@ public interface IPackageService : IService, /// /// /// Whether loading is successful. Returns true on an empty list. - void LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false); - void LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo); - void AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo); + FluentResults.Result LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false); + FluentResults.Result LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo); + FluentResults.Result AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo); #if CLIENT - void LoadStyles([NotNull]IStylesResourcesInfo stylesInfo); + FluentResults.Result LoadStyles([NotNull]IStylesResourcesInfo stylesInfo); #endif - void LoadConfig([NotNull]IConfigsResourcesInfo configsResourcesInfo, [NotNull]IConfigProfilesResourcesInfo configProfilesResourcesInfo); + FluentResults.Result LoadConfig([NotNull]IConfigsResourcesInfo configsResourcesInfo, [NotNull]IConfigProfilesResourcesInfo configProfilesResourcesInfo); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs new file mode 100644 index 000000000..7769498e7 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Immutable; +using System.Reflection; +using Barotrauma.LuaCs.Data; +using Microsoft.CodeAnalysis; + +namespace Barotrauma.LuaCs.Services; + +public interface IPluginManagementService : IReusableService +{ + /// + /// Checks if an assembly with either the fully-qualified name globally or a 'friendly name' within loaded plugins + /// with the given name is loaded. + /// + /// + /// + bool IsAssemblyLoadedGlobal(string friendlyName); + + // TODO: Documentation. + FluentResults.Result> GetTypes( + ContentPackage package = null, + string namespacePrefix = null, + bool includeInterfaces = false, + bool includeAbstractTypes = false, + bool includeDefaultContext = true, + bool includeExplicitAssembliesOnly = false); + + /// + /// Gets the assembly MetadataReference collection for the BCL and base game assemblies. + /// + /// + ImmutableArray GetStandardMetadataReferences(); + + /// + /// + /// + /// + ImmutableArray GetPluginMetadataReferences(); + + /// + /// + /// + /// + /// + FluentResults.Result> GetCachedAssembliesForPackage(ContentPackage package); + + /// + /// + /// + /// + /// Success/Failure and list of failed resources, if any. + FluentResults.Result> LoadAssemblyResources(ImmutableArray resource); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginService.cs similarity index 74% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginService.cs index cbdd9ba74..cfafe8e56 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IPluginService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginService.cs @@ -6,7 +6,7 @@ using Barotrauma.LuaCs.Data; namespace Barotrauma.LuaCs.Services; -public interface IPluginService : IService +public interface IPluginService : IReusableService { bool IsAssemblyLoaded(string friendlyName); /// @@ -17,21 +17,21 @@ public interface IPluginService : IService /// /// /// - bool TryLoadAndInstanceTypes(IEnumerable assemblyResourcesInfo, bool injectServices, out ImmutableArray typeInstances) where T : class, IAssemblyPlugin; - ImmutableArray GetLoadedPluginTypesInPackage() where T : class, IAssemblyPlugin; + FluentResults.Result LoadAndInstanceTypes(IEnumerable assemblyResourcesInfo, bool injectServices, out ImmutableArray typeInstances) where T : class, IAssemblyPlugin; + FluentResults.Result> GetLoadedPluginTypesInPackage() where T : class, IAssemblyPlugin; /// /// Advances the loading/execution state of the plugin. IMPORTANT: You cannot set the execution state of plugins /// to 'Disposed'. You must instead call the 'DisposePlugins' method. /// /// /// - bool AdvancePluginStates(PluginRunState newState); + FluentResults.Result AdvancePluginStates(PluginRunState newState); /// /// Disposes of all running plugins hosted by the service and releases their references to allow unloading. /// /// Success of the operation. Returns false if any plugin threw errors during disposal. - bool DisposePlugins(); + FluentResults.Result DisposePlugins(); /// /// Gets the current plugin execution state. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IService.cs new file mode 100644 index 000000000..680f51fc2 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IService.cs @@ -0,0 +1,29 @@ +using System; + +namespace Barotrauma.LuaCs.Services; + +/// +/// Defines a service that can be reset to it's post-constructor state and reused without needing to be disposed. +/// Intended for persistent services. +/// +public interface IReusableService : IService +{ + /// + /// Returns the service to its original state (post-instantiation). + /// Allows a service instance to be reused without disposing of the instance. + /// + FluentResults.Result Reset(); +} + +/// +/// Base interface inherited by all services. +/// +public interface IService : IDisposable +{ + bool IsDisposed { get; } + public void CheckDisposed() + { + if (IsDisposed) + throw new ObjectDisposedException($"Tried to call method on disposed object '{this.GetType().Name}'!"); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IServicesProvider.cs similarity index 86% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IServicesProvider.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IServicesProvider.cs index 0304b7da4..8cffa329f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/IServicesProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IServicesProvider.cs @@ -19,7 +19,7 @@ public interface IServicesProvider /// /// /// - void RegisterServiceType(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface, new(); + void RegisterServiceType(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IReusableService where TService : class, IReusableService, TSvcInterface; /// /// Registers a type as a service for a given interface that can be requested by name. @@ -29,7 +29,7 @@ public interface IServicesProvider /// /// /// - void RegisterServiceType(string name, ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface, new(); + void RegisterServiceType(string name, ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IReusableService where TService : class, IReusableService, TSvcInterface; /// /// Called whenever a new service type for a given interface is implemented. @@ -61,7 +61,7 @@ public interface IServicesProvider /// /// /// - bool TryGetService(out IService service) where TSvcInterface : class, IService; + bool TryGetService(out TSvcInterface service) where TSvcInterface : class, IReusableService; /// /// Tries to get a service for the given name and interface, returns success/failure. @@ -71,14 +71,14 @@ public interface IServicesProvider /// /// /// - bool TryGetService(string name, out IService service) where TSvcInterface : class, IService; + bool TryGetService(string name, out TSvcInterface service) where TSvcInterface : class, IReusableService; /// /// Called whenever a new service is created/instanced. /// Args[0]: The interface type of the service. /// Args[1]: The instance of the service. /// - event System.Action OnServiceInstanced; + event System.Action OnServiceInstanced; #endregion @@ -89,7 +89,7 @@ public interface IServicesProvider /// /// /// - ImmutableArray GetAllServices() where TSvc : class, IService; + ImmutableArray GetAllServices() where TSvc : class, IReusableService; #endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs new file mode 100644 index 000000000..b43a595ba --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs @@ -0,0 +1,42 @@ +using System.Collections.Immutable; +using System.Xml.Linq; + +namespace Barotrauma.LuaCs.Services; + +public interface IStorageService : IReusableService +{ + #region LocalGameData + + FluentResults.Result LoadLocalXml(ContentPackage package, string localFilePath); + FluentResults.Result LoadLocalBinary(ContentPackage package, string localFilePath); + FluentResults.Result LoadLocalText(ContentPackage package, string localFilePath); + FluentResults.Result FileExistsInLocalData(ContentPackage package, string localFilePath); + + #endregion + + #region ContentPackageData + FluentResults.Result LoadPackageXml(ContentPackage package, string localFilePath, out XDocument document); + FluentResults.Result LoadPackageBinary(ContentPackage package, string localFilePath, out byte[] bytes); + FluentResults.Result LoadPackageText(ContentPackage package, string localFilePath, out string text); + + FluentResults.Result> LoadPackageXmlFiles(ContentPackage package, ImmutableArray localFilePath); + FluentResults.Result> TryLoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePath); + FluentResults.Result> TryLoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePath); + + FluentResults.Result> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively); + FluentResults.Result FileExistsInPackage(ContentPackage package, string localFilePath); + + #endregion + + #region AbsolutePaths + + FluentResults.Result TryLoadXml(string filePatht); + FluentResults.Result TrySaveXml(string filePath, in XDocument document); + FluentResults.Result TryLoadBinary(string filePath); + FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes); + FluentResults.Result TryLoadText(string filePath); + FluentResults.Result TrySaveText(string filePath, string text); + FluentResults.Result FileExists(string filePath); + + #endregion +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/ACsMod.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/ACsMod.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/ApplicationMode.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ApplicationMode.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/ApplicationMode.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ApplicationMode.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs new file mode 100644 index 000000000..1dea21e4a --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs @@ -0,0 +1,428 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Dynamic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Loader; +using System.Threading; +using Barotrauma.LuaCs; +using Barotrauma.LuaCs.Events; +using Microsoft.CodeAnalysis; +using Basic.Reference.Assemblies; +using FluentResults; +using FluentResults.LuaCs; +using LightInject; +using Microsoft.CodeAnalysis.CSharp; +using OneOf; +using Path = Barotrauma.IO.Path; + +[assembly: InternalsVisibleTo(IAssemblyLoaderService.InternalsAwareAssemblyName)] + +namespace Barotrauma.LuaCs.Services; +public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService +{ + public Guid Id { get; init; } + public bool IsReferenceOnlyMode { get; init; } + public bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } + private int _isDisposed; + + //internal + private readonly IAssemblyManagementService _assemblyManagementService; + private readonly IEventService _eventService; + private readonly Action _onUnload; + /// + /// This lock is just to ensure that we do not load while disposing + /// + private readonly ReaderWriterLockSlim _operationsLock = new(LockRecursionPolicy.SupportsRecursion); + private readonly ConcurrentDictionary _dependencyResolvers = new(); + private readonly ConcurrentDictionary _loadedAssemblyData = new(); + + private ThreadLocal _isResolving = new(static()=>false); // cyclic resolution exit + + #region PublicAPI + + public AssemblyLoader(IAssemblyManagementService assemblyManagementService, + IEventService eventService, + Guid id, string name, + bool isReferenceOnlyMode, Action onUnload) + : base(isCollectible: true, name: name) + { + _assemblyManagementService = assemblyManagementService; + _eventService = eventService; + Id = id; + IsReferenceOnlyMode = isReferenceOnlyMode; + _onUnload = onUnload; + if (_onUnload is not null) + { + base.Unloading += OnUnload; + } + + } + + public FluentResults.Result AddDependencyPaths(ImmutableArray paths) + { + if (paths.Length == 0) + return FluentResults.Result.Ok(); + var res = new FluentResults.Result(); + foreach (var path in paths) + { + try + { + var p = Path.GetFullPath(path.CleanUpPath()); + _dependencyResolvers[p] = new AssemblyDependencyResolver(p); + } + catch (Exception ex) + { + return res.WithError(new ExceptionalError(ex) + .WithMetadata(MetadataType.Sources, path)); + } + } + return FluentResults.Result.Ok(); + } + + public FluentResults.Result CompileScriptAssembly( + [NotNull] string assemblyName, + bool compileWithInternalAccess, + ImmutableArray syntaxTrees, + ImmutableArray metadataReferences, + CSharpCompilationOptions compilationOptions = null) + { + if (assemblyName.IsNullOrWhiteSpace()) + { + return new FluentResults.Result().WithError(new Error($"The name provided is null!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, syntaxTrees)); + } + + if (_loadedAssemblyData.ContainsKey(assemblyName)) + { + return new FluentResults.Result().WithError(new Error($"The name provided is already assigned to an assembly!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, syntaxTrees)); + } + + var compilationAssemblyName = compileWithInternalAccess ? IAssemblyLoaderService.InternalsAwareAssemblyName : assemblyName; + + compilationOptions ??= new CSharpCompilationOptions( + outputKind: OutputKind.DynamicallyLinkedLibrary, + optimizationLevel: OptimizationLevel.Release, + concurrentBuild: true, + reportSuppressedDiagnostics: true, + allowUnsafe: true); + + if (!compileWithInternalAccess) + { + typeof(CSharpCompilationOptions) + .GetProperty("TopLevelBinderFlags", BindingFlags.Instance | BindingFlags.NonPublic) + ?.SetValue(compilationOptions, (uint)1 << 22); + } + + using var asmMemoryStream = new MemoryStream(); + var result = CSharpCompilation.Create(compilationAssemblyName, syntaxTrees, metadataReferences, compilationOptions).Emit(asmMemoryStream); + if (!result.Success) + { + var res = new FluentResults.Result().WithError( + new Error($"Compilation failed for assembly {assemblyName}!")); + 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; + } + + asmMemoryStream.Seek(0, SeekOrigin.Begin); + try + { + var data = new AssemblyData(LoadFromStream(asmMemoryStream), asmMemoryStream.ToArray()); + _loadedAssemblyData[data.Assembly] = data; + return new FluentResults.Result().WithSuccess($"Compiled assembly {assemblyName} successful.").WithValue(data.Assembly); + } + catch (Exception ex) + { + return new FluentResults.Result().WithError(new ExceptionalError(ex)); + } + } + + public FluentResults.Result LoadAssemblyFromFile(string assemblyFilePath, + ImmutableArray additionalDependencyPaths) + { + if (assemblyFilePath.IsNullOrWhiteSpace()) + return new FluentResults.Result().WithError(new Error($"The path provided is null!")); + + if (additionalDependencyPaths.Any()) + { + var r = AddDependencyPaths(additionalDependencyPaths); + if (!r.IsFailed) + { + // we have errors, loading may not work. + return FluentResults.Result.Fail(new Error($"Failed to load dependency paths") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, assemblyFilePath)) + .WithErrors(r.Errors); + } + } + + string sanitizedFilePath = Path.GetFullPath(assemblyFilePath.CleanUpPath()); + string directoryKey = Path.GetDirectoryName(sanitizedFilePath); + + if (directoryKey is null) + { + return FluentResults.Result.Fail(new Error($"Unable to load assembly: bath file path: {assemblyFilePath}") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, sanitizedFilePath)); + } + + try + { + var assembly = LoadFromAssemblyPath(sanitizedFilePath); + _loadedAssemblyData[assembly] = new AssemblyData(assembly, sanitizedFilePath); + return new Result().WithSuccess($"Loaded assembly'{assembly.GetName()}'").WithValue(assembly); + } + catch (ArgumentNullException ane) + { + return FluentResults.Result.Fail(new ExceptionalError(ane) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, assemblyFilePath) + .WithMetadata(MetadataType.ExceptionDetails, ane.Message) + .WithMetadata(MetadataType.StackTrace, ane.StackTrace)); + } + catch (ArgumentException ae) + { + return FluentResults.Result.Fail(new ExceptionalError(ae) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, assemblyFilePath) + .WithMetadata(MetadataType.ExceptionDetails, ae.Message) + .WithMetadata(MetadataType.StackTrace, ae.StackTrace)); + } + catch (FileLoadException fle) + { + return FluentResults.Result.Fail(new ExceptionalError(fle) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, assemblyFilePath) + .WithMetadata(MetadataType.ExceptionDetails, fle.Message) + .WithMetadata(MetadataType.StackTrace, fle.StackTrace)); + } + catch (FileNotFoundException fnfe) + { + return FluentResults.Result.Fail(new ExceptionalError(fnfe) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, assemblyFilePath) + .WithMetadata(MetadataType.ExceptionDetails, fnfe.Message) + .WithMetadata(MetadataType.StackTrace, fnfe.StackTrace)); + } + catch (BadImageFormatException bife) + { + return FluentResults.Result.Fail(new ExceptionalError(bife) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, assemblyFilePath) + .WithMetadata(MetadataType.ExceptionDetails, bife.Message) + .WithMetadata(MetadataType.StackTrace, bife.StackTrace)); + } + catch (Exception e) + { + return FluentResults.Result.Fail(new ExceptionalError(e) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, assemblyFilePath) + .WithMetadata(MetadataType.ExceptionDetails, e.Message) + .WithMetadata(MetadataType.StackTrace, e.StackTrace)); + } + } + + public FluentResults.Result GetAssemblyByName(string assemblyName) + { + if (assemblyName.IsNullOrWhiteSpace()) + { + return FluentResults.Result.Fail(new Error($"Assembly name is null") + .WithMetadata(MetadataType.ExceptionObject, this)); + } + + if (_loadedAssemblyData.TryGetValue(assemblyName, out var data)) + { + return new FluentResults.Result().WithSuccess(new Success($"Assembly found")).WithValue(data.Assembly); + } + + foreach (var assembly1 in this.Assemblies.Where(a => !_loadedAssemblyData.ContainsKey(a))) + { + if (assembly1.GetName().FullName == assemblyName) + { + try + { + if (!assembly1.Location.IsNullOrWhiteSpace()) + { + _loadedAssemblyData[assembly1] = new AssemblyData(assembly1, assembly1.Location); + } + // we don't have the original byte array so we can't store it. + } + catch (NotSupportedException nse) // dynamic assembly or location property threw + { + // ignored + } + + return new FluentResults.Result().WithSuccess(new Success($"Assembly found")).WithValue(assembly1); + } + } + + return FluentResults.Result.Fail(new Error($"Assembly named { assemblyName } not found!")); + } + + public FluentResults.Result> GetTypesInAssemblies() + { + try + { + return new FluentResults.Result>().WithValue(_loadedAssemblyData.SelectMany(kvp=> kvp.Value.Types).ToImmutableArray()); + } + catch (Exception e) + { + return FluentResults.Result.Fail(new ExceptionalError(e)); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + + #region Internals + + protected override Assembly Load(AssemblyName assemblyName) + { + if (_isResolving.Value) + return null; + + _isResolving.Value = true; + try + { + if (_loadedAssemblyData.TryGetValue(assemblyName.FullName, out var data)) + return data.Assembly; + var idSpan = new[] { this.Id }; + if (_assemblyManagementService.GetLoadedAssembly(assemblyName, in idSpan) is { IsSuccess: true } ret) + return ret.Value; + return null; + } + catch (ArgumentNullException _) + { + return null; + } + finally + { + _isResolving.Value = false; + } + } + + // Use the default import resolver since native libraries are niche and not blocking for unloading. + // Implement if conflicts become an issue. + /*protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + // Implement NativeLibrary::InternalLoadUnmanagedDll() + throw new NotImplementedException(); + }*/ + + private void OnUnload(AssemblyLoadContext context) + { + base.Unloading -= OnUnload; + var wf = new WeakReference(this); + _eventService.PublishEvent((sub) => sub.OnAssemblyUnloading(wf)); + _onUnload?.Invoke(this); + this.Dispose(true); + } + + private void Dispose(bool disposing) + { + if (ModUtils.Threading.CheckClearAndSetBool(ref _isDisposed)) + { + _operationsLock.EnterWriteLock(); + try + { + _loadedAssemblyData.Clear(); + + } + finally + { + _operationsLock.ExitWriteLock(); + } + } + } + + private readonly record struct AssemblyData + { + public readonly Assembly Assembly; + public readonly OneOf AssemblyImageOrPath; + public readonly MetadataReference AssemblyReference; + public readonly ImmutableArray Types; + + public AssemblyData(Assembly assembly, byte[] assemblyImage) + { + Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly)); + AssemblyImageOrPath = assemblyImage ?? throw new ArgumentNullException(nameof(assemblyImage)); + AssemblyReference = MetadataReference.CreateFromImage(assemblyImage); + Types = assembly.GetSafeTypes().ToImmutableArray(); + } + + public AssemblyData(Assembly assembly, string path) + { + Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly)); + AssemblyImageOrPath = path ?? throw new ArgumentNullException(nameof(path)); + AssemblyReference = MetadataReference.CreateFromFile(path); + Types = assembly.GetSafeTypes().ToImmutableArray(); + } + } + + private readonly record struct AssemblyOrStringKey : IEquatable, IEqualityComparer + { + public Assembly Assembly { get; init; } + public string AssemblyName { get; init; } + public readonly int HashCode; + + public AssemblyOrStringKey(Assembly assembly) + { + if(assembly == null) + throw new ArgumentNullException(nameof(assembly)); + Assembly = assembly; + AssemblyName = assembly.GetName().FullName; + if (AssemblyName == null) + throw new ArgumentNullException(nameof(AssemblyName)); + HashCode = AssemblyName.GetHashCode(); + } + + public AssemblyOrStringKey(string assemblyName) + { + if (assemblyName.IsNullOrWhiteSpace()) + throw new ArgumentNullException(nameof(assemblyName)); + Assembly = null; + AssemblyName = assemblyName; + HashCode = AssemblyName.GetHashCode(); + } + + public bool Equals(AssemblyOrStringKey x, AssemblyOrStringKey y) + { + if (x.Assembly is not null && y.Assembly is not null) + return x.Assembly == y.Assembly; + return x.AssemblyName == y.AssemblyName; + } + + public int GetHashCode(AssemblyOrStringKey obj) + { + return obj.HashCode; + } + + public static implicit operator AssemblyOrStringKey(Assembly assembly) => new AssemblyOrStringKey(assembly); + public static implicit operator AssemblyOrStringKey(string name) => new AssemblyOrStringKey(name); + } + + #endregion +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/AssemblyLoadingSuccessState.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoadingSuccessState.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/AssemblyLoadingSuccessState.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoadingSuccessState.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/CsPackageManager.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/CsPackageManager.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/CsPackageManager.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/CsPackageManager.cs index a23d70f7b..d323ac849 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/CsPackageManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/CsPackageManager.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading; +using Barotrauma.LuaCs; using Barotrauma.LuaCs.Services; using Barotrauma.Steam; using Microsoft.CodeAnalysis; @@ -630,8 +631,9 @@ public sealed class CsPackageManager : IDisposable bool ShouldRunPackage(ContentPackage package, RunConfig config) { - return (!_luaCsSetup.Config.TreatForcedModsAsNormal && config.IsForced()) - || (ContentPackageManager.EnabledPackages.All.Contains(package) && config.IsForcedOrStandard()); + throw new NotImplementedException(); + /*return (!_luaCsSetup.Config.TreatForcedModsAsNormal && config.IsForced()) + || (ContentPackageManager.EnabledPackages.All.Contains(package) && config.IsForcedOrStandard());*/ } void UpdatePackagesToDisable(ref HashSet set, diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs new file mode 100644 index 000000000..629964ff7 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.CompilerServices; +using Barotrauma.LuaCs.Services; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Barotrauma.LuaCs; + +public interface IAssemblyLoaderService : IService +{ + /// + /// Assembly loader factory for DI registration. + /// + /// The assembly hosting management service. + /// The event service for publishing. + /// The referencing ID. Intended to be used to distinguish between instances. + /// The name of the friendly name instance, used for error messages. + /// Loaded assemblies are not intended for execution, just MetadataReferences. + delegate IAssemblyLoaderService AssemblyLoaderDelegate( + IAssemblyManagementService assemblyManagementService, + IEventService eventService, Guid id, string name, + bool isReferenceOnlyMode, Action onUnload); + + /// + /// ID for this instance. + /// + Guid Id { get; } + /// + /// Indicates that the assemblies in this load context are metadata references only and not + /// intended for execution. + /// + bool IsReferenceOnlyMode { get; } + /// + /// Runtime value of constant for extensibility use. + /// + public static readonly string InternalsAccessAssemblyName = InternalsAwareAssemblyName; + /// + /// Name for all runtime-compiled assemblies requiring access to internal assembly components. + /// + public const string InternalsAwareAssemblyName = "InternalsAwareAssembly"; + + /// + /// Add additional locations for dependency resolution to use. + /// + /// + /// + public FluentResults.Result AddDependencyPaths(ImmutableArray paths); + + /// + /// Compiles the supplied syntaxtrees and options into an in-memory assembly image. + /// Builds metadata from loaded assemblies, only supply your own if you have in-memory images not managed by the + /// AssemblyManager class. + /// + /// [NotNull]Name reference of the assembly. + /// [IMPORTANT] This is used to reference this assembly as the true name will be forced if + /// publicized assemblies are not used (InternalsVisibleTo Attrib). + /// Must be supplied for in-memory assemblies. + /// Must be unique to all other assemblies explicitly loaded using this context. + /// Forces the assembly name to and grants access to internal. + /// [IMPORTANT]Cannot be null or empty if is false. + /// [NotNull]Syntax trees to compile into the assembly. + /// All MetadataReferences to be used for compilation. + /// [IMPORTANT] This method builds metadata from loaded assemblies, only supply your own if you have in-memory + /// images not managed by the AssemblyManager class. + /// [NotNull]CSharp compilation options. This method automatically adds the 'IgnoreAccessChecks' property for compilation. + /// Success state of the operation. + public FluentResults.Result CompileScriptAssembly( + [NotNull] string assemblyName, + bool compileWithInternalAccess, + ImmutableArray syntaxTrees, + ImmutableArray metadataReferences, + CSharpCompilationOptions compilationOptions = null); + + /// + /// Loads the assembly from the provided location and registers all new paths provided with dependency resolution. + /// + /// Absolute path to the managed assembly. + /// Additional paths for dependency resolution. + /// Success and reference to the assembly if successful. + public FluentResults.Result LoadAssemblyFromFile(string assemblyFilePath, + ImmutableArray additionalDependencyPaths); + + /// + /// Returns the already loaded assembly with the same name. + /// + /// Name of the assembly. + /// Operation success on assembly found and assembly. + public FluentResults.Result GetAssemblyByName(string assemblyName); + + /// + /// Gets the list of Types from loaded assemblies. + /// + /// + public FluentResults.Result> GetTypesInAssemblies(); + + /// + /// List of loaded assemblies. + /// + public IEnumerable Assemblies { get; } +} + + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyPlugin.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyPlugin.cs new file mode 100644 index 000000000..964ce312f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyPlugin.cs @@ -0,0 +1,6 @@ +using System; +using Barotrauma.LuaCs.Events; + +namespace Barotrauma; + +public interface IAssemblyPlugin : IDisposable, IEventPluginPreInitialize, IEventPluginInitialize, IEventPluginLoadCompleted { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/MemoryFileAssemblyContextLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/MemoryFileAssemblyContextLoader.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/MemoryFileAssemblyContextLoader.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/MemoryFileAssemblyContextLoader.cs index e01db5f4c..d5ccc6a86 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/MemoryFileAssemblyContextLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/MemoryFileAssemblyContextLoader.cs @@ -14,7 +14,7 @@ using Microsoft.CodeAnalysis.CSharp; [assembly: InternalsVisibleTo("CompiledAssembly")] -namespace Barotrauma.LuaCs; +namespace Barotrauma.LuaCs.Services; /// /// AssemblyLoadContext to compile from syntax trees in memory and to load from disk/file. Provides dependency resolution. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/RunConfig.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/RunConfig.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Plugins/RunConfig.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/RunConfig.cs From 76fc52e042274b23fe16efd3a86fc2c92028437f Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 12 Dec 2024 17:07:08 -0500 Subject: [PATCH 007/288] - Work on storage service. Pre-squash commit. --- .../SharedSource/LuaCs/ModUtils.cs | 5 + .../LuaCs/Services/PluginManagementService.cs | 43 +- .../LuaCs/Services/StorageService.cs | 407 ++++++++++++++++++ .../_Interfaces/IPluginManagementService.cs | 31 +- .../Services/_Interfaces/IStorageService.cs | 58 ++- 5 files changed, 468 insertions(+), 76 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs index bc181f36e..e903b0f9a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs @@ -23,6 +23,11 @@ namespace Barotrauma.LuaCs public static class ModUtils { + public static class Definitions + { + public const string LuaCsForBarotrauma = nameof(LuaCsForBarotrauma); + } + public static class Environment { internal static void SetCurrentThreadAsMain() => MainThreadId = Thread.CurrentThread.ManagedThreadId; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 64f36d63f..31822e438 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -5,48 +5,7 @@ using Microsoft.CodeAnalysis; namespace Barotrauma.LuaCs.Services; -public class PluginManagementService : IPluginManagementService +public class PluginManagementService : IPluginManagementService, IAssemblyManagementService { - - public void Dispose() - { - throw new System.NotImplementedException(); - } - - public FluentResults.Result Reset() - { - throw new System.NotImplementedException(); - } - - public bool IsAssemblyLoadedGlobal(string friendlyName) - { - throw new System.NotImplementedException(); - } - - public Result> GetTypes(ContentPackage package = null, string namespacePrefix = null, bool includeInterfaces = false, - bool includeAbstractTypes = false, bool includeDefaultContext = true, bool includeExplicitAssembliesOnly = false) - { - throw new System.NotImplementedException(); - } - - public ImmutableArray GetStandardMetadataReferences() - { - throw new System.NotImplementedException(); - } - - public ImmutableArray GetPluginMetadataReferences() - { - throw new System.NotImplementedException(); - } - - public Result> GetCachedAssembliesForPackage(ContentPackage package) - { - throw new System.NotImplementedException(); - } - - public Result> LoadAssemblyResources(ImmutableArray resource) - { - throw new System.NotImplementedException(); - } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs new file mode 100644 index 000000000..facf6b395 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs @@ -0,0 +1,407 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Reflection; +using System.Security; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; +using Barotrauma.LuaCs.Configuration; +using Barotrauma.LuaCs.Networking; +using Barotrauma.Steam; +using FluentResults; +using FluentResults.LuaCs; +using OneOf.Types; +using Error = FluentResults.Error; +using File = Barotrauma.IO.File; +using Path = Barotrauma.IO.Path; +using Success = OneOf.Types.Success; + +namespace Barotrauma.LuaCs.Services; + +public class StorageService : IStorageService +{ + + public StorageService(Lazy configService) + { + _configService = configService; + } + private readonly Lazy _configService; + private IConfigEntry _kLocalStoragePath = null; + private IConfigEntry _kLocalFilePathRules = null; + private const string _packagePathKeyword = ""; + private readonly string _runLocation = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location.CleanUpPath()); + private IConfigEntry LocalStoragePath => _kLocalStoragePath ??= GetOrCreateConfig(nameof(LocalStoragePath), "/Data/Mods"); + private IConfigEntry LocalFilePathRule => _kLocalFilePathRules ??= GetOrCreateConfig(nameof(LocalFilePathRule), _packagePathKeyword); + private IConfigEntry GetOrCreateConfig(string name, string defaultValue) + { + var c = _configService.Value + .GetConfig>(ModUtils.Definitions.LuaCsForBarotrauma, name); + if (c.IsSuccess) + { + return c.Value; + } + else + { + c = _configService.Value.AddConfigEntry( + ModUtils.Definitions.LuaCsForBarotrauma, + name, defaultValue, NetSync.None, valueChangePredicate: (value) => false); + if (c.IsSuccess) + return c.Value; + else + throw new KeyNotFoundException("Cannot find storage value for key: " + name); + } + } + public bool IsDisposed { get; private set; } + + public void Dispose() + { + if (IsDisposed) + return; + IsDisposed = true; + _kLocalStoragePath = null; + _kLocalFilePathRules = null; + } + + public FluentResults.Result LoadLocalXml(ContentPackage package, string localFilePath) + { + var r = LoadLocalText(package, localFilePath); + if (r is { IsSuccess: true, Value: not null }) + return XDocument.Parse(r.Value); + else + { + return r.ToResult(s => null) + .WithError(GetGeneralError(nameof(LoadLocalXml), localFilePath, package)); + } + } + + + public FluentResults.Result LoadLocalBinary(ContentPackage package, string localFilePath) => TryLoadBinary(GetAbsFromLocal(package, localFilePath)); + public FluentResults.Result LoadLocalText(ContentPackage package, string localFilePath) => TryLoadText(GetAbsFromLocal(package, localFilePath)); + public FluentResults.Result SaveLocalXml(ContentPackage package, string localFilePath, XDocument document) => TrySaveXml(GetAbsFromLocal(package, localFilePath), document); + + public FluentResults.Result SaveLocalBinary(ContentPackage package, string localFilePath, in byte[] bytes) => TrySaveBinary(GetAbsFromLocal(package, localFilePath), bytes); + + public FluentResults.Result SaveLocalText(ContentPackage package, string localFilePath, in string text) => TrySaveText(GetAbsFromLocal(package, localFilePath), text); + + public async Task> LoadLocalXmlAsync(ContentPackage package, string localFilePath) + { + var r = await LoadLocalTextAsync(package, localFilePath); + if (r is { IsSuccess: true, Value: not null }) + return XDocument.Parse(r.Value); + else + { + return r.ToResult(s => null) + .WithError(GetGeneralError(nameof(LoadLocalXml), localFilePath, package)); + } + } + + public Task> LoadLocalBinaryAsync(ContentPackage package, string localFilePath) => + TryLoadBinaryAsync(GetAbsFromLocal(package, localFilePath)); + public Task> LoadLocalTextAsync(ContentPackage package, string localFilePath) => TryLoadTextAsync(GetAbsFromLocal(package, localFilePath)); + public Task SaveLocalXmlAsync(ContentPackage package, string localFilePath, XDocument document) => TrySaveXmlAsync(GetAbsFromLocal(package, localFilePath), document); + public Task SaveLocalBinaryAsync(ContentPackage package, string localFilePath, byte[] bytes) => TrySaveBinaryAsync(GetAbsFromLocal(package, localFilePath), bytes); + public Task SaveLocalTextAsync(ContentPackage package, string localFilePath, string text) => TrySaveTextAsync(GetAbsFromLocal(package, localFilePath), text); + public FluentResults.Result LoadPackageXml(ContentPackage package, string localFilePath) => TryLoadXml(Path.GetFullPath(package.Path.CleanUpPath())); + public FluentResults.Result LoadPackageBinary(ContentPackage package, string localFilePath) => TryLoadBinary(Path.GetFullPath(package.Path.CleanUpPath())); + public FluentResults.Result LoadPackageText(ContentPackage package, string localFilePath) => TryLoadText(Path.GetFullPath(package.Path.CleanUpPath())); + public FluentResults.Result> LoadPackageXmlFiles(ContentPackage package, ImmutableArray localFilePaths) + { + ((IService)this).CheckDisposed(); + if (localFilePaths.IsDefaultOrEmpty) + return new FluentResults.Result>().WithError(new ExceptionalError(new ArgumentNullException(nameof(localFilePaths)))); + var builder = ImmutableArray.CreateBuilder(); + foreach (var path in localFilePaths) + { + if (TryLoadXml(path) is { IsSuccess: true, Value: var document }) + { + + } + } + } + + public FluentResults.Result> LoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePaths) + { + ((IService)this).CheckDisposed(); + throw new NotImplementedException(); + } + + public FluentResults.Result> LoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePaths) + { + ((IService)this).CheckDisposed(); + throw new NotImplementedException(); + } + + public FluentResults.Result> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively) + { + ((IService)this).CheckDisposed(); + throw new NotImplementedException(); + } + + public Task> LoadPackageXmlAsync(ContentPackage package, string localFilePath) + { + throw new NotImplementedException(); + } + + public Task> LoadPackageBinaryAsync(ContentPackage package, string localFilePath) + { + throw new NotImplementedException(); + } + + public Task> LoadPackageTextAsync(ContentPackage package, string localFilePath) + { + throw new NotImplementedException(); + } + + public Task>> LoadPackageXmlFilesAsync(ContentPackage package, ImmutableArray localFilePaths) + { + throw new NotImplementedException(); + } + + public Task>> LoadPackageBinaryFilesAsync(ContentPackage package, ImmutableArray localFilePaths) + { + throw new NotImplementedException(); + } + + public Task>> LoadPackageTextFilesAsync(ContentPackage package, ImmutableArray localFilePaths) + { + throw new NotImplementedException(); + } + + public FluentResults.Result TryLoadXml(string filePath, Encoding encoding = null) + { + ((IService)this).CheckDisposed(); + var r = TryLoadText(filePath, encoding); + if (r is { IsSuccess: true, Value: not null }) + return XDocument.Parse(r.Value); + else + { + return r.ToResult(s => null) + .WithError(GetGeneralError(nameof(LoadLocalXml), filePath)); + } + } + + public FluentResults.Result TryLoadText(string filePath, Encoding encoding = null) + { + ((IService)this).CheckDisposed(); + return IOExceptionsOperationRunner(nameof(TryLoadText), filePath, () => + { + var fp = filePath.CleanUpPath(); + fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); + var fileText = encoding is null ? System.IO.File.ReadAllText(fp) : System.IO.File.ReadAllText(fp, encoding); + return new FluentResults.Result().WithSuccess($"Loaded file successfully").WithValue(fileText); + }); + } + + public FluentResults.Result TryLoadBinary(string filePath) + { + ((IService)this).CheckDisposed(); + return IOExceptionsOperationRunner(nameof(TryLoadBinary), filePath, () => + { + var fp = filePath.CleanUpPath(); + fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); + var fileData = System.IO.File.ReadAllBytes(fp); + return new FluentResults.Result().WithSuccess($"Loaded file successfully").WithValue(fileData); + }); + } + + public FluentResults.Result TrySaveXml(string filePath, in XDocument document, Encoding encoding = null) + { + ((IService)this).CheckDisposed(); + return IOExceptionsOperationRunner(nameof(TrySaveXml), filePath, () => + { + + }); + } + + public FluentResults.Result TrySaveText(string filePath, in string text, Encoding encoding = null) + { + ((IService)this).CheckDisposed(); + throw new NotImplementedException(); + } + + public FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes) + { + ((IService)this).CheckDisposed(); + throw new NotImplementedException(); + } + + public FluentResults.Result FileExists(string filePath) + { + ((IService)this).CheckDisposed(); + throw new NotImplementedException(); + } + + public async Task> TryLoadXmlAsync(string filePath, Encoding encoding = null) + { + throw new NotImplementedException(); + } + + public async Task> TryLoadTextAsync(string filePath, Encoding encoding = null) + { + throw new NotImplementedException(); + } + + public async Task> TryLoadBinaryAsync(string filePath) + { + throw new NotImplementedException(); + } + + public async Task TrySaveXmlAsync(string filePath, XDocument document, Encoding encoding = null) + { + throw new NotImplementedException(); + } + + public async Task TrySaveTextAsync(string filePath, string text, Encoding encoding = null) + { + throw new NotImplementedException(); + } + + public async Task TrySaveBinaryAsync(string filePath, byte[] bytes) + { + throw new NotImplementedException(); + } + + private async Task> IOExceptionsOperationRunnerAsync(string funcName, string filepath, Func>> operation) + { + try + { + return await operation?.Invoke()!; + } + catch (ArgumentNullException ane) + { + return ReturnException(ane, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (ArgumentException ae) + { + return ReturnException(ae, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (PathTooLongException ptle) + { + return ReturnException(ptle, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (NotSupportedException nse) + { + return ReturnException(nse, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (UnauthorizedAccessException uae) + { + return ReturnException(uae, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (DirectoryNotFoundException dnfe) + { + return ReturnException(dnfe, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (FileNotFoundException fnfe) + { + return ReturnException(fnfe, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (SecurityException se) + { + return ReturnException(se, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (IOException ioe) + { + return ReturnException(ioe, filepath).WithError(GetGeneralError(nameof(SaveLocalXml), filepath)); + } + } + + private FluentResults.Result IOExceptionsOperationRunner(string funcName, string filepath, Func> operation) + { + try + { + return operation?.Invoke(); + } + catch (ArgumentNullException ane) + { + return ReturnException(ane, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (ArgumentException ae) + { + return ReturnException(ae, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (PathTooLongException ptle) + { + return ReturnException(ptle, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (NotSupportedException nse) + { + return ReturnException(nse, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (UnauthorizedAccessException uae) + { + return ReturnException(uae, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (DirectoryNotFoundException dnfe) + { + return ReturnException(dnfe, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (FileNotFoundException fnfe) + { + return ReturnException(fnfe, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (SecurityException se) + { + return ReturnException(se, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (IOException ioe) + { + return ReturnException(ioe, filepath) + .WithError(GetGeneralError(nameof(SaveLocalXml), filepath)); + } + } + + private Error GetGeneralError(string funcName, string localfp, ContentPackage package) => + new Error($"{funcName}: Failed to load local file.") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.Sources, localfp) + .WithMetadata(MetadataType.RootObject, package); + + private Error GetGeneralError(string funcName, string localfp) => + new Error($"{funcName}: Failed to load local file.") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.Sources, localfp); + + private string GetAbsFromLocal(ContentPackage package, string localFilePath) + { + return System.IO.Path.GetFullPath(System.IO.Path.Combine( + _runLocation, + LocalStoragePath.Value, + LocalFilePathRule.Value.Replace(_packagePathKeyword, package.Name.IsNullOrWhiteSpace() + ? package.TryExtractSteamWorkshopId(out var id) + ? id.Value.ToString() + : "_fallbackFolder" + : package.Name), + localFilePath)); + } + + private FluentResults.Result ReturnException(TException exception, ContentPackage package) where TException : Exception + { + return new FluentResults.Result().WithError(new ExceptionalError(exception) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, package)); + } + + private FluentResults.Result ReturnException(TException exception, ContentPackage package) where TException : Exception + { + return new FluentResults.Result().WithError(new ExceptionalError(exception) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, package)); + } + + private FluentResults.Result ReturnException(TException exception, string filePath) where TException : Exception + { + return new FluentResults.Result().WithError(new ExceptionalError(exception) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, filePath)); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs index 7769498e7..0a06c13b9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs @@ -9,14 +9,23 @@ namespace Barotrauma.LuaCs.Services; public interface IPluginManagementService : IReusableService { /// - /// Checks if an assembly with either the fully-qualified name globally or a 'friendly name' within loaded plugins - /// with the given name is loaded. + /// Checks if the supplied resource is currently loaded. /// - /// + /// The resource to check. /// - bool IsAssemblyLoadedGlobal(string friendlyName); + bool IsResourceLoaded(T resource) where T : IAssemblyResourceInfo; - // TODO: Documentation. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// FluentResults.Result> GetTypes( ContentPackage package = null, string namespacePrefix = null, @@ -24,18 +33,6 @@ public interface IPluginManagementService : IReusableService bool includeAbstractTypes = false, bool includeDefaultContext = true, bool includeExplicitAssembliesOnly = false); - - /// - /// Gets the assembly MetadataReference collection for the BCL and base game assemblies. - /// - /// - ImmutableArray GetStandardMetadataReferences(); - - /// - /// - /// - /// - ImmutableArray GetPluginMetadataReferences(); /// /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs index b43a595ba..1c830ab78 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs @@ -1,42 +1,66 @@ using System.Collections.Immutable; +using System.Text; +using System.Threading.Tasks; using System.Xml.Linq; namespace Barotrauma.LuaCs.Services; -public interface IStorageService : IReusableService +public interface IStorageService : IService { #region LocalGameData FluentResults.Result LoadLocalXml(ContentPackage package, string localFilePath); FluentResults.Result LoadLocalBinary(ContentPackage package, string localFilePath); FluentResults.Result LoadLocalText(ContentPackage package, string localFilePath); - FluentResults.Result FileExistsInLocalData(ContentPackage package, string localFilePath); + FluentResults.Result SaveLocalXml(ContentPackage package, string localFilePath, XDocument document); + FluentResults.Result SaveLocalBinary(ContentPackage package, string localFilePath, in byte[] bytes); + FluentResults.Result SaveLocalText(ContentPackage package, string localFilePath, in string text); + // async + Task> LoadLocalXmlAsync(ContentPackage package, string localFilePath); + Task> LoadLocalBinaryAsync(ContentPackage package, string localFilePath); + Task> LoadLocalTextAsync(ContentPackage package, string localFilePath); + Task SaveLocalXmlAsync(ContentPackage package, string localFilePath, XDocument document); + Task SaveLocalBinaryAsync(ContentPackage package, string localFilePath, byte[] bytes); + Task SaveLocalTextAsync(ContentPackage package, string localFilePath, string text); #endregion #region ContentPackageData - FluentResults.Result LoadPackageXml(ContentPackage package, string localFilePath, out XDocument document); - FluentResults.Result LoadPackageBinary(ContentPackage package, string localFilePath, out byte[] bytes); - FluentResults.Result LoadPackageText(ContentPackage package, string localFilePath, out string text); - - FluentResults.Result> LoadPackageXmlFiles(ContentPackage package, ImmutableArray localFilePath); - FluentResults.Result> TryLoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePath); - FluentResults.Result> TryLoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePath); - + // singles + FluentResults.Result LoadPackageXml(ContentPackage package, string localFilePath); + FluentResults.Result LoadPackageBinary(ContentPackage package, string localFilePath); + FluentResults.Result LoadPackageText(ContentPackage package, string localFilePath); + // collections + FluentResults.Result> LoadPackageXmlFiles(ContentPackage package, ImmutableArray localFilePaths); + FluentResults.Result> LoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePaths); + FluentResults.Result> LoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePaths); FluentResults.Result> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively); - FluentResults.Result FileExistsInPackage(ContentPackage package, string localFilePath); + // async + // singles + Task> LoadPackageXmlAsync(ContentPackage package, string localFilePath); + Task> LoadPackageBinaryAsync(ContentPackage package, string localFilePath); + Task> LoadPackageTextAsync(ContentPackage package, string localFilePath); + // collections + Task>> LoadPackageXmlFilesAsync(ContentPackage package, ImmutableArray localFilePaths); + Task>> LoadPackageBinaryFilesAsync(ContentPackage package, ImmutableArray localFilePaths); + Task>> LoadPackageTextFilesAsync(ContentPackage package, ImmutableArray localFilePaths); #endregion #region AbsolutePaths - - FluentResults.Result TryLoadXml(string filePatht); - FluentResults.Result TrySaveXml(string filePath, in XDocument document); + FluentResults.Result TryLoadXml(string filePath, Encoding encoding = null); + FluentResults.Result TryLoadText(string filePath, Encoding encoding = null); FluentResults.Result TryLoadBinary(string filePath); + FluentResults.Result TrySaveXml(string filePath, in XDocument document, Encoding encoding = null); + FluentResults.Result TrySaveText(string filePath, in string text, Encoding encoding = null); FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes); - FluentResults.Result TryLoadText(string filePath); - FluentResults.Result TrySaveText(string filePath, string text); FluentResults.Result FileExists(string filePath); - + //async + Task> TryLoadXmlAsync(string filePath, Encoding encoding = null); + Task> TryLoadTextAsync(string filePath, Encoding encoding = null); + Task> TryLoadBinaryAsync(string filePath); + Task TrySaveXmlAsync(string filePath, XDocument document, Encoding encoding = null); + Task TrySaveTextAsync(string filePath, string text, Encoding encoding = null); + Task TrySaveBinaryAsync(string filePath, byte[] bytes); #endregion } From 1da82cdec22c3f3abbad22a193cbb25381597309 Mon Sep 17 00:00:00 2001 From: Regalis11 Date: Wed, 11 Dec 2024 13:26:13 +0200 Subject: [PATCH 008/288] v1.7.7.0 (Winter Update 2024) --- Barotrauma/BarotraumaServer/LinuxServer.csproj | 4 +++- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index e2dfeb5b2..029e0ccc1 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -13,6 +13,7 @@ ..\BarotraumaShared\Icon.ico Debug;Release;Unstable true + latest ;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 latest @@ -54,8 +55,9 @@ ..\bin\$(Configuration)Linux\ true - + + diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 55ea36236..566b5fc54 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -14,7 +14,7 @@ Debug;Release;Unstable true ;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 - latest + latest From 4b2bac7cd8d9acb5eef56d26c9b8a4c137e8a478 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 16 Dec 2024 16:30:13 -0500 Subject: [PATCH 009/288] [Milestone] StorageService completed. --- .../LuaCs/Services/StorageService.cs | 451 ++++++++++++++---- .../Services/_Interfaces/IStorageService.cs | 12 +- 2 files changed, 355 insertions(+), 108 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs index facf6b395..d20d77942 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs @@ -12,6 +12,7 @@ using Barotrauma.LuaCs.Networking; using Barotrauma.Steam; using FluentResults; using FluentResults.LuaCs; +using Microsoft.CodeAnalysis; using OneOf.Types; using Error = FluentResults.Error; using File = Barotrauma.IO.File; @@ -64,110 +65,143 @@ public class StorageService : IStorageService _kLocalFilePathRules = null; } - public FluentResults.Result LoadLocalXml(ContentPackage package, string localFilePath) - { - var r = LoadLocalText(package, localFilePath); - if (r is { IsSuccess: true, Value: not null }) - return XDocument.Parse(r.Value); - else - { - return r.ToResult(s => null) - .WithError(GetGeneralError(nameof(LoadLocalXml), localFilePath, package)); - } - } + public FluentResults.Result LoadLocalXml(ContentPackage package, string localFilePath) => + GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + ? TryLoadXml(r.Value) : r.ToResult(); + public FluentResults.Result LoadLocalBinary(ContentPackage package, string localFilePath) => + GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + ? TryLoadBinary(r.Value) : r.ToResult(); + public FluentResults.Result LoadLocalText(ContentPackage package, string localFilePath) => + GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + ? TryLoadText(r.Value) : r.ToResult(); + public FluentResults.Result SaveLocalXml(ContentPackage package, string localFilePath, XDocument document) => + GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + ? TrySaveXml(r.Value, document) : r.ToResult(); + public FluentResults.Result SaveLocalBinary(ContentPackage package, string localFilePath, in byte[] bytes) => + GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + ? TrySaveBinary(r.Value, bytes) : r.ToResult(); + public FluentResults.Result SaveLocalText(ContentPackage package, string localFilePath, in string text) => + GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + ? TrySaveText(r.Value, text) : r.ToResult(); + public async Task> LoadLocalXmlAsync(ContentPackage package, string localFilePath) => + GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + ? await TryLoadXmlAsync(r.Value) : r.ToResult(); + public async Task> LoadLocalBinaryAsync(ContentPackage package, string localFilePath) => + GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + ? await TryLoadBinaryAsync(r.Value) : r.ToResult(); + public async Task> LoadLocalTextAsync(ContentPackage package, string localFilePath) => + GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + ? await TryLoadTextAsync(r.Value) : r.ToResult(); + public async Task SaveLocalXmlAsync(ContentPackage package, string localFilePath, XDocument document) => + GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + ? await TrySaveXmlAsync(r.Value, document) : r.ToResult(); + public async Task SaveLocalBinaryAsync(ContentPackage package, string localFilePath, byte[] bytes) => + GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + ? await TrySaveBinaryAsync(r.Value, bytes) : r.ToResult(); + public async Task SaveLocalTextAsync(ContentPackage package, string localFilePath, string text) => + GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + ? await TrySaveTextAsync(r.Value, text) : r.ToResult(); + public FluentResults.Result LoadPackageXml(ContentPackage package, string localFilePath) => + GetAbsFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + ? TryLoadXml(r.Value) : r.ToResult(); + public FluentResults.Result LoadPackageBinary(ContentPackage package, string localFilePath) => + GetAbsFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + ? TryLoadBinary(r.Value) : r.ToResult(); + public FluentResults.Result LoadPackageText(ContentPackage package, string localFilePath) => + GetAbsFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + ? TryLoadText(r.Value) : r.ToResult(); - - public FluentResults.Result LoadLocalBinary(ContentPackage package, string localFilePath) => TryLoadBinary(GetAbsFromLocal(package, localFilePath)); - public FluentResults.Result LoadLocalText(ContentPackage package, string localFilePath) => TryLoadText(GetAbsFromLocal(package, localFilePath)); - public FluentResults.Result SaveLocalXml(ContentPackage package, string localFilePath, XDocument document) => TrySaveXml(GetAbsFromLocal(package, localFilePath), document); - - public FluentResults.Result SaveLocalBinary(ContentPackage package, string localFilePath, in byte[] bytes) => TrySaveBinary(GetAbsFromLocal(package, localFilePath), bytes); - - public FluentResults.Result SaveLocalText(ContentPackage package, string localFilePath, in string text) => TrySaveText(GetAbsFromLocal(package, localFilePath), text); - - public async Task> LoadLocalXmlAsync(ContentPackage package, string localFilePath) - { - var r = await LoadLocalTextAsync(package, localFilePath); - if (r is { IsSuccess: true, Value: not null }) - return XDocument.Parse(r.Value); - else - { - return r.ToResult(s => null) - .WithError(GetGeneralError(nameof(LoadLocalXml), localFilePath, package)); - } - } - - public Task> LoadLocalBinaryAsync(ContentPackage package, string localFilePath) => - TryLoadBinaryAsync(GetAbsFromLocal(package, localFilePath)); - public Task> LoadLocalTextAsync(ContentPackage package, string localFilePath) => TryLoadTextAsync(GetAbsFromLocal(package, localFilePath)); - public Task SaveLocalXmlAsync(ContentPackage package, string localFilePath, XDocument document) => TrySaveXmlAsync(GetAbsFromLocal(package, localFilePath), document); - public Task SaveLocalBinaryAsync(ContentPackage package, string localFilePath, byte[] bytes) => TrySaveBinaryAsync(GetAbsFromLocal(package, localFilePath), bytes); - public Task SaveLocalTextAsync(ContentPackage package, string localFilePath, string text) => TrySaveTextAsync(GetAbsFromLocal(package, localFilePath), text); - public FluentResults.Result LoadPackageXml(ContentPackage package, string localFilePath) => TryLoadXml(Path.GetFullPath(package.Path.CleanUpPath())); - public FluentResults.Result LoadPackageBinary(ContentPackage package, string localFilePath) => TryLoadBinary(Path.GetFullPath(package.Path.CleanUpPath())); - public FluentResults.Result LoadPackageText(ContentPackage package, string localFilePath) => TryLoadText(Path.GetFullPath(package.Path.CleanUpPath())); - public FluentResults.Result> LoadPackageXmlFiles(ContentPackage package, ImmutableArray localFilePaths) + public ImmutableArray<(string, FluentResults.Result)> LoadPackageXmlFiles(ContentPackage package, ImmutableArray localFilePaths) { ((IService)this).CheckDisposed(); if (localFilePaths.IsDefaultOrEmpty) - return new FluentResults.Result>().WithError(new ExceptionalError(new ArgumentNullException(nameof(localFilePaths)))); - var builder = ImmutableArray.CreateBuilder(); + return ImmutableArray<(string, FluentResults.Result)>.Empty; + var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result)>(localFilePaths.Length); foreach (var path in localFilePaths) - { - if (TryLoadXml(path) is { IsSuccess: true, Value: var document }) - { - - } - } + builder.Add((path, LoadPackageXml(package, path))); + return builder.MoveToImmutable(); } - public FluentResults.Result> LoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePaths) + public ImmutableArray<(string, FluentResults.Result)> LoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePaths) { ((IService)this).CheckDisposed(); - throw new NotImplementedException(); + if (localFilePaths.IsDefaultOrEmpty) + return ImmutableArray<(string, FluentResults.Result)>.Empty; + var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result)>(localFilePaths.Length); + foreach (var path in localFilePaths) + builder.Add((path, LoadPackageBinary(package, path))); + return builder.MoveToImmutable(); } - public FluentResults.Result> LoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePaths) + public ImmutableArray<(string, FluentResults.Result)> LoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePaths) { ((IService)this).CheckDisposed(); - throw new NotImplementedException(); + if (localFilePaths.IsDefaultOrEmpty) + return ImmutableArray<(string, FluentResults.Result)>.Empty; + var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result)>(localFilePaths.Length); + foreach (var path in localFilePaths) + builder.Add((path, LoadPackageText(package, path))); + return builder.MoveToImmutable(); } public FluentResults.Result> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively) { ((IService)this).CheckDisposed(); - throw new NotImplementedException(); + var r = GetAbsFromPackage(package, localSubfolder); + if (r is { IsFailed: true }) + return r.ToResult(); + var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result>)>(); + var sOption = searchRecursively ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; + string[] arr = Directory.GetFiles(localSubfolder, regexFilter.IsNullOrWhiteSpace() ? "*.*" : regexFilter, sOption); + return new FluentResults.Result>().WithSuccess($"Files found.") + .WithValue(arr.ToImmutableArray()); } - public Task> LoadPackageXmlAsync(ContentPackage package, string localFilePath) + public async Task> LoadPackageXmlAsync(ContentPackage package, string localFilePath) => + GetAbsFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + ? await TryLoadXmlAsync(r.Value) : r.ToResult(); + + public async Task> LoadPackageBinaryAsync(ContentPackage package, string localFilePath) => + GetAbsFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + ? await TryLoadBinaryAsync(r.Value) : r.ToResult(); + + public async Task> LoadPackageTextAsync(ContentPackage package, string localFilePath) => + GetAbsFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + ? await TryLoadTextAsync(r.Value) : r.ToResult(); + + public async Task)>> LoadPackageXmlFilesAsync(ContentPackage package, ImmutableArray localFilePaths) { - throw new NotImplementedException(); + ((IService)this).CheckDisposed(); + if (localFilePaths.IsDefaultOrEmpty) + return ImmutableArray<(string, FluentResults.Result)>.Empty; + var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result)>(localFilePaths.Length); + foreach (var path in localFilePaths) + builder.Add((path, await LoadPackageXmlAsync(package, path))); + return builder.MoveToImmutable(); } - public Task> LoadPackageBinaryAsync(ContentPackage package, string localFilePath) + public async Task)>> LoadPackageBinaryFilesAsync(ContentPackage package, ImmutableArray localFilePaths) { - throw new NotImplementedException(); + ((IService)this).CheckDisposed(); + if (localFilePaths.IsDefaultOrEmpty) + return ImmutableArray<(string, FluentResults.Result)>.Empty; + var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result)>(localFilePaths.Length); + foreach (var path in localFilePaths) + builder.Add((path, await LoadPackageBinaryAsync(package, path))); + return builder.MoveToImmutable(); } - public Task> LoadPackageTextAsync(ContentPackage package, string localFilePath) + public async Task)>> LoadPackageTextFilesAsync(ContentPackage package, ImmutableArray localFilePaths) { - throw new NotImplementedException(); + ((IService)this).CheckDisposed(); + if (localFilePaths.IsDefaultOrEmpty) + return ImmutableArray<(string, FluentResults.Result)>.Empty; + var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result)>(localFilePaths.Length); + foreach (var path in localFilePaths) + builder.Add((path, await LoadPackageTextAsync(package, path))); + return builder.MoveToImmutable(); } - public Task>> LoadPackageXmlFilesAsync(ContentPackage package, ImmutableArray localFilePaths) - { - throw new NotImplementedException(); - } - - public Task>> LoadPackageBinaryFilesAsync(ContentPackage package, ImmutableArray localFilePaths) - { - throw new NotImplementedException(); - } - - public Task>> LoadPackageTextFilesAsync(ContentPackage package, ImmutableArray localFilePaths) - { - throw new NotImplementedException(); - } public FluentResults.Result TryLoadXml(string filePath, Encoding encoding = null) { @@ -206,63 +240,133 @@ public class StorageService : IStorageService }); } - public FluentResults.Result TrySaveXml(string filePath, in XDocument document, Encoding encoding = null) - { - ((IService)this).CheckDisposed(); - return IOExceptionsOperationRunner(nameof(TrySaveXml), filePath, () => - { - - }); - } - + public FluentResults.Result TrySaveXml(string filePath, in XDocument document, Encoding encoding = null) => TrySaveText(filePath, document.ToString(), encoding); public FluentResults.Result TrySaveText(string filePath, in string text, Encoding encoding = null) { ((IService)this).CheckDisposed(); - throw new NotImplementedException(); + if (text.IsNullOrWhiteSpace()) + { + return FluentResults.Result.Fail($"Contents are empty for {filePath}") + .WithError(new Error($"Contents are empty for {filePath}") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.Sources, filePath)); + } + + string t = text; //copy + return IOExceptionsOperationRunner(nameof(TrySaveText), filePath, () => + { + var fp = filePath.CleanUpPath(); + fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); + System.IO.File.WriteAllText(fp, t, encoding); + return new FluentResults.Result().WithSuccess($"Saved to file successfully"); + }); } public FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes) { ((IService)this).CheckDisposed(); - throw new NotImplementedException(); + if (bytes is null || bytes.Length == 0) + { + return FluentResults.Result.Fail($"Byte array is null or empty for {filePath}") + .WithError(new Error($"Byte array is null or empty for {filePath}") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.Sources, filePath)); + } + byte[] b = new byte[bytes.Length]; + System.Buffer.BlockCopy(bytes, 0, b, 0, bytes.Length); + return IOExceptionsOperationRunner(nameof(TrySaveBinary), filePath, () => + { + var fp = filePath.CleanUpPath(); + fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); + System.IO.File.WriteAllBytes(fp, b); + return new FluentResults.Result().WithSuccess($"Saved to file successfully"); + }); } public FluentResults.Result FileExists(string filePath) { ((IService)this).CheckDisposed(); - throw new NotImplementedException(); + return IOExceptionsOperationRunner(nameof(FileExists), filePath, () => + { + var fp = filePath.CleanUpPath(); + fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); + return System.IO.File.Exists(fp); + }); } public async Task> TryLoadXmlAsync(string filePath, Encoding encoding = null) { - throw new NotImplementedException(); + var r = await TryLoadTextAsync(filePath, encoding); + if (r is { IsSuccess: true, Value: {} value } && !value.IsNullOrWhiteSpace()) + return XDocument.Parse(value); + return FluentResults.Result.Fail(GetGeneralError(nameof(TryLoadXml), filePath)); } public async Task> TryLoadTextAsync(string filePath, Encoding encoding = null) { - throw new NotImplementedException(); + ((IService)this).CheckDisposed(); + return await IOExceptionsOperationRunnerAsync(nameof(TryLoadTextAsync), filePath, async () => + { + var fp = filePath.CleanUpPath(); + fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); + return await System.IO.File.ReadAllTextAsync(fp); + }); } public async Task> TryLoadBinaryAsync(string filePath) { - throw new NotImplementedException(); - } - - public async Task TrySaveXmlAsync(string filePath, XDocument document, Encoding encoding = null) - { - throw new NotImplementedException(); + ((IService)this).CheckDisposed(); + return await IOExceptionsOperationRunnerAsync(nameof(TryLoadTextAsync), filePath, async () => + { + var fp = filePath.CleanUpPath(); + fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); + return await System.IO.File.ReadAllBytesAsync(fp); + }); } + public async Task TrySaveXmlAsync(string filePath, XDocument document, Encoding encoding = null) => await TrySaveTextAsync(filePath, document.ToString(), encoding); public async Task TrySaveTextAsync(string filePath, string text, Encoding encoding = null) { - throw new NotImplementedException(); + ((IService)this).CheckDisposed(); + if (text.IsNullOrWhiteSpace()) + { + return FluentResults.Result.Fail($"Contents are empty for {filePath}") + .WithError(new Error($"Contents are empty for {filePath}") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.Sources, filePath)); + } + + string t = text.ToString(); //copy + return await IOExceptionsOperationRunnerAsync(nameof(TrySaveText), filePath, async () => + { + var fp = filePath.CleanUpPath(); + fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); + await System.IO.File.WriteAllTextAsync(fp, t, encoding); + return new FluentResults.Result().WithSuccess($"Saved to file successfully"); + }); } public async Task TrySaveBinaryAsync(string filePath, byte[] bytes) { - throw new NotImplementedException(); + ((IService)this).CheckDisposed(); + if (bytes is null || bytes.Length == 0) + { + return FluentResults.Result.Fail($"Byte array is null or empty for {filePath}") + .WithError(new Error($"Byte array is null or empty for {filePath}") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.Sources, filePath)); + } + byte[] b = new byte[bytes.Length]; + System.Buffer.BlockCopy(bytes, 0, b, 0, bytes.Length); + return await IOExceptionsOperationRunnerAsync(nameof(TrySaveBinary), filePath, async () => + { + var fp = filePath.CleanUpPath(); + fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); + await System.IO.File.WriteAllBytesAsync(fp, b); + return new FluentResults.Result().WithSuccess($"Saved to file successfully"); + }); } - + private async Task> IOExceptionsOperationRunnerAsync(string funcName, string filepath, Func>> operation) { try @@ -307,6 +411,50 @@ public class StorageService : IStorageService } } + private async Task IOExceptionsOperationRunnerAsync(string funcName, string filepath, Func> operation) + { + try + { + return await operation?.Invoke()!; + } + catch (ArgumentNullException ane) + { + return ReturnException(ane, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (ArgumentException ae) + { + return ReturnException(ae, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (PathTooLongException ptle) + { + return ReturnException(ptle, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (NotSupportedException nse) + { + return ReturnException(nse, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (UnauthorizedAccessException uae) + { + return ReturnException(uae, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (DirectoryNotFoundException dnfe) + { + return ReturnException(dnfe, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (FileNotFoundException fnfe) + { + return ReturnException(fnfe, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (SecurityException se) + { + return ReturnException(se, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (IOException ioe) + { + return ReturnException(ioe, filepath).WithError(GetGeneralError(nameof(SaveLocalXml), filepath)); + } + } + private FluentResults.Result IOExceptionsOperationRunner(string funcName, string filepath, Func> operation) { try @@ -360,6 +508,59 @@ public class StorageService : IStorageService } } + private FluentResults.Result IOExceptionsOperationRunner(string funcName, string filepath, Func operation) + { + try + { + return operation?.Invoke(); + } + catch (ArgumentNullException ane) + { + return ReturnException(ane, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (ArgumentException ae) + { + return ReturnException(ae, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (PathTooLongException ptle) + { + return ReturnException(ptle, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (NotSupportedException nse) + { + return ReturnException(nse, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (UnauthorizedAccessException uae) + { + return ReturnException(uae, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (DirectoryNotFoundException dnfe) + { + return ReturnException(dnfe, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (FileNotFoundException fnfe) + { + return ReturnException(fnfe, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (SecurityException se) + { + return ReturnException(se, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (IOException ioe) + { + return ReturnException(ioe, filepath) + .WithError(GetGeneralError(nameof(SaveLocalXml), filepath)); + } + } + private Error GetGeneralError(string funcName, string localfp, ContentPackage package) => new Error($"{funcName}: Failed to load local file.") .WithMetadata(MetadataType.ExceptionObject, this) @@ -370,10 +571,27 @@ public class StorageService : IStorageService new Error($"{funcName}: Failed to load local file.") .WithMetadata(MetadataType.ExceptionObject, this) .WithMetadata(MetadataType.Sources, localfp); - - private string GetAbsFromLocal(ContentPackage package, string localFilePath) + + private FluentResults.Result GetAbsFromLocal(ContentPackage package, string localFilePath) { - return System.IO.Path.GetFullPath(System.IO.Path.Combine( + if (Path.IsPathRooted(localFilePath)) + { + return new FluentResults.Result().WithError( + new Error($"The path '{localFilePath}' is a rooted path. Must be relative!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, localFilePath)); + } + + if (package is null) + { + return new FluentResults.Result().WithError( + new Error($"{nameof(GetAbsFromPackage)} The package reference for {localFilePath} is null!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, localFilePath)); + } + + return new FluentResults.Result().WithSuccess($"Path constructed") + .WithValue( System.IO.Path.GetFullPath(System.IO.Path.Combine( _runLocation, LocalStoragePath.Value, LocalFilePathRule.Value.Replace(_packagePathKeyword, package.Name.IsNullOrWhiteSpace() @@ -381,7 +599,36 @@ public class StorageService : IStorageService ? id.Value.ToString() : "_fallbackFolder" : package.Name), - localFilePath)); + localFilePath))); + } + + private FluentResults.Result GetAbsFromPackage(ContentPackage package, string localFilePath) + { + if (package is null) + { + return new FluentResults.Result().WithError( + new Error($"{nameof(GetAbsFromPackage)} The package reference for {localFilePath} is null!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, localFilePath)); + } + + if (localFilePath.IsNullOrWhiteSpace()) + { + return new FluentResults.Result().WithValue(Path.GetFullPath(package.Path.CleanUpPath())); + } + + var path = localFilePath.CleanUpPath(); + + if (Path.IsPathRooted(path)) + { + return new FluentResults.Result().WithError( + new Error($"The path '{localFilePath}' is a rooted path. Must be relative!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, localFilePath)); + } + + return new FluentResults.Result().WithSuccess($"Path constructed") + .WithValue(Path.Combine(Path.GetFullPath(package.Path.CleanUpPath()), path)); } private FluentResults.Result ReturnException(TException exception, ContentPackage package) where TException : Exception diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs index 1c830ab78..0d132fcdd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs @@ -31,9 +31,9 @@ public interface IStorageService : IService FluentResults.Result LoadPackageBinary(ContentPackage package, string localFilePath); FluentResults.Result LoadPackageText(ContentPackage package, string localFilePath); // collections - FluentResults.Result> LoadPackageXmlFiles(ContentPackage package, ImmutableArray localFilePaths); - FluentResults.Result> LoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePaths); - FluentResults.Result> LoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePaths); + ImmutableArray<(string, FluentResults.Result)> LoadPackageXmlFiles(ContentPackage package, ImmutableArray localFilePaths); + ImmutableArray<(string, FluentResults.Result)> LoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePaths); + ImmutableArray<(string, FluentResults.Result)> LoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePaths); FluentResults.Result> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively); // async // singles @@ -41,9 +41,9 @@ public interface IStorageService : IService Task> LoadPackageBinaryAsync(ContentPackage package, string localFilePath); Task> LoadPackageTextAsync(ContentPackage package, string localFilePath); // collections - Task>> LoadPackageXmlFilesAsync(ContentPackage package, ImmutableArray localFilePaths); - Task>> LoadPackageBinaryFilesAsync(ContentPackage package, ImmutableArray localFilePaths); - Task>> LoadPackageTextFilesAsync(ContentPackage package, ImmutableArray localFilePaths); + Task)>> LoadPackageXmlFilesAsync(ContentPackage package, ImmutableArray localFilePaths); + Task)>> LoadPackageBinaryFilesAsync(ContentPackage package, ImmutableArray localFilePaths); + Task)>> LoadPackageTextFilesAsync(ContentPackage package, ImmutableArray localFilePaths); #endregion From d2b9ca4c1beffe7d4db3a68fe47e59da72f846a1 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 20 Dec 2024 16:17:14 -0500 Subject: [PATCH 010/288] [Refactor-Minor] - Refactored interface definition. - Plugin Loading System Refactor (incomplete). --- .../LuaCs/Data/IResourceInfoDeclarations.cs | 2 +- .../LuaCs/Services/PackageService.cs | 2 +- .../LuaCs/Services/PackageService.cs | 2 +- Barotrauma/BarotraumaShared/Luatrauma.props | 2 +- .../SharedSource/LuaCs/Data/IDataInfo.cs | 38 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 6 +- .../SharedSource/LuaCs/ModUtils.cs | 1 + .../LuaCs/Services/AssemblyManager.cs | 759 ------------------ .../LuaCs/Services/PackageService.cs | 2 +- .../LuaCs/Services/PluginManagementService.cs | 159 +++- ...Service.cs => IModConfigCreatorService.cs} | 2 +- .../_Interfaces/IPluginManagementService.cs | 16 +- .../Services/_Interfaces/IServicesProvider.cs | 14 +- .../Services/_Interfaces/IStorageService.cs | 12 +- .../LuaCs/_Plugins/AssemblyLoader.cs | 541 ++++++++----- .../LuaCs/_Plugins/IAssemblyLoaderService.cs | 15 + 16 files changed, 563 insertions(+), 1010 deletions(-) delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/AssemblyManager.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/{IModConfigParserService.cs => IModConfigCreatorService.cs} (81%) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs index bd9e60424..8624189ed 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs @@ -4,7 +4,7 @@ namespace Barotrauma.LuaCs.Data; public partial interface IModConfigInfo : IStylesResourcesInfo { } -public interface IStylesResourceInfo : IResourceInfo, IResourceCultureInfo, IDataInfo, IPackageDependenciesInfo { } +public interface IStylesResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageInfo, IPackageDependenciesInfo { } public interface IStylesResourcesInfo { diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs index d1ceb600c..96c92093b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs @@ -12,7 +12,7 @@ public partial class PackageService : IStylesResourcesInfo public IStylesService Styles => _stylesService.Value; public PackageService( - Lazy configParserService, + Lazy configParserService, Lazy luaScriptService, Lazy localizationService, Lazy pluginService, diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs index e4d92ec2f..d64643ebb 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs @@ -8,7 +8,7 @@ namespace Barotrauma.LuaCs.Services; public partial class PackageService { public PackageService( - Lazy configParserService, + Lazy configParserService, Lazy luaScriptService, Lazy localizationService, Lazy pluginService, diff --git a/Barotrauma/BarotraumaShared/Luatrauma.props b/Barotrauma/BarotraumaShared/Luatrauma.props index 1adac0339..5b7154571 100644 --- a/Barotrauma/BarotraumaShared/Luatrauma.props +++ b/Barotrauma/BarotraumaShared/Luatrauma.props @@ -8,7 +8,7 @@ - + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IDataInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IDataInfo.cs index 330b08ac4..34933f7d1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IDataInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IDataInfo.cs @@ -1,12 +1,15 @@ -namespace Barotrauma.LuaCs.Data; +using System; +using System.Collections.Generic; + +namespace Barotrauma.LuaCs.Data; /// /// Serves as a compound-key to refer to all resources and information that comes from a specific source. /// -public interface IDataInfo +public interface IDataInfo : IEqualityComparer, IEquatable { /// - /// Package-Unique name to be used internally for all representations of, and references to, this information. + /// Internal name unique within the resources inside a package. /// string InternalName { get; } /// @@ -17,4 +20,33 @@ public interface IDataInfo /// Used in place of the package data when the OwnerPackage is missing. /// string FallbackPackageName { get; } + + bool IEqualityComparer.Equals(IDataInfo x, IDataInfo y) + { + if (x is null || y is null) + return false; + if (x.OwnerPackage is null) + throw new NullReferenceException($"ContentPackage not set for resource {x}!"); + if (y.OwnerPackage is null) + throw new NullReferenceException($"ContentPackage not set for resource {y}!"); + if (x.InternalName.IsNullOrWhiteSpace()) + throw new NullReferenceException($"InternalName not set for resource {x}!"); + if (y.InternalName.IsNullOrWhiteSpace()) + throw new NullReferenceException($"InternalName not set for resource {y}!"); + return x.OwnerPackage == y.OwnerPackage && x.InternalName == y.InternalName; + } + + bool IEquatable.Equals(IDataInfo other) + { + return Equals(this, other); + } + + int IEqualityComparer.GetHashCode(IDataInfo obj) + { + if (obj.OwnerPackage is null) + throw new NullReferenceException($"ContentPackage not set for resource {obj}!"); + if (obj.InternalName.IsNullOrWhiteSpace()) + throw new NullReferenceException($"InternalName is null for object {obj}!"); + return obj.InternalName.GetHashCode() + obj.OwnerPackage.GetHashCode(); + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 56a8536f7..232ea11bc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -43,7 +43,7 @@ namespace Barotrauma RegisterServices(); // load manifest - if (!_servicesProvider.TryGetService(out IModConfigParserService modConfigSvc)) + if (!_servicesProvider.TryGetService(out IModConfigCreatorService modConfigSvc)) throw new NullReferenceException("LuaCsSetup: Failed to get mod config parser service!"); // we should crash here var luaConfig = modConfigSvc.BuildConfigFromManifest(Directory.GetCurrentDirectory() + LuaCsConfigFile); if (!luaConfig.IsSuccess) @@ -421,7 +421,7 @@ namespace Barotrauma #endif } - + */ public void Stop() { @@ -439,7 +439,7 @@ namespace Barotrauma IsInitialized = true; Logger.Log($"Initializing LuaCs, git revision = {AssemblyInfo.GitRevision}"); - }*/ + } public void Update() { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs index e903b0f9a..981c74bad 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs @@ -18,6 +18,7 @@ using Microsoft.Xna.Framework; using OneOf; using Platform = Barotrauma.LuaCs.Data.Platform; +// This file is cursed, we put everything in it, and I'm not sorry about it. namespace Barotrauma.LuaCs { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/AssemblyManager.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/AssemblyManager.cs deleted file mode 100644 index e2017fbee..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/AssemblyManager.cs +++ /dev/null @@ -1,759 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.Loader; -using System.Threading; -using FluentResults; -using FluentResults.LuaCs; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; - -// ReSharper disable EventNeverSubscribedTo.Global -// ReSharper disable InconsistentNaming - -namespace Barotrauma.LuaCs.Services; - -/*** - * Note: This class was written to be thread-safe in order to allow parallelization in loading in the future if the need - * becomes necessary as there is almost no serial performance overhead for adding threading protection. - */ - -/// -/// Provides functionality for the loading, unloading and management of plugins implementing IAssemblyPlugin. -/// All plugins are loaded into their own AssemblyLoadContext along with their dependencies. -/// -[Obsolete] -public class AssemblyManager : IAssemblyManagementService, IPluginManagementService -{ - #region ExternalAPI - - public event Action OnAssemblyLoaded; - public event Action OnAssemblyUnloading; - public event Action OnException; - public event Action OnACLUnload; - public ImmutableList> StillUnloadingACLs - { - get - { - OpsLockUnloaded.EnterReadLock(); - try - { - return UnloadingACLs.ToImmutableList(); - } - finally - { - OpsLockUnloaded.ExitReadLock(); - } - } - } - public bool IsCurrentlyUnloading - { - get - { - OpsLockUnloaded.EnterReadLock(); - try - { - return UnloadingACLs.Any(); - } - catch (Exception) - { - return false; - } - finally - { - OpsLockUnloaded.ExitReadLock(); - } - } - } - public IEnumerable GetSubTypesInLoadedAssemblies(bool rebuildList) - { - Type targetType = typeof(T); - string typeName = targetType.FullName ?? targetType.Name; - - // rebuild - if (rebuildList) - RebuildTypesList(); - - // check cache - if (_subTypesLookupCache.TryGetValue(typeName, out var subTypeList)) - { - return subTypeList; - } - - // build from scratch - OpsLockLoaded.EnterReadLock(); - try - { - // build list - var list1 = _defaultContextTypes - .Where(kvp1 => targetType.IsAssignableFrom(kvp1.Value) && !kvp1.Value.IsInterface) - .Concat(LoadedACLs - .SelectMany(kvp => kvp.Value.AssembliesTypes) - .Where(kvp2 => targetType.IsAssignableFrom(kvp2.Value) && !kvp2.Value.IsInterface)) - .Select(kvp3 => kvp3.Value) - .ToImmutableList(); - - // only add if we find something - if (list1.Count > 0) - { - if (!_subTypesLookupCache.TryAdd(typeName, list1)) - { - ModUtils.Logging.PrintError( - $"{nameof(AssemblyManager)}: Unable to add subtypes to cache of type {typeName}!"); - } - } - else - { - ModUtils.Logging.PrintMessage( - $"{nameof(AssemblyManager)}: Warning: No types found during search for subtypes of {typeName}"); - } - - return list1; - } - catch (Exception e) - { - this.OnException?.Invoke($"{nameof(AssemblyManager)}::{nameof(GetSubTypesInLoadedAssemblies)}() | Error: {e.Message}", e); - return ImmutableList.Empty; - } - finally - { - OpsLockLoaded.ExitReadLock(); - } - } - public bool TryGetSubTypesFromACL(Guid id, out IEnumerable types) - { - Type targetType = typeof(T); - - if (TryGetACL(id, out var acl)) - { - types = acl.AssembliesTypes - .Where(kvp => targetType.IsAssignableFrom(kvp.Value) && !kvp.Value.IsInterface) - .Select(kvp => kvp.Value); - return true; - } - - types = null; - return false; - } - public bool TryGetSubTypesFromACL(Guid id, out IEnumerable types) - { - if (TryGetACL(id, out var acl)) - { - types = acl.AssembliesTypes.Select(kvp => kvp.Value); - return true; - } - - types = null; - return false; - } - public IEnumerable GetTypesByName(string typeName) - { - List types = new(); - if (typeName.IsNullOrWhiteSpace()) - return types; - - bool byRef = false; - if (typeName.StartsWith("out ") || typeName.StartsWith("ref ")) - { - typeName = typeName.Remove(0, 4); - byRef = true; - } - - - TypesListHelper(); - if (types.Count > 0) - return types; - - // we couldn't find it, rebuild and try one more time - RebuildTypesList(); - TypesListHelper(); - - if (types.Count > 0) - return types; - - OpsLockLoaded.EnterReadLock(); - try - { - // fallback to Type.GetType - Type t = Type.GetType(typeName, false, false); - if (t is not null) - { - types.Add(byRef ? t.MakeByRefType() : t); - return types; - } - - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) - { - try - { - t = assembly.GetType(typeName, false, false); - if (t is not null) - types.Add(byRef ? t.MakeByRefType() : t); - } - catch (Exception e) - { - this.OnException?.Invoke( - $"{nameof(AssemblyManager)}::{nameof(GetTypesByName)}() | Error: {e.Message}", e); - } - } - } - finally - { - OpsLockLoaded.ExitReadLock(); - } - - return types; - - void TypesListHelper() - { - if (_defaultContextTypes.TryGetValue(typeName, out var type1)) - { - if (type1 is not null) - types.Add(byRef ? type1.MakeByRefType() : type1); - } - - OpsLockLoaded.EnterReadLock(); - try - { - foreach (KeyValuePair loadedAcl in LoadedACLs) - { - var at = loadedAcl.Value.AssembliesTypes; - if (at.TryGetValue(typeName, out var type2)) - { - if (type2 is not null) - types.Add(byRef ? type2.MakeByRefType() : type2); - } - } - } - finally - { - OpsLockLoaded.ExitReadLock(); - } - } - } - public IEnumerable GetAllTypesInLoadedAssemblies() - { - OpsLockLoaded.EnterReadLock(); - try - { - return _defaultContextTypes - .Select(kvp => kvp.Value) - .Concat(LoadedACLs - .SelectMany(kvp => kvp.Value?.AssembliesTypes.Select(kv => kv.Value))) - .ToImmutableList(); - } - catch - { - return ImmutableList.Empty; - } - finally - { - OpsLockLoaded.ExitReadLock(); - } - } - public IEnumerable GetAllLoadedACLs() - { - OpsLockLoaded.EnterReadLock(); - try - { - if (!LoadedACLs.Any()) - { - return ImmutableList.Empty; - } - - return LoadedACLs.Select(kvp => kvp.Value).ToImmutableList(); - } - catch - { - return ImmutableList.Empty; - } - finally - { - OpsLockLoaded.ExitReadLock(); - } - } - - public bool IsAssemblyLoadedGlobal(string friendlyName) - { - throw new NotImplementedException(); - } - - #endregion - - #region InternalAPI - - [MethodImpl(MethodImplOptions.Synchronized | MethodImplOptions.NoInlining)] - ImmutableList IAssemblyManagementService.UnsafeGetAllLoadedACLs() - { - if (LoadedACLs.IsEmpty) - return ImmutableList.Empty; - return LoadedACLs.Select(kvp => kvp.Value).ToImmutableList(); - } - public event System.Func IsReadyToUnloadACL; - public AssemblyLoadingSuccessState LoadAssemblyFromMemory([NotNull] string compiledAssemblyName, - [NotNull] IEnumerable syntaxTree, - IEnumerable externalMetadataReferences, - [NotNull] CSharpCompilationOptions compilationOptions, - string friendlyName, - ref Guid id, - IEnumerable externFileAssemblyRefs = null) - { - // validation - if (compiledAssemblyName.IsNullOrWhiteSpace()) - return AssemblyLoadingSuccessState.BadName; - - if (syntaxTree is null) - return AssemblyLoadingSuccessState.InvalidAssembly; - - if (!GetOrCreateACL(id, friendlyName, out var acl)) - return AssemblyLoadingSuccessState.ACLLoadFailure; - - id = acl.Id; // pass on true id returned - - // this acl is already hosting an in-memory assembly - if (acl.Acl.CompiledAssembly is not null) - return AssemblyLoadingSuccessState.AlreadyLoaded; - - // compile - AssemblyLoadingSuccessState state; - string messages; - try - { - state = acl.Acl.CompileAndLoadScriptAssembly(compiledAssemblyName, syntaxTree, externalMetadataReferences, - compilationOptions, out messages, externFileAssemblyRefs); - } - catch (Exception e) - { - ModUtils.Logging.PrintError($"{nameof(AssemblyManager)}::{nameof(LoadAssemblyFromMemory)}() | Failed to compile and load assemblies for [ {compiledAssemblyName} / {friendlyName} ]! Details: {e.Message} | {e.StackTrace}"); - return AssemblyLoadingSuccessState.InvalidAssembly; - } - - // get types - if (state is AssemblyLoadingSuccessState.Success) - { - _subTypesLookupCache.Clear(); - acl.RebuildTypesList(); - OnAssemblyLoaded?.Invoke(acl.Acl.CompiledAssembly); - } - else - { - ModUtils.Logging.PrintError($"Unable to compile assembly '{compiledAssemblyName}' due to errors: {messages}"); - } - - return state; - } - public bool SetACLToTemplateMode(Guid guid) - { - if (!TryGetACL(guid, out var acl)) - return false; - acl.Acl.IsTemplateMode = true; - return true; - } - public AssemblyLoadingSuccessState LoadAssembliesFromLocations([NotNull] IEnumerable filePaths, - string friendlyName, ref Guid id) - { - - if (filePaths is null) - { - var exception = new ArgumentNullException( - $"{nameof(AssemblyManager)}::{nameof(LoadAssembliesFromLocations)}() | file paths supplied is null!"); - this.OnException?.Invoke($"Error: {exception.Message}", exception); - throw exception; - } - - ImmutableList assemblyFilePaths = filePaths.ToImmutableList(); // copy the list before loading - - if (!assemblyFilePaths.Any()) - { - return AssemblyLoadingSuccessState.NoAssemblyFound; - } - - if (GetOrCreateACL(id, friendlyName, out var loadedAcl)) - { - var state = loadedAcl.Acl.LoadFromFiles(assemblyFilePaths); - // if failure, we dispose of the acl - if (state != AssemblyLoadingSuccessState.Success) - { - DisposeACL(loadedAcl.Id); - ModUtils.Logging.PrintError($"ACL {friendlyName} failed, unloading..."); - return state; - } - // build types list - _subTypesLookupCache.Clear(); - loadedAcl.RebuildTypesList(); - id = loadedAcl.Id; - foreach (Assembly assembly in loadedAcl.Acl.Assemblies) - { - OnAssemblyLoaded?.Invoke(assembly); - } - return state; - } - - return AssemblyLoadingSuccessState.ACLLoadFailure; - } - [MethodImpl(MethodImplOptions.NoInlining)] - public bool TryBeginDispose() - { - OpsLockLoaded.EnterWriteLock(); - OpsLockUnloaded.EnterWriteLock(); - try - { - _subTypesLookupCache.Clear(); - _defaultContextTypes = _defaultContextTypes.Clear(); - - foreach (KeyValuePair loadedAcl in LoadedACLs) - { - if (loadedAcl.Value.Acl is not null) - { - if (IsReadyToUnloadACL is not null) - { - foreach (Delegate del in IsReadyToUnloadACL.GetInvocationList()) - { - if (del is System.Func { } func) - { - if (!func.Invoke(loadedAcl.Value)) - return false; // Not ready, exit - } - } - } - - foreach (Assembly assembly in loadedAcl.Value.Acl.Assemblies) - { - OnAssemblyUnloading?.Invoke(assembly); - } - - UnloadingACLs.Add(new WeakReference(loadedAcl.Value.Acl, true)); - loadedAcl.Value.ClearTypesList(); - loadedAcl.Value.Acl.Unload(); - loadedAcl.Value.ClearACLRef(); - OnACLUnload?.Invoke(loadedAcl.Value.Id); - } - } - - LoadedACLs.Clear(); - return true; - } - catch(Exception e) - { - // should never happen - this.OnException?.Invoke($"{nameof(TryBeginDispose)}() | Error: {e.Message}", e); - return false; - } - finally - { - OpsLockUnloaded.ExitWriteLock(); - OpsLockLoaded.ExitWriteLock(); - } - } - [MethodImpl(MethodImplOptions.NoInlining)] - public bool FinalizeDispose() - { - bool isUnloaded; - OpsLockUnloaded.EnterUpgradeableReadLock(); - try - { - GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); // force the gc to collect unloaded acls. - List> toRemove = new(); - foreach (WeakReference weakReference in UnloadingACLs) - { - if (!weakReference.TryGetTarget(out _)) - { - toRemove.Add(weakReference); - } - } - - if (toRemove.Any()) - { - OpsLockUnloaded.EnterWriteLock(); - try - { - foreach (WeakReference reference in toRemove) - { - UnloadingACLs.Remove(reference); - } - } - finally - { - OpsLockUnloaded.ExitWriteLock(); - } - } - isUnloaded = !UnloadingACLs.Any(); - } - finally - { - OpsLockUnloaded.ExitUpgradeableReadLock(); - } - - return isUnloaded; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public bool TryGetACL(Guid id, out LoadedACL acl) - { - acl = null; - OpsLockLoaded.EnterReadLock(); - try - { - if (id.Equals(Guid.Empty) || !LoadedACLs.ContainsKey(id)) - return false; - acl = LoadedACLs[id]; - return true; - } - finally - { - OpsLockLoaded.ExitReadLock(); - } - } - - - /// - /// Gets or creates an AssemblyCtxLoader for the given ID. Creates if the ID is empty or no ACL can be found. - /// [IMPORTANT] After calling this method, the id you use should be taken from the acl container (acl.Id). - /// - /// - /// A non-unique name for later reference. Optional. - /// - /// Should only return false if an error occurs. - [MethodImpl(MethodImplOptions.NoInlining)] - private bool GetOrCreateACL(Guid id, string friendlyName, out LoadedACL acl) - { - OpsLockLoaded.EnterUpgradeableReadLock(); - try - { - if (id.Equals(Guid.Empty) || !LoadedACLs.ContainsKey(id) || LoadedACLs[id] is null) - { - OpsLockLoaded.EnterWriteLock(); - try - { - id = Guid.NewGuid(); - acl = new LoadedACL(id, this, friendlyName); - LoadedACLs[id] = acl; - return true; - } - finally - { - OpsLockLoaded.ExitWriteLock(); - } - } - else - { - acl = LoadedACLs[id]; - return true; - } - - } - catch(Exception e) - { - this.OnException?.Invoke($"{nameof(GetOrCreateACL)}Error: {e.Message}", e); - acl = null; - return false; - } - finally - { - OpsLockLoaded.ExitUpgradeableReadLock(); - } - } - - - [MethodImpl(MethodImplOptions.NoInlining)] - private bool DisposeACL(Guid id) - { - OpsLockLoaded.EnterWriteLock(); - OpsLockUnloaded.EnterWriteLock(); - try - { - if (LoadedACLs.ContainsKey(id) && LoadedACLs[id] == null) - { - if (!LoadedACLs.TryRemove(id, out _)) - { - ModUtils.Logging.PrintWarning($"An ACL with the GUID {id.ToString()} was found as null. Unable to remove null ACL entry."); - } - } - - if (id.Equals(Guid.Empty) || !LoadedACLs.ContainsKey(id)) - { - return false; // nothing to dispose of - } - - var acl = LoadedACLs[id]; - - foreach (Assembly assembly in acl.Acl.Assemblies) - { - OnAssemblyUnloading?.Invoke(assembly); - } - - _subTypesLookupCache.Clear(); - UnloadingACLs.Add(new WeakReference(acl.Acl, true)); - acl.Acl.Unload(); - acl.ClearACLRef(); - OnACLUnload?.Invoke(acl.Id); - - return true; - } - catch (Exception e) - { - this.OnException?.Invoke($"{nameof(DisposeACL)}() | Error: {e.Message}", e); - return false; - } - finally - { - OpsLockLoaded.ExitWriteLock(); - OpsLockUnloaded.ExitWriteLock(); - } - } - - internal AssemblyManager() - { - RebuildTypesList(); - } - - /// - /// Rebuilds the list of types in the default assembly load context. - /// - private void RebuildTypesList() - { - try - { - _defaultContextTypes = AssemblyLoadContext.Default.Assemblies - .SelectMany(a => a.GetSafeTypes()) - .ToImmutableDictionary(t => t.FullName ?? t.Name, t => t); - _subTypesLookupCache.Clear(); - } - catch(ArgumentException ae) - { - this.OnException?.Invoke($"{nameof(RebuildTypesList)}() | Error: {ae.Message}", ae); - try - { - // some types must've had duplicate type names, build the list while filtering - Dictionary types = new(); - foreach (var type in AssemblyLoadContext.Default.Assemblies.SelectMany(a => a.GetSafeTypes())) - { - try - { - types.TryAdd(type.FullName ?? type.Name, type); - } - catch - { - // ignore, null key exception - } - } - - _defaultContextTypes = types.ToImmutableDictionary(); - } - catch (Exception e) - { - this.OnException?.Invoke($"{nameof(RebuildTypesList)}() | Error: {e.Message}", e); - ModUtils.Logging.PrintError($"{nameof(AssemblyManager)}: Unable to create list of default assembly types! Default AssemblyLoadContext types searching not available."); -#if DEBUG - ModUtils.Logging.PrintError($"{nameof(AssemblyManager)}: Exception Details :{e.Message} | {e.InnerException}"); -#endif - _defaultContextTypes = ImmutableDictionary.Empty; - } - } - } - - #endregion - - #region Data - - private readonly ConcurrentDictionary> _subTypesLookupCache = new(); - private ImmutableDictionary _defaultContextTypes; - private readonly ConcurrentDictionary LoadedACLs = new(); - private readonly List> UnloadingACLs= new(); - private readonly ReaderWriterLockSlim OpsLockLoaded = new (); - private readonly ReaderWriterLockSlim OpsLockUnloaded = new (); - - #endregion - - #region TypeDefs - - - public sealed class LoadedACL - { - public readonly Guid Id; - private ImmutableDictionary _assembliesTypes = ImmutableDictionary.Empty; - public MemoryFileAssemblyContextLoader Acl { get; private set; } - - internal LoadedACL(Guid id, AssemblyManager manager, string friendlyName) - { - this.Id = id; - this.Acl = new(manager) - { - FriendlyName = friendlyName - }; - } - public ref readonly ImmutableDictionary AssembliesTypes => ref _assembliesTypes; - - /// - /// Warning: For use by the Assembly Manager only! Do not call this method otherwise. - /// - internal void ClearACLRef() - { - Acl = null; - } - - /// - /// Rebuild the list of types from assemblies loaded in the AsmCtxLoader. - /// - internal void RebuildTypesList() - { - if (this.Acl is null) - { - ModUtils.Logging.PrintWarning($"{nameof(RebuildTypesList)}() | ACL with GUID {Id.ToString()} is null, cannot rebuild."); - return; - } - - ClearTypesList(); - try - { - _assembliesTypes = this.Acl.Assemblies - .SelectMany(a => a.GetSafeTypes()) - .ToImmutableDictionary(t => t.FullName ?? t.Name, t => t); - } - catch(ArgumentException) - { - // some types must've had duplicate type names, build the list while filtering - Dictionary types = new(); - foreach (var type in this.Acl.Assemblies.SelectMany(a => a.GetSafeTypes())) - { - try - { - types.TryAdd(type.FullName ?? type.Name, type); - } - catch - { - // ignore, null key exception - } - } - - _assembliesTypes = types.ToImmutableDictionary(); - } - } - - internal void ClearTypesList() - { - _assembliesTypes = ImmutableDictionary.Empty; - } - } - - #endregion - - public void Dispose() - { - TryBeginDispose(); - } - - public FluentResults.Result Reset() - { - return TryBeginDispose() ? FluentResults.Result.Ok() - : FluentResults.Result.Fail(new Error($"{nameof(AssemblyManager)}: failed to Reset service.") - .WithMetadata(MetadataType.ExceptionObject, this)); - } -} - - diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs index 219685d5b..83f133fc2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs @@ -23,7 +23,7 @@ public partial class PackageService : IPackageService // mod config / package scanners/parsers - private readonly Lazy _configParserService; + private readonly Lazy _configParserService; private readonly Lazy _luaScriptService; private readonly Lazy _localizationService; private readonly Lazy _pluginService; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 31822e438..6f46b99e1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -1,4 +1,14 @@ -using System.Collections.Immutable; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Loader; +using System.Threading; +using Barotrauma.Extensions; using Barotrauma.LuaCs.Data; using FluentResults; using Microsoft.CodeAnalysis; @@ -7,5 +17,152 @@ namespace Barotrauma.LuaCs.Services; public class PluginManagementService : IPluginManagementService, IAssemblyManagementService { + public bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } + private int _isDisposed; + private readonly ReaderWriterLockSlim _operationsLock = new(LockRecursionPolicy.SupportsRecursion); + + private readonly ConcurrentDictionary _assemblyServices = new(); + private readonly ConcurrentDictionary _resourceData = new(); + private readonly Lazy _eventService; + private readonly Func _assemblyServiceFactory; + private ImmutableDictionary _cachedTypes = null; + private ImmutableDictionary DefaultTypeCache => _cachedTypes ??= AssemblyLoadContext.Default.Assemblies + .SelectMany(ass => ass.GetSafeTypes()).ToImmutableDictionary(type => type.FullName, type => type); + + + public bool IsResourceLoaded(T resource) where T : IAssemblyResourceInfo + { + ((IService)this).CheckDisposed(); + return _resourceData.ContainsKey(resource); + } + + public Result> GetImplementingTypes(string namespacePrefix = null, bool includeInterfaces = false, + bool includeAbstractTypes = false, bool includeDefaultContext = true) + { + ((IService)this).CheckDisposed(); + var types = ImmutableArray.CreateBuilder(); + _operationsLock.EnterReadLock(); + try + { + if (AssemblyLoaderServices.Any()) + { + types.AddRange(AssemblyLoaderServices + .SelectMany(als => als.UnsafeGetTypesInAssemblies()) + .Where(t => t is not null) + .Where(type => typeof(T).IsAssignableFrom(type)) + .Where(type => includeInterfaces || !type.IsInterface) + .Where(type => includeAbstractTypes || !type.IsAbstract) + .Where(type => namespacePrefix is not null && type.FullName is not null && type.FullName.StartsWith(namespacePrefix))); + } + + if (includeDefaultContext) + { + types.AddRange(AssemblyLoadContext.Default.Assemblies + .SelectMany(ass => ass.GetSafeTypes()) + .Where(t => t is not null) + .Where(type => typeof(T).IsAssignableFrom(type)) + .Where(type => includeInterfaces || !type.IsInterface) + .Where(type => includeAbstractTypes || !type.IsAbstract) + .Where(type => namespacePrefix is not null && type.FullName is not null && type.FullName.StartsWith(namespacePrefix))); + } + + return types.MoveToImmutable(); + } + finally + { + _operationsLock.ExitReadLock(); + } + } + + public Type GetType(string typeName) + { + ((IService)this).CheckDisposed(); + _operationsLock.EnterReadLock(); + try + { + if (DefaultTypeCache.TryGetValue(typeName, out var type)) + return type; + if (AssemblyLoaderServices.None()) + return null; + foreach (var loaderService in AssemblyLoaderServices) + { + if (loaderService.GetTypeInAssemblies(typeName) is { IsSuccess: true, Value: not null } ret) + return ret.Value; + } + return null; + } + finally + { + _operationsLock.ExitReadLock(); + } + } + + public Result> LoadAssemblyResources(ImmutableArray resource) + { + throw new NotImplementedException(); + } + + public Result GetLoadedAssembly(string assemblyName, in Guid[] excludedContexts) + { + ((IService)this).CheckDisposed(); + _operationsLock.EnterReadLock(); + try + { + foreach (var (guid, context) in _assemblyServices) + { + if (excludedContexts.Length > 0 && excludedContexts.Contains(guid)) + continue; + if (context.GetAssemblyByName(assemblyName) is { IsSuccess: true, Value: not null } ret) + return ret.Value; + } + return FluentResults.Result.Fail($"Could not find assembly {assemblyName}"); + } + finally + { + _operationsLock.ExitReadLock(); + } + } + + public Result GetLoadedAssembly(AssemblyName assemblyName, in Guid[] excludedContexts) + => GetLoadedAssembly(assemblyName.FullName, excludedContexts); + + public ImmutableArray GetDefaultMetadataReferences() => + Basic.Reference.Assemblies.Net60.References.All.Select(Unsafe.As).ToImmutableArray(); + + public ImmutableArray GetAddInContextsMetadataReferences() + { + ((IService)this).CheckDisposed(); + _operationsLock.EnterReadLock(); + try + { + if (_assemblyServices.IsEmpty) + return ImmutableArray.Empty; + var builder = ImmutableArray.CreateBuilder(); + foreach (var context in _assemblyServices.Values) + builder.AddRange(context.AssemblyReferences); + return builder.ToImmutable(); + } + finally + { + _operationsLock.ExitReadLock(); + } + } + + public ImmutableArray AssemblyLoaderServices { get; } + + public void Dispose() + { + // TODO release managed resources here + throw new NotImplementedException(); + } + + public FluentResults.Result Reset() + { + throw new NotImplementedException(); + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IModConfigParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IModConfigCreatorService.cs similarity index 81% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IModConfigParserService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IModConfigCreatorService.cs index e90f4d598..f91bdba34 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IModConfigParserService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IModConfigCreatorService.cs @@ -2,7 +2,7 @@ namespace Barotrauma.LuaCs.Services.Processing; -public interface IModConfigParserService : IReusableService +public interface IModConfigCreatorService : IService { FluentResults.Result BuildConfigForPackage(ContentPackage package); FluentResults.Result BuildConfigFromManifest(string manifestPath); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs index 0a06c13b9..d81ef4dac 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs @@ -18,28 +18,24 @@ public interface IPluginManagementService : IReusableService /// /// /// - /// /// /// /// /// - /// /// /// - FluentResults.Result> GetTypes( - ContentPackage package = null, + FluentResults.Result> GetImplementingTypes( string namespacePrefix = null, bool includeInterfaces = false, bool includeAbstractTypes = false, - bool includeDefaultContext = true, - bool includeExplicitAssembliesOnly = false); - + bool includeDefaultContext = true); + /// - /// + /// Tries to get the /// - /// + /// /// - FluentResults.Result> GetCachedAssembliesForPackage(ContentPackage package); + Type GetType(string typeName); /// /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IServicesProvider.cs index 8cffa329f..40dd31e6c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IServicesProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IServicesProvider.cs @@ -19,7 +19,7 @@ public interface IServicesProvider /// /// /// - void RegisterServiceType(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IReusableService where TService : class, IReusableService, TSvcInterface; + void RegisterServiceType(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface; /// /// Registers a type as a service for a given interface that can be requested by name. @@ -29,7 +29,7 @@ public interface IServicesProvider /// /// /// - void RegisterServiceType(string name, ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IReusableService where TService : class, IReusableService, TSvcInterface; + void RegisterServiceType(string name, ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface; /// /// Called whenever a new service type for a given interface is implemented. @@ -58,27 +58,25 @@ public interface IServicesProvider /// Tries to get a service for the given interface, returns success/failure. /// /// - /// /// /// - bool TryGetService(out TSvcInterface service) where TSvcInterface : class, IReusableService; + bool TryGetService(out TSvcInterface service) where TSvcInterface : class, IService; /// /// Tries to get a service for the given name and interface, returns success/failure. /// /// /// - /// /// /// - bool TryGetService(string name, out TSvcInterface service) where TSvcInterface : class, IReusableService; + bool TryGetService(string name, out TSvcInterface service) where TSvcInterface : class, IService; /// /// Called whenever a new service is created/instanced. /// Args[0]: The interface type of the service. /// Args[1]: The instance of the service. /// - event System.Action OnServiceInstanced; + event System.Action OnServiceInstanced; #endregion @@ -89,7 +87,7 @@ public interface IServicesProvider /// /// /// - ImmutableArray GetAllServices() where TSvc : class, IReusableService; + ImmutableArray GetAllServices() where TSvc : class, IService; #endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs index 0d132fcdd..bbcac52a4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs @@ -7,8 +7,7 @@ namespace Barotrauma.LuaCs.Services; public interface IStorageService : IService { - #region LocalGameData - + // -- local game folder storage FluentResults.Result LoadLocalXml(ContentPackage package, string localFilePath); FluentResults.Result LoadLocalBinary(ContentPackage package, string localFilePath); FluentResults.Result LoadLocalText(ContentPackage package, string localFilePath); @@ -22,10 +21,8 @@ public interface IStorageService : IService Task SaveLocalXmlAsync(ContentPackage package, string localFilePath, XDocument document); Task SaveLocalBinaryAsync(ContentPackage package, string localFilePath, byte[] bytes); Task SaveLocalTextAsync(ContentPackage package, string localFilePath, string text); - - #endregion - #region ContentPackageData + // -- package directory // singles FluentResults.Result LoadPackageXml(ContentPackage package, string localFilePath); FluentResults.Result LoadPackageBinary(ContentPackage package, string localFilePath); @@ -45,9 +42,7 @@ public interface IStorageService : IService Task)>> LoadPackageBinaryFilesAsync(ContentPackage package, ImmutableArray localFilePaths); Task)>> LoadPackageTextFilesAsync(ContentPackage package, ImmutableArray localFilePaths); - #endregion - - #region AbsolutePaths + // -- absolute paths FluentResults.Result TryLoadXml(string filePath, Encoding encoding = null); FluentResults.Result TryLoadText(string filePath, Encoding encoding = null); FluentResults.Result TryLoadBinary(string filePath); @@ -62,5 +57,4 @@ public interface IStorageService : IService Task TrySaveXmlAsync(string filePath, XDocument document, Encoding encoding = null); Task TrySaveTextAsync(string filePath, string text, Encoding encoding = null); Task TrySaveBinaryAsync(string filePath, byte[] bytes); - #endregion } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs index 1dea21e4a..ebfc6269e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs @@ -3,23 +3,20 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; -using System.Dynamic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Loader; using System.Threading; +using Barotrauma.Extensions; using Barotrauma.LuaCs; -using Barotrauma.LuaCs.Events; using Microsoft.CodeAnalysis; -using Basic.Reference.Assemblies; using FluentResults; using FluentResults.LuaCs; -using LightInject; using Microsoft.CodeAnalysis.CSharp; using OneOf; -using Path = Barotrauma.IO.Path; +using Path = System.IO.Path; [assembly: InternalsVisibleTo(IAssemblyLoaderService.InternalsAwareAssemblyName)] @@ -34,59 +31,92 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService private set => ModUtils.Threading.SetBool(ref _isDisposed, value); } private int _isDisposed; + + /// + /// This bool-int wrapper increments/decrements when set as true/false respectively and return true if the value > 0. + /// + private bool AreOperationRunning + { + get => Interlocked.CompareExchange(ref _operationsRunning, 0, 0) > 0; + set // we use the set as our inc/decr + { + if (value) + { + Interlocked.Add(ref _operationsRunning, 1); + } + else + { + Interlocked.Add(ref _operationsRunning, -1); + } + } + } + private int _operationsRunning; //internal private readonly IAssemblyManagementService _assemblyManagementService; - private readonly IEventService _eventService; private readonly Action _onUnload; - /// - /// This lock is just to ensure that we do not load while disposing - /// - private readonly ReaderWriterLockSlim _operationsLock = new(LockRecursionPolicy.SupportsRecursion); private readonly ConcurrentDictionary _dependencyResolvers = new(); private readonly ConcurrentDictionary _loadedAssemblyData = new(); - private ThreadLocal _isResolving = new(static()=>false); // cyclic resolution exit - - #region PublicAPI + private readonly ThreadLocal _isResolving = new(static()=>false); // cyclic resolution exit public AssemblyLoader(IAssemblyManagementService assemblyManagementService, - IEventService eventService, Guid id, string name, bool isReferenceOnlyMode, Action onUnload) : base(isCollectible: true, name: name) { _assemblyManagementService = assemblyManagementService; - _eventService = eventService; Id = id; IsReferenceOnlyMode = isReferenceOnlyMode; _onUnload = onUnload; if (_onUnload is not null) - { base.Unloading += OnUnload; - } - + } + + public IEnumerable AssemblyReferences + { + get + { + if (IsDisposed || _loadedAssemblyData.IsEmpty) + yield return null; + AreOperationRunning = true; + foreach (var data in _loadedAssemblyData.Values) + { + yield return data.AssemblyReference; + } + AreOperationRunning = false; + } } public FluentResults.Result AddDependencyPaths(ImmutableArray paths) { - if (paths.Length == 0) - return FluentResults.Result.Ok(); - var res = new FluentResults.Result(); - foreach (var path in paths) + if (IsDisposed) + return FluentResults.Result.Fail($"Loader is disposed!"); + AreOperationRunning = true; + try { - try + if (paths.Length == 0) + return FluentResults.Result.Ok(); + var res = new FluentResults.Result(); + foreach (var path in paths) { - var p = Path.GetFullPath(path.CleanUpPath()); - _dependencyResolvers[p] = new AssemblyDependencyResolver(p); - } - catch (Exception ex) - { - return res.WithError(new ExceptionalError(ex) - .WithMetadata(MetadataType.Sources, path)); + try + { + var p = Path.GetFullPath(path.CleanUpPath()); + _dependencyResolvers[p] = new AssemblyDependencyResolver(p); + } + catch (Exception ex) + { + return res.WithError(new ExceptionalError(ex) + .WithMetadata(MetadataType.Sources, path)); + } } + return FluentResults.Result.Ok(); + } + finally + { + AreOperationRunning = false; } - return FluentResults.Result.Ok(); } public FluentResults.Result CompileScriptAssembly( @@ -96,209 +126,303 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService ImmutableArray metadataReferences, CSharpCompilationOptions compilationOptions = null) { - if (assemblyName.IsNullOrWhiteSpace()) - { - return new FluentResults.Result().WithError(new Error($"The name provided is null!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, syntaxTrees)); - } - - if (_loadedAssemblyData.ContainsKey(assemblyName)) - { - return new FluentResults.Result().WithError(new Error($"The name provided is already assigned to an assembly!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, syntaxTrees)); - } - - var compilationAssemblyName = compileWithInternalAccess ? IAssemblyLoaderService.InternalsAwareAssemblyName : assemblyName; - - compilationOptions ??= new CSharpCompilationOptions( - outputKind: OutputKind.DynamicallyLinkedLibrary, - optimizationLevel: OptimizationLevel.Release, - concurrentBuild: true, - reportSuppressedDiagnostics: true, - allowUnsafe: true); - - if (!compileWithInternalAccess) - { - typeof(CSharpCompilationOptions) - .GetProperty("TopLevelBinderFlags", BindingFlags.Instance | BindingFlags.NonPublic) - ?.SetValue(compilationOptions, (uint)1 << 22); - } - - using var asmMemoryStream = new MemoryStream(); - var result = CSharpCompilation.Create(compilationAssemblyName, syntaxTrees, metadataReferences, compilationOptions).Emit(asmMemoryStream); - if (!result.Success) - { - var res = new FluentResults.Result().WithError( - new Error($"Compilation failed for assembly {assemblyName}!")); - 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; - } - - asmMemoryStream.Seek(0, SeekOrigin.Begin); + if (IsDisposed) + return FluentResults.Result.Fail($"Loader is disposed!"); + AreOperationRunning = true; try { - var data = new AssemblyData(LoadFromStream(asmMemoryStream), asmMemoryStream.ToArray()); - _loadedAssemblyData[data.Assembly] = data; - return new FluentResults.Result().WithSuccess($"Compiled assembly {assemblyName} successful.").WithValue(data.Assembly); + if (assemblyName.IsNullOrWhiteSpace()) + { + return new Result().WithError(new Error($"The name provided is null!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, syntaxTrees)); + } + + if (_loadedAssemblyData.ContainsKey(assemblyName)) + { + return new Result().WithError(new Error($"The name provided is already assigned to an assembly!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, syntaxTrees)); + } + + var compilationAssemblyName = compileWithInternalAccess ? IAssemblyLoaderService.InternalsAwareAssemblyName : assemblyName; + + compilationOptions ??= new CSharpCompilationOptions( + outputKind: OutputKind.DynamicallyLinkedLibrary, + optimizationLevel: OptimizationLevel.Release, + concurrentBuild: true, + reportSuppressedDiagnostics: true, + allowUnsafe: true); + + if (!compileWithInternalAccess) + { + typeof(CSharpCompilationOptions) + .GetProperty("TopLevelBinderFlags", BindingFlags.Instance | BindingFlags.NonPublic) + ?.SetValue(compilationOptions, + (uint)1 << 25 // CSharp.BinderFlags.AllowAwaitInUnsafeContext + | (uint)1 << 22 // CSharp.BinderFlags.IgnoreAccessibility + | (uint)1 << 1 // CSharp.BinderFlags.SuppressObsoleteChecks + ); + } + + using var asmMemoryStream = new MemoryStream(); + var result = CSharpCompilation.Create(compilationAssemblyName, syntaxTrees, metadataReferences, compilationOptions).Emit(asmMemoryStream); + if (!result.Success) + { + var res = new FluentResults.Result().WithError( + new Error($"Compilation failed for assembly {assemblyName}!") + .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; + } + + asmMemoryStream.Seek(0, SeekOrigin.Begin); + try + { + var data = new AssemblyData(LoadFromStream(asmMemoryStream), asmMemoryStream.ToArray()); + _loadedAssemblyData[data.Assembly] = data; + return new Result().WithSuccess($"Compiled assembly {assemblyName} successful.").WithValue(data.Assembly); + } + catch (Exception ex) + { + return new FluentResults.Result().WithError(new ExceptionalError(ex)); + } } - catch (Exception ex) + finally { - return new FluentResults.Result().WithError(new ExceptionalError(ex)); + AreOperationRunning = false; } } public FluentResults.Result LoadAssemblyFromFile(string assemblyFilePath, ImmutableArray additionalDependencyPaths) { - if (assemblyFilePath.IsNullOrWhiteSpace()) - return new FluentResults.Result().WithError(new Error($"The path provided is null!")); - - if (additionalDependencyPaths.Any()) - { - var r = AddDependencyPaths(additionalDependencyPaths); - if (!r.IsFailed) - { - // we have errors, loading may not work. - return FluentResults.Result.Fail(new Error($"Failed to load dependency paths") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, assemblyFilePath)) - .WithErrors(r.Errors); - } - } - - string sanitizedFilePath = Path.GetFullPath(assemblyFilePath.CleanUpPath()); - string directoryKey = Path.GetDirectoryName(sanitizedFilePath); - - if (directoryKey is null) - { - return FluentResults.Result.Fail(new Error($"Unable to load assembly: bath file path: {assemblyFilePath}") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, sanitizedFilePath)); - } + if (IsDisposed) + return FluentResults.Result.Fail($"Loader is disposed!"); + AreOperationRunning = true; try { - var assembly = LoadFromAssemblyPath(sanitizedFilePath); - _loadedAssemblyData[assembly] = new AssemblyData(assembly, sanitizedFilePath); - return new Result().WithSuccess($"Loaded assembly'{assembly.GetName()}'").WithValue(assembly); + if (assemblyFilePath.IsNullOrWhiteSpace()) + return new Result().WithError(new Error($"The path provided is null!")); + + if (additionalDependencyPaths.Any()) + { + var r = AddDependencyPaths(additionalDependencyPaths); + if (!r.IsFailed) + { + // we have errors, loading may not work. + return FluentResults.Result.Fail(new Error($"Failed to load dependency paths") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, assemblyFilePath)) + .WithErrors(r.Errors); + } + } + + string sanitizedFilePath = Path.GetFullPath(assemblyFilePath.CleanUpPath()); + string directoryKey = Path.GetDirectoryName(sanitizedFilePath); + + if (directoryKey is null) + { + return FluentResults.Result.Fail(new Error($"Unable to load assembly: bath file path: {assemblyFilePath}") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, sanitizedFilePath)); + } + + try + { + var assembly = LoadFromAssemblyPath(sanitizedFilePath); + _loadedAssemblyData[assembly] = new AssemblyData(assembly, sanitizedFilePath); + return new Result().WithSuccess($"Loaded assembly'{assembly.GetName()}'").WithValue(assembly); + } + catch (ArgumentNullException ane) + { + return FluentResults.Result.Fail(new ExceptionalError(ane) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, assemblyFilePath) + .WithMetadata(MetadataType.ExceptionDetails, ane.Message) + .WithMetadata(MetadataType.StackTrace, ane.StackTrace)); + } + catch (ArgumentException ae) + { + return FluentResults.Result.Fail(new ExceptionalError(ae) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, assemblyFilePath) + .WithMetadata(MetadataType.ExceptionDetails, ae.Message) + .WithMetadata(MetadataType.StackTrace, ae.StackTrace)); + } + catch (FileLoadException fle) + { + return FluentResults.Result.Fail(new ExceptionalError(fle) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, assemblyFilePath) + .WithMetadata(MetadataType.ExceptionDetails, fle.Message) + .WithMetadata(MetadataType.StackTrace, fle.StackTrace)); + } + catch (FileNotFoundException fnfe) + { + return FluentResults.Result.Fail(new ExceptionalError(fnfe) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, assemblyFilePath) + .WithMetadata(MetadataType.ExceptionDetails, fnfe.Message) + .WithMetadata(MetadataType.StackTrace, fnfe.StackTrace)); + } + catch (BadImageFormatException bife) + { + return FluentResults.Result.Fail(new ExceptionalError(bife) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, assemblyFilePath) + .WithMetadata(MetadataType.ExceptionDetails, bife.Message) + .WithMetadata(MetadataType.StackTrace, bife.StackTrace)); + } + catch (Exception e) + { + return FluentResults.Result.Fail(new ExceptionalError(e) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, assemblyFilePath) + .WithMetadata(MetadataType.ExceptionDetails, e.Message) + .WithMetadata(MetadataType.StackTrace, e.StackTrace)); + } } - catch (ArgumentNullException ane) + finally { - return FluentResults.Result.Fail(new ExceptionalError(ane) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, assemblyFilePath) - .WithMetadata(MetadataType.ExceptionDetails, ane.Message) - .WithMetadata(MetadataType.StackTrace, ane.StackTrace)); - } - catch (ArgumentException ae) - { - return FluentResults.Result.Fail(new ExceptionalError(ae) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, assemblyFilePath) - .WithMetadata(MetadataType.ExceptionDetails, ae.Message) - .WithMetadata(MetadataType.StackTrace, ae.StackTrace)); - } - catch (FileLoadException fle) - { - return FluentResults.Result.Fail(new ExceptionalError(fle) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, assemblyFilePath) - .WithMetadata(MetadataType.ExceptionDetails, fle.Message) - .WithMetadata(MetadataType.StackTrace, fle.StackTrace)); - } - catch (FileNotFoundException fnfe) - { - return FluentResults.Result.Fail(new ExceptionalError(fnfe) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, assemblyFilePath) - .WithMetadata(MetadataType.ExceptionDetails, fnfe.Message) - .WithMetadata(MetadataType.StackTrace, fnfe.StackTrace)); - } - catch (BadImageFormatException bife) - { - return FluentResults.Result.Fail(new ExceptionalError(bife) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, assemblyFilePath) - .WithMetadata(MetadataType.ExceptionDetails, bife.Message) - .WithMetadata(MetadataType.StackTrace, bife.StackTrace)); - } - catch (Exception e) - { - return FluentResults.Result.Fail(new ExceptionalError(e) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, assemblyFilePath) - .WithMetadata(MetadataType.ExceptionDetails, e.Message) - .WithMetadata(MetadataType.StackTrace, e.StackTrace)); + AreOperationRunning = false; } } public FluentResults.Result GetAssemblyByName(string assemblyName) { + if (IsDisposed) + return FluentResults.Result.Fail(new Error($"Loader is disposed!")); if (assemblyName.IsNullOrWhiteSpace()) { return FluentResults.Result.Fail(new Error($"Assembly name is null") .WithMetadata(MetadataType.ExceptionObject, this)); } - - if (_loadedAssemblyData.TryGetValue(assemblyName, out var data)) + AreOperationRunning = true; + try { - return new FluentResults.Result().WithSuccess(new Success($"Assembly found")).WithValue(data.Assembly); - } - - foreach (var assembly1 in this.Assemblies.Where(a => !_loadedAssemblyData.ContainsKey(a))) - { - if (assembly1.GetName().FullName == assemblyName) + if (_loadedAssemblyData.TryGetValue(assemblyName, out var data)) { - try - { - if (!assembly1.Location.IsNullOrWhiteSpace()) - { - _loadedAssemblyData[assembly1] = new AssemblyData(assembly1, assembly1.Location); - } - // we don't have the original byte array so we can't store it. - } - catch (NotSupportedException nse) // dynamic assembly or location property threw - { - // ignored - } - - return new FluentResults.Result().WithSuccess(new Success($"Assembly found")).WithValue(assembly1); + return new Result().WithSuccess(new Success($"Assembly found")).WithValue(data.Assembly); } - } - return FluentResults.Result.Fail(new Error($"Assembly named { assemblyName } not found!")); + // search any assemblies that were background loaded and we're unaware of. + foreach (var assembly1 in this.Assemblies.Where(a => !_loadedAssemblyData.ContainsKey(a))) + { + if (assembly1.GetName().FullName == assemblyName) + { + try + { + if (!assembly1.Location.IsNullOrWhiteSpace()) + { + _loadedAssemblyData[assembly1] = new AssemblyData(assembly1, assembly1.Location); + } + // we don't have the original byte array so we can't store it. + } + catch (NotSupportedException nse) // dynamic assembly or location property threw + { + // ignored + } + + return new Result().WithSuccess(new Success($"Assembly found")).WithValue(assembly1); + } + } + + return FluentResults.Result.Fail(new Error($"Assembly named { assemblyName } not found!")); + } + finally + { + AreOperationRunning = false; + } } public FluentResults.Result> GetTypesInAssemblies() { + if (IsDisposed) + return FluentResults.Result.Fail(new Error($"Loader is disposed!")); + AreOperationRunning = true; try { - return new FluentResults.Result>().WithValue(_loadedAssemblyData.SelectMany(kvp=> kvp.Value.Types).ToImmutableArray()); + return new FluentResults.Result>().WithValue(_loadedAssemblyData + .SelectMany(kvp => kvp.Value.Types).ToImmutableArray()); } catch (Exception e) { return FluentResults.Result.Fail(new ExceptionalError(e)); } + finally + { + AreOperationRunning = false; + } } - + + public IEnumerable UnsafeGetTypesInAssemblies() + { + if (IsDisposed) + yield return null; + AreOperationRunning = true; + try + { + if (_loadedAssemblyData.None()) + { + yield return null; + } + else + { + foreach (var assemblyData in _loadedAssemblyData.Values) + { + foreach (var type in assemblyData.Types) + { + yield return type; + } + } + } + } + finally + { + AreOperationRunning = false; + } + } + + public Result GetTypeInAssemblies(string typeName) + { + if (IsDisposed) + return FluentResults.Result.Fail(new Error($"Loader is disposed!")); + AreOperationRunning = true; + try + { + if (_loadedAssemblyData.IsEmpty) + return FluentResults.Result.Fail(new Error($"No assemblies loaded!")); + foreach (var assemblyData in _loadedAssemblyData) + { + if (assemblyData.Value.TypesByName.TryGetValue(typeName, out var type)) + return new FluentResults.Result().WithSuccess($"Found type.").WithValue(type); + } + return FluentResults.Result.Fail(new Error($"No matching types found for { typeName }!")); + } + finally + { + AreOperationRunning = false; + } + } + public void Dispose() { - Dispose(true); + if (IsDisposed) + return; // we don't want to invoke events twice nor cause strong GC handles. + IsDisposed = true; + this.Unload(); GC.SuppressFinalize(this); } - #endregion - - #region Internals - protected override Assembly Load(AssemblyName assemblyName) { if (_isResolving.Value) @@ -309,8 +433,8 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService { if (_loadedAssemblyData.TryGetValue(assemblyName.FullName, out var data)) return data.Assembly; - var idSpan = new[] { this.Id }; - if (_assemblyManagementService.GetLoadedAssembly(assemblyName, in idSpan) is { IsSuccess: true } ret) + var ids = new[] { this.Id }; + if (_assemblyManagementService.GetLoadedAssembly(assemblyName, in ids) is { IsSuccess: true } ret) return ret.Value; return null; } @@ -334,28 +458,22 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService private void OnUnload(AssemblyLoadContext context) { + IsDisposed = true; + + // Try to wait for loading ops on other threads if they happen to occur. + // Minor race condition on the loop exit but this loader is not intended to be thread-safe by design, this is just to cover edge cases. + DateTime timeout = DateTime.Now.AddSeconds(5); + while (timeout > DateTime.Now) + { + if (!AreOperationRunning) + break; + } + base.Unloading -= OnUnload; var wf = new WeakReference(this); - _eventService.PublishEvent((sub) => sub.OnAssemblyUnloading(wf)); _onUnload?.Invoke(this); - this.Dispose(true); - } - - private void Dispose(bool disposing) - { - if (ModUtils.Threading.CheckClearAndSetBool(ref _isDisposed)) - { - _operationsLock.EnterWriteLock(); - try - { - _loadedAssemblyData.Clear(); - - } - finally - { - _operationsLock.ExitWriteLock(); - } - } + this._dependencyResolvers.Clear(); + this._loadedAssemblyData.Clear(); } private readonly record struct AssemblyData @@ -364,6 +482,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService public readonly OneOf AssemblyImageOrPath; public readonly MetadataReference AssemblyReference; public readonly ImmutableArray Types; + public readonly ImmutableDictionary TypesByName; public AssemblyData(Assembly assembly, byte[] assemblyImage) { @@ -371,6 +490,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService AssemblyImageOrPath = assemblyImage ?? throw new ArgumentNullException(nameof(assemblyImage)); AssemblyReference = MetadataReference.CreateFromImage(assemblyImage); Types = assembly.GetSafeTypes().ToImmutableArray(); + TypesByName = Types.ToImmutableDictionary(type => type.FullName, type => type); } public AssemblyData(Assembly assembly, string path) @@ -379,6 +499,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService AssemblyImageOrPath = path ?? throw new ArgumentNullException(nameof(path)); AssemblyReference = MetadataReference.CreateFromFile(path); Types = assembly.GetSafeTypes().ToImmutableArray(); + TypesByName = Types.ToImmutableDictionary(type => type.FullName, type => type); } } @@ -423,6 +544,4 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService public static implicit operator AssemblyOrStringKey(Assembly assembly) => new AssemblyOrStringKey(assembly); public static implicit operator AssemblyOrStringKey(string name) => new AssemblyOrStringKey(name); } - - #endregion } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs index 629964ff7..6f4dc1159 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs @@ -97,10 +97,25 @@ public interface IAssemblyLoaderService : IService /// public FluentResults.Result> GetTypesInAssemblies(); + /// + /// Gets the list of Types from loaded assemblies. Does not create a defensive copy and blocks loading/unloading. + /// + /// + public IEnumerable UnsafeGetTypesInAssemblies(); + + /// + /// Returns the first found type given it's fully qualified name. + /// + /// + /// + public FluentResults.Result GetTypeInAssemblies(string typeName); + /// /// List of loaded assemblies. /// public IEnumerable Assemblies { get; } + + public IEnumerable AssemblyReferences { get; } } From 7436ea3e8c2dcc9ebc327d60eeec770b0176e5dd Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 14 Feb 2025 12:34:59 -0500 Subject: [PATCH 011/288] - Finished most of LuaCsSetup top-level functionality. - Removed some unneeded interface definitions. - Clean-slated some Services that need to be re-written. --- .../BarotraumaClient/ClientSource/GameMain.cs | 10 +- .../GameModes/Tutorials/Tutorial.cs | 2 - .../LuaCs/Data/DataInterfaceDefinitions.cs | 1 + .../ClientSource/LuaCs/LuaCsInstaller.cs | 2 +- .../ClientSource/LuaCs/LuaCsSettingsMenu.cs | 31 +- .../ClientSource/LuaCs/LuaCsSetup.cs | 86 +- .../Services/IStylesManagementService.cs | 13 + .../LuaCs/Services/IStylesService.cs | 25 +- .../LuaCs/Services/PackageService.cs | 42 - .../Processing/StylesManagementService.cs | 6 + .../LuaCs/Services/StylesService.cs | 46 +- .../ClientSource/Screens/ModDownloadScreen.cs | 5 +- .../BarotraumaServer/ServerSource/GameMain.cs | 6 +- .../ServerSource/LuaCs/LuaCsSetup.cs | 22 + .../LuaCs/Services/PackageService.cs | 29 - .../ContentPackageManager.cs | 28 +- .../LuaCs/Configuration/IConfigEntry.cs | 2 - .../LuaCs/Data/DataInterfaceDefinitions.cs | 4 +- .../LuaCs/Data/IResourceInfoDeclarations.cs | 4 +- .../SharedSource/LuaCs/IEvents.cs | 37 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 835 ++++++++++++------ .../SharedSource/LuaCs/LuaCsUtility.cs | 3 +- .../LuaCs/Services/EventService.cs | 16 +- .../LuaCs/Services/LoggerService.cs | 2 + .../Services/LuaScriptManagementService.cs | 14 + .../LuaCs/Services/LuaScriptService.cs | 176 ---- .../Services/PackageManagementService.cs | 450 +--------- .../LuaCs/Services/PackageService.cs | 686 -------------- .../LuaCs/Services/PluginManagementService.cs | 16 + .../IConverterServiceDefinitions.cs | 43 +- .../LuaCs/Services/StorageService.cs | 27 +- .../Services/_Interfaces/IConfigService.cs | 25 +- .../_Interfaces/ILocalizationService.cs | 4 +- .../ILuaScriptManagementService.cs | 88 ++ .../Services/_Interfaces/ILuaScriptService.cs | 85 -- .../_Interfaces/IPackageManagementService.cs | 120 +-- .../Services/_Interfaces/IPackageService.cs | 38 - .../_Interfaces/IPluginManagementService.cs | 24 +- .../SharedSource/Screens/Screen.cs | 1 + 39 files changed, 1009 insertions(+), 2045 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesManagementService.cs delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/StylesManagementService.cs create mode 100644 Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs delete mode 100644 Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index 953062de9..1126a166a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -18,12 +18,14 @@ using System.Reflection; using System.Threading; using Barotrauma.Extensions; using System.Collections.Immutable; +using Barotrauma.LuaCs.Events; namespace Barotrauma { class GameMain : Game { - public static LuaCsSetup LuaCs; + private static LuaCsSetup _luaCs; + public static LuaCsSetup LuaCs => _luaCs ??= new LuaCsSetup(); public static bool ShowFPS; public static bool ShowPerf; public static bool DebugDraw; @@ -244,8 +246,6 @@ namespace Barotrauma throw new Exception("Content folder not found. If you are trying to compile the game from the source code and own a legal copy of the game, you can copy the Content folder from the game's files to BarotraumaShared/Content."); } - LuaCs = new LuaCsSetup(); - GameSettings.Init(); CreatureMetrics.Init(); @@ -1054,7 +1054,7 @@ namespace Barotrauma SoundManager?.Update(); - GameMain.LuaCs.Update(); + LuaCs.EventService.PublishEvent(sub => sub.OnUpdate(Timing.Step)); Timing.Accumulator -= Timing.Step; @@ -1237,8 +1237,6 @@ namespace Barotrauma GUIMessageBox.CloseAll(); MainMenuScreen.Select(); GameSession = null; - - GameMain.LuaCs.Stop(); } public void ShowBugReporter() diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs index 4dfea9e86..3b6ac9488 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/Tutorials/Tutorial.cs @@ -179,8 +179,6 @@ namespace Barotrauma.Tutorials public void Start() { - GameMain.LuaCs.CheckInitialize(); - GameMain.Instance.ShowLoading(Loading()); ObjectiveManager.ResetObjectives(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs index d7be1b0cf..60e4ec227 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs @@ -18,5 +18,6 @@ public record StylesResourceInfo : IStylesResourceInfo public ImmutableArray SupportedCultures { get; init; } public string InternalName { get; init; } public ContentPackage OwnerPackage { get; init; } + public string FallbackPackageName { get; } public ImmutableArray Dependencies { get; init; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs index 422147beb..c18660d89 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs @@ -59,7 +59,7 @@ namespace Barotrauma { if (!File.Exists(LuaCsSetup.VersionFile)) { return; } - ContentPackage luaPackage = LuaCsSetup.GetPackage(LuaCsSetup.LuaForBarotraumaId); + ContentPackage luaPackage = LuaCsSetup.GetPackage(new SteamWorkshopId(GameMain.LuaCs.LuaForBarotraumaSteamId?.Value ?? 0)); if (luaPackage == null) { return; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs index 613a863d9..f1e5985bb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs @@ -19,65 +19,55 @@ namespace Barotrauma new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Enable CSharp Scripting") { - Selected = GameMain.LuaCs.Config.EnableCsScripting, + Selected = GameMain.LuaCs.IsCsEnabled?.Value ?? false, ToolTip = "This enables CSharp Scripting for mods to use, WARNING: CSharp is NOT sandboxed, be careful with what mods you download.", OnSelected = (GUITickBox tick) => { - GameMain.LuaCs.Config.EnableCsScripting = tick.Selected; - GameMain.LuaCs.WriteSettings(); - + GameMain.LuaCs.IsCsEnabled?.TrySetValue(tick.Selected); return true; } }; new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Treat Forced Mods As Normal") { - Selected = GameMain.LuaCs.Config.TreatForcedModsAsNormal, + Selected = GameMain.LuaCs.TreatForcedModsAsNormal?.Value ?? false, ToolTip = "This makes mods that were setup to run even when disabled to only run when enabled.", OnSelected = (GUITickBox tick) => { - GameMain.LuaCs.Config.TreatForcedModsAsNormal = tick.Selected; - GameMain.LuaCs.WriteSettings(); - + GameMain.LuaCs.TreatForcedModsAsNormal?.TrySetValue(tick.Selected); return true; } }; new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Prefer To Use Workshop Lua Setup") { - Selected = GameMain.LuaCs.Config.PreferToUseWorkshopLuaSetup, + Selected = GameMain.LuaCs.PreferToUseWorkshopLuaSetup?.Value ?? false, ToolTip = "This makes Lua look first for the Lua/LuaSetup.lua located in the Workshop package instead of the one located locally.", OnSelected = (GUITickBox tick) => { - GameMain.LuaCs.Config.PreferToUseWorkshopLuaSetup = tick.Selected; - GameMain.LuaCs.WriteSettings(); - + GameMain.LuaCs.PreferToUseWorkshopLuaSetup?.TrySetValue(tick.Selected); return true; } }; new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Disable Error GUI Overlay") { - Selected = GameMain.LuaCs.Config.DisableErrorGUIOverlay, + Selected = GameMain.LuaCs.DisableErrorGUIOverlay?.Value ?? false, ToolTip = "", OnSelected = (GUITickBox tick) => { - GameMain.LuaCs.Config.DisableErrorGUIOverlay = tick.Selected; - GameMain.LuaCs.WriteSettings(); - + GameMain.LuaCs.DisableErrorGUIOverlay?.TrySetValue(tick.Selected); return true; } }; new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Hide usernames In Error Logs") { - Selected = GameMain.LuaCs.Config.HideUserNames, + Selected = GameMain.LuaCs.HideUserNamesInLogs?.Value ?? false, ToolTip = "Hides the operating system username when displaying error logs (eg your username on windows).", OnSelected = (GUITickBox tick) => { - GameMain.LuaCs.Config.HideUserNames = tick.Selected; - GameMain.LuaCs.WriteSettings(); - + GameMain.LuaCs.HideUserNamesInLogs?.TrySetValue(tick.Selected); return true; } }; @@ -100,7 +90,6 @@ namespace Barotrauma OnClicked = (GUIButton button, object obj) => { Close(); - return true; } }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index b85175ded..19649aea8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -1,6 +1,13 @@ -using System.Collections.Generic; +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.Services; + +// ReSharper disable ObjectCreationAsStatement namespace Barotrauma { @@ -8,47 +15,42 @@ namespace Barotrauma { public void AddToGUIUpdateList() { - if (!GameMain.LuaCs.Config.DisableErrorGUIOverlay) + if (!DisableErrorGUIOverlay.Value) { LuaCsLogger.AddToGUIUpdateList(); } } - public void CheckInitialize() - { - List csharpMods = new List(); - foreach (ContentPackage cp in ContentPackageManager.EnabledPackages.All) - { - if (Directory.Exists(cp.Dir + "/CSharp") || Directory.Exists(cp.Dir + "/bin")) - { - csharpMods.Add(cp); - } - } + private partial bool ShouldRunCs() => IsCsEnabled.Value; + + public IStylesManagementService StylesManagementService => _servicesProvider.TryGetService(out var svc) + ? svc : throw new NullReferenceException("Networking Manager service not found!"); - if (csharpMods.Count == 0 || ShouldRunCs) - { - Initialize(); + public void CheckCsEnabled() + { + + var csharpMods = PackageManagementService.Assemblies + .GroupBy(ass => ass.OwnerPackage) + .Select(grp => grp.Key) + .Where(ContentPackageManager.EnabledPackages.All.Contains) + .ToImmutableArray(); + + if (!csharpMods.Any()) return; - } StringBuilder sb = new StringBuilder(); foreach (ContentPackage cp in csharpMods) { if (cp.UgcId.TryUnwrap(out ContentPackageId id)) - { sb.AppendLine($"- {cp.Name} ({id})"); - } else - { sb.AppendLine($"- {cp.Name} (Not On Workshop)"); - } } if (GameMain.Client == null || GameMain.Client.IsServerOwner) { new GUIMessageBox("", $"You have CSharp mods enabled but don't have the CSharp Scripting enabled, those mods might not work, go to the Main Menu, click on LuaCs Settings and check Enable CSharp Scripting.\n\n{sb}"); - Initialize(); return; } @@ -59,17 +61,51 @@ namespace Barotrauma msg.Buttons[0].OnClicked = (GUIButton button, object obj) => { - Initialize(true); - msg.Close(); + this.IsCsEnabled.TrySetValue(true); return true; }; msg.Buttons[1].OnClicked = (GUIButton button, object obj) => { - Initialize(); - msg.Close(); + this.IsCsEnabled.TrySetValue(false); return true; }; } + + /// + /// Handles changes in game states tracked by screen changes. + /// + /// The new game screen. + public partial void OnScreenSelected(Screen screen) + { + switch (screen) + { + // menus and navigation states + case MainMenuScreen: + case ModDownloadScreen: + case ServerListScreen: + SetRunState(RunState.Configuration); + break; + // running lobby or editor states + case CampaignEndScreen: + case CharacterEditorScreen: + case EventEditorScreen: + case GameScreen: + case LevelEditorScreen: + case NetLobbyScreen: + case ParticleEditorScreen: + case RoundSummaryScreen: + case SpriteEditorScreen: + case SubEditorScreen: + case TestScreen: // notes: TestScreen is a Linux edge case editor screen and is deprecated. + CheckCsEnabled(); + SetRunState(RunState.Running); + break; + default: + Logger.LogError($"{nameof(LuaCsSetup)}: Received an unknown screen {screen?.GetType().Name ?? "'null screen'"}. Retarding load state to 'unloaded'."); + SetRunState(RunState.Unloaded); + break; + } + } } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesManagementService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesManagementService.cs new file mode 100644 index 000000000..dc5dfc69a --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesManagementService.cs @@ -0,0 +1,13 @@ +using System.Collections.Immutable; +using System.Threading.Tasks; +using Barotrauma.LuaCs.Data; + +namespace Barotrauma.LuaCs.Services; + +public interface IStylesManagementService : IReusableService +{ + Task LoadStylesAsync(ImmutableArray styles); + FluentResults.Result GetStylesService(ContentPackage package); + Task DisposeAllStyles(); + Task DisposeStylesForPackage(ContentPackage package); +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs index 05cde12b4..6b07dbc21 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs @@ -1,4 +1,6 @@ -namespace Barotrauma.LuaCs.Services; +using System.Threading.Tasks; + +namespace Barotrauma.LuaCs.Services; // TODO: Rework interface to support resource infos. /// @@ -7,43 +9,48 @@ public interface IStylesService : IReusableService { /// - /// Tries to load the styles file for the given contentpackage and path into a new UIStylesProcessor instance. + /// Tries to load the styles file for the given and path into a new instance. /// /// /// /// - FluentResults.Result LoadStylesFile(ContentPackage package, ContentPath path); + Task LoadStylesFileAsync(ContentPackage package, ContentPath path); + /// - /// Unloads all styles assets and UIStyleProcessor instances. + /// Unloads all styles assets and instances. /// FluentResults.Result UnloadAllStyles(); /// - /// Tries to the get the font asset by xml asset name, returns null on failure. + /// Tries to the get the asset by xml asset name, returns null on failure. /// /// XML Name of the asset. /// The asset or null if none are found. GUIFont GetFont(string fontName); + /// - /// Tries to the get the sprite asset by xml asset name, returns null on failure. + /// Tries to the get the asset by xml asset name, returns null on failure. /// /// XML Name of the asset. /// The asset or null if none are found. GUISprite GetSprite(string spriteName); + /// - /// Tries to the get the sprite sheet asset by xml asset name, returns null on failure. + /// Tries to the get the asset by xml asset name, returns null on failure. /// /// XML Name of the asset. /// The asset or null if none are found. GUISpriteSheet GetSpriteSheet(string spriteSheetName); + /// - /// Tries to the get the cursor asset by xml asset name, returns null on failure. + /// Tries to the get the asset by xml asset name, returns null on failure. /// /// XML Name of the asset. /// The asset or null if none are found. GUICursor GetCursor(string cursorName); + /// - /// Tries to the get the color asset by xml asset name, returns null on failure. + /// Tries to the get the asset by xml asset name, returns null on failure. /// /// XML Name of the asset. /// The asset or null if none are found. diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs deleted file mode 100644 index 96c92093b..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageService.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services.Processing; - -namespace Barotrauma.LuaCs.Services; - -public partial class PackageService : IStylesResourcesInfo -{ - private readonly Lazy _stylesService; - public IStylesService Styles => _stylesService.Value; - - public PackageService( - Lazy configParserService, - Lazy luaScriptService, - Lazy localizationService, - Lazy pluginService, - Lazy stylesService, - Lazy configService, - IPackageManagementService packageManagementService, - IStorageService storageService, - ILoggerService loggerService) - { - _configParserService = configParserService; - _luaScriptService = luaScriptService; - _localizationService = localizationService; - _pluginService = pluginService; - _stylesService = stylesService; - _configService = configService; - _packageManagementService = packageManagementService; - _storageService = storageService; - _loggerService = loggerService; - } - - public ImmutableArray StylesResourceInfos => ModConfigInfo?.StylesResourceInfos ?? ImmutableArray.Empty; - - public FluentResults.Result LoadStyles([NotNull]IStylesResourcesInfo stylesInfo) - { - throw new NotImplementedException(); - } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/StylesManagementService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/StylesManagementService.cs new file mode 100644 index 000000000..49bd12eb2 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/StylesManagementService.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Services.Processing; + +public class StylesManagementService : IStylesManagementService +{ + +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs index 16c384157..29cb65429 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs @@ -1,16 +1,19 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using System.Xml.Linq; using FluentResults; using FluentResults.LuaCs; namespace Barotrauma.LuaCs.Services; +// TODO: Complete rewrite public class StylesService : IStylesService { - private readonly Dictionary _loadedProcessors = new(); + private readonly ConcurrentDictionary _loadedProcessors = new(); private readonly IStorageService _storageService; private readonly ILoggerService _loggerService; @@ -19,40 +22,11 @@ public class StylesService : IStylesService _storageService = storageService; _loggerService = loggerService; } - - public FluentResults.Result LoadStylesFile(ContentPackage package, ContentPath path) + + + public async Task LoadStylesFileAsync(ContentPackage package, ContentPath path) { - //check if file already in dict - if (_loadedProcessors.ContainsKey(path.FullPath)) - { - return FluentResults.Result.Ok(); - } - //check if file exists - if (_storageService.FileExists(path.FullPath) is {} result - && result.IsFailed | (result.IsSuccess & result.Value == false)) - { - return FluentResults.Result.Fail(result.Errors) - .WithError(new Error($"{nameof(StylesService)}.{nameof(LoadStylesFile)} file does not exist!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, package)); - } - - try - { - var styleProcessor = new UIStyleProcessor(package, path); - styleProcessor.LoadFile(); - _loadedProcessors.Add(path.FullPath, styleProcessor); - } - catch (InvalidDataException exception) - { - return FluentResults.Result.Fail(new Error($"{nameof(StylesService)}.{nameof(LoadStylesFile)} failed for ContentPackage {package.Name}: Exception: {exception.Message}") - .WithMetadata(MetadataType.ExceptionDetails, exception.Message) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, package) - .WithMetadata(MetadataType.StackTrace, exception.StackTrace)); - } - - return FluentResults.Result.Ok(); + throw new NotImplementedException(); } public FluentResults.Result UnloadAllStyles() @@ -134,7 +108,7 @@ public class StylesService : IStylesService return null; } - private bool NoProcessorsLoaded => _loadedProcessors.Count < 1; + private bool NoProcessorsLoaded => _loadedProcessors.IsEmpty; public void Dispose() { @@ -146,4 +120,6 @@ public class StylesService : IStylesService { return UnloadAllStyles(); } + + public bool IsDisposed { get; private set; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs index e3d878e81..8c0889e6e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using Barotrauma.Extensions; using Barotrauma.IO; +using Barotrauma.LuaCs.Events; using Barotrauma.Networking; using Barotrauma.Steam; using Microsoft.Xna.Framework; @@ -118,7 +119,6 @@ namespace Barotrauma ContentPackageManager.EnabledPackages.SetRegular(regularPackages); } GameMain.NetLobbyScreen.Select(); - GameMain.LuaCs.CheckInitialize(); return; } @@ -366,7 +366,7 @@ namespace Barotrauma ContentPackageManager.EnabledPackages.BackUp(); ContentPackageManager.EnabledPackages.SetCore(corePackage); ContentPackageManager.EnabledPackages.SetRegular(regularPackages); - + //see if any of the packages we enabled contain subs that we were missing previously, and update their paths foreach (var serverSub in GameMain.Client.ServerSubmarines) { @@ -379,7 +379,6 @@ namespace Barotrauma } GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.SubList, GameMain.Client.ServerSubmarines); GameMain.NetLobbyScreen.Select(); - GameMain.LuaCs.CheckInitialize(); } } else if (GameMain.Client.FileReceiver.ActiveTransfers.None()) diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs index b95af9c57..18bce7393 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs @@ -13,6 +13,7 @@ using System.Xml.Linq; using MoonSharp.Interpreter; using System.Net; using Barotrauma.Extensions; +using Barotrauma.LuaCs.Events; namespace Barotrauma { @@ -34,7 +35,8 @@ namespace Barotrauma set { world = value; } } - public static LuaCsSetup LuaCs; + private static LuaCsSetup _luaCs; + public static LuaCsSetup LuaCs => _luaCs ??= new LuaCsSetup(); public static GameServer Server; public static NetworkMember NetworkMember @@ -364,7 +366,7 @@ namespace Barotrauma TaskPool.Update(); CoroutineManager.Update(paused: false, (float)Timing.Step); - GameMain.LuaCs.Update(); + LuaCs.EventService.PublishEvent(sub => sub.OnUpdate(Timing.Step)); performanceCounterTimer.Stop(); if (GameMain.LuaCs.PerformanceCounter.EnablePerformanceCounter) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs new file mode 100644 index 000000000..9eaca6c7d --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs @@ -0,0 +1,22 @@ +using Barotrauma.Networking; + +namespace Barotrauma; + +partial class LuaCsSetup +{ + /// + /// Handles changes in game states tracked by screen changes. + /// + /// The new game screen. + public partial void OnScreenSelected(Screen screen) + { + // the server is always in the running state unless explicitly stopped. + if (screen == UnimplementedScreen.Instance) + SetRunState(RunState.Unloaded); + SetRunState(RunState.Running); + } + + private partial bool ShouldRunCs() => IsCsEnabled.Value || + (GetPackage(new SteamWorkshopId(CsForBarotraumaSteamId.Value), false, false) is { } + && GameMain.Server.ServerPeer is LidgrenServerPeer); +} diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs deleted file mode 100644 index d64643ebb..000000000 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageService.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using Barotrauma.LuaCs.Services.Processing; - -// ReSharper disable once CheckNamespace -namespace Barotrauma.LuaCs.Services; - -public partial class PackageService -{ - public PackageService( - Lazy configParserService, - Lazy luaScriptService, - Lazy localizationService, - Lazy pluginService, - Lazy configService, - IPackageManagementService packageManagementService, - IStorageService storageService, - ILoggerService loggerService) - { - _configParserService = configParserService; - _luaScriptService = luaScriptService; - _localizationService = localizationService; - _pluginService = pluginService; - _configService = configService; - _packageManagementService = packageManagementService; - _storageService = storageService; - _loggerService = loggerService; - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs index 79ce7be3f..20f0a7970 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs @@ -9,6 +9,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Xml.Linq; using Barotrauma.IO; +using Barotrauma.LuaCs.Events; using Barotrauma.Steam; using Microsoft.Xna.Framework; @@ -48,7 +49,12 @@ namespace Barotrauma public static ImmutableArray? Regular; } - public static void SetCore(CorePackage newCore) => SetCoreEnumerable(newCore).Consume(); + public static void SetCore(CorePackage newCore) + { + SetCoreEnumerable(newCore).Consume(); + GameMain.LuaCs.EventService.PublishEvent( + sub => sub.OnEnabledPackageListChanged(Core, Regular)); + } public static IEnumerable SetCoreEnumerable(CorePackage newCore) { @@ -85,7 +91,11 @@ namespace Barotrauma } public static void SetRegular(IReadOnlyList newRegular) - => SetRegularEnumerable(newRegular).Consume(); + { + SetRegularEnumerable(newRegular).Consume(); + GameMain.LuaCs.EventService.PublishEvent( + sub => sub.OnEnabledPackageListChanged(Core, Regular)); + } public static IEnumerable SetRegularEnumerable(IReadOnlyList inNewRegular) { @@ -327,6 +337,12 @@ namespace Barotrauma Debug.WriteLine($"Loaded \"{newPackage.Name}\""); } + + GameMain.LuaCs.EventService.PublishEvent(sub => + sub.OnAllPackageListChanged(corePackages + .Select((ContentPackage p) => p) + .Union(regularPackages.Select((ContentPackage p) => p)) + .ToImmutableArray())); } private readonly string directory; @@ -566,6 +582,12 @@ namespace Barotrauma yield return p.Transform(loadingRange); } + GameMain.LuaCs.EventService.PublishEvent( + sub => sub.OnAllPackageListChanged(CorePackages, RegularPackages)); + + GameMain.LuaCs.EventService.PublishEvent( + sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); + yield return LoadProgress.Progress(1.0f); } @@ -597,4 +619,4 @@ namespace Barotrauma } } } -} \ No newline at end of file +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs index 8f5325eca..30ba129c6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs @@ -9,6 +9,4 @@ public interface IConfigEntry : IConfigBase, INetVar where T : IConvertible, bool TrySetValue(T value); bool IsAssignable(T value); void Initialize(IVarId id, T defaultValue); - - } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs index f620bbbce..f5e169005 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs @@ -19,7 +19,7 @@ public partial record ModConfigInfo : IModConfigInfo public ImmutableArray SupportedCultures { get; init; } public ImmutableArray Assemblies { get; init; } public ImmutableArray Localizations { get; init; } - public ImmutableArray LuaScripts { get; init; } + public ImmutableArray LuaScripts { get; init; } public ImmutableArray Configs { get; init; } public ImmutableArray ConfigProfiles { get; init; } } @@ -160,7 +160,7 @@ public record LocalizationResourceInfo : ILocalizationResourceInfo public bool Optional { get; init; } } -public readonly struct LuaScriptResourceInfo : ILuaResourceInfo +public readonly struct LuaScriptScriptResourceInfo : ILuaScriptResourceInfo { public ContentPackage OwnerPackage { get; init; } public string FallbackPackageName { get; init; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs index c92fc1ec0..281b20b04 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs @@ -10,7 +10,7 @@ public interface ILocalizationResourceInfo : IResourceInfo, IResourceCultureInfo /// /// Represents loadable Lua files. /// -public interface ILuaResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { } +public interface ILuaScriptResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { } public interface IAssemblyResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { /// @@ -39,7 +39,7 @@ public interface ILocalizationsResourcesInfo public interface ILuaScriptsResourcesInfo { - ImmutableArray LuaScripts { get; } + ImmutableArray LuaScripts { get; } } public interface IConfigsResourcesInfo diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index b82eef22f..1e7bc2620 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -29,6 +29,35 @@ public interface IEvent : IEvent where T : IEvent } } +#region RuntimeEvents + +/// +/// Called when the current (game state) changes. Upstream Type 'Screen' is internal. +/// +internal interface IEventScreenSelected : IEvent +{ + void OnScreenSelected(Screen screen); +} + +/// +/// Called whenever the list of all (enabled and disabled) on disk has changed. +/// +internal interface IEventAllPackageListChanged : IEvent +{ + void OnAllPackageListChanged(IEnumerable corePackages, IEnumerable regularPackages); +} + +/// +/// Called whenever the list of enabled has changed. +/// +internal interface IEventEnabledPackageListChanged : IEvent +{ + void OnEnabledPackageListChanged(CorePackage package, IEnumerable regularPackages); +} + + +#endregion + #region GameEvents /// @@ -62,11 +91,11 @@ public interface IEventRoundStarted : IEvent /// public interface IEventUpdate : IEvent { - void OnUpdate(float fixedDeltaTime); + void OnUpdate(double fixedDeltaTime); static IEventUpdate IEvent.GetLuaRunner(IDictionary luaFunc) => new { IsLuaRunner = Return.Arguments(() => true), - OnUpdate = ReturnVoid.Arguments((fixedDeltaTime) => luaFunc[nameof(OnUpdate)](fixedDeltaTime)) + OnUpdate = ReturnVoid.Arguments((fixedDeltaTime) => luaFunc[nameof(OnUpdate)](fixedDeltaTime)) }.ActLike(); } @@ -75,11 +104,11 @@ public interface IEventUpdate : IEvent /// public interface IEventDrawUpdate : IEvent { - void OnDrawUpdate(float deltaTime); + void OnDrawUpdate(double deltaTime); static IEventDrawUpdate IEvent.GetLuaRunner(IDictionary luaFunc) => new { IsLuaRunner = Return.Arguments(() => true), - OnDrawUpdate = ReturnVoid.Arguments((deltaTime) => luaFunc[nameof(OnDrawUpdate)](deltaTime)) + OnDrawUpdate = ReturnVoid.Arguments((deltaTime) => luaFunc[nameof(OnDrawUpdate)](deltaTime)) }.ActLike(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 232ea11bc..cf3250c2a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -1,130 +1,77 @@ using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Barotrauma.LuaCs.Configuration; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Events; using Barotrauma.LuaCs.Services; using Barotrauma.LuaCs.Services.Compatibility; using Barotrauma.LuaCs.Services.Processing; using Barotrauma.Networking; +using FluentResults; +using ImpromptuInterface; namespace Barotrauma { - class LuaCsSetupConfig - { - public bool EnableCsScripting = false; - public bool TreatForcedModsAsNormal = true; - public bool PreferToUseWorkshopLuaSetup = false; - public bool DisableErrorGUIOverlay = false; - public bool HideUserNames - { - get { return LuaCsLogger.HideUserNames; } - set { LuaCsLogger.HideUserNames = value; } - } - - public LuaCsSetupConfig() { } - public LuaCsSetupConfig(LuaCsSetupConfig config) - { - EnableCsScripting = config.EnableCsScripting; - TreatForcedModsAsNormal = config.TreatForcedModsAsNormal; - PreferToUseWorkshopLuaSetup = config.PreferToUseWorkshopLuaSetup; - DisableErrorGUIOverlay = config.DisableErrorGUIOverlay; - } - } - internal delegate void LuaCsMessageLogger(string message); internal delegate void LuaCsErrorHandler(Exception ex, LuaCsMessageOrigin origin); internal delegate void LuaCsExceptionHandler(Exception ex, LuaCsMessageOrigin origin); - partial class LuaCsSetup : IDisposable + partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventAllPackageListChanged, IEventEnabledPackageListChanged { public LuaCsSetup() { - // load services + // == startup _servicesProvider = new ServicesProvider(); RegisterServices(); - - // load manifest - if (!_servicesProvider.TryGetService(out IModConfigCreatorService modConfigSvc)) - throw new NullReferenceException("LuaCsSetup: Failed to get mod config parser service!"); // we should crash here - var luaConfig = modConfigSvc.BuildConfigFromManifest(Directory.GetCurrentDirectory() + LuaCsConfigFile); - if (!luaConfig.IsSuccess) - { - Logger.LogResults(luaConfig.ToResult()); - throw new FileLoadException("LuaCsSetup: Failed to load config file!"); - } - - // load resources - RegisterLocalizations(); - RegisterConfigs(); - - LuaForBarotraumaId = new SteamWorkshopId(LuaForBarotraumaSteamId.Value); + ValidateLuaCsContent(); + SubscribeToLuaCsEvents(); return; - //--- + // == end + // == helpers void RegisterServices() { _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); +#if CLIENT + _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); +#endif _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + // TODO: ILocalizationService // TODO: IConfigService // TODO: INetworkingService // TODO: [Resource Converter/Parser Services] - // TODO: ILocalizationService - // TODO: IEventService + _servicesProvider.Compile(); } - void RegisterLocalizations() + // Validates LuaCs assets in /Content are valid and ready to use. + void ValidateLuaCsContent() { - LocalizationService.LoadLocalizations(luaConfig.Value.Localizations); + throw new NotImplementedException(); } - - void RegisterConfigs() - { - if (ConfigService.AddConfigs(luaConfig.Value.Configs) is { IsSuccess: false } res1) - { - Logger.LogResults(res1); - throw new Exception("LuaCsSetup: Failed to load config!"); - } - - if (ConfigService.AddConfigsProfiles(luaConfig.Value.ConfigProfiles) is { IsSuccess: false } res2) - { - Logger.LogResults(res2); - throw new Exception("LuaCsSetup: Failed to load config profiles!"); - } - - IsCsEnabled = GetOrThrowForConfig(luaConfig.Value.PackageName, "IsCsEnabled"); - TreatForcedModsAsNormal = GetOrThrowForConfig(luaConfig.Value.PackageName, "TreatForcedModsAsNormal"); - PreferToUseWorkshopLuaSetup = GetOrThrowForConfig(luaConfig.Value.PackageName, "PreferToUseWorkshopLuaSetup"); - DisableErrorGUIOverlay = GetOrThrowForConfig(luaConfig.Value.PackageName, "DisableErrorGUIOverlay"); - EnableThreadedLoading = GetOrThrowForConfig(luaConfig.Value.PackageName, "EnableThreadedLoading"); - HideUserNamesInLogs = GetOrThrowForConfig(luaConfig.Value.PackageName, "HideUserNamesInLogs"); - LuaForBarotraumaSteamId = GetOrThrowForConfig(luaConfig.Value.PackageName, "LuaForBarotraumaSteamId"); - - return; - //--- - - IConfigEntry GetOrThrowForConfig(string packName, string internalName) where T : IConvertible, IEquatable - { - var cfgRes = ConfigService.GetConfig>(packName, internalName); - if (cfgRes.IsSuccess) - { - return cfgRes.Value; - } - Logger.LogResults(cfgRes.ToResult()); - throw new Exception($"LuaCsSetup: Failed to load config for {internalName}!"); - } - } - - + } + + void SubscribeToLuaCsEvents() + { + EventService.Subscribe(this); // game state hook in + EventService.Subscribe(this); + EventService.Subscribe(this); } #region CONST_DEF - - public const string LuaCsConfigFile = "LuaCsConfig.xml"; #if SERVER public const bool IsServer = true; @@ -154,7 +101,7 @@ namespace Barotrauma ? svc : throw new NullReferenceException("Package Manager service not found!"); public IPluginManagementService PluginManagementService => _servicesProvider.TryGetService(out var svc) ? svc : throw new NullReferenceException("Plugin Manager service not found!"); - public ILuaScriptManagementService LuaScriptService => _servicesProvider.TryGetService(out var svc) + public ILuaScriptManagementService LuaScriptManagementService => _servicesProvider.TryGetService(out var svc) ? svc : throw new NullReferenceException("Lua Script Manager service not found!"); public ILocalizationService LocalizationService => _servicesProvider.TryGetService(out var svc) ? svc : throw new NullReferenceException("Localization Manager service not found!"); @@ -187,17 +134,42 @@ namespace Barotrauma /// public IConfigEntry DisableErrorGUIOverlay { get; private set; } - /// - /// [Experimental] Whether multithreading should be used for loading. - /// - public IConfigEntry EnableThreadedLoading { get; private set; } - /// /// Whether usernames are anonymized or show in logs. /// public IConfigEntry HideUserNamesInLogs { get; private set; } - private IConfigEntry LuaForBarotraumaSteamId { get; set; } + /// + /// The SteamId of the Workshop LuaCs CPackage in use, if available. + /// + public IConfigEntry LuaForBarotraumaSteamId { get; private set; } + + /// + /// The SteamId of the Workshop LuaCs CsForBarotrauma add-on, if available. + /// + public IConfigEntry CsForBarotraumaSteamId { get; private set; } + + /// + /// Whether to (re)load all package assets when a lobby starts/code session begins. + /// Intended for development use, or when packages are expected to change outside of External Updates (ie. Steam Workshop). + /// + public IConfigEntry ReloadPackagesOnLobbyStart { get; private set; } + + /** + * == Ops Vars + */ + private RunState _runState; + /// + /// The current run state of all services managed by LuaCs. + /// + public RunState CurrentRunState => _runState; + + private bool CPacksParsed => CurrentRunState >= RunState.Parsed; + private bool IsStaticAssetsLoaded => CurrentRunState >= RunState.Configuration; + private bool IsCodeRunning => CurrentRunState >= RunState.Running; + + private readonly ConcurrentQueue _toLoad = new(); + private readonly ConcurrentQueue _toUnload = new(); #endregion @@ -208,13 +180,6 @@ namespace Barotrauma #endregion - /// - /// Whether mod content is loaded and being executed. - /// - public bool IsModContentRunning { get; private set; } - - public readonly ContentPackageId LuaForBarotraumaId; - public static bool IsRunningInsideWorkshop { get @@ -227,84 +192,16 @@ namespace Barotrauma } } - /*public Script Lua { get; private set; } - public LuaScriptLoader LuaScriptLoader { get; private set; } - - public LuaGame Game { get; private set; } - public LuaCsHook Hook { get; private set; } - public LuaCsTimer Timer { get; private set; } - public LuaCsNetworking Networking { get; private set; } - public LuaCsSteam Steam { get; private set; } - - // must be available at anytime - private static AssemblyManager _assemblyManager; - public static AssemblyManager AssemblyManager => _assemblyManager ??= new AssemblyManager(); - - private CsPackageManager _pluginPackageManager; - public CsPackageManager PluginPackageManager => _pluginPackageManager ??= new CsPackageManager(AssemblyManager, this); - private LuaRequire Require { get; set; } - public LuaCsSetupConfig Config { get; private set; } - public MoonSharpVsCodeDebugServer DebugServer { get; private set; } - public bool IsInitialized { get; private set; }*/ - - private bool ShouldRunCs - { - get - { -#if SERVER - if (GetPackage(CsForBarotraumaId, false, false) != null && GameMain.Server.ServerPeer is LidgrenServerPeer) { return true; } -#endif - return IsCsEnabled.Value; - } - } - + private partial bool ShouldRunCs(); - [Obsolete("Use AssemblyManager::GetTypesByName()")] + // TODO: Rework + [Obsolete("Use IPluginManagementService::GetTypesByName()")] public static Type GetType(string typeName, bool throwOnError = false, bool ignoreCase = false) { throw new NotImplementedException(); //return AssemblyManager.GetTypesByName(typeName).FirstOrDefault((Type)null); } - - // Old config ref - /*public void ReadSettings() - { - Config = new LuaCsSetupConfig(); - - if (File.Exists(configFileName)) - { - try - { - using (var file = File.Open(configFileName, FileMode.Open, FileAccess.Read)) - { - XDocument document = XDocument.Load(file); - Config.EnableCsScripting = document.Root.GetAttributeBool("EnableCsScripting", Config.EnableCsScripting); - Config.TreatForcedModsAsNormal = document.Root.GetAttributeBool("TreatForcedModsAsNormal", Config.TreatForcedModsAsNormal); - Config.PreferToUseWorkshopLuaSetup = document.Root.GetAttributeBool("PreferToUseWorkshopLuaSetup", Config.PreferToUseWorkshopLuaSetup); - Config.DisableErrorGUIOverlay = document.Root.GetAttributeBool("DisableErrorGUIOverlay", Config.DisableErrorGUIOverlay); - Config.HideUserNames = document.Root.GetAttributeBool("HideUserNames", Config.HideUserNames); - } - } - catch (Exception e) - { - LuaCsLogger.HandleException(e, LuaCsMessageOrigin.LuaCs); - } - } - } - - public void WriteSettings() - { - XDocument document = new XDocument(); - document.Add(new XElement("LuaCsSetupConfig")); - document.Root.SetAttributeValue("EnableCsScripting", Config.EnableCsScripting); - document.Root.SetAttributeValue("EnableCsScripting", Config.EnableCsScripting); - document.Root.SetAttributeValue("TreatForcedModsAsNormal", Config.TreatForcedModsAsNormal); - document.Root.SetAttributeValue("PreferToUseWorkshopLuaSetup", Config.PreferToUseWorkshopLuaSetup); - document.Root.SetAttributeValue("DisableErrorGUIOverlay", Config.DisableErrorGUIOverlay); - document.Root.SetAttributeValue("HideUserNames", Config.HideUserNames); - document.Save(configFileName); - }*/ public static ContentPackage GetPackage(ContentPackageId id, bool fallbackToAll = true, bool useBackup = false) { @@ -349,111 +246,495 @@ namespace Barotrauma return null; } - // Old code ref - /*private DynValue DoFile(string file, Table globalContext = null, string codeStringFriendly = null) - { - if (!LuaCsFile.CanReadFromPath(file)) - { - throw new ScriptRuntimeException($"dofile: File access to {file} not allowed."); - } - - if (!LuaCsFile.Exists(file)) - { - throw new ScriptRuntimeException($"dofile: File {file} not found."); - } - - return Lua.DoFile(file, globalContext, codeStringFriendly); - } - - private DynValue LoadFile(string file, Table globalContext = null, string codeStringFriendly = null) - { - if (!LuaCsFile.CanReadFromPath(file)) - { - throw new ScriptRuntimeException($"loadfile: File access to {file} not allowed."); - } - - if (!LuaCsFile.Exists(file)) - { - throw new ScriptRuntimeException($"loadfile: File {file} not found."); - } - - return Lua.LoadFile(file, globalContext, codeStringFriendly); - } - - public DynValue CallLuaFunction(object function, params object[] args) - { - // XXX: `lua` might be null if `LuaCsSetup.Stop()` is called while - // a patched function is still running. - if (Lua == null) { return null; } - - lock (Lua) - { - try - { - return Lua.Call(function, args); - } - catch (Exception e) - { - LuaCsLogger.HandleException(e, LuaCsMessageOrigin.LuaMod); - } - return null; - } - } - - private void SetModulePaths(string[] str) - { - LuaScriptLoader.ModulePaths = str; - } - - public void Update() - { - Timer?.Update(); - Steam?.Update(); - -#if CLIENT - Stopwatch luaSw = new Stopwatch(); - luaSw.Start(); -#endif - Hook?.Call("think"); -#if CLIENT - luaSw.Stop(); - GameMain.PerformanceCounter.AddElapsedTicks("Think Hook", luaSw.ElapsedTicks); -#endif - } - - */ - public void Stop() - { - - - IsInitialized = false; - } - - public void Initialize(bool forceEnableCs = false) - { - if (IsInitialized) - { - Stop(); - } - - IsInitialized = true; - - Logger.Log($"Initializing LuaCs, git revision = {AssemblyInfo.GitRevision}"); - } - - public void Update() - { - throw new NotImplementedException(); - } - - public void Reset() - { - throw new NotImplementedException(); - } - public void Dispose() { - // TODO release managed resources here + try + { + SetRunState(RunState.Unloaded); + } + catch (Exception e) + { + Logger.LogError(e.Message); + } + + try + { + DisposeLuaCsConfig(); + + PluginManagementService.Dispose(); + LuaScriptManagementService.Dispose(); +#if CLIENT + StylesManagementService.Dispose(); +#endif + ConfigService.Dispose(); + LocalizationService.Dispose(); + PackageManagementService.Dispose(); + // TODO: Add all missing services. + //NetworkingService.Dispose(); + EventService.Dispose(); + + _servicesProvider.DisposeAndReset(); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + + GC.SuppressFinalize(this); + } + + /// + /// Handles changes in game states tracked by screen changes. + /// + /// The new game screen. + public partial void OnScreenSelected(Screen screen); + + public void OnAllPackageListChanged(IEnumerable corePackages, IEnumerable regularPackages) + { + UpdateLoadedPackagesList(); + } + + public void OnEnabledPackageListChanged(CorePackage corePackage, IEnumerable regularPackages) + { + UpdateLoadedPackagesList(); + } + + private void UpdateLoadedPackagesList() + { + var newPackSet = ContentPackageManager.AllPackages + .Union(ContentPackageManager.EnabledPackages.All) + .ToHashSet(); + var currPackSet = PackageManagementService.GetAllLoadedPackages().ToHashSet(); + var toAdd = newPackSet.Except(currPackSet); + var toRemove = currPackSet.Except(newPackSet); + foreach (var package in toAdd) + _toLoad.Enqueue(package); + foreach (var package in toRemove) + _toUnload.Enqueue(package); + + + ProcessPackagesListDifferences(); + } + + void ProcessPackagesListDifferences() + { + if (IsCodeRunning) + return; + + // no reason to do anything if we're fully unloaded. + if (!CPacksParsed) + { + _toLoad.Clear(); + _toUnload.Clear(); + } + + while (_toUnload.TryDequeue(out var cp)) + { + LuaScriptManagementService.DisposePackageResources(cp); + ConfigService.DisposeConfigsProfiles(cp); + ConfigService.DisposeConfigs(cp); +#if CLIENT + StylesManagementService.DisposeStylesForPackage(cp); +#endif + LocalizationService.DisposePackage(cp); + PackageManagementService.DisposePackageInfos(cp); + } + + var ls = new List(); + + while (_toLoad.TryDequeue(out var cp)) + { + if (PackageManagementService.LoadPackageInfosAsync(cp).GetAwaiter().GetResult() is + { IsFailed: true } failure) + { + Logger.LogError($"Failed to load package infos for {cp.Name}"); + Logger.LogResults(failure); + continue; + } + + ls.Add(cp); + } + + if (ls.Any()) + { + LoadStaticAssetsAsync(ls).GetAwaiter().GetResult(); + } + } + + void SetRunState(RunState runState) + { + if (CurrentRunState == runState) + return; + if (runState > CurrentRunState) + { + if (CurrentRunState < RunState.Parsed) + LoadCurrentContentPackageInfos(); + + if (runState <= CurrentRunState) + return; + + if (CurrentRunState < RunState.Configuration) + LoadStaticAssets(); + + if (runState <= CurrentRunState) + return; + + if (CurrentRunState < RunState.Running) + RunScripts(); + } + else if (runState < CurrentRunState) + { + if (CurrentRunState >= RunState.Running) + { + StopScripts(); + ProcessPackagesListDifferences(); + _runState = RunState.Configuration; + } + + if (runState >= CurrentRunState) + return; + + if (CurrentRunState == RunState.Configuration) + { + UnloadStaticAssets(); + _runState = RunState.Parsed; + } + + if (runState >= CurrentRunState) + return; + + if (CurrentRunState == RunState.Parsed) + { + UnloadContentPackageInfos(); + _runState = RunState.Unloaded; + } + + // we should be unloaded completely now | RunState.Unloaded + } + } + + void LoadCurrentContentPackageInfos() + { + if (CurrentRunState >= RunState.Parsed) + return; + + // load core + var result1 = PackageManagementService.LoadPackageInfosAsync(ContentPackageManager.VanillaCorePackage) + .GetAwaiter().GetResult(); + if (result1.IsFailed) + { + Logger.LogError($"Unable to load LuaCs CorePackage resources! Running in degraded mode."); + Logger.LogResults(result1); + } + + // load regular + var list = ContentPackageManager.RegularPackages + .Union(ContentPackageManager.EnabledPackages.All) + .ToImmutableList(); + + LoadContentPackagesInfos(list); + + if (CurrentRunState < RunState.Parsed) + _runState = RunState.Parsed; + } + + void LoadContentPackagesInfos(IReadOnlyList packages) + { + var result2 = PackageManagementService.LoadPackagesInfosAsync(packages) + .GetAwaiter().GetResult(); + + foreach (var entry in result2) + { + if (entry.Item2.IsSuccess) + Logger.LogMessage($"Successfully parsed package: {entry.Item1.Name}"); + else if (entry.Item2.IsFailed) + Logger.LogResults(entry.Item2); + } + } + + void LoadStaticAssets() + { + if (CurrentRunState < RunState.Parsed) + { + throw new InvalidOperationException($"{nameof(LoadStaticAssets)} cannot load assets in the '{CurrentRunState}' state."); + } + + if (CurrentRunState >= RunState.Configuration) + return; + + while (_toUnload.TryDequeue(out var cp)) + PackageManagementService.DisposePackageInfos(cp); + + LoadStaticAssetsAsync(PackageManagementService.GetAllLoadedPackages()).GetAwaiter().GetResult(); + LoadLuaCsConfig(); + + if (CurrentRunState < RunState.Configuration) + _runState = RunState.Configuration; + } + + void LoadLuaCsConfig() + { + IsCsEnabled = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "IsCsEnabled") + ?? throw new NullReferenceException($"{nameof(IsCsEnabled)} cannot be loaded."); + TreatForcedModsAsNormal = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "TreatForcedModsAsNormal") + ?? throw new NullReferenceException($"{nameof(TreatForcedModsAsNormal)} cannot be loaded."); + DisableErrorGUIOverlay = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "DisableErrorGUIOverlay") + ?? throw new NullReferenceException($"{nameof(DisableErrorGUIOverlay)} cannot be loaded."); + HideUserNamesInLogs = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "HideUserNamesInLogs") + ?? throw new NullReferenceException($"{nameof(HideUserNamesInLogs)} cannot be loaded."); + LuaForBarotraumaSteamId = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "LuaForBarotraumaSteamId") + ?? throw new NullReferenceException($"{nameof(LuaForBarotraumaSteamId)} cannot be loaded."); + CsForBarotraumaSteamId = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "CsForBarotraumaSteamId") + ?? throw new NullReferenceException($"{nameof(CsForBarotraumaSteamId)} cannot be loaded."); + } + + void DisposeLuaCsConfig() + { + IsCsEnabled = null; + TreatForcedModsAsNormal = null; + DisableErrorGUIOverlay = null; + HideUserNamesInLogs = null; + LuaForBarotraumaSteamId = null; + CsForBarotraumaSteamId = null; + } + + async Task LoadStaticAssetsAsync(IReadOnlyList packages) + { + var locRes = ImmutableArray.Empty; + var cfgRes = ImmutableArray.Empty; + var cfpRes = ImmutableArray.Empty; + var luaRes = ImmutableArray.Empty; + +#if CLIENT + var styleRes = ImmutableArray.Empty; +#endif + + var tasksBuilder = ImmutableArray.CreateBuilder(); + + //---- get resource infos + tasksBuilder.AddRange(new Func(async () => + { + var res = await PackageManagementService.GetLocalizationsInfosAsync(packages); + if (res.IsSuccess) + locRes = res.Value.Localizations; + if (res.Errors.Any()) + ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), + res.ToResult()); + })(), + new Func(async () => + { + var res = await PackageManagementService.GetConfigsInfosAsync(packages); + if (res.IsSuccess) + cfgRes = res.Value.Configs; + if (res.Errors.Any()) + ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), + res.ToResult()); + })(), + new Func(async () => + { + var res = await PackageManagementService.GetConfigProfilesInfosAsync(packages); + if (res.IsSuccess) + cfpRes = res.Value.ConfigProfiles; + if (res.Errors.Any()) + ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), + res.ToResult()); + })(), + new Func(async () => + { + var res = await PackageManagementService.GetLuaScriptsInfosAsync(packages); + if (res.IsSuccess) + luaRes = res.Value.LuaScripts; + if (res.Errors.Any()) + ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), + res.ToResult()); + })()); + +#if CLIENT + tasksBuilder.Add(new Func(async () => + { + var res = await PackageManagementService.GetStylesInfosAsync(packages); + if (res.IsSuccess) + styleRes = res.Value.StylesResourceInfos; + if (res.Errors.Any()) + ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), + res.ToResult()); + })()); +#endif + await Task.WhenAll(tasksBuilder.MoveToImmutable()); + tasksBuilder.Clear(); + + //---- load resources + tasksBuilder.AddRange(new Func(async () => + { + var res = await ConfigService.LoadConfigsAsync(cfgRes); + if (res.Errors.Any()) + Logger.LogResults(res); + res = await ConfigService.LoadConfigsProfilesAsync(cfpRes); + if (res.Errors.Any()) + Logger.LogResults(res); + })(), + new Func(async () => + { + var res = await LuaScriptManagementService.LoadScriptResourcesAsync(luaRes); + if (res.Errors.Any()) + Logger.LogResults(res); + })()); + +#if CLIENT + tasksBuilder.Add(new Func(async () => + { + var res = await StylesManagementService.LoadStylesAsync(styleRes); + if (res.Errors.Any()) + Logger.LogResults(res); + })()); +#endif + + // load localizations first + if (!locRes.IsDefaultOrEmpty) + { + var res = await LocalizationService.LoadLocalizations(locRes); + if (res.Errors.Any()) + Logger.LogResults(res); + } + + await Task.WhenAll(tasksBuilder.MoveToImmutable()); + } + + void RunScripts() + { + if (!IsStaticAssetsLoaded) + { + throw new InvalidOperationException($"{nameof(RunScripts)} cannot load assets in the '{CurrentRunState}' state."); + } + + if (CurrentRunState >= RunState.Running) + return; + + if (ShouldRunCs()) + { + var asmRes = + PackageManagementService.GetAssembliesInfos(PackageManagementService + .GetAllLoadedPackages() + .Where(ContentPackageManager.EnabledPackages.All.Contains) + .ToList()); + if (asmRes.IsFailed) + { + Logger.LogError($"{nameof(RunScripts)}: Errors will retrieving assembly resources, cannot load scripts!"); + Logger.LogResults(asmRes.ToResult()); + return; + } + var res = PluginManagementService.LoadAssemblyResources(asmRes.Value.Assemblies); + if (res.IsFailed) + { + Logger.LogError($"{nameof(RunScripts)}: Failed to initialize scripts!"); + Logger.LogResults(res.ToResult()); + } + else + { + if (res.Errors.Any()) + Logger.LogResults(res.ToResult()); + if (PluginManagementService.GetImplementingTypes() is {IsSuccess: true} types) + { + var typeInst = PluginManagementService.ActivateTypeInstances(types.Value, true, true); + foreach (var loadRes in typeInst) + { + if (loadRes is { IsSuccess: true, Value: { Item2: { } pluginInstance } }) + { + EventService.Subscribe(pluginInstance); + EventService.Subscribe(pluginInstance); + EventService.Subscribe(pluginInstance); + } + else + { + Logger.LogResults(loadRes.ToResult()); + } + } + + EventService.PublishEvent(sub => sub.PreInitPatching()); + EventService.PublishEvent(sub => sub.Initialize()); + EventService.PublishEvent(sub => sub.OnLoadCompleted()); + } + } + } + + //lua + var luaRes = PackageManagementService.GetLuaScriptsInfos(PackageManagementService + .GetAllLoadedPackages() + .Where(ContentPackageManager.EnabledPackages.All.Contains) + .ToList()); + if (luaRes.IsFailed) + { + Logger.LogError($"{nameof(RunScripts)}: Failed to get enabled lua script resources!"); + Logger.LogResults(luaRes.ToResult()); + return; + } + + if (luaRes.Errors.Any()) + Logger.LogResults(luaRes.ToResult()); + + + LuaScriptManagementService.ExecuteLoadedScripts(luaRes.Value.LuaScripts); + + if (CurrentRunState < RunState.Running) + _runState = RunState.Running; + } + + void UnloadContentPackageInfos() + { + if (IsStaticAssetsLoaded) + { + throw new InvalidOperationException($"{nameof(UnloadStaticAssets)}: Cannot unload static assets when the current run state is {CurrentRunState}."); + } + + PackageManagementService.Reset(); + _toUnload.Clear(); + } + + void UnloadStaticAssets() + { + if (IsCodeRunning) + { + throw new InvalidOperationException($"{nameof(UnloadStaticAssets)}: Cannot unload static assets when the current run state is {CurrentRunState}."); + } + + PluginManagementService.Reset(); + LuaScriptManagementService.Reset(); + ConfigService.Reset(); +#if CLIENT + StylesManagementService.Reset(); +#endif + LocalizationService.Reset(); + + if (CurrentRunState >= RunState.Configuration) + { + _runState = RunState.Parsed; + } + } + + void StopScripts() + { + EventService.ClearAllSubscribers(); + LuaScriptManagementService.UnloadActiveScripts(); + PluginManagementService.UnloadAllAssemblyResources(); + SubscribeToLuaCsEvents(); + + if (IsCodeRunning) + { + _runState = RunState.Configuration; + } } } + + /// + /// Specifies the current run state of the LuaCs Modding System. + /// [Important]Enum State values ordering must be in the form of (lower state) === (higher state) + /// + public enum RunState : byte + { + Unloaded = 0, // No asset data loaded. + Parsed, // CPacks' ResourceInfos are parsed. + Configuration, // localization and configuration assets loaded. + Running // all assets loaded, code running. + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsUtility.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsUtility.cs index 6ed7c87de..f801a23ef 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsUtility.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsUtility.cs @@ -60,7 +60,8 @@ namespace Barotrauma foreach (var package in ContentPackageManager.AllPackages) { - if (package.UgcId.ValueEquals(LuaCsSetup.LuaForBarotraumaId) && pathStartsWith(getFullPath(package.Path))) + if (package.UgcId.ValueEquals(new SteamWorkshopId(GameMain.LuaCs.LuaForBarotraumaSteamId?.Value ?? 0ul)) + && pathStartsWith(getFullPath(package.Path))) { return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs index 59a1e983f..c0cde07dc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs @@ -83,6 +83,7 @@ public class EventService : IEventService, IEventAssemblyContextUnloading public EventService(Lazy pluginManagementService) { _pluginManagementService = pluginManagementService ?? throw new ArgumentNullException(nameof(pluginManagementService)); + this.Subscribe(this); } public bool IsDisposed { get; private set; } = false; @@ -301,8 +302,19 @@ public class EventService : IEventService, IEventAssemblyContextUnloading dict.Remove(OneOf.FromT1(subscriber)); } - public void ClearAllEventSubscribers() where T : IEvent => _subscriptions.Remove(typeof(T)); - public void ClearAllSubscribers() => _subscriptions.Clear(); + public void ClearAllEventSubscribers() where T : IEvent + { + _subscriptions.Remove(typeof(T)); + if (typeof(IEventAssemblyContextUnloading) == typeof(T)) + { + this.Subscribe(this); + } + } + public void ClearAllSubscribers() + { + _subscriptions.Clear(); + this.Subscribe(this); + } public FluentResults.Result PublishEvent(Action action) where T : IEvent { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs index a551aca9f..7ad55c0e7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs @@ -151,4 +151,6 @@ public partial class LoggerService : ILoggerService public void Dispose() { } public FluentResults.Result Reset() => FluentResults.Result.Ok(); + + public bool IsDisposed { get; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs new file mode 100644 index 000000000..aa0c74610 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs @@ -0,0 +1,14 @@ +using Barotrauma.LuaCs.Data; +using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Interop; +using System; +using System.Collections.Immutable; +using System.Reflection; +using System.Threading.Tasks; + +namespace Barotrauma.LuaCs.Services; + +public class LuaScriptManagementService : ILuaScriptManagementService +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs deleted file mode 100644 index eef266305..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptService.cs +++ /dev/null @@ -1,176 +0,0 @@ -using Barotrauma.LuaCs.Data; -using MoonSharp.Interpreter; -using MoonSharp.Interpreter.Interop; -using System; -using System.Collections.Immutable; -using System.Reflection; - -namespace Barotrauma.LuaCs.Services; - -public class LuaScriptService : ILuaScriptService, ILuaScriptManagementService -{ - public void AddField(IUserDataDescriptor descriptor, string fieldName, DynValue value) - { - throw new NotImplementedException(); - } - - public void AddMethod(IUserDataDescriptor descriptor, string methodName, object function) - { - throw new NotImplementedException(); - } - - public FluentResults.Result AddScriptFiles(ImmutableArray luaResource) - { - throw new System.NotImplementedException(); - } - - public object CreateEnumTable(string typeName) - { - throw new NotImplementedException(); - } - - public object CreateStatic(string typeName) - { - throw new NotImplementedException(); - } - - public DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor descriptor) - { - throw new NotImplementedException(); - } - - public DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType) - { - throw new NotImplementedException(); - } - - public void Dispose() - { - throw new System.NotImplementedException(); - } - - public FluentResults.Result ExecuteLoadedScripts(ContentPackage package, bool pauseExecutionOnError = false, bool verboseLogging = false) - { - throw new NotImplementedException(); - } - - public FluentResults.Result ExecuteLoadedScripts(ImmutableArray scripts, bool pauseExecutionOnError = false, bool verboseLogging = false) - { - throw new NotImplementedException(); - } - - public FluentResults.Result ExecuteLoadedScripts(bool pauseExecutionOnError = false, bool verboseLogging = false) - { - throw new NotImplementedException(); - } - - public FluentResults.Result ExecuteScripts(bool pauseExecutionOnScriptError = false, bool verboseLogging = false) - { - throw new System.NotImplementedException(); - } - - public FieldInfo FindFieldRecursively(Type type, string fieldName) - { - throw new NotImplementedException(); - } - - public MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null) - { - throw new NotImplementedException(); - } - - public PropertyInfo FindPropertyRecursively(Type type, string propertyName) - { - throw new NotImplementedException(); - } - - public ImmutableArray GetScriptResources() - { - throw new System.NotImplementedException(); - } - - public bool HasMember(object obj, string memberName) - { - throw new NotImplementedException(); - } - - public bool IsRegistered(Type type) - { - throw new NotImplementedException(); - } - - public bool IsTargetType(object obj, string typeName) - { - throw new NotImplementedException(); - } - - public void MakeFieldAccessible(IUserDataDescriptor descriptor, string fieldName) - { - throw new NotImplementedException(); - } - - public void MakeMethodAccessible(IUserDataDescriptor descriptor, string methodName, string[] parameters = null) - { - throw new NotImplementedException(); - } - - public void MakePropertyAccessible(IUserDataDescriptor descriptor, string propertyName) - { - throw new NotImplementedException(); - } - - public IUserDataDescriptor RegisterGenericType(Type type) - { - throw new NotImplementedException(); - } - - public IUserDataDescriptor RegisterGenericType(string typeName, params string[] typeNameArgs) - { - throw new NotImplementedException(); - } - - public IUserDataDescriptor RegisterType(Type type) - { - throw new NotImplementedException(); - } - - public IUserDataDescriptor RegisterType(string typeName) - { - throw new NotImplementedException(); - } - - public void RemoveMember(IUserDataDescriptor descriptor, string memberName) - { - throw new NotImplementedException(); - } - - public void RemoveScriptFiles(ImmutableArray luaResource) - { - throw new System.NotImplementedException(); - } - - public FluentResults.Result Reset() - { - throw new System.NotImplementedException(); - } - - public string TypeOf(object obj) - { - throw new NotImplementedException(); - } - - public void UnregisterAllTypes() - { - throw new NotImplementedException(); - } - - public void UnregisterType(Type type) - { - throw new NotImplementedException(); - } - - public void UnregisterType(string typeName) - { - throw new NotImplementedException(); - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index 8fda053e1..c9d31605e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -1,461 +1,13 @@ -using System; -using System.Collections.Concurrent; + using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; using System.Threading.Tasks; -using Barotrauma.Extensions; using Barotrauma.LuaCs.Data; -using Barotrauma.Steam; using FluentResults; -using FluentResults.LuaCs; -using QuikGraph; namespace Barotrauma.LuaCs.Services; public class PackageManagementService : IPackageManagementService { - private readonly Func _contentPackageServiceFactory; - private readonly Lazy _assemblyManagementService; - private readonly ConcurrentDictionary _contentPackages = new(); - private readonly ConcurrentQueue _queuedPackages = new(); - private readonly ConcurrentDictionary _packageDependencyInfos = new(); - - /// - /// ConcurrentDictionary handles access/read synchronization. This is to ensure that we are not trying to - /// access the collection during a load/unload/modify operation. - /// - private readonly ReaderWriterLockSlim _contentPackagesModificationsLock = new(); - /// - /// This lock ensures that we are not adding new entries to the queue between when we read the contents and - /// empty the buffer. - /// - private readonly ReaderWriterLockSlim _packageQueueProcessingLock = new(); - - public PackageManagementService( - Func getPackageService, - Lazy assemblyManagementService) - { - this._contentPackageServiceFactory = getPackageService; - this._assemblyManagementService = assemblyManagementService; - } - - #region STATE_RESET - - public void Dispose() - { - // TODO release managed resources here - } - - public FluentResults.Result Reset() - { - throw new NotImplementedException(); - } - - #endregion - - public void QueuePackages(ImmutableArray packages) - { - _packageQueueProcessingLock.EnterReadLock(); - try - { - foreach (LoadablePackage package in packages) - _queuedPackages.Enqueue(package); - } - finally - { - _packageQueueProcessingLock.ExitReadLock(); - } - } - - public FluentResults.Result ParseQueuedPackages(bool loadParallel = true, bool reportFailOnDuplicates = false) - { - if (!ModUtils.Environment.IsMainThread) - throw new InvalidOperationException($"{nameof(ParseQueuedPackages)}: This method can only be called on the main thread."); - - ImmutableArray packagesToProcess = ImmutableArray.Empty; - - _packageQueueProcessingLock.EnterWriteLock(); - try - { - Interlocked.MemoryBarrier(); - if (_queuedPackages.IsEmpty) - return FluentResults.Result.Ok().WithSuccess($"{nameof(ParseQueuedPackages)}: The Queue is empty."); - packagesToProcess = _queuedPackages.Where(p => p.Package is not null) - .Distinct().ToImmutableArray(); - _queuedPackages.Clear(); - } - finally - { - _packageQueueProcessingLock.ExitWriteLock(); - } - - FluentResults.Result[] loadResults = new FluentResults.Result[packagesToProcess.Length]; - FluentResults.Result res = new FluentResults.Result(); - - // Load ModConfigInfo - _contentPackagesModificationsLock.EnterWriteLock(); - try - { - Stopwatch stopwatch = new Stopwatch(); - stopwatch.Start(); - - Interlocked.MemoryBarrier(); - if (loadParallel) - { - Parallel.For(0, loadResults.Length, new ParallelOptions() - { - /* - * This is an IO-bound operation. The purpose of parallelism here is to allow loaded package - * data to be processed while another package is waiting on the storage device for its info. - */ - MaxDegreeOfParallelism = 2 - },i => - { - loadResults[i] = LoadPackageInfo(packagesToProcess[i]); - }); - } - else - { - for (int i = 0; i < loadResults.Length; i++) - { - loadResults[i] = LoadPackageInfo(packagesToProcess[i]); - } - } - - stopwatch.Stop(); - - res.WithSuccess(new Success( - $"Completed parsing of {loadResults.Length} packages in {stopwatch.ElapsedMilliseconds} milliseconds.")); - - for (int i = 0; i < loadResults.Length; i++) - { - res = loadResults[i].IsSuccess - ? res.WithSuccesses(loadResults[i].Successes) - : res.WithErrors(loadResults[i].Errors); - } - - return res; - } - catch (AggregateException ae) - { - return FluentResults.Result.Fail(new Error($"{nameof(ParseQueuedPackages)}: Failed to load packages! AE.") - .WithMetadata(MetadataType.ExceptionDetails, ae.InnerException?.Message ?? ae.Message) - .WithMetadata(MetadataType.StackTrace, ae.StackTrace) - .WithMetadata(MetadataType.ExceptionObject, this)); - } - catch (ArgumentNullException ane) - { - return FluentResults.Result.Fail( - new Error($"{nameof(ParseQueuedPackages)}: Failed to load packages! ANE.") - .WithMetadata(MetadataType.ExceptionDetails, ane.InnerException?.Message ?? ane.Message) - .WithMetadata(MetadataType.StackTrace, ane.StackTrace) - .WithMetadata(MetadataType.ExceptionObject, this)); - } - finally - { - _contentPackagesModificationsLock.ExitWriteLock(); - } - - - /* - * Helper functions - */ - - // register in the list so we can check against it. - FluentResults.Result LoadPackageInfo(LoadablePackage package) - { - try - { - if (package.Package == null) - { - return FluentResults.Result.Fail( - new Error($"{nameof(LoadPackageInfo)}: Package is null!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, package)); - } - - if (_contentPackages.TryGetValue(package.Package, out var packageService)) - { - if (reportFailOnDuplicates) - { - return FluentResults.Result.Fail(new Error($"The package {package.Package?.Name} is already loaded.") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, package.Package)); - } - - return FluentResults.Result.Ok(); - } - - packageService = _contentPackageServiceFactory.Invoke(); - _contentPackages[package.Package] = packageService; - return packageService.LoadResourcesInfo(package); - } - catch (NullReferenceException nre) - { - return FluentResults.Result.Fail(new Error($"{nameof(LoadPackageInfo)}: NRE while loading package {package.Package?.Name}!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.StackTrace, nre.StackTrace ?? "StackTrace not available") - .WithMetadata(MetadataType.ExceptionDetails, nre.InnerException?.Message ?? nre.Message) - .WithMetadata(MetadataType.RootObject, package)); - } - } - } - - public FluentResults.Result LoadPackageConfigsResourcesGroup(bool loadParallel = true) - { - throw new NotImplementedException(); - } - - public FluentResults.Result LoadAllPackageResources(bool loadParallel = true, bool safeResourcesOnly = true) - { - throw new NotImplementedException(); - } - - public FluentResults.Result UnloadPackages() - { - if (!ModUtils.Environment.IsMainThread) - { - return FluentResults.Result.Fail( - new ExceptionalError(new InvalidOperationException($"{nameof(UnloadPackages)}: This method can only be called on the main thread.")) - .WithMetadata(MetadataType.ExceptionObject, this)); - } - - var res = new FluentResults.Result(); - _contentPackagesModificationsLock.EnterWriteLock(); - try - { - // TODO: Finish him - } - finally - { - _contentPackagesModificationsLock.ExitWriteLock(); - } - - throw new NotImplementedException(); - } - - public bool IsPackageLoaded(ContentPackage package) => package is not null && _contentPackages.ContainsKey(package); - - public bool CheckDependencyLoaded(IPackageDependencyInfo info) => - info is not null && IsPackageLoaded(info.DependencyPackage); - - public bool CheckDependenciesLoaded([NotNull]IEnumerable infos, out ImmutableArray missingPackages) - { - var missing = ImmutableArray.CreateBuilder(); - missing.AddRange(infos - .Where(i => i.DependencyPackage is not null) - .DistinctBy(i => i.DependencyPackage) - .Where(i => !CheckDependencyLoaded(i))); - missingPackages = missing.MoveToImmutable(); - return missingPackages.Length == 0; - } - - public bool CheckEnvironmentSupported(IPlatformInfo platform) - { - return (platform.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 - && (platform.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0; - } - - public Result GetPackageDependencyInfoRecord(ContentPackage package, bool addIfMissing = false) - { - if (package is null) - { - return new FluentResults.Result() - .WithError(new Error($"{nameof(GetPackageDependencyInfoRecord)}: Package is null!") - .WithMetadata(MetadataType.ExceptionObject, this)); - } - - if (_packageDependencyInfos.TryGetValue(package, out var result)) - { - return new FluentResults.Result() - .WithValue(result); - } - - if (addIfMissing) - { - return AddDependencyRecord(package, package.Name, package.Path, - package.TryExtractSteamWorkshopId(out var id) ? id.Value : 0, - false); - } - - return FluentResults.Result.Fail(new Error($"Could not find package {package.Name}!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, package)); - } - - public Result GetPackageDependencyInfoRecord(ulong steamWorkshopId, string packageName, string folderPath = null, - bool addIfMissing = false) - { - if (packageName.IsNullOrWhiteSpace() || folderPath.IsNullOrWhiteSpace()) - { - return new FluentResults.Result() - .WithError(new Error($"{nameof(GetPackageDependencyInfoRecord)}: folder path and/or package name are null!") - .WithMetadata(MetadataType.ExceptionObject, this)); - } - - if (_packageDependencyInfos.TryGetValue((packageName,steamWorkshopId,folderPath), out var result)) - { - return new FluentResults.Result() - .WithValue(result); - } - - // TODO: Finish this - throw new NotImplementedException(); - } - - public Result GetPackageDependencyInfoRecord(string folderPath) - { - throw new NotImplementedException(); - } - - - public IPackageDependencyInfo CreateOrphanPackageDependencyInfoRecord( - string packageName, - string packagePath, - ulong steamWorkshopId) - { - return new DependencyInfo() - { - DependencyPackage = null, - FallbackPackageName = packageName, - FolderPath = packagePath.IsNullOrWhiteSpace() ? null : System.IO.Path.GetFullPath(packagePath), - SteamWorkshopId = steamWorkshopId, - IsMissing = true, - IsWorkshopInstallation = false - }; - } - - private Result AddDependencyRecord( - ContentPackage package, - string packageName, - string folderPath, - ulong steamWorkshopId, - bool isMissing) - { - // TODO: Redo - try - { - var dependencyInfo = new DependencyInfo() - { - DependencyPackage = package, - FallbackPackageName = packageName, - FolderPath = System.IO.Path.GetFullPath(folderPath), - SteamWorkshopId = steamWorkshopId, - IsMissing = isMissing, - IsWorkshopInstallation = steamWorkshopId != 0 - }; - if (package is not null) - { - _packageDependencyInfos.AddOrUpdate(package, pack => dependencyInfo, - (pack, dep) => dependencyInfo); - } - return new FluentResults.Result() - .WithValue(dependencyInfo) - .WithSuccess($"New value created."); - } - catch (Exception ex) - { - return new FluentResults.Result() - .WithError(new ExceptionalError(ex) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.ExceptionDetails, ex.Message) - .WithMetadata(MetadataType.RootObject, package) - .WithMetadata(MetadataType.StackTrace, ex.StackTrace ?? "StackTrace not available")); - } - } - - private readonly record struct DependencyEntryKey : IEqualityComparer, IEquatable - { - public ContentPackage Package { get; init; } - public string FolderPath { get; init; } - public string PackageName { get; init; } - public ulong SteamWorkshopId { get; init; } - - public DependencyEntryKey(ContentPackage package) - { - Package = package ?? throw new ArgumentNullException(nameof(package), $"{nameof(DependencyEntryKey)}.ctor: Package cannot be null!"); - PackageName = package.Name; - SteamWorkshopId = package.TryExtractSteamWorkshopId(out var id) ? id.Value : (ulong)0; - FolderPath = package.Path; - } - - public DependencyEntryKey(string packageName, string folderPath, ulong steamWorkshopId) - { - PackageName = packageName; - SteamWorkshopId = steamWorkshopId; - FolderPath = folderPath; - Package = null; - } - - public DependencyEntryKey(string packageName, ulong steamWorkshopId) - { - PackageName = packageName; - SteamWorkshopId = steamWorkshopId; - FolderPath = null; - Package = null; - } - - public bool Equals(DependencyEntryKey other) - { - return Equals(this, other); - } - - public override int GetHashCode() - { - return GetHashCode(this); - } - - public bool Equals(DependencyEntryKey x, DependencyEntryKey y) - { - if (x == y) - return true; - - if (x.Package is not null && y.Package is not null && x.Package == Package) - return true; - - // folder should be a unique key if not unset. - if (!x.FolderPath.IsNullOrWhiteSpace() && !y.FolderPath.IsNullOrWhiteSpace() && - x.FolderPath == FolderPath) - return true; - - if (!x.PackageName.IsNullOrWhiteSpace() && !y.PackageName.IsNullOrWhiteSpace() - && x.SteamWorkshopId != 0 && y.SteamWorkshopId != 0) - return x.PackageName == y.PackageName && x.SteamWorkshopId == y.SteamWorkshopId; - - if (!x.PackageName.IsNullOrWhiteSpace() && !y.PackageName.IsNullOrWhiteSpace() && x.PackageName == PackageName) - return true; - - if (x.SteamWorkshopId != 0 && y.SteamWorkshopId != 0 && - x.SteamWorkshopId == y.SteamWorkshopId) - return true; - - return false; - } - - public int GetHashCode(DependencyEntryKey obj) - { - if (!obj.PackageName.IsNullOrWhiteSpace()) - return obj.PackageName.GetHashCode(); - if (obj.SteamWorkshopId != 0) - return obj.SteamWorkshopId.GetHashCode(); - if (obj.Package is not null) - return obj.Package.GetHashCode(); - // We don't want to check the FolderPath because we want to resolve dependencies using packages - // that might be local instead in the workshop folder. - return 2342568; // random const value: collisions are fine as we want to call Equals() - } - - public static implicit operator DependencyEntryKey(ContentPackage package) => new(package); - public static implicit operator DependencyEntryKey((string packageName, ulong steamWorkshopId) tuple1) => - new (tuple1.packageName, tuple1.steamWorkshopId); - public static implicit operator DependencyEntryKey((string packageName, ulong steamWorkshopId, string folderPath) tuple1) => - new (tuple1.packageName, tuple1.folderPath, tuple1.steamWorkshopId); - } - } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs deleted file mode 100644 index 83f133fc2..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageService.cs +++ /dev/null @@ -1,686 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using Barotrauma.Extensions; -using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services.Processing; -using FluentResults; -using FluentResults.LuaCs; -using OneOf; - -namespace Barotrauma.LuaCs.Services; - -public partial class PackageService : IPackageService -{ - private readonly ReaderWriterLockSlim _operationsUsageLock = new(); - // only stops race conditions for pointer access - - - // mod config / package scanners/parsers - private readonly Lazy _configParserService; - private readonly Lazy _luaScriptService; - private readonly Lazy _localizationService; - private readonly Lazy _pluginService; - private readonly Lazy _configService; - private readonly IPackageManagementService _packageManagementService; - private readonly IStorageService _storageService; - private readonly ILoggerService _loggerService; - - // .ctor in server source and client source - - // state monitors - private int _configsLoaded, _localizationsLoaded, _luaScriptsLoaded, _pluginsLoaded, _isDisposed; - private int _loadingOperationsRunning; - private int _isEnabledInModList; - - public bool ConfigsLoaded - { - get => ModUtils.Threading.GetBool(ref _configsLoaded); - private set => ModUtils.Threading.SetBool(ref _configsLoaded, value); - } - public bool LocalizationsLoaded - { - get => ModUtils.Threading.GetBool(ref _localizationsLoaded); - private set => ModUtils.Threading.SetBool(ref _localizationsLoaded, value); - } - public bool LuaScriptsLoaded - { - get => ModUtils.Threading.GetBool(ref _luaScriptsLoaded); - private set => ModUtils.Threading.SetBool(ref _luaScriptsLoaded, value); - } - public bool PluginsLoaded - { - get => ModUtils.Threading.GetBool(ref _pluginsLoaded); - private set => ModUtils.Threading.SetBool(ref _pluginsLoaded, value); - } - public bool IsDisposed - { - get => ModUtils.Threading.GetBool(ref _isDisposed); - private set => ModUtils.Threading.SetBool(ref _isDisposed, value); - } - - private bool LoadingOperationsRunning - { - get => Interlocked.CompareExchange(ref _loadingOperationsRunning, 0, 0) > 0; - set // we use the set as our inc/decr - { - if (value) - { - Interlocked.Add(ref _loadingOperationsRunning, 1); - } - else - { - Interlocked.Add(ref _loadingOperationsRunning, -1); - } - } - } - - #region Member: ContentPackage - - private readonly ReaderWriterLockSlim _packageAccessLock = new(); - private ContentPackage _package; - public ContentPackage Package - { - get - { - _packageAccessLock.EnterReadLock(); - try - { - return _package; - } - finally - { - _packageAccessLock.ExitReadLock(); - } - } - private set - { - _packageAccessLock.EnterWriteLock(); - try - { - _package = value; - } - finally - { - _packageAccessLock.ExitWriteLock(); - } - } - } - - #endregion - - #region DataContracts - - #region Member: ModConfigInfo - - private readonly ReaderWriterLockSlim _modConfigUsageLock = new(); - private IModConfigInfo _modConfigInfo; - public IModConfigInfo ModConfigInfo - { - get - { - _modConfigUsageLock.EnterReadLock(); - try - { - return _modConfigInfo; - } - finally - { - _modConfigUsageLock.ExitReadLock(); - } - } - private set - { - _modConfigUsageLock.EnterWriteLock(); - try - { - _modConfigInfo = value; - } - finally - { - _modConfigUsageLock.ExitWriteLock(); - } - } - } - - public bool IsEnabledInModList - { - get => ModUtils.Threading.GetBool(ref _isEnabledInModList); - private set => ModUtils.Threading.SetBool(ref _isEnabledInModList, value); - } - - #endregion - - public ImmutableArray SupportedCultures => ModConfigInfo?.SupportedCultures ?? ImmutableArray.Empty; - public ImmutableArray Assemblies => ModConfigInfo?.Assemblies ?? ImmutableArray.Empty; - public ImmutableArray Localizations => ModConfigInfo?.Localizations ?? ImmutableArray.Empty; - public ImmutableArray LuaScripts => ModConfigInfo?.LuaScripts ?? ImmutableArray.Empty; - public ImmutableArray Configs => ModConfigInfo?.Configs ?? ImmutableArray.Empty; - public ImmutableArray ConfigProfiles => ModConfigInfo?.ConfigProfiles ?? ImmutableArray.Empty; - - #endregion - - #region PublicAPI - - public FluentResults.Result LoadResourcesInfo(LoadablePackage cpackage) - { - if (cpackage.Package == null) - { - return FluentResults.Result.Fail(new Error($"{nameof(LoadResourcesInfo)}: Package is null!") - .WithMetadata(MetadataType.ExceptionObject,this) - .WithMetadata(MetadataType.RootObject, cpackage)); - } - ContentPackage package = cpackage.Package; - - _operationsUsageLock.EnterWriteLock(); - LoadingOperationsRunning = true; - try - { - if (IsDisposed) - { - return FluentResults.Result.Fail( - new Error("Service is disposed.") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, package)); - } - - var res = _configParserService.Value.BuildConfigForPackage(package); - - if (res.IsFailed) - { - return FluentResults.Result.Fail(res.Errors) - .WithError(new Error("PackageService failed to load ModConfigInfo") - .WithMetadata(MetadataType.ExceptionObject, _configParserService) - .WithMetadata(MetadataType.RootObject, package)); - } - - this.ModConfigInfo = res.Value; - this.IsEnabledInModList = cpackage.IsEnabled; - return FluentResults.Result.Ok(); - } - catch (Exception e) - { - return FluentResults.Result.Fail(new Error(e.Message) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, package) - .WithMetadata(MetadataType.StackTrace, e.StackTrace)); - } - finally - { - LoadingOperationsRunning = false; - _operationsUsageLock.ExitWriteLock(); - } - } - - public FluentResults.Result LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false) - { - _operationsUsageLock.EnterReadLock(); - LoadingOperationsRunning = true; - try - { - if (CheckResourceSanitation(OneOf - .FromT0(assembliesInfo)) is { IsFailed: true } failed) - { - return failed; - } - - // Order these assemblies by internal dependencies - ImmutableArray resources; - if (ignoreDependencySorting) - { - resources = assembliesInfo.Assemblies; - } - else // sort by load order - { - resources = assembliesInfo.Assemblies - .OrderByDescending(a => a.LoadPriority) - .ToImmutableArray(); - } - - // Try loading them, throw on failure. - if (_pluginService.Value.LoadAndInstanceTypes(resources, true, out var instancedTypes) is { IsFailed: true} failed2) - { - return failed2.WithError(new Error($"{nameof(LoadPlugins)}: Failed to load plugins for {this.Package.Name}") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, assembliesInfo)); - } - - PluginsLoaded = true; - return FluentResults.Result.Ok(); - } - finally - { - LoadingOperationsRunning = false; - _operationsUsageLock.ExitReadLock(); - } - } - - public FluentResults.Result LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo) - { - _operationsUsageLock.EnterReadLock(); - LoadingOperationsRunning = true; - try - { - if (CheckResourceSanitation(OneOf - .FromT1(localizationsInfo)) is { IsFailed: true } failed) - { - return failed; - } - - if (_localizationService.Value.LoadLocalizations(localizationsInfo.Localizations) is { IsFailed: true} failed2) - { - return failed2.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load localizations") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, localizationsInfo)); - } - - LocalizationsLoaded = true; - return FluentResults.Result.Ok(); - } - finally - { - LoadingOperationsRunning = false; - _operationsUsageLock.ExitReadLock(); - } - } - - public FluentResults.Result AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo) - { - _operationsUsageLock.EnterReadLock(); - LoadingOperationsRunning = true; - try - { - if (CheckResourceSanitation(OneOf - .FromT4(luaScriptsInfo)) is { IsFailed: true } failed) - { - return failed; - } - - if (_luaScriptService.Value.AddScriptFiles(luaScriptsInfo.LuaScripts) is { IsFailed: true} failed2) - { - return failed2.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load lua scripts.") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, luaScriptsInfo)); - } - - LuaScriptsLoaded = true; - return FluentResults.Result.Ok(); - } - finally - { - LoadingOperationsRunning = false; - _operationsUsageLock.ExitReadLock(); - } - } - - public FluentResults.Result LoadConfig( - [NotNull]IConfigsResourcesInfo configsResourcesInfo, - [NotNull]IConfigProfilesResourcesInfo configProfilesResourcesInfo) - { - _operationsUsageLock.EnterReadLock(); - LoadingOperationsRunning = true; - try - { - // register configs - if (CheckResourceSanitation(OneOf - .FromT2(configsResourcesInfo)) is { IsFailed: true } failed) - { - return failed; - } - - if (_configService.Value.AddConfigs(configsResourcesInfo.Configs) is { IsFailed: true} failed2) - { - return failed2.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load configs.") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, configsResourcesInfo)); - } - - // register config profiles - if (CheckResourceSanitation(OneOf - .FromT3(configProfilesResourcesInfo)) is { IsFailed: true } failed3) - { - return failed3; - } - - if (_configService.Value.AddConfigsProfiles(configProfilesResourcesInfo.ConfigProfiles) is { IsFailed: true} failed4) - { - return failed4.WithError(new Error($"{nameof(LoadLocalizations)}: Failed to load config profiles.") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, configProfilesResourcesInfo)); - } - - ConfigsLoaded = true; - return FluentResults.Result.Ok(); - } - finally - { - LoadingOperationsRunning = false; - _operationsUsageLock.ExitReadLock(); - } - } - - public void Dispose() - { - /* - * Notes: we need to unload this package from services in the order that the services are dependent on each other. - * Unloading Order: Lua Scripts > Assemblies > Config Profiles > Configs > Styles > Localizations - */ - _operationsUsageLock.EnterWriteLock(); - try - { - if (this.Package is null) - { - _loggerService.LogError( - $"Package Service: cannot Dispose of service as ContentPackage and info is not set!"); - return; - } - - if (this.ModConfigInfo is null) - { - _loggerService.LogError($"Package Service: cannot Dispose of service as ModConfigInfo is not loaded!"); - return; - } - - /* - * To be graceful, we want to ensure that any async calls and other threads are allowed to be processed before we begin - * disposal to reduce friction with other thread operations, so we release the lock and periodically check it - * to see of other threads have finished operations before cleaning everything up. - */ - - IsDisposed = true; // set stop flag, callers should handle exception cases - Interlocked.MemoryBarrier(); //ensure cache states - - DateTime timeoutLimit = DateTime.Now.AddSeconds(10); - while (LoadingOperationsRunning) - { - _operationsUsageLock.ExitWriteLock(); - Thread.Sleep(1); - _operationsUsageLock.EnterWriteLock(); - if (timeoutLimit < DateTime.Now) - { - _loggerService.LogError($"Package Service: Dispose() time out reached while waiting for other operations. Continuing."); - break; - } - } - - GC.SuppressFinalize(this); - - _luaScriptService.Value.RemoveScriptFiles(this.LuaScripts); - _pluginService.Value.DisposePlugins(); - _configService.Value.RemoveConfigsProfiles(this.ConfigProfiles); - _configService.Value.RemoveConfigs(this.Configs); -#if CLIENT - _stylesService.Value.UnloadAllStyles(); -#endif - _localizationService.Value.Remove(this.Localizations); - - ModConfigInfo = null; - Package = null; - } - catch - { - _loggerService.LogError($"Package Service: exception while running Dispose()."); - throw; - } - finally - { - _operationsUsageLock.ExitWriteLock(); - } - } - - public FluentResults.Result Reset() - { - _operationsUsageLock.EnterWriteLock(); - - try - { - if (this.Package is null) - { - return FluentResults.Result.Fail(new Error($"Package Service: cannot Dispose of service as ContentPackage and info is not set!") - .WithMetadata(MetadataType.ExceptionDetails, nameof(Reset)) - .WithMetadata(MetadataType.ExceptionObject, this)); - } - - if (this.ModConfigInfo is null) - { - return FluentResults.Result.Fail(new Error($"Package Service: cannot Dispose of service as ModConfigInfo is not set!") - .WithMetadata(MetadataType.ExceptionDetails, nameof(Reset)) - .WithMetadata(MetadataType.ExceptionObject, this)); - } - - Interlocked.MemoryBarrier(); //ensure cache states - - DateTime timeoutLimit = DateTime.Now.AddSeconds(10); - while (LoadingOperationsRunning) - { - _operationsUsageLock.ExitWriteLock(); - Thread.Sleep(1); - _operationsUsageLock.EnterWriteLock(); - if (timeoutLimit < DateTime.Now) - { - _loggerService.LogError($"Package Service: Dispose() grace time-out reached while waiting for other operations. Continuing."); - break; - } - } - - if (LuaScriptsLoaded) - { - _luaScriptService.Value.RemoveScriptFiles(this.LuaScripts); - LuaScriptsLoaded = false; - } - - if (PluginsLoaded) - { - _pluginService.Value.DisposePlugins(); - PluginsLoaded = false; - } - - if (ConfigsLoaded) - { - _configService.Value.RemoveConfigsProfiles(this.ConfigProfiles); - _configService.Value.RemoveConfigs(this.Configs); - ConfigsLoaded = false; - } - - if (LocalizationsLoaded) - { - _localizationService.Value.Remove(this.Localizations); - LocalizationsLoaded = false; - } - return FluentResults.Result.Ok(); - } - finally - { - _operationsUsageLock.ExitWriteLock(); - } - } - - #endregion - - #region INTERNAL - - /// - /// [Thread Unsafe] Performs sanitation and null checks on resources and returns the results. - /// NOTE: Requires that resource locks be set by the caller. - /// - /// - /// - private FluentResults.Result CheckResourceSanitation( - OneOf.OneOf resourcesInfos) - { - // execute checks based on known types - return resourcesInfos.Match( - ass => ChecksDispatcher(ass, nameof(ass.Assemblies), nameof(LoadPlugins), - ass.Assemblies, this.Assemblies), - loc => ChecksDispatcher(loc, nameof(loc.Localizations), nameof(LoadLocalizations), - loc.Localizations, this.Localizations), - cfg => ChecksDispatcher(cfg, nameof(cfg.Configs), nameof(LoadConfig), - cfg.Configs, this.Configs), - cfp => ChecksDispatcher(cfp, nameof(cfp.ConfigProfiles), nameof(LoadConfig), - cfp.ConfigProfiles, this.ConfigProfiles), - lua => ChecksDispatcher(lua, nameof(lua.LuaScripts), nameof(AddLuaScripts), - lua.LuaScripts, this.LuaScripts)); - - - /* - * Helper functions - */ - FluentResults.Result ChecksDispatcher(object obj, string resName, string callerName, - ImmutableArray resList, ImmutableArray compareList) - where T : class, IPackageInfo, IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo - { - string errMsg = $"{callerName}: Failed to load {resName}."; - if (DisposeCheck(obj) is { IsFailed: true } failed) - return failed; - if (SanitationChecksCore(obj, resName, callerName) is { IsFailed: true } failed1) - return failed1.WithError(new Error(errMsg)); - if (SanitationChecksEnumerable(resList, resName, callerName) is { IsFailed: true } failed2) - return failed2.WithError(new Error(errMsg)); - if (DebugCheck(resList, compareList, resName) is {IsFailed: true} failed3) - return failed3.WithError(new Error(errMsg)); - return FluentResults.Result.Ok(); - } - - FluentResults.Result DisposeCheck(object obj) - { - if (IsDisposed) - { - return FluentResults.Result.Fail(new Error($"{nameof(PackageService)}: Tried to load resources when disposed.") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, obj)); - } - return FluentResults.Result.Ok(); - } - - FluentResults.Result DebugCheck(ImmutableArray resList, ImmutableArray compareList, string resName) - where T : class, IPackageInfo - { -#if DEBUG - Stack errors = new(); - resList.ForEach(res => - { - if (!compareList.Contains(res)) - { - errors.Push(new Error($"Failed to load {resName} for: {this.Package.Name}") - .WithMetadata(MetadataType.ExceptionDetails, $"Tries to load {resName} resource {res.InternalName} but it is not from this package!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, res)); - } - }); - if (errors.Count > 0) - { - return FluentResults.Result.Fail(errors).WithError( - new Error($"{nameof(LoadPlugins)}: errors in {resName} resources.") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, this.Package)); - } -#endif - return FluentResults.Result.Ok(); - } - } - - private FluentResults.Result SanitationChecksCore(object obj, string resTypeInfoName, string callerName) - { - Error e = null; - - if (obj is null) - { - e = new Error($"{nameof(SanitationChecksCore)}: null checks failed!") - .WithMetadata(MetadataType.ExceptionDetails, "Object is null!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.Sources, new List() { resTypeInfoName, callerName }); - } - - if (this.Package is null) - { - e = (e ?? new Error($"{nameof(SanitationChecksCore)}: null checks failed!")) - .WithMetadata(MetadataType.ExceptionDetails, "The Package is null!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.Sources, new List() { resTypeInfoName, callerName }); - } - - return e is null ? FluentResults.Result.Ok() : FluentResults.Result.Fail(e); - } - - private FluentResults.Result SanitationChecksEnumerable(ImmutableArray resourceInfos, string resTypeInfoName, string callerName) where T : IResourceInfo, IResourceCultureInfo, IPackageInfo, IPackageDependenciesInfo - { - // Check if list is empty. Nothing more to do. - if (resourceInfos.IsDefaultOrEmpty) - return FluentResults.Result.Ok(); - - Stack errors = new(); - - // Check if all resources in the list are registered to this package, throw if not. - foreach (var resourceInfo in resourceInfos) - { - // ownership checks - if (resourceInfo.OwnerPackage is null) - { - errors.Push(new Error($"Error for resource: {resTypeInfoName}. OwnerPackage is null!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, resourceInfo)); - continue; - } - - if (resourceInfo.OwnerPackage != this.Package) - { - errors.Push(new Error($"Error for resource: {resTypeInfoName}. $\"OwnerPackage {{resourceInfo.OwnerPackage?.Name}} is not the same as this package: {{this.Package}}") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, resourceInfo)); - continue; - } - - if (resourceInfo.Dependencies.IsDefaultOrEmpty) - continue; - - // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator - foreach (var pdi in resourceInfo.Dependencies) - { - // for clarification: all resources passed to the function should always be loaded. - // unneeded optional resources should be filtered out before the list is sent. - // left this as a reminder :) - /*if (pdi.Optional) - return;*/ - if (!_packageManagementService.CheckDependencyLoaded(pdi)) - { - errors.Push(new Error($"Dependency missing for resource: {resourceInfo.OwnerPackage.Name}") - .WithMetadata(MetadataType.ExceptionDetails, $"Missing dependency: {pdi.DependencyPackage?.Name ?? (pdi.FallbackPackageName.IsNullOrWhiteSpace() ? pdi.SteamWorkshopId.ToString() : pdi.FallbackPackageName)}") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, resourceInfo)); - } - } - - // check runtime platform - if (!_packageManagementService.CheckEnvironmentSupported(resourceInfo)) - { - errors.Push(new Error($"The resource {resourceInfo.OwnerPackage?.Name} does not support the current platform!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, resourceInfo)); - } - - // check local culture - if (!_localizationService.Value.IsCurrentCultureSupported(resourceInfo)) - { - errors.Push(new Error($"The resource {resourceInfo.OwnerPackage?.Name} does not support the current culture/region!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, resourceInfo)); - } - } - - return errors.Count > 0 ? FluentResults.Result.Fail(errors) : FluentResults.Result.Ok(); - } - - #endregion -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 6f46b99e1..5289e946a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -107,6 +107,22 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage throw new NotImplementedException(); } + public IReadOnlyList> ActivateTypeInstances(ImmutableArray types, bool serviceInjection = true, + bool hostInstanceReference = false) where T : IDisposable + { + throw new NotImplementedException(); + } + + public FluentResults.Result UnloadHostedReferences() + { + throw new NotImplementedException(); + } + + public FluentResults.Result UnloadAllAssemblyResources() + { + throw new NotImplementedException(); + } + public Result GetLoadedAssembly(string assemblyName, in Guid[] excludedContexts) { ((IService)this).CheckDisposed(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs index 0b8c8ad70..a71445714 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using System.Xml.Linq; using Barotrauma.LuaCs.Data; using FluentResults; @@ -7,46 +8,16 @@ namespace Barotrauma.LuaCs.Services.Processing; #region TypeDef -// ReSharper disable once TypeParameterCanBeVariant -public interface IConverterService : IReusableService +public interface IConverterService : IReusableService { Result TryParseResource(TSrc src); Result TryParseResources(IEnumerable sources); } -public interface IXmlResourceConverterService : IConverterService { } -public interface IResourceToXmlConverterService : IConverterService { } - -#endregion - -/// -/// Parses Xml to produce loading metadata info for linked loadable files. -/// -#region XmlToResourceInfoParsers - -public interface IXmlAssemblyResConverter : IXmlResourceConverterService { } -public interface IXmlConfigResConverterService : IXmlResourceConverterService { } -public interface IXmlLocalizationResConverterService : IXmlResourceConverterService { } - -#endregion - -/// -/// Parses Xml to produce ready-to-use info/data without any additional file/data loading. -/// -#region XmlToInfoParsers -public interface IXmlDependencyConverterService : IXmlResourceConverterService { } -public interface IXmlModConfigConverterService : IXmlResourceConverterService { } -/// -/// Parses legacy packages that make use of the RunConfig.xml structure to produce a ModConfig. -/// -public interface IXmlLegacyModConfigConverterService : IXmlResourceConverterService { } - -#endregion - - -#region ResToInfoParsers -public interface ILocalizationResToInfoParser : IConverterService { } -public interface IConfigResConverterService : IConverterService { } -public interface IConfigProfileResConverterService : IConverterService { } +public interface IConverterServiceAsync : IReusableService +{ + Task> TryParseResourceAsync(TSrc src); + Task> TryParseResourcesAsync(IEnumerable sources); +} #endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs index d20d77942..d812e767d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs @@ -33,26 +33,25 @@ public class StorageService : IStorageService private IConfigEntry _kLocalFilePathRules = null; private const string _packagePathKeyword = ""; private readonly string _runLocation = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location.CleanUpPath()); + + // TODO: Rewrite the config to get info from .ctor. private IConfigEntry LocalStoragePath => _kLocalStoragePath ??= GetOrCreateConfig(nameof(LocalStoragePath), "/Data/Mods"); private IConfigEntry LocalFilePathRule => _kLocalFilePathRules ??= GetOrCreateConfig(nameof(LocalFilePathRule), _packagePathKeyword); private IConfigEntry GetOrCreateConfig(string name, string defaultValue) { var c = _configService.Value .GetConfig>(ModUtils.Definitions.LuaCsForBarotrauma, name); - if (c.IsSuccess) - { - return c.Value; - } - else - { - c = _configService.Value.AddConfigEntry( - ModUtils.Definitions.LuaCsForBarotrauma, - name, defaultValue, NetSync.None, valueChangePredicate: (value) => false); - if (c.IsSuccess) - return c.Value; - else - throw new KeyNotFoundException("Cannot find storage value for key: " + name); - } + if (c is not null) + return c; + + var c1 = _configService.Value.AddConfigEntry( + ModUtils.Definitions.LuaCsForBarotrauma, + name, defaultValue, NetSync.None, valueChangePredicate: (value) => false); + if (c1.IsSuccess) + return c1.Value; + + throw new KeyNotFoundException("Cannot find storage value for key: " + name); + } public bool IsDisposed { get; private set; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs index b40a1da69..c540f3b78 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Networking; @@ -15,20 +16,14 @@ public partial interface IConfigService : IReusableService, ILuaConfigService /* * Resource Files. */ - FluentResults.Result AddConfigs(ImmutableArray configResources); - FluentResults.Result AddConfigsProfiles(ImmutableArray configProfileResources); - FluentResults.Result RemoveConfigs(ImmutableArray configResources); - FluentResults.Result RemoveConfigsProfiles(ImmutableArray configProfilesResources); + Task LoadConfigsAsync(ImmutableArray configResources); + Task LoadConfigsProfilesAsync(ImmutableArray configProfileResources); + FluentResults.Result DisposeConfigs(ImmutableArray configResources); + FluentResults.Result DisposeConfigsProfiles(ImmutableArray configProfilesResources); + FluentResults.Result DisposeConfigs(ContentPackage package); + FluentResults.Result DisposeConfigsProfiles(ContentPackage package); - /* - * From resources - */ - FluentResults.Result AddConfigs(ImmutableArray configs); - FluentResults.Result AddConfigsProfiles(ImmutableArray configProfiles); - FluentResults.Result RemoveConfigs(ImmutableArray configs); - FluentResults.Result RemoveConfigsProfiles(ImmutableArray configProfiles); - /* * Immediate mode */ @@ -79,8 +74,6 @@ public partial interface IConfigService : IReusableService, ILuaConfigService FluentResults.Result> GetConfigsForPackage(ContentPackage package); FluentResults.Result> GetConfigsForPackage(string packageName); IReadOnlyDictionary<(ContentPackage, string), IConfigBase> GetAllConfigs(); - FluentResults.Result GetConfig(ContentPackage package, string name); - FluentResults.Result GetConfig(string packageName, string name); - FluentResults.Result GetConfig(ContentPackage package, string name) where T : IConfigBase; - FluentResults.Result GetConfig(string packageName, string name) where T : IConfigBase; + T GetConfig(ContentPackage package, string name) where T : IConfigBase; + T GetConfig(string packageName, string name) where T : IConfigBase; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILocalizationService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILocalizationService.cs index 26575f720..59abcfe90 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILocalizationService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILocalizationService.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Collections.Generic; using System.Collections.Immutable; +using System.Threading.Tasks; using Barotrauma.LuaCs.Data; namespace Barotrauma.LuaCs.Services; @@ -10,9 +11,10 @@ public interface ILocalizationService : IReusableService { IReadOnlyCollection GetLoadedLocales(); void Remove(ImmutableArray localizations); + void DisposePackage(ContentPackage package); FluentResults.Result SetCurrentCulture(CultureInfo culture); FluentResults.Result SetCurrentCulture(string cultureName); - FluentResults.Result LoadLocalizations(ImmutableArray localizationResources); + Task LoadLocalizations(ImmutableArray localizationResources); /// /// Tries to get a localized string without a fallback. Returns success/failure and associated data. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs new file mode 100644 index 000000000..88e432400 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Reflection; +using System.Threading.Tasks; +using Barotrauma.LuaCs.Data; +using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Interop; + +namespace Barotrauma.LuaCs.Services; + +public interface ILuaScriptManagementService : IReusableService +{ + #region Script_Ops + + Task LoadScriptResourcesAsync(ImmutableArray resourcesInfo); + + FluentResults.Result ExecuteLoadedScripts(ContentPackage package, bool pauseExecutionOnError = false, bool verboseLogging = false); + FluentResults.Result ExecuteLoadedScripts(ImmutableArray scripts, bool pauseExecutionOnError = false, bool verboseLogging = false); + FluentResults.Result ExecuteLoadedScripts(bool pauseExecutionOnError = false, bool verboseLogging = false); + FluentResults.Result DisposePackageResources(ContentPackage package); + FluentResults.Result UnloadActiveScripts(); + FluentResults.Result DisposeAllPackageResources(); + + #endregion + + #region Type_Registration + + IUserDataDescriptor RegisterType(Type type); + /// + /// [Deprecated]
+ /// Use () instead. + /// Gets the type information for an already registered type. + ///
+ /// The fully qualified name of the type and namespace. + /// The for the type, if registered. Null if none is found. + [Obsolete($"Use {nameof(GetTypeInfo)} instead.")] + IUserDataDescriptor RegisterType(string typeName) => GetTypeInfo(typeName); + IUserDataDescriptor RegisterGenericType(Type type); + /// + /// [Deprecated]
+ /// Use () instead. + /// Gets the generic type information for an already registered type. + ///
+ /// The fully qualified name of the generic type and namespace. + /// The fully qualified name of the template types. + /// The for the type, if registered. Null if none is found. + [Obsolete($"Use {nameof(GetGenericTypeInfo)} instead.")] + IUserDataDescriptor RegisterGenericType(string typeName, params string[] typeNameArgs) => GetGenericTypeInfo(typeName, typeNameArgs); + /// + /// Gets the type information for an already registered type. + /// + /// The fully qualified name of the type and namespace. + /// The for the type, if registered. Null if none is found. + IUserDataDescriptor GetTypeInfo(string typeName); + /// + /// Gets the generic type information for an already registered type. + /// + /// The fully qualified name of the generic type and namespace. + /// The fully qualified name of the template types. + /// The for the type, if registered. Null if none is found. + IUserDataDescriptor GetGenericTypeInfo(string typeName, params string[] typeNameArgs); + void UnregisterType(Type type); + + #endregion + + #region Type_Checks_&Utilities + + bool IsRegistered(Type type); + bool IsTargetType(object obj, string typeName); + string TypeOf(object obj); + object CreateStatic(string typeName); + object CreateEnumTable(string typeName); + FieldInfo FindFieldRecursively(Type type, string fieldName); + void MakeFieldAccessible(IUserDataDescriptor descriptor, string fieldName); + MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null); + void MakeMethodAccessible(IUserDataDescriptor descriptor, string methodName, string[] parameters = null); + PropertyInfo FindPropertyRecursively(Type type, string propertyName); + void MakePropertyAccessible(IUserDataDescriptor descriptor, string propertyName); + void AddMethod(IUserDataDescriptor descriptor, string methodName, object function); + void AddField(IUserDataDescriptor descriptor, string fieldName, DynValue value); + void RemoveMember(IUserDataDescriptor descriptor, string memberName); + bool HasMember(object obj, string memberName); + DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor descriptor); + DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType); + + #endregion +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptService.cs deleted file mode 100644 index 99f9fa516..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptService.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Reflection; -using Barotrauma.LuaCs.Data; -using MoonSharp.Interpreter; -using MoonSharp.Interpreter.Interop; - -namespace Barotrauma.LuaCs.Services; - -public interface ILuaScriptService : IReusableService -{ - #region Script_File_Collector - - /// - /// Adds the script files to the runner but does not execute them. - /// - /// - /// - FluentResults.Result AddScriptFiles(ImmutableArray luaResource); - - /// - /// Removes the specific resources from the script runner. Important: Does not stop the - /// execution of any code related to the files nor guarantee cleanup of resources! - /// - /// - void RemoveScriptFiles(ImmutableArray luaResource); - - /// - /// Executes loaded script files on the management service. - /// - /// - /// - /// - FluentResults.Result ExecuteScripts(bool pauseExecutionOnScriptError = false, bool verboseLogging = false); - - ImmutableArray GetScriptResources(); - - #endregion -} - -public interface ILuaScriptManagementService : IReusableService -{ - #region Script_File_Execution - - FluentResults.Result ExecuteLoadedScripts(ContentPackage package, bool pauseExecutionOnError = false, bool verboseLogging = false); - FluentResults.Result ExecuteLoadedScripts(ImmutableArray scripts, bool pauseExecutionOnError = false, bool verboseLogging = false); - FluentResults.Result ExecuteLoadedScripts(bool pauseExecutionOnError = false, bool verboseLogging = false); - - #endregion - - #region Type_Registration - - IUserDataDescriptor RegisterType(Type type); - IUserDataDescriptor RegisterType(string typeName); - IUserDataDescriptor RegisterGenericType(Type type); - IUserDataDescriptor RegisterGenericType(string typeName, params string[] typeNameArgs); - void UnregisterType(Type type); - void UnregisterType(string typeName); - void UnregisterAllTypes(); - - #endregion - - #region Type_Checks_&Utilities - - bool IsRegistered(Type type); - bool IsTargetType(object obj, string typeName); - string TypeOf(object obj); - object CreateStatic(string typeName); - object CreateEnumTable(string typeName); - FieldInfo FindFieldRecursively(Type type, string fieldName); - void MakeFieldAccessible(IUserDataDescriptor descriptor, string fieldName); - MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null); - void MakeMethodAccessible(IUserDataDescriptor descriptor, string methodName, string[] parameters = null); - PropertyInfo FindPropertyRecursively(Type type, string propertyName); - void MakePropertyAccessible(IUserDataDescriptor descriptor, string propertyName); - void AddMethod(IUserDataDescriptor descriptor, string methodName, object function); - void AddField(IUserDataDescriptor descriptor, string fieldName, DynValue value); - void RemoveMember(IUserDataDescriptor descriptor, string memberName); - bool HasMember(object obj, string memberName); - DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor descriptor); - DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType); - - #endregion -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs index 397a871b4..e3013afe9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs @@ -3,89 +3,61 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Threading.Tasks; using Barotrauma.Extensions; using Barotrauma.LuaCs.Data; namespace Barotrauma.LuaCs.Services; -public interface IPackageManagementService : IReusableService +public interface IPackageManagementService : IReusableService, ILocalizationsResourcesInfo, IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo, IAssembliesResourcesInfo +#if CLIENT + ,IStylesResourcesInfo +#endif + { /// - /// Adds packages to the queue of loadable packages without initializing them. + /// Loads and parses the provided for supported by the current runtime environment. /// /// - void QueuePackages(ImmutableArray packages); + /// + Task LoadPackageInfosAsync(ContentPackage packages); + /// + /// Loads and parses the provided collection for supported by the current runtime environment. + /// + /// + /// + Task> LoadPackagesInfosAsync(IReadOnlyList packages); + IReadOnlyList GetAllLoadedPackages(); + void DisposePackageInfos(ContentPackage package); + void DisposePackagesInfos(IReadOnlyList packages); + void DisposeAllPackagesInfos(); - /// - /// Generates the ModConfigInfo for all queued packages and adds them to the store. - /// - /// Use multithreaded loading. - /// Whether duplicate packages should be reported as errors. - /// Failure/Success records for each package. - FluentResults.Result ParseQueuedPackages(bool loadParallel = true, bool reportFailOnDuplicates = false); - /// - /// Loads only the localizations, configs, and config profiles for stored packages. - /// - /// - /// - FluentResults.Result LoadPackageConfigsResourcesGroup(bool loadParallel = true); - /// - /// Loads all resources for stored packages. - /// - /// Use multithreaded loading. - /// Only load safe scripting resources, such as Lua. C# plugins disabled. - /// - FluentResults.Result LoadAllPackageResources(bool loadParallel = true, bool safeResourcesOnly = true); - FluentResults.Result UnloadPackages(); - bool IsPackageLoaded(ContentPackage package); - bool CheckDependencyLoaded(IPackageDependencyInfo info); - bool CheckDependenciesLoaded([NotNull]IEnumerable infos, out ImmutableArray missingPackages); - bool CheckEnvironmentSupported(IPlatformInfo platform); - /// - /// Tries to get the package dependency record to refer to that specific package if it exists, optionally create it. - /// - /// ContentPackage reference - /// Register a new IPackageDependencyInfo reference. - /// - FluentResults.Result GetPackageDependencyInfoRecord(ContentPackage package, - bool addIfMissing = false); - /// - /// Tries to get the package dependency record to refer to that specific package if it exists, optionally create it. - /// - /// The Steam Workshop ID, if available, if not enter zero ('0'). - /// The name of the package. - /// The folder path, as formatted in [ContentPackage.Path]. - /// Register a new IPackageDependencyInfo reference. - /// - FluentResults.Result GetPackageDependencyInfoRecord(ulong steamWorkshopId, - string packageName, string folderPath = null, bool addIfMissing = false); - /// - /// Tries to get the package dependency record to refer to that specific package if it exists. - /// Note: This overload does not allow the registration of a new dependency. - /// - /// The folder path, as formatted in [ContentPackage.Path]. - /// - FluentResults.Result GetPackageDependencyInfoRecord(string folderPath); - - IPackageDependencyInfo CreateOrphanPackageDependencyInfoRecord(string packageName, - string packagePath, ulong steamWorkshopId); -} - -public readonly record struct LoadablePackage -{ - public ContentPackage Package { get; } - public bool IsEnabled { get; } - - public LoadablePackage(ContentPackage package, bool isEnabled) - { - Package = package; - IsEnabled = isEnabled; - } + // single + FluentResults.Result GetAssembliesInfos(ContentPackage package, bool onlySupportedResources = true); + FluentResults.Result GetConfigsInfos(ContentPackage package, bool onlySupportedResources = true); + FluentResults.Result GetConfigProfilesInfos(ContentPackage package, bool onlySupportedResources = true); + FluentResults.Result GetLocalizationsInfos(ContentPackage package, bool onlySupportedResources = true); + FluentResults.Result GetLuaScriptsInfos(ContentPackage package, bool onlySupportedResources = true); +#if CLIENT + FluentResults.Result GetStylesInfos(ContentPackage package, bool onlySupportedResources = true); +#endif + // collection + FluentResults.Result GetAssembliesInfos(IReadOnlyList packages, bool onlySupportedResources = true); + FluentResults.Result GetConfigsInfos(IReadOnlyList packages, bool onlySupportedResources = true); + FluentResults.Result GetConfigProfilesInfos(IReadOnlyList packages, bool onlySupportedResources = true); + FluentResults.Result GetLocalizationsInfos(IReadOnlyList packages, bool onlySupportedResources = true); + FluentResults.Result GetLuaScriptsInfos(IReadOnlyList packages, bool onlySupportedResources = true); +#if CLIENT + FluentResults.Result GetStylesInfos(IReadOnlyList packages, bool onlySupportedResources = true); +#endif + + Task> GetAssembliesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); + Task> GetConfigsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); + Task> GetConfigProfilesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); + Task> GetLocalizationsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); + Task> GetLuaScriptsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); +#if CLIENT + Task> GetStylesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); +#endif - public static ImmutableArray FromEnumerable(IEnumerable packages, bool isEnabled) - { - var builder = ImmutableArray.CreateBuilder(); - packages.ForEach(p => builder.Add(new LoadablePackage(p, isEnabled))); - return builder.ToImmutable(); - } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageService.cs deleted file mode 100644 index 3f1189250..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageService.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using Barotrauma.LuaCs.Data; -using FluentResults; - -namespace Barotrauma.LuaCs.Services; - -public interface IPackageService : IReusableService, - // These allow us the pass the IContentPackageService to anything that needs the data without having to directly reference the member - IResourceCultureInfo, IAssembliesResourcesInfo, ILocalizationsResourcesInfo, ILuaScriptsResourcesInfo -{ - ContentPackage Package { get; } - IModConfigInfo ModConfigInfo { get; } - bool IsEnabledInModList { get; } - /// - /// Try to load the XML config and resources information from the given package. - /// - /// - /// Whether the package was parsed without errors. - FluentResults.Result LoadResourcesInfo([NotNull]LoadablePackage package); - /// - /// Tries to load all assemblies and instance plugins for the given resources list, regardless whether they're marked as optional and/or lazy load. - /// Will sort by load priority unless overriden/bypassed. - /// - /// - /// - /// Whether loading is successful. Returns true on an empty list. - FluentResults.Result LoadPlugins([NotNull]IAssembliesResourcesInfo assembliesInfo, bool ignoreDependencySorting = false); - FluentResults.Result LoadLocalizations([NotNull]ILocalizationsResourcesInfo localizationsInfo); - FluentResults.Result AddLuaScripts([NotNull]ILuaScriptsResourcesInfo luaScriptsInfo); -#if CLIENT - FluentResults.Result LoadStyles([NotNull]IStylesResourcesInfo stylesInfo); -#endif - FluentResults.Result LoadConfig([NotNull]IConfigsResourcesInfo configsResourcesInfo, [NotNull]IConfigProfilesResourcesInfo configProfilesResourcesInfo); -} - diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs index d81ef4dac..6319573bb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Reflection; using Barotrauma.LuaCs.Data; @@ -31,16 +32,35 @@ public interface IPluginManagementService : IReusableService bool includeDefaultContext = true); /// - /// Tries to get the + /// Tries to get the Type given the fully qualified name. /// /// /// Type GetType(string typeName); /// - /// + /// Loads the provided assembly resources in the order of their dependencies and intra-mod priority load order. /// /// /// Success/Failure and list of failed resources, if any. FluentResults.Result> LoadAssemblyResources(ImmutableArray resource); + + /// + /// Creates instances of the given type and provides Property Injection and instance reference caching. Disposes of + /// all references that throw errors on + /// + /// List of Types + /// + /// + /// + IReadOnlyList> ActivateTypeInstances(ImmutableArray types, bool serviceInjection = true, + bool hostInstanceReference = false) where T : IDisposable; + + FluentResults.Result UnloadHostedReferences(); + + /// + /// Tries to gracefully unload all hosted plugin references + /// + /// + FluentResults.Result UnloadAllAssemblyResources(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs index 622a47591..abe2c0f3d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs @@ -43,6 +43,7 @@ GUI.SettingsMenuOpen = false; #endif Selected = this; + GameMain.LuaCs.EventService.PublishEvent(sub => sub.OnScreenSelected(this)); } public virtual Camera Cam => null; From cb88d215fa47b43f438d79cd08a5facf2abacfde Mon Sep 17 00:00:00 2001 From: EvilFactory Date: Wed, 26 Feb 2025 12:25:42 -0300 Subject: [PATCH 012/288] LuaGame legacy service --- .../SharedSource/LuaCs/LuaCsSetup.cs | 6 ++++-- .../{Lua/LuaClasses => Services}/LuaGame.cs | 20 ++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Lua/LuaClasses => Services}/LuaGame.cs (96%) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index cf3250c2a..e2dcffd5d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -49,6 +49,7 @@ namespace Barotrauma _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); // TODO: ILocalizationService // TODO: IConfigService // TODO: INetworkingService @@ -109,6 +110,8 @@ namespace Barotrauma ? svc : throw new NullReferenceException("Networking Manager service not found!"); public IEventService EventService => _servicesProvider.TryGetService(out var svc) ? svc : throw new NullReferenceException("Networking Manager service not found!"); + public LuaGame Game => _servicesProvider.TryGetService(out var svc) + ? svc : throw new NullReferenceException("LuaGame service not found!"); /* * === Config Vars @@ -175,8 +178,7 @@ namespace Barotrauma #region LegacyRedirects - public ILuaCsHook Hook => this.EventService; - + public ILuaCsHook Hook => this.EventService; #endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaGame.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaGame.cs similarity index 96% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaGame.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaGame.cs index 5b6dba8d1..3a767a3b0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaGame.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaGame.cs @@ -3,14 +3,15 @@ using System.Collections.Generic; using System.Linq; using System.Xml.Linq; using Barotrauma.Items.Components; +using Barotrauma.LuaCs.Services; using Barotrauma.Networking; using FarseerPhysics.Dynamics; using Microsoft.Xna.Framework; using MoonSharp.Interpreter; -namespace Barotrauma +namespace Barotrauma.LuaCs.Services { - partial class LuaGame + partial class LuaGame : IReusableService { public bool IsSingleplayer => GameMain.IsSingleplayer; public bool IsMultiplayer => GameMain.IsMultiplayer; @@ -463,6 +464,8 @@ namespace Barotrauma public List Commands => DebugConsole.Commands; + public bool IsDisposed => throw new NotImplementedException(); + public void AssignOnExecute(string names, object onExecute) => DebugConsole.AssignOnExecute(names, (string[] a) => { @@ -529,7 +532,7 @@ namespace Barotrauma GameMain.Server.EndGame(); } - public void AssignOnClientRequestExecute(string names, object onExecute) => DebugConsole.AssignOnClientRequestExecute(names, (Client a, Vector2 b, string[] c) => { GameMain.LuaCs.CallLuaFunction(onExecute, new object[] { a, b, c }); }); + //public void AssignOnClientRequestExecute(string names, object onExecute) => DebugConsole.AssignOnClientRequestExecute(names, (Client a, Vector2 b, string[] c) => { GameMain.LuaCs.CallLuaFunction(onExecute, new object[] { a, b, c }); }); #endif @@ -543,6 +546,17 @@ namespace Barotrauma DebugConsole.Commands.Remove(cmd); } } + + public FluentResults.Result Reset() + { + Stop(); + return FluentResults.Result.Ok(); + } + + public void Dispose() + { + Stop(); + } } } From 52d920d969c54be9a190777f36a734b35cbe541d Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 26 Feb 2025 12:48:34 -0500 Subject: [PATCH 013/288] [Milestone] PackageManagementService completed. - ContentPackageInfoLookup Service completed. - Implemented ModConfigService.cs - Implemented some of the resource processors. --- .../ClientSource/DebugConsole.cs | 13 +- .../BarotraumaClient/ClientSource/GameMain.cs | 13 + .../LuaCs/Configuration/DisplayableData.cs | 18 - .../IConfigDisplayDefinitions.cs | 6 - .../LuaCs/Configuration/IDisplayables.cs | 63 -- .../LuaCs/Data/DataInterfaceDefinitions.cs | 6 +- .../ClientSource/LuaCs/Data/IConfigInfo.cs | 16 +- .../LuaCs/Data/IResourceInfoDeclarations.cs | 4 +- .../ClientSource/LuaCs/LuaCsInstaller.cs | 4 + .../LuaCs/Services/IConfigService.cs | 2 +- .../LuaCs/Services/INetworkingService.cs | 8 + .../LuaCs/Services/IStylesService.cs | 2 +- .../LuaCs/Services/NetworkingService.cs | 9 +- .../Services/PackageManagementService.cs | 81 +++ .../Processing/IClientParserDefinitions.cs | 9 - .../Services/Processing/ModConfigService.cs | 39 ++ .../LuaCs/Services/StylesService.cs | 5 - .../ClientSource/Networking/GameClient.cs | 2 - .../Screens/MainMenuScreen/MainMenuScreen.cs | 12 +- .../Characters/CharacterNetworking.cs | 2 +- .../ServerSource/DebugConsole.cs | 10 +- .../BarotraumaServer/ServerSource/GameMain.cs | 15 +- .../ServerSource/LuaCs/LuaCsInstaller.cs | 6 +- .../LuaCs/Services/INetworkingService.cs | 9 + .../LuaCs/Services/NetworkingService.cs | 5 +- .../Services/PackageManagementService.cs | 26 + .../Services/Processing/ModConfigService.cs | 33 + .../ServerSource/Networking/GameServer.cs | 3 +- .../ContentPackageManager.cs | 6 +- .../LuaCs/AsyncReaderWriterLock.cs | 76 ++ .../LuaCs/Configuration/IConfigEntry.cs | 2 +- .../LuaCs/Configuration/IConfigList.cs | 2 +- .../LuaCs/Data/DataInterfaceDefinitions.cs | 209 +++--- .../LuaCs/Data/EPlatformsTargets.cs | 11 +- .../LuaCs/Data/IBaseInfoDefinitions.cs | 5 - .../SharedSource/LuaCs/Data/IConfigInfo.cs | 36 +- .../SharedSource/LuaCs/Data/IDataInfo.cs | 4 - .../SharedSource/LuaCs/Data/IModConfigInfo.cs | 4 +- .../LuaCs/Data/IPackageDependency.cs | 66 ++ .../LuaCs/Data/IPackageDependencyInfo.cs | 40 -- .../LuaCs/Data/IResourceInfoDeclarations.cs | 18 +- .../SharedSource/LuaCs/IEvents.cs | 4 + .../LuaCs/Lua/LuaBarotraumaAdditions.cs | 4 +- .../LuaCs/Lua/LuaClasses/LuaUserData.cs | 38 +- .../SharedSource/LuaCs/Lua/LuaConverters.cs | 4 +- .../SharedSource/LuaCs/LuaCsHook.cs | 4 +- .../SharedSource/LuaCs/LuaCsNetworking.cs | 12 +- .../LuaCs/LuaCsPerformanceCounter.cs | 1 + .../SharedSource/LuaCs/LuaCsSetup.cs | 55 +- .../SharedSource/LuaCs/ModUtils.cs | 43 +- .../LuaCs/Networking/INetCallback.cs | 2 +- .../SharedSource/LuaCs/Networking/INetVar.cs | 4 +- .../LuaCs/Networking/NetInterfaceCompat.cs | 2 +- .../LuaCs/Services/ConfigService.cs | 6 + .../Services/ContentPackageInfoLookup.cs | 383 ++++++++++ .../LuaCs/Services/EventService.cs | 10 - .../LuaCs/Services/LocalizationService.cs | 6 + .../SharedSource/LuaCs/Services/LuaGame.cs | 5 +- .../LuaCs/Services/NetworkingService.cs | 2 +- .../Services/PackageListRetrievalService.cs | 30 + .../Services/PackageManagementService.cs | 380 +++++++++- .../IConverterServiceDefinitions.cs | 21 +- .../Processing/IModConfigCreatorService.cs | 9 - .../Services/Processing/ModConfigService.cs | 661 ++++++++++++++++++ .../Processing/ResourceInfoProcessors.cs | 53 ++ .../LuaCs/Services/StorageService.cs | 32 +- .../Services/_Interfaces/IConfigService.cs | 2 +- .../_Interfaces/INetworkingService.cs | 4 +- .../_Interfaces/IPackageInfoLookupService.cs | 15 + .../IPackageListRetrievalService.cs | 9 + .../_Interfaces/IPackageManagementService.cs | 10 +- .../LuaCs/Services/_Interfaces/IService.cs | 1 + .../Services/_Interfaces/IStorageService.cs | 3 + .../LuaCs/_Plugins/CsPackageManager.cs | 5 +- .../MemoryFileAssemblyContextLoader.cs | 4 +- .../BarotraumaTest/LuaCs/HookPatchHelpers.cs | 6 +- .../BarotraumaTest/LuaCs/HookPatchTests.cs | 17 +- .../BarotraumaTest/LuaCs/LuaCsFixture.cs | 6 +- 78 files changed, 2331 insertions(+), 422 deletions(-) delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/DisplayableData.cs delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IConfigDisplayDefinitions.cs delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayables.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/INetworkingService.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageManagementService.cs delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/IClientParserDefinitions.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/ModConfigService.cs create mode 100644 Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/INetworkingService.cs create mode 100644 Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageManagementService.cs create mode 100644 Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/Processing/ModConfigService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/AsyncReaderWriterLock.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependency.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependencyInfo.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ContentPackageInfoLookup.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LocalizationService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageListRetrievalService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IModConfigCreatorService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ResourceInfoProcessors.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageInfoLookupService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageListRetrievalService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index 33f058b9f..fbd680b86 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -16,6 +16,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml.Linq; +using Barotrauma.LuaCs.Events; using static Barotrauma.FabricationRecipe; namespace Barotrauma @@ -665,8 +666,6 @@ namespace Barotrauma bool.TryParse(args[3], out luaCsEnabled); } - if (luaCsEnabled) { GameMain.LuaCs.Initialize(); } - GameMain.MainMenuScreen.QuickStart(fixedSeed: false, subName, difficulty, levelGenerationParams); }, getValidArgs: () => new[] { SubmarineInfo.SavedSubmarines.Select(s => s.Name).Distinct().OrderBy(s => s).ToArray() })); @@ -4226,7 +4225,8 @@ namespace Barotrauma commands.Add(new Command("cl_lua", $"cl_lua: Runs a string on the client.", (string[] args) => { - if (GameMain.Client != null && !GameMain.Client.HasPermission(ClientPermissions.ConsoleCommands)) + throw new NotImplementedException(); + /*if (GameMain.Client != null && !GameMain.Client.HasPermission(ClientPermissions.ConsoleCommands)) { ThrowError("Command not permitted."); return; @@ -4245,12 +4245,12 @@ namespace Barotrauma catch(Exception ex) { LuaCsLogger.HandleException(ex, LuaCsMessageOrigin.LuaMod); - } + }*/ })); commands.Add(new Command("cl_reloadlua|cl_reloadcs|cl_reloadluacs", "Re-initializes the LuaCs environment.", (string[] args) => { - GameMain.LuaCs.Initialize(); + GameMain.LuaCs.EventService.PublishEvent(sub => sub.OnReloadAllPackages()); })); commands.Add(new Command("cl_toggleluadebug", "Toggles the MoonSharp Debug Server.", (string[] args) => @@ -4262,7 +4262,8 @@ namespace Barotrauma int.TryParse(args[0], out port); } - GameMain.LuaCs.ToggleDebugger(port); + throw new NotImplementedException(); + //GameMain.LuaCs.ToggleDebugger(port); })); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index 1126a166a..92b024ed0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -1299,6 +1299,19 @@ namespace Barotrauma { IsExiting = true; CreatureMetrics.Save(); + try + { + if (_luaCs is not null) + { + _luaCs.Dispose(); + _luaCs = null; + } + } + catch (Exception e) + { + DebugConsole.ThrowError($"Error while disposing of LuaCsForBarotrauma: {e.Message} | {e.StackTrace}"); + } + DebugConsole.NewMessage("Exiting..."); Client?.Quit(); SteamManager.ShutDown(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/DisplayableData.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/DisplayableData.cs deleted file mode 100644 index 59df05404..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/DisplayableData.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.Xna.Framework; - -namespace Barotrauma.LuaCs.Configuration; - -public record DisplayableData : IDisplayableData -{ - public string InternalName { get; init; } - public ContentPackage OwnerPackage { get; init; } - public string FallbackPackageName { get; init; } - public string DisplayName { get; init; } - public string DisplayModName { get; init; } - public string DisplayCategory { get; init; } - public string Tooltip { get; init; } - public string ImageIcon { get; init; } - public Point IconResolution { get; init; } - public bool ShowWhenNotLoaded { get; init; } - public string Description { get; init; } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IConfigDisplayDefinitions.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IConfigDisplayDefinitions.cs deleted file mode 100644 index 3c5fbc5f7..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IConfigDisplayDefinitions.cs +++ /dev/null @@ -1,6 +0,0 @@ -using Barotrauma.LuaCs.Configuration; -using Barotrauma.LuaCs.Data; - -namespace Barotrauma.LuaCs.Configuration; - -public partial interface IConfigBase : IDisplayableData, IDisplayableInitialize { } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayables.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayables.cs deleted file mode 100644 index 0422fb91c..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayables.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Numerics; -using Barotrauma.LuaCs.Data; -using Microsoft.Xna.Framework; - -namespace Barotrauma.LuaCs.Configuration; - -/// -/// Contains the Display Data for use with Menus. -/// -public interface IDisplayableData : IDataInfo -{ - /// - /// The name to display in GUIs and Menus. - /// - string DisplayName { get; } - /// - /// The mod name to display in GUIs and Menus. - /// - string DisplayModName { get; } - /// - /// Category this instance falls under. Used by menus when filtering by category. - /// - string DisplayCategory { get; } - /// - /// The tooltip shown on hover. - /// - string Tooltip { get; } - /// - /// The fully qualified filepath to the image icon for this config. - /// - string ImageIcon { get; } - /// - /// Required if ImageIcon is set. X,Y resolution of the image. - /// - Point IconResolution { get; } - /// - /// Whether to show the entry in the menu when not loaded. - /// - bool ShowWhenNotLoaded { get; } - /// - /// What does this setting do? - /// - string Description { get; } -} - -public interface IDisplayableInitialize -{ - void Initialize(IDisplayableData values); - - // copy this as needed - /*public void Initialize(IDisplayableData values) - { - this.InternalName = values.InternalName; - this.OwnerPackage = values.OwnerPackage; - this.DisplayName = values.DisplayName; - this.DisplayModName = values.DisplayModName; - this.DisplayCategory = values.DisplayCategory; - this.Tooltip = values.Tooltip; - this.ImageIcon = values.ImageIcon; - this.IconResolution = values.IconResolution; - this.ShowWhenNotLoaded = values.ShowWhenNotLoaded; - }*/ -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs index 60e4ec227..570f48f9b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs @@ -5,7 +5,7 @@ namespace Barotrauma.LuaCs.Data; public partial record ModConfigInfo : IModConfigInfo { - public ImmutableArray StylesResourceInfos { get; init; } + public ImmutableArray Styles { get; init; } } public record StylesResourceInfo : IStylesResourceInfo @@ -18,6 +18,6 @@ public record StylesResourceInfo : IStylesResourceInfo public ImmutableArray SupportedCultures { get; init; } public string InternalName { get; init; } public ContentPackage OwnerPackage { get; init; } - public string FallbackPackageName { get; } - public ImmutableArray Dependencies { get; init; } + public string FallbackPackageName { get; init; } + public ImmutableArray Dependencies { get; init; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs index 45d563fa7..576d18118 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs @@ -2,4 +2,18 @@ using Barotrauma.LuaCs.Configuration; namespace Barotrauma.LuaCs.Data; -public partial interface IConfigInfo : IDisplayableData { } +public partial interface IConfigInfo +{ + /// + /// Should this config be displayed in end-user menus. + /// + bool ShowInMenus { get; } + /// + /// User-friendly on-hover tooltip text or Localization Token. + /// + string Tooltip { get; } + /// + /// Icon for display in menus, if available. + /// + string ImageIconPath { get; } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs index 8624189ed..de47ef225 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs @@ -4,12 +4,12 @@ namespace Barotrauma.LuaCs.Data; public partial interface IModConfigInfo : IStylesResourcesInfo { } -public interface IStylesResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageInfo, IPackageDependenciesInfo { } +public interface IStylesResourceInfo : IResourceInfo, IResourceCultureInfo, IDataInfo, IPackageDependenciesInfo { } public interface IStylesResourcesInfo { /// /// Collection of loadable styles data. /// - ImmutableArray StylesResourceInfos { get; } + ImmutableArray Styles { get; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs index c18660d89..490ce38d1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs @@ -57,6 +57,9 @@ namespace Barotrauma public static void CheckUpdate() { + throw new NotImplementedException(); + /*// TODO: Rewrite this to not rely on LuaCsSetup. + if (!File.Exists(LuaCsSetup.VersionFile)) { return; } ContentPackage luaPackage = LuaCsSetup.GetPackage(new SteamWorkshopId(GameMain.LuaCs.LuaForBarotraumaSteamId?.Value ?? 0)); @@ -116,6 +119,7 @@ namespace Barotrauma msg.Close(); return true; }; + */ } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs index 6d817456c..89b2316a3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using Barotrauma.LuaCs.Configuration; -using Barotrauma.LuaCs.Networking; +using Barotrauma.LuaCs.Services; using Barotrauma.Networking; namespace Barotrauma.LuaCs.Services; diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/INetworkingService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/INetworkingService.cs new file mode 100644 index 000000000..d79efba19 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/INetworkingService.cs @@ -0,0 +1,8 @@ +using Barotrauma.Networking; + +namespace Barotrauma.LuaCs.Services; + +internal partial interface INetworkingService : IReusableService +{ + void NetMessageReceived(IReadMessage message, ServerPacketHeader header); +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs index 6b07dbc21..26c8b2404 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs @@ -6,7 +6,7 @@ namespace Barotrauma.LuaCs.Services; /// /// Loads XML Style assets from the given content package. /// -public interface IStylesService : IReusableService +public interface IStylesService : IService { /// /// Tries to load the styles file for the given and path into a new instance. diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs index 8d9c0f0c0..597709cc5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs @@ -3,9 +3,9 @@ using Barotrauma.Networking; using System; using System.Collections.Generic; -namespace Barotrauma.LuaCs.Networking; +namespace Barotrauma.LuaCs.Services; -partial class NetworkingService +partial class NetworkingService : INetworkingService { private Dictionary> receiveQueue = new Dictionary>(); @@ -44,6 +44,11 @@ partial class NetworkingService } } + public void NetMessageReceived(IReadMessage message, ServerPacketHeader header) + { + throw new NotImplementedException(); + } + public INetWriteMessage Start(Guid netId) { var message = new WriteOnlyMessage(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageManagementService.cs new file mode 100644 index 000000000..7b722751a --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageManagementService.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Services.Processing; +using FluentResults; +// ReSharper disable UseCollectionExpression + +namespace Barotrauma.LuaCs.Services; + +public partial class PackageManagementService : IPackageManagementService +{ + public PackageManagementService( + IConverterServiceAsync modConfigParserService, + IProcessorService, IAssembliesResourcesInfo> assemblyInfoConverter, + IProcessorService, IConfigsResourcesInfo> configsInfoConverter, + IProcessorService, IConfigProfilesResourcesInfo> configProfilesConverter, + IProcessorService, ILocalizationsResourcesInfo> localizationsConverter, + IProcessorService, ILuaScriptsResourcesInfo> luaScriptsConverter, + IPackageInfoLookupService packageInfoLookupService, Func, IStylesResourcesInfo> stylesInfoConverter) + { + _stylesInfoConverter = stylesInfoConverter; + _modConfigParserService = modConfigParserService; + _assemblyInfoConverter = assemblyInfoConverter; + _configsInfoConverter = configsInfoConverter; + _configProfilesConverter = configProfilesConverter; + _localizationsConverter = localizationsConverter; + _luaScriptsConverter = luaScriptsConverter; + _packageInfoLookupService = packageInfoLookupService; + } + + private readonly Func, IStylesResourcesInfo> _stylesInfoConverter; + + public ImmutableArray Styles => _modInfos.IsEmpty ? ImmutableArray.Empty + : _modInfos.SelectMany(kvp => kvp.Value.Styles).ToImmutableArray(); + + public Result GetStylesInfos(ContentPackage package, bool onlySupportedResources = true) + { + ((IService)this).CheckDisposed(); + if (package is null) + return FluentResults.Result.Fail($"{nameof(GetStylesInfos)}: ContentPackage is null."); + if (_modInfos.TryGetValue(package, out var result)) + return FluentResults.Result.Ok(_stylesInfoConverter(onlySupportedResources? + result.Styles.Where(r => + (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 + && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() + : result.Styles + )); + return FluentResults.Result.Fail( + $"{nameof(GetStylesInfos)}: ContentPackage {package.Name} is not registered."); + } + + public Result GetStylesInfos(IReadOnlyList packages, bool onlySupportedResources = true) + { + ((IService)this).CheckDisposed(); + if (packages is null || packages.Count == 0) + return FluentResults.Result.Fail($"{nameof(GetStylesInfos)}: ContentPackage list is null or empty."); + var builder = ImmutableArray.CreateBuilder(); + foreach (var package in packages) + { + if (_modInfos.TryGetValue(package, out var result) && result.Styles is { IsEmpty: false }) + { + builder.AddRange(onlySupportedResources? + result.Styles.Where(r => + (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 + && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() + : result.Styles); + } + } + + return FluentResults.Result.Ok(_stylesInfoConverter(builder.MoveToImmutable())); + } + + public async Task> GetStylesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true) + { + return await Task.Run(() => GetStylesInfos(packages, onlySupportedResources)); + + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/IClientParserDefinitions.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/IClientParserDefinitions.cs deleted file mode 100644 index 8c81c7fb5..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/IClientParserDefinitions.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Xml.Linq; -using Barotrauma.LuaCs.Data; - -namespace Barotrauma.LuaCs.Services.Processing; - -#region XmlToResourceParsers -public interface IXmlStylesToResConverterService : IXmlResourceConverterService { } - -#endregion diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/ModConfigService.cs new file mode 100644 index 000000000..386a66940 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/ModConfigService.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using System.Xml.Linq; +using Barotrauma.LuaCs.Data; +using FluentResults; + +namespace Barotrauma.LuaCs.Services.Processing; + +public partial class ModConfigService +{ + private partial async Task> GetModConfigInfoAsync(ContentPackage package, XElement root) + { + var asm = root.GetChildElements("Assembly").ToImmutableArray(); + var loc = root.GetChildElements("Localization").ToImmutableArray(); + var cfg = root.GetChildElements("Config").ToImmutableArray(); + var lua = root.GetChildElements("Lua").ToImmutableArray(); + var stl = root.GetChildElements("Style").ToImmutableArray(); + + return FluentResults.Result.Ok(new ModConfigInfo() + { + Package = package, + PackageName = package.Name, + Assemblies = asm.Any() ? GetAssemblies(package, asm) : ImmutableArray.Empty, + Localizations = loc.Any() ? GetLocalizations(package, loc) : ImmutableArray.Empty, + Configs = cfg.Any() ? GetConfigs(package, cfg) : ImmutableArray.Empty, + ConfigProfiles = cfg.Any() ? GetConfigProfiles(package, cfg) : ImmutableArray.Empty, + LuaScripts = lua.Any() ? GetLuaScripts(package, lua) : ImmutableArray.Empty, + Styles = stl.Any() ? GetStyles(package, stl) : ImmutableArray.Empty + }); + } + + private ImmutableArray GetStyles(ContentPackage src, IEnumerable elements) + { + throw new NotImplementedException(); + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs index 29cb65429..6e608a5fc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs @@ -116,10 +116,5 @@ public class StylesService : IStylesService GC.SuppressFinalize(this); } - public FluentResults.Result Reset() - { - return UnloadAllStyles(); - } - public bool IsDisposed { get; private set; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index 33b4a8aec..5b908beef 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -2905,8 +2905,6 @@ namespace Barotrauma.Networking public void Quit() { - GameMain.LuaCs.Stop(); - ClientPeer?.Close(PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected)); GUIMessageBox.MessageBoxes.RemoveAll(c => c?.UserData is RoundSummary); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs index 31bfd266b..233f6b666 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs @@ -542,8 +542,10 @@ namespace Barotrauma } }; - string version = File.Exists(LuaCsSetup.VersionFile) ? File.ReadAllText(LuaCsSetup.VersionFile) : "Github"; - + // TODO: Implement version reading. + //string version = File.Exists(LuaCsSetup.VersionFile) ? File.ReadAllText(LuaCsSetup.VersionFile) : "Github"; + string version = "NOT_IMPLEMENTED"; + new GUITextBlock(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(10, 10) }, $"Using LuaCsForBarotrauma revision {AssemblyInfo.GitRevision} version {version}", Color.Red) { IgnoreLayoutGroups = false @@ -703,8 +705,6 @@ namespace Barotrauma #region Selection public override void Select() { - GameMain.LuaCs.Stop(); - ResetModUpdateButton(); if (WorkshopItemsToUpdate.Any()) @@ -1314,8 +1314,6 @@ namespace Barotrauma return; } - GameMain.LuaCs.CheckInitialize(); - selectedSub = new SubmarineInfo(Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub")); GameMain.GameSession = new GameSession(selectedSub, Option.None, CampaignDataPath.CreateRegular(savePath), GameModePreset.SinglePlayerCampaign, settings, mapSeed); @@ -1331,8 +1329,6 @@ namespace Barotrauma { if (string.IsNullOrWhiteSpace(path)) return; - GameMain.LuaCs.CheckInitialize(); - try { CampaignDataPath dataPath = diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs index 59c91a4a5..ea7816fba 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs @@ -815,7 +815,7 @@ namespace Barotrauma var tempBuffer = new ReadWriteMessage(); WriteStatus(tempBuffer, forceAfflictionData: true); - if (msgLengthBeforeStatus + tempBuffer.LengthBytes >= 255 && restrictMessageSize && GameMain.LuaCs.Networking.RestrictMessageSize) + if (msgLengthBeforeStatus + tempBuffer.LengthBytes >= 255 && restrictMessageSize && (GameMain.LuaCs.RestrictMessageSize?.Value ?? false)) { msg.WriteBoolean(false); if (msgLengthBeforeStatus < 255) diff --git a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs index b5ba08e21..555d790d3 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Text; using Barotrauma.Steam; using Barotrauma.Extensions; +using Barotrauma.LuaCs.Events; namespace Barotrauma { @@ -1292,7 +1293,8 @@ namespace Barotrauma { try { - GameMain.LuaCs.Lua.DoString(string.Join(" ", args)); + throw new NotImplementedException(); + //GameMain.LuaCs.Lua.DoString(string.Join(" ", args)); } catch (Exception ex) { @@ -1302,7 +1304,8 @@ namespace Barotrauma commands.Add(new Command("reloadlua|reloadcs|reloadluacs", "Re-initializes the LuaCs environment.", (string[] args) => { - GameMain.LuaCs.Initialize(); + //GameMain.LuaCs.Initialize(); + GameMain.LuaCs.EventService.PublishEvent(sub => sub.OnReloadAllPackages()); })); commands.Add(new Command("toggleluadebug", "Toggles the MoonSharp Debug Server.", (string[] args) => @@ -1314,7 +1317,8 @@ namespace Barotrauma int.TryParse(args[0], out port); } - GameMain.LuaCs.ToggleDebugger(port); + throw new NotImplementedException(); + //GameMain.LuaCs.ToggleDebugger(port); })); commands.Add(new Command("install_cl_lua|install_cl|install_cl_cs|install_cl_luacs", "Installs Client-Side LuaCs into your client.", (string[] args) => diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs index 18bce7393..66d8ab350 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs @@ -132,8 +132,6 @@ namespace Barotrauma NetLobbyScreen = new NetLobbyScreen(); CheckContentPackage(); - - LuaCs = new LuaCsSetup(); } @@ -454,7 +452,18 @@ namespace Barotrauma public void Exit() { ShouldRun = false; - GameMain.LuaCs.Dispose(); + try + { + if (_luaCs is not null) + { + _luaCs.Dispose(); + _luaCs = null; + } + } + catch (Exception e) + { + DebugConsole.ThrowError($"Error while disposing of LuaCsForBarotrauma: {e.Message} | {e.StackTrace}"); + } } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsInstaller.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsInstaller.cs index cd54fe591..a75ada38e 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsInstaller.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsInstaller.cs @@ -9,7 +9,9 @@ namespace Barotrauma { public static void Install() { - ContentPackage luaPackage = LuaCsSetup.GetPackage(LuaCsSetup.LuaForBarotraumaId); + throw new NotImplementedException(); + // TODO: Refactor the installer to not be dependent on LuaCsSetup. + /*ContentPackage luaPackage = LuaCsSetup.GetPackage(); if (luaPackage == null) { @@ -64,7 +66,7 @@ namespace Barotrauma return; } - GameMain.Server.SendChatMessage("Client-Side LuaCs installed, restart your game to apply changes.", ChatMessageType.ServerMessageBox); + GameMain.Server.SendChatMessage("Client-Side LuaCs installed, restart your game to apply changes.", ChatMessageType.ServerMessageBox);*/ } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/INetworkingService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/INetworkingService.cs new file mode 100644 index 000000000..43e7e2f95 --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/INetworkingService.cs @@ -0,0 +1,9 @@ + +using Barotrauma.Networking; + +namespace Barotrauma.LuaCs.Services; + +internal partial interface INetworkingService : IReusableService +{ + void NetMessageReceived(IReadMessage message, ClientPacketHeader header, Client client); +} diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs index 5cdcff59a..683558d35 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs @@ -4,9 +4,10 @@ using System; using System.Collections.Generic; using System.Linq; -namespace Barotrauma.LuaCs.Networking; +// ReSharper disable once CheckNamespace +namespace Barotrauma.LuaCs.Services; -partial class NetworkingService +partial class NetworkingService : INetworkingService { private const int MaxRegisterPerClient = 1000; diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageManagementService.cs new file mode 100644 index 000000000..435facc4d --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageManagementService.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Services.Processing; + +namespace Barotrauma.LuaCs.Services; + +public partial class PackageManagementService +{ + public PackageManagementService( + IConverterServiceAsync modConfigParserService, + IProcessorService, IAssembliesResourcesInfo> assemblyInfoConverter, + IProcessorService, IConfigsResourcesInfo> configsInfoConverter, + IProcessorService, IConfigProfilesResourcesInfo> configProfilesConverter, + IProcessorService, ILocalizationsResourcesInfo> localizationsConverter, + IProcessorService, ILuaScriptsResourcesInfo> luaScriptsConverter, + IPackageInfoLookupService packageInfoLookupService) + { + _modConfigParserService = modConfigParserService; + _assemblyInfoConverter = assemblyInfoConverter; + _configsInfoConverter = configsInfoConverter; + _configProfilesConverter = configProfilesConverter; + _localizationsConverter = localizationsConverter; + _luaScriptsConverter = luaScriptsConverter; + _packageInfoLookupService = packageInfoLookupService; + } +} diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/Processing/ModConfigService.cs new file mode 100644 index 000000000..ee93eccf5 --- /dev/null +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/Processing/ModConfigService.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using System.Xml.Linq; +using Barotrauma.LuaCs.Data; +using FluentResults; + +namespace Barotrauma.LuaCs.Services.Processing; + +public partial class ModConfigService +{ + private partial async Task> GetModConfigInfoAsync(ContentPackage package, XElement root) + { + var asm = root.GetChildElements("Assembly").ToImmutableArray(); + var loc = root.GetChildElements("Localization").ToImmutableArray(); + var cfg = root.GetChildElements("Config").ToImmutableArray(); + var lua = root.GetChildElements("Lua").ToImmutableArray(); + + return FluentResults.Result.Ok(new ModConfigInfo() + { + Package = package, + PackageName = package.Name, + Assemblies = asm.Any() ? GetAssemblies(package, asm) : ImmutableArray.Empty, + Localizations = loc.Any() ? GetLocalizations(package, loc) : ImmutableArray.Empty, + Configs = cfg.Any() ? GetConfigs(package, cfg) : ImmutableArray.Empty, + ConfigProfiles = cfg.Any() ? GetConfigProfiles(package, cfg) : ImmutableArray.Empty, + LuaScripts = lua.Any() ? GetLuaScripts(package, lua) : ImmutableArray.Empty + }); + } +} diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index d3aa1d893..8a734eec6 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -245,7 +245,6 @@ namespace Barotrauma.Networking VoipServer = new VoipServer(serverPeer); - GameMain.LuaCs.Initialize(); Log("Server started", ServerLog.MessageType.ServerMessage); GameMain.NetLobbyScreen.Select(); @@ -838,7 +837,7 @@ namespace Barotrauma.Networking ClientPacketHeader header = (ClientPacketHeader)inc.ReadByte(); - GameMain.LuaCs.Networking.NetMessageReceived(inc, header, connectedClient); + GameMain.LuaCs.NetworkingService.NetMessageReceived(inc, header, connectedClient); switch (header) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs index 20f0a7970..3a1f8d0f0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs @@ -12,6 +12,7 @@ using Barotrauma.IO; using Barotrauma.LuaCs.Events; using Barotrauma.Steam; using Microsoft.Xna.Framework; +using OneOf.Types; namespace Barotrauma { @@ -339,10 +340,7 @@ namespace Barotrauma } GameMain.LuaCs.EventService.PublishEvent(sub => - sub.OnAllPackageListChanged(corePackages - .Select((ContentPackage p) => p) - .Union(regularPackages.Select((ContentPackage p) => p)) - .ToImmutableArray())); + sub.OnAllPackageListChanged(ContentPackageManager.CorePackages, ContentPackageManager.RegularPackages)); } private readonly string directory; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/AsyncReaderWriterLock.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/AsyncReaderWriterLock.cs new file mode 100644 index 000000000..074b04c2e --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/AsyncReaderWriterLock.cs @@ -0,0 +1,76 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Barotrauma.LuaCs; + + +// taken from: +public sealed class AsyncReaderWriterLock : IDisposable +{ + readonly SemaphoreSlim _readSemaphore = new SemaphoreSlim(1, 1); + readonly SemaphoreSlim _writeSemaphore = new SemaphoreSlim(1, 1); + int _readerCount; + + public async Task AcquireWriterLock(CancellationToken token = default) + { + await _writeSemaphore.WaitAsync(token).ConfigureAwait(false); + try + { + await _readSemaphore.WaitAsync(token).ConfigureAwait(false); + } + catch + { + _writeSemaphore.Release(); + throw; + } + + return new LockToken(ReleaseWriterLock); + } + + private void ReleaseWriterLock() + { + _readSemaphore.Release(); + _writeSemaphore.Release(); + } + + public async Task AcquireReaderLock(CancellationToken token = default) + { + await _writeSemaphore.WaitAsync(token).ConfigureAwait(false); + if (Interlocked.Increment(ref _readerCount) == 1) + { + try + { + await _readSemaphore.WaitAsync(token).ConfigureAwait(false); + } + catch + { + Interlocked.Decrement(ref _readerCount); + _writeSemaphore.Release(); + throw; + } + } + + _writeSemaphore.Release(); + return new LockToken(ReleaseReaderLock); + } + + private void ReleaseReaderLock() + { + if (Interlocked.Decrement(ref _readerCount) == 0) + _readSemaphore.Release(); + } + + public void Dispose() + { + _writeSemaphore.Dispose(); + _readSemaphore.Dispose(); + } + + private sealed class LockToken : IDisposable + { + private readonly Action _action; + public LockToken(Action action) => _action = action; + public void Dispose() => _action?.Invoke(); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs index 30ba129c6..a39032258 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs @@ -1,5 +1,5 @@ using System; -using Barotrauma.LuaCs.Networking; +using Barotrauma.LuaCs.Services; namespace Barotrauma.LuaCs.Configuration; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigList.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigList.cs index 226ddbe08..0d291f215 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigList.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigList.cs @@ -1,4 +1,4 @@ -using Barotrauma.LuaCs.Networking; +using Barotrauma.LuaCs.Services; namespace Barotrauma.LuaCs.Configuration; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs index f5e169005..6dd98c625 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Linq; using System.Runtime.InteropServices; using System.Text; +using Barotrauma.Steam; namespace Barotrauma.LuaCs.Data; @@ -14,9 +15,6 @@ public partial record ModConfigInfo : IModConfigInfo { public ContentPackage Package { get; init; } public string PackageName { get; init; } - public TargetRunMode RunModes { get; init; } - - public ImmutableArray SupportedCultures { get; init; } public ImmutableArray Assemblies { get; init; } public ImmutableArray Localizations { get; init; } public ImmutableArray LuaScripts { get; init; } @@ -28,10 +26,15 @@ public partial record ModConfigInfo : IModConfigInfo #region DataContracts -public record AssemblyResourceInfo : IAssemblyResourceInfo +public record AssemblyResourcesInfo(ImmutableArray Assemblies) : IAssembliesResourcesInfo; +public record LocalizationResourcesInfo(ImmutableArray Localizations) : ILocalizationsResourcesInfo; +public record LuaScriptsResourcesInfo(ImmutableArray LuaScripts) : ILuaScriptsResourcesInfo; +public record ConfigResourcesInfo(ImmutableArray Configs) : IConfigsResourcesInfo; +public record ConfigProfilesResourcesInfo(ImmutableArray ConfigProfiles) : IConfigProfilesResourcesInfo; + +public record AssemblyResourceInfo : IAssemblyResourceInfo { public ContentPackage OwnerPackage { get; init; } - public string FallbackPackageName { get; init; } public string FriendlyName { get; init; } public bool IsScript { get; init; } public string InternalName { get; init; } @@ -41,138 +44,148 @@ public record AssemblyResourceInfo : IAssemblyResourceInfo public int LoadPriority { get; init; } public ImmutableArray FilePaths { get; init; } public ImmutableArray SupportedCultures { get; init; } - public ImmutableArray Dependencies { get; init; } + public ImmutableArray Dependencies { get; init; } public bool Optional { get; init; } } -public record DependencyInfo : IPackageDependencyInfo +public record PackageDependency : IPackageDependency { + public PackageDependency(ContentPackage package, IPackageInfo dependencyInfo, string internalName) + { + Dependency = dependencyInfo ?? throw new ArgumentNullException(nameof(dependencyInfo)); + OwnerPackage = package ?? throw new ArgumentNullException(nameof(package)); + InternalName = internalName ?? throw new ArgumentNullException(nameof(internalName)); + } public string InternalName { get; init; } public ContentPackage OwnerPackage { get; init; } - public string FolderPath { get; init; } - public string FallbackPackageName { get; init; } - public ulong SteamWorkshopId { get; init; } - public ContentPackage DependencyPackage { get; init; } - public bool IsMissing { get; init; } - public bool IsWorkshopInstallation { get; init; } - - public virtual bool Equals(DependencyInfo other) => Equals(this, other); - - public override int GetHashCode() - { - if (DependencyPackage is not null) - return DependencyPackage.GetHashCode(); - if (SteamWorkshopId != 0) - return SteamWorkshopId.GetHashCode(); - if (!FallbackPackageName.IsNullOrWhiteSpace() && !FolderPath.IsNullOrWhiteSpace()) - return string.Concat(FallbackPackageName, FolderPath).GetHashCode(); - if (!InternalName.IsNullOrWhiteSpace() && !FolderPath.IsNullOrWhiteSpace()) - return string.Concat(InternalName, FolderPath).GetHashCode(); - - return base.GetHashCode(); - } - - bool IEqualityComparer.Equals(IPackageDependencyInfo x, IPackageDependencyInfo y) => DependencyInfo.Equals(x, y); + public IPackageInfo Dependency { get; init; } + public override int GetHashCode() => Dependency.GetHashCode(); - public static bool operator ==(IPackageDependencyInfo x, DependencyInfo y) => y?.Equals(x) ?? false; - public static bool operator !=(IPackageDependencyInfo x, DependencyInfo y) => y?.Equals(x) ?? false; - public static bool Equals(IPackageDependencyInfo x, IPackageDependencyInfo y) - { - if (x is null) - return false; - if (y is null) - return false; - if (x == y) - return true; - - if (x.DependencyPackage is not null && y.DependencyPackage is not null) - return y.DependencyPackage == x.DependencyPackage; - - if (!x.FolderPath.IsNullOrWhiteSpace() - && !y.FolderPath.IsNullOrWhiteSpace() - && y.FolderPath == x.FolderPath) - return true; - - if (!x.FolderPath.IsNullOrWhiteSpace() != !y.FolderPath.IsNullOrWhiteSpace()) - return false; - - if (!x.FallbackPackageName.IsNullOrWhiteSpace() - && !y.FallbackPackageName.IsNullOrWhiteSpace() - && y.FallbackPackageName == x.FallbackPackageName) - return true; - - if (x.SteamWorkshopId != 0 && y.SteamWorkshopId == x.SteamWorkshopId) - return true; +} - return false; - } +public record PackageInfo : IPackageInfo +{ + public string Name { get; private set; } + public ulong SteamWorkshopId { get; private set; } + public uint Id { get; private set; } - /// - /// Returns the hash code unique for the package reference. - /// - /// - /// - /// The hash should only be collision-free when referring to different packages. - public int GetHashCode(IPackageDependencyInfo obj) - { - int hashCode = Seed; - hashCode = ApplyHashString(hashCode, obj.FallbackPackageName); - hashCode = ApplyHashString(hashCode, obj.InternalName); - if (obj.SteamWorkshopId > 0) - hashCode ^= (int)obj.SteamWorkshopId; - + private readonly Func _getPackage; - int ApplyHashString(int currentValue, string str) + public ContentPackage GetPackage() => _getPackage?.Invoke(this) ?? null; + + public void UpdateInfo(string name, ulong steamId, uint packageId) + { + if (name.IsNullOrWhiteSpace() || steamId == 0 || packageId == 0) { - try - { - if (str is null || str.Length < 1) - return currentValue; - byte[] b = Encoding.UTF8.GetBytes(str); - for (int i = 0; i < Math.Min(24, b.Length-1); i++) - currentValue ^= b[i]; - return currentValue; - } - catch - { - return currentValue; - } + throw new ArgumentException( + $"{nameof(PackageInfo)}: You cannot update a package with an invalid name or steam id with a valid id, or vice-versa."); } - return hashCode; + Name = name; + SteamWorkshopId = steamId; + Id = packageId; + } + + public PackageInfo(ContentPackage package, uint id, Func getPackage) + { + if (package is null) + throw new ArgumentNullException($"{nameof(PackageInfo)}: package is null"); + if (id == 0) + throw new ArgumentNullException($"{nameof(PackageInfo)}: id is zero."); + + this.Name = package.Name; + this.SteamWorkshopId = package.TryExtractSteamWorkshopId(out var sId) ? sId.Value : 0; + this.Id = id; + this._getPackage = getPackage; } - private static readonly int Seed = new Random().Next(436457, int.MaxValue-900); + public PackageInfo(string name, ulong steamWorkshopId, uint id, Func getPackage) + { + Name = !name.IsNullOrWhiteSpace() ? name : throw new ArgumentNullException($"{nameof(PackageInfo)}: name cannot be null or empty."); + SteamWorkshopId = steamWorkshopId != 0 ? steamWorkshopId : throw new ArgumentNullException($"{nameof(PackageInfo)}: steam id cannot be 0."); + this.Id = id; + this._getPackage = getPackage; + } + + public PackageInfo(string name, uint id, Func getPackage) + { + Name = name ?? throw new ArgumentNullException($"{nameof(PackageInfo)}: name cannot be null or empty."); + this.SteamWorkshopId = 0; + this.Id = id; + this._getPackage = getPackage; + } + + public PackageInfo(ulong steamWorkshopId, uint id, Func getPackage) + { + SteamWorkshopId = steamWorkshopId != 0 ? steamWorkshopId : throw new ArgumentNullException($"{nameof(PackageInfo)}: steamid cannot be 0."); + this.Id = id; + this._getPackage = getPackage; + } + + public override int GetHashCode() + { + return (int)Id; + } + + public virtual bool Equals(PackageInfo other) + { + return ((IEquatable)this).Equals(other); + } +} + + + +public record ConfigResourceInfo : IConfigResourceInfo +{ + public Platform SupportedPlatforms { get; init; } + public Target SupportedTargets { get; init; } + public int LoadPriority { get; init; } + public ImmutableArray FilePaths { get; init; } + public bool Optional { get; init; } + public ImmutableArray SupportedCultures { get; init; } + public ImmutableArray Dependencies { get; init; } + public string InternalName { get; init; } + public ContentPackage OwnerPackage { get; init; } +} + +public record ConfigProfileResourceInfo : IConfigProfileResourceInfo +{ + public Platform SupportedPlatforms { get; init; } + public Target SupportedTargets { get; init; } + public int LoadPriority { get; init; } + public ImmutableArray FilePaths { get; init; } + public bool Optional { get; init; } + public ImmutableArray SupportedCultures { get; init; } + public ImmutableArray Dependencies { get; init; } + public string InternalName { get; init; } + public ContentPackage OwnerPackage { get; init; } } public record LocalizationResourceInfo : ILocalizationResourceInfo { public string InternalName { get; init; } public ContentPackage OwnerPackage { get; init; } - public string FallbackPackageName { get; init; } - public CultureInfo TargetCulture { get; init; } public Platform SupportedPlatforms { get; init; } public Target SupportedTargets { get; init; } public int LoadPriority { get; init; } public ImmutableArray FilePaths { get; init; } public ImmutableArray SupportedCultures { get; init; } - public ImmutableArray Dependencies { get; init; } + public ImmutableArray Dependencies { get; init; } public bool Optional { get; init; } } public readonly struct LuaScriptScriptResourceInfo : ILuaScriptResourceInfo { public ContentPackage OwnerPackage { get; init; } - public string FallbackPackageName { get; init; } public Platform SupportedPlatforms { get; init; } public Target SupportedTargets { get; init; } public int LoadPriority { get; init; } public ImmutableArray FilePaths { get; init; } public ImmutableArray SupportedCultures { get; init; } - public ImmutableArray Dependencies { get; init; } + public ImmutableArray Dependencies { get; init; } public bool Optional { get; init; } public string InternalName { get; init; } - public bool LazyLoad { get; init; } + public bool IsAutorun { get; init; } } #endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs index bdf4188dc..aaf289f8e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs @@ -7,7 +7,7 @@ namespace Barotrauma.LuaCs.Data; public enum Platform { Linux=0x1, - MacOS=0x2, + OSX=0x2, Windows=0x4 } @@ -17,12 +17,3 @@ public enum Target Client=0x1, Server=0x2 } - -[Flags] -public enum TargetRunMode -{ - ClientEnabled = 0x1, - ClientAlways = 0x2, - ServerEnabled = 0x4, - ServerAlways = 0x8 -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs index 4b269abe4..cc9387175 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs @@ -21,11 +21,6 @@ public interface IPlatformInfo Target SupportedTargets { get; } } -/// -/// All info we should have on a package for a given resource. -/// -public interface IPackageInfo : IDataInfo { } - /// /// ResourceInfos contain metadata about a resource. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs index fa1c487b7..cfa46a629 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs @@ -1,5 +1,5 @@ using System; -using Barotrauma.LuaCs.Networking; +using Barotrauma.LuaCs.Services; using Barotrauma.Networking; namespace Barotrauma.LuaCs.Data; @@ -11,13 +11,41 @@ public partial interface IConfigInfo : IDataInfo /// Specifies the data type this should be initialized to (ie. string, int, vector, etc.) /// Custom types can be registered by mods. /// - string DataType { get; } + Type DataType { get; } + /// + /// String version of the default value. + /// string DefaultValue { get; } + /// + /// The value the last time this config was saved, if found in /data/. + /// string StoredValue { get; } + /// + /// Custom data storage for other type-specific information needed. IE. Used to store the min, + /// max and step values for the IConfigRangeEntry(T). + /// + string CustomParameters { get; } + /// + /// [Multiplayer]
+ /// What permissions do clients require to change this setting. + ///
ClientPermissions RequiredPermissions { get; } /// - /// Whether a value can be changed at runtime. + /// In what s is this config editable. + ///
+ /// Note: Setting this to value lower than 'Configuration` will render this config read-only. + ///
+ RunState CanEditStates { get; } + /// + /// Network synchronization rules for this config. /// - bool IsReadOnly { get; } NetSync NetSync { get; } + /// + /// User friendly name or Localization Token. + /// + string DisplayName { get; } + /// + /// User friendly description or Localization Token. + /// + string Description { get; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IDataInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IDataInfo.cs index 34933f7d1..d49be249b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IDataInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IDataInfo.cs @@ -16,10 +16,6 @@ public interface IDataInfo : IEqualityComparer, IEquatable /// The package this information belongs to. ///
ContentPackage OwnerPackage { get; } - /// - /// Used in place of the package data when the OwnerPackage is missing. - /// - string FallbackPackageName { get; } bool IEqualityComparer.Equals(IDataInfo x, IDataInfo y) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs index 8a324f870..7d60bf562 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs @@ -2,13 +2,11 @@ namespace Barotrauma.LuaCs.Data; -public partial interface IModConfigInfo : IResourceCultureInfo, IAssembliesResourcesInfo, +public partial interface IModConfigInfo : IAssembliesResourcesInfo, ILocalizationsResourcesInfo, ILuaScriptsResourcesInfo, IConfigsResourcesInfo, IConfigProfilesResourcesInfo { // package info ContentPackage Package { get; } string PackageName { get; } - // configuration - TargetRunMode RunModes { get; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependency.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependency.cs new file mode 100644 index 000000000..a094d122f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependency.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using Barotrauma; +using Barotrauma.LuaCs.Data; + +namespace Barotrauma.LuaCs.Data; + +public interface IPackageDependency : IDataInfo, IEquatable +{ + public IPackageInfo Dependency { get; } + + bool IEquatable.Equals(IPackageDependency other) + { + return other is not null && Dependency.Equals(other.Dependency); + } +} + +public interface IPackageInfo : IEquatable +{ + /// + /// Name of the content package. + /// + public string Name { get; } + /// + /// Steam ID of the package. + /// + public ulong SteamWorkshopId { get; } + /// + /// The Guid for the runtime instance of the package. + /// + public uint Id { get; } + + /// + /// Gets the reference to the best-match target ContentPackage that meets the requirement. + /// + /// The reference, or null if none was found. + public ContentPackage GetPackage(); + + /// + /// Tries to retrieve the current best and returns true if none was found. + /// + public bool IsMissing => GetPackage() is null; + + bool IEquatable.Equals(IPackageInfo other) + { + if (other is null) + return false; + if (ReferenceEquals(other, this)) + return true; + if (!this.IsMissing && !other.IsMissing && ReferenceEquals(other.GetPackage, this.GetPackage)) + return true; + if (this.SteamWorkshopId != 0 && other.SteamWorkshopId == this.SteamWorkshopId) + return true; + return this.Name == other.Name; + } +} + +public interface IPackageDependenciesInfo +{ + /// + /// List of required packages. + /// + ImmutableArray Dependencies { get; } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependencyInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependencyInfo.cs deleted file mode 100644 index 7da431927..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependencyInfo.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Collections.Immutable; - -namespace Barotrauma.LuaCs.Data; - -public interface IPackageDependencyInfo : IPackageInfo, - IEqualityComparer -{ - /// - /// Root folder of the content package. - /// - public string FolderPath { get; } - /// - /// Steam ID of the package. - /// - public ulong SteamWorkshopId { get; } - /// - /// The dependency package, if found in the ALL Packages List. - /// - public ContentPackage DependencyPackage { get; } - - /// - /// This dependency was not found. - /// - public bool IsMissing { get; } - - /// - /// Whether the package is installed from the workshop. False means installation is from local mods. - /// - public bool IsWorkshopInstallation { get; } -} - -public interface IPackageDependenciesInfo -{ - /// - /// List of required packages. - /// - ImmutableArray Dependencies { get; } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs index 281b20b04..ad6bb45d3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs @@ -4,14 +4,22 @@ using System.Globalization; namespace Barotrauma.LuaCs.Data; -public interface IConfigResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { } -public interface IConfigProfileResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { } -public interface ILocalizationResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { } +public interface IConfigResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IDataInfo { } +public interface IConfigProfileResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IDataInfo { } +public interface ILocalizationResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IDataInfo { } + /// /// Represents loadable Lua files. /// -public interface ILuaScriptResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo { } -public interface IAssemblyResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IPackageInfo +public interface ILuaScriptResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IDataInfo +{ + /// + /// Should this script be run automatically. + /// + public bool IsAutorun { get; } +} + +public interface IAssemblyResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IDataInfo { /// /// The friendly name of the assembly. Script files belonging to the same assembly should all have the same name. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index 1e7bc2620..36d8d9b82 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -55,6 +55,10 @@ internal interface IEventEnabledPackageListChanged : IEvent regularPackages); } +internal interface IEventReloadAllPackages : IEvent +{ + void OnReloadAllPackages(); +} #endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaBarotraumaAdditions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaBarotraumaAdditions.cs index 357c02bb2..cacdf5bf9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaBarotraumaAdditions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaBarotraumaAdditions.cs @@ -59,8 +59,8 @@ namespace Barotrauma { public object GetComponentString(string component) { - Type type = LuaUserData.GetType("Barotrauma.Items.Components." + component); - + Type type = GameMain.LuaCs.PluginManagementService.GetType("Barotrauma.Items.Components." + component); + if (type == null) { return null; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs index 959ee31a9..dd5ae0469 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs @@ -1,24 +1,15 @@ -using MoonSharp.Interpreter; -using MoonSharp.Interpreter.Interop; +/* using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using System.Reflection; +using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Interop; namespace Barotrauma { partial class LuaUserData { - public static ReadOnlyDictionary Descriptors => new ReadOnlyDictionary(descriptors); - private static ConcurrentDictionary descriptors = new ConcurrentDictionary(); - - public IUserDataDescriptor this[string index] - { - get => Descriptors.GetValueOrDefault(index); - } - public static Type GetType(string typeName) => LuaCsSetup.GetType(typeName); public static IUserDataDescriptor RegisterType(string typeName) @@ -30,15 +21,7 @@ namespace Barotrauma throw new ScriptRuntimeException($"tried to register a type that doesn't exist: {typeName}."); } - var descriptor = UserData.RegisterType(type); - descriptors.TryAdd(typeName, descriptor); - - return descriptor; - } - - public static IUserDataDescriptor RegisterTypeBarotrauma(string typeName) - { - return RegisterType($"Barotrauma.{typeName}"); + return UserData.RegisterType(type); } public static void RegisterExtensionType(string typeName) @@ -120,9 +103,7 @@ namespace Barotrauma MethodInfo method = typeof(UserData).GetMethod(nameof(UserData.CreateStatic), 1, new Type[0]); MethodInfo generic = method.MakeGenericMethod(type); - var result = generic.Invoke(null, null); - - return result; + return generic.Invoke(null, null); } public static object CreateEnumTable(string typeName) @@ -379,13 +360,6 @@ namespace Barotrauma descriptor ??= new StandardUserDataDescriptor(desiredType, InteropAccessMode.Default); return CreateUserDataFromDescriptor(scriptObject, descriptor); } - - public static void AddCallMetaTable(object userdata) { } - - - public static void Clear() - { - descriptors.Clear(); - } } } +*/ diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaConverters.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaConverters.cs index e2c038ebe..c7a0ba351 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaConverters.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaConverters.cs @@ -1,4 +1,5 @@ -using System; +/* +using System; using MoonSharp.Interpreter; using Microsoft.Xna.Framework; using FarseerPhysics.Dynamics; @@ -391,3 +392,4 @@ namespace Barotrauma } } } +*/ diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsHook.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsHook.cs index 11de83634..344e091d2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsHook.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsHook.cs @@ -840,7 +840,7 @@ namespace Barotrauma private static MethodBase ResolveMethod(string className, string methodName, string[] parameters) { - var classType = LuaUserData.GetType(className); + var classType = GameMain.LuaCs.PluginManagementService.GetType(className); if (classType == null) throw new ScriptRuntimeException($"invalid class name '{className}'"); const BindingFlags BINDING_FLAGS = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; @@ -855,7 +855,7 @@ namespace Barotrauma for (int i = 0; i < parameters.Length; i++) { - Type type = LuaUserData.GetType(parameters[i]); + Type type = GameMain.LuaCs.PluginManagementService.GetType(parameters[i]); if (type == null) { throw new ScriptRuntimeException($"invalid parameter type '{parameters[i]}'"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsNetworking.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsNetworking.cs index 845f90559..38042d1c1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsNetworking.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsNetworking.cs @@ -131,20 +131,24 @@ namespace Barotrauma } } + throw new NotImplementedException(); + string responseBody = await response.Content.ReadAsStringAsync(); - GameMain.LuaCs.Timer.Wait((object[] par) => + /*GameMain.LuaCs.Timer.Wait((object[] par) => { callback(responseBody, (int)response.StatusCode, response.Headers); - }, 0); + }, 0);*/ } catch (HttpRequestException e) { - GameMain.LuaCs.Timer.Wait((object[] par) => { callback(e.Message, e.StatusCode, null); }, 0); + throw new NotImplementedException(); + //GameMain.LuaCs.Timer.Wait((object[] par) => { callback(e.Message, e.StatusCode, null); }, 0); } catch (Exception e) { - GameMain.LuaCs.Timer.Wait((object[] par) => { callback(e.Message, null, null); }, 0); + throw new NotImplementedException(); + //GameMain.LuaCs.Timer.Wait((object[] par) => { callback(e.Message, null, null); }, 0); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsPerformanceCounter.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsPerformanceCounter.cs index b59ae4dac..591ed005b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsPerformanceCounter.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsPerformanceCounter.cs @@ -77,5 +77,6 @@ namespace Barotrauma } public void Dispose() { } + public bool IsDisposed { get; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index e2dcffd5d..c409ac4d3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -22,7 +22,7 @@ namespace Barotrauma internal delegate void LuaCsErrorHandler(Exception ex, LuaCsMessageOrigin origin); internal delegate void LuaCsExceptionHandler(Exception ex, LuaCsMessageOrigin origin); - partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventAllPackageListChanged, IEventEnabledPackageListChanged + partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventAllPackageListChanged, IEventEnabledPackageListChanged, IEventReloadAllPackages { public LuaCsSetup() { @@ -35,9 +35,11 @@ namespace Barotrauma return; // == end - // == helpers + // == sub processes void RegisterServices() { + _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); @@ -55,13 +57,27 @@ namespace Barotrauma // TODO: INetworkingService // TODO: [Resource Converter/Parser Services] + // IResourceInfo wrappers and mutators. + _servicesProvider.RegisterServiceType, IAssembliesResourcesInfo>, ResourceInfoArrayPacker>(ServiceLifetime.Transient); + _servicesProvider.RegisterServiceType, IConfigsResourcesInfo>, ResourceInfoArrayPacker>(ServiceLifetime.Transient); + _servicesProvider.RegisterServiceType, IConfigProfilesResourcesInfo>, ResourceInfoArrayPacker>(ServiceLifetime.Transient); + _servicesProvider.RegisterServiceType, ILocalizationsResourcesInfo>, ResourceInfoArrayPacker>(ServiceLifetime.Transient); + _servicesProvider.RegisterServiceType, ILuaScriptsResourcesInfo>, ResourceInfoArrayPacker>(ServiceLifetime.Transient); + + _servicesProvider.RegisterServiceType, ModConfigService>(ServiceLifetime.Transient); + _servicesProvider.RegisterServiceType, ModConfigService>(ServiceLifetime.Transient); + + + _servicesProvider.Compile(); } // Validates LuaCs assets in /Content are valid and ready to use. void ValidateLuaCsContent() { - throw new NotImplementedException(); + // check if /Content/Lua/ModConfig.xml exists + // if not, try to copy it from the Workshop Mod (ie. installation mode) + // if that fails, throw an error and exit. } } @@ -70,6 +86,7 @@ namespace Barotrauma EventService.Subscribe(this); // game state hook in EventService.Subscribe(this); EventService.Subscribe(this); + EventService.Subscribe(this); } #region CONST_DEF @@ -157,6 +174,11 @@ namespace Barotrauma /// Intended for development use, or when packages are expected to change outside of External Updates (ie. Steam Workshop). /// public IConfigEntry ReloadPackagesOnLobbyStart { get; private set; } + + /// + /// TODO: @evilfactory@users.noreply.github.com + /// + public IConfigEntry RestrictMessageSize { get; private set; } /** * == Ops Vars @@ -301,6 +323,22 @@ namespace Barotrauma { UpdateLoadedPackagesList(); } + + public void OnReloadAllPackages() + { + if (CurrentRunState <= RunState.Unloaded) + return; + var state = CurrentRunState; + SetRunState(RunState.Unloaded); + SetRunState(CurrentRunState); + } + + public void ForceRunState(RunState newState) + { + if (CurrentRunState == newState) + return; + SetRunState(newState); + } private void UpdateLoadedPackagesList() { @@ -489,6 +527,11 @@ namespace Barotrauma ?? throw new NullReferenceException($"{nameof(LuaForBarotraumaSteamId)} cannot be loaded."); CsForBarotraumaSteamId = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "CsForBarotraumaSteamId") ?? throw new NullReferenceException($"{nameof(CsForBarotraumaSteamId)} cannot be loaded."); + RestrictMessageSize = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "RestrictMessageSize") + ?? throw new NullReferenceException($"{nameof(RestrictMessageSize)} cannot be loaded."); + ReloadPackagesOnLobbyStart = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "ReloadPackagesOnLobbyStart") + ?? throw new NullReferenceException($"{nameof(ReloadPackagesOnLobbyStart)} cannot be loaded."); + } void DisposeLuaCsConfig() @@ -499,6 +542,8 @@ namespace Barotrauma HideUserNamesInLogs = null; LuaForBarotraumaSteamId = null; CsForBarotraumaSteamId = null; + RestrictMessageSize = null; + ReloadPackagesOnLobbyStart = null; } async Task LoadStaticAssetsAsync(IReadOnlyList packages) @@ -557,7 +602,7 @@ namespace Barotrauma { var res = await PackageManagementService.GetStylesInfosAsync(packages); if (res.IsSuccess) - styleRes = res.Value.StylesResourceInfos; + styleRes = res.Value.Styles; if (res.Errors.Any()) ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), res.ToResult()); @@ -726,6 +771,8 @@ namespace Barotrauma _runState = RunState.Configuration; } } + + } /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs index 981c74bad..ad34c723d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading; +using System.Threading.Tasks; using System.Xml.Serialization; using Barotrauma; using Barotrauma.Items.Components; @@ -17,6 +18,7 @@ using Microsoft.CodeAnalysis; using Microsoft.Xna.Framework; using OneOf; using Platform = Barotrauma.LuaCs.Data.Platform; +// ReSharper disable ConvertClosureToMethodGroup // This file is cursed, we put everything in it, and I'm not sorry about it. namespace Barotrauma.LuaCs @@ -435,7 +437,7 @@ namespace Barotrauma.LuaCs /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool GetBool(ref int var) => Interlocked.CompareExchange(ref var, 1, 1) > 0; - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetBool(ref int var, bool value) { @@ -455,7 +457,7 @@ namespace Barotrauma.LuaCs /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool CheckClearAndSetBool(ref int var) + public static bool CheckIfClearAndSetBool(ref int var) { return Interlocked.CompareExchange(ref var, 1, 0) < 1; } @@ -466,7 +468,7 @@ namespace Barotrauma.LuaCs /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool CheckSetAndClearBool(ref int var) + public static bool CheckIfSetAndClearBool(ref int var) { return Interlocked.CompareExchange(ref var, 0, 1) > 0; } @@ -521,8 +523,43 @@ namespace Barotrauma.LuaCs } } } + + public static class CollectionExtensions + { + /// + /// Executes a series of asynchronous tasks with limited parallelism to maintain execution efficiency. + /// + /// + /// + /// + /// + /// + public static Task ParallelForEachAsync(this IEnumerable source, Func funcBody, int maxDegreeOfParallelism = 4) + { + async Task AwaitParallelLimit(IEnumerator partition) + { + using (partition) + { + while (partition.MoveNext()) + { + await Task.Yield(); // prevents a sync/hot thread hangup + await funcBody(partition.Current); + } + } + } + + return Task.WhenAll( + Partitioner + .Create(source) + .GetPartitions(maxDegreeOfParallelism) + .AsParallel() + .Select(p => AwaitParallelLimit(p))); + } + } } + + #region ExceptionData namespace FluentResults.LuaCs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetCallback.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetCallback.cs index f498da372..9d3c86853 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetCallback.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetCallback.cs @@ -1,6 +1,6 @@ using System; -namespace Barotrauma.LuaCs.Networking; +namespace Barotrauma.LuaCs.Services; public partial interface INetCallback { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs index 38c962046..a907b18fa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs @@ -1,10 +1,10 @@ using System; using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Networking; +using Barotrauma.LuaCs.Services; using Barotrauma.Networking; -namespace Barotrauma.LuaCs.Networking; +namespace Barotrauma.LuaCs.Services; public interface INetVar : IVarId { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs index 8799208df..161bf3e1c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs @@ -1,7 +1,7 @@ using Barotrauma.Networking; using Microsoft.Xna.Framework; -namespace Barotrauma.LuaCs.Networking; +namespace Barotrauma.LuaCs.Services; #region Wrapper-IWriteMessage diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs new file mode 100644 index 000000000..f6962300d --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Services; + +public class ConfigService : IConfigService +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ContentPackageInfoLookup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ContentPackageInfoLookup.cs new file mode 100644 index 000000000..4a2f8e594 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ContentPackageInfoLookup.cs @@ -0,0 +1,383 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Events; +using Barotrauma.Steam; +using FluentResults; +using OneOf; + +namespace Barotrauma.LuaCs.Services; + +/// +/// Provides resolution for dynamically locating the best matching package at the time of consumption. +/// +public sealed class ContentPackageInfoLookup : IPackageInfoLookupService, IEventEnabledPackageListChanged, IEventAllPackageListChanged +{ + #region INTERNAL + + // packageinfo query data + private readonly ConcurrentDictionary, IPackageInfo> _packageInfoMap = new(); + // package query data + private readonly ConcurrentDictionary> _packageIdGroups = new(); + private readonly ConcurrentDictionary> _reversePackageIdGroups = new(); + private readonly HashSet _enabledPackages; + private readonly HashSet _allPackages; + // threading + private readonly AsyncReaderWriterLock _packageIdGroupsLock = new(); + private readonly AsyncReaderWriterLock _packageSetsLock = new(); + // services + private readonly IEventService _eventService; + private readonly IPackageListRetrievalService _packageListRetrievalService; + + private int _isDisposed = 0; + private uint _idCounter = 0; + + // returns ++_idCounter; + private uint GetNextId() => Interlocked.Increment(ref _idCounter); + + private ContentPackage GetBestMatchPackage(IPackageInfo packageInfo) + { + if (packageInfo is null) + return null; + if (!_packageIdGroups.TryGetValue(packageInfo.Id, out var packageGroup) + || packageGroup.IsDefaultOrEmpty) + return null; + if (packageGroup.Length == 1) + return packageGroup[0]; + + bool nameGood = !packageInfo.Name.IsNullOrWhiteSpace(); + + // try by enabled + var prev = packageGroup; + + var packList = packageGroup; + using (_packageSetsLock.AcquireReaderLock().GetAwaiter().GetResult()) + { + packList = packList + .Where(p => p is not null && _enabledPackages.Contains(p)) + .ToImmutableArray(); + } + + if (ReturnValue()) + return packList[0]; + + // try by steam id + if (packageInfo.SteamWorkshopId != 0) + { + packList = packList + .Where(p => p.TryExtractSteamWorkshopId(out var sId) && sId.Value == packageInfo.SteamWorkshopId) + .ToImmutableArray(); + + if (ReturnValue()) + return packList[0]; + } + + // try by name + if (nameGood) + { + packList = packList + .Where(p => p.Name == packageInfo.Name) + .ToImmutableArray(); + + if (ReturnValue()) + return packList[0]; + } + + // try by localmods + packList = packList.Where(p => p.Path.ToLowerInvariant().Contains("localmods")) + .ToImmutableArray(); + + if (ReturnValue()) + return packList[0]; + + // get the first in the list + return packList.First(); + + bool ReturnValue() + { + if (packList.IsDefaultOrEmpty) + packList = prev; + else if (packList.Length == 1) + return true; + else + prev = packList; + return false; + } + } + + private async Task SyncPackagesLists(IReadOnlyList enabledPackages, + IReadOnlyList allPackages) + { + if (enabledPackages is null || allPackages is null) + return; + + // take all locks + using var l1 = await _packageIdGroupsLock.AcquireWriterLock(); + using var l2 = await _packageSetsLock.AcquireWriterLock(); + + // calc diffs + var toAddAll = allPackages.Except(_allPackages).ToHashSet(); + var toAddEnabled = enabledPackages.Except(_enabledPackages).ToHashSet(); + var toRemoveAll = _allPackages.Except(allPackages).ToHashSet(); + var toRemoveEnabled = _enabledPackages.Except(enabledPackages).ToHashSet(); + + // remove old + if (toRemoveAll.Any()) + { + foreach (var package in toRemoveAll) + { + if (package is null) + continue; + + _allPackages.Remove(package); + + // try to find id lookup + if (!_reversePackageIdGroups.TryGetValue(package, out var idGroup)) + continue; + + // found packs + if (!idGroup.IsDefaultOrEmpty) + { + foreach (var id in idGroup) + { + if (!_packageIdGroups.TryGetValue(id, out var packageGroup) + || packageGroup.IsDefaultOrEmpty) + continue; + _packageIdGroups[id] = packageGroup.RemoveAll(p => toRemoveAll.Contains(p)); + } + } + + // remove ref + _reversePackageIdGroups.Remove(package, out _); + } + } + + if (toRemoveEnabled.Any()) + { + foreach (var package in toRemoveEnabled) + { + if (package is null) + continue; + _enabledPackages.Remove(package); + } + } + + // add new + if (toAddAll.Any()) + { + foreach (var package in toAddAll) + { + if (package is null) + continue; + + _allPackages.Add(package); + + var steamId = package.TryExtractSteamWorkshopId(out var id) ? id.Value : 0; + IPackageInfo packageInfo; + Queue idListsToAdd = new(); + if (!package.Name.IsNullOrWhiteSpace() && steamId > 0) + { + // combined key + packageInfo = GetOrCreateInfoForMap(package, (package.Name, steamId)); + AddToPackageIdGroups(packageInfo.Id, package); + // string key + packageInfo = GetOrCreateInfoForMap(package, package.Name); + AddToPackageIdGroups(packageInfo.Id, package); + // steamId key + packageInfo = GetOrCreateInfoForMap(package, steamId); + AddToPackageIdGroups(packageInfo.Id, package); + } + + // try find in the existing list, or make a new one + IPackageInfo GetOrCreateInfoForMap(ContentPackage package, OneOf.OneOf infoKey) + { + return _packageInfoMap.TryGetValue(infoKey, out var pInfo) + ? pInfo + : new PackageInfo(package, GetNextId(), GetBestMatchPackage); + } + + // add to package lookups + void AddToPackageIdGroups(uint id, ContentPackage package) + { + if (_packageIdGroups.TryGetValue(id, out var packageGroup)) + { + if (!packageGroup.Contains(package)) + _packageIdGroups[id] = packageGroup.Add(package); + } + else + _packageIdGroups[id] = new[] { package }.ToImmutableArray(); + + if (_reversePackageIdGroups.TryGetValue(package, out var idGroup)) + { + if (!idGroup.Contains(id)) + _reversePackageIdGroups[package] = idGroup.Add(id); + } + else + _reversePackageIdGroups[package] = new[] { id }.ToImmutableArray(); + } + } + } + + if (toAddEnabled.Any()) + { + foreach (var package in toAddEnabled) + { + if (package is null) + continue; + _enabledPackages.Add(package); + } + } + } + + private async Task> LookupInternal(OneOf.OneOf infoKey) + { + using (await _packageIdGroupsLock.AcquireReaderLock()) + { + if (_packageInfoMap.TryGetValue(infoKey, out var packageInfo)) + return FluentResults.Result.Ok(packageInfo); + } + + // change to write lock + using (await _packageIdGroupsLock.AcquireWriterLock()) + { + // create one + var packageInfo = infoKey.Match( + sPackName => new PackageInfo(sPackName, GetNextId(), GetBestMatchPackage), + uSteamId => new PackageInfo(uSteamId, GetNextId(), GetBestMatchPackage), + cKey => new PackageInfo(cKey.Item1, cKey.Item2, GetNextId(), GetBestMatchPackage) + ); + _packageInfoMap[infoKey] = packageInfo; + // empty array + _packageIdGroups[packageInfo.Id] = ImmutableArray.Empty; + return FluentResults.Result.Ok(packageInfo); + } + } + + #endregion + + public ContentPackageInfoLookup(IEventService eventService, IPackageListRetrievalService packageListRetrievalService) + { + _eventService = eventService ?? throw new ArgumentNullException( + $"{nameof(ContentPackageInfoLookup)}: {nameof(eventService)} cannot be null."); + _packageListRetrievalService = packageListRetrievalService ?? throw new ArgumentNullException(nameof(packageListRetrievalService)); + this._enabledPackages = new HashSet(); + this._allPackages = new HashSet(); + } + + public void Dispose() + { + IsDisposed = true; + // locks + using var l1 = _packageIdGroupsLock.AcquireWriterLock().GetAwaiter().GetResult(); + using var l2 = _packageSetsLock.AcquireWriterLock().GetAwaiter().GetResult(); + + _eventService.Unsubscribe(this); + _eventService.Unsubscribe(this); + + _packageIdGroups.Clear(); + _packageInfoMap.Clear(); + _reversePackageIdGroups.Clear(); + } + + public bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } + + public FluentResults.Result Reset() + { + if (IsDisposed) + return FluentResults.Result.Fail($"Service is disposed."); + + using var l1 = _packageIdGroupsLock.AcquireWriterLock().GetAwaiter().GetResult(); + using var l2 = _packageSetsLock.AcquireWriterLock().GetAwaiter().GetResult(); + + _packageIdGroups.Clear(); + _packageInfoMap.Clear(); + _reversePackageIdGroups.Clear(); + + RefreshPackageLists(); + + return FluentResults.Result.Ok(); + } + + public void OnEnabledPackageListChanged(CorePackage package, IEnumerable regularPackages) + { + ((IService)this).CheckDisposed(); + SyncPackagesLists( + regularPackages.Select(p => (ContentPackage)p).ToImmutableArray().Add(package), + _allPackages.ToImmutableArray()) + .GetAwaiter().GetResult(); + } + + public void OnAllPackageListChanged(IEnumerable corePackages, IEnumerable regularPackages) + { + ((IService)this).CheckDisposed(); + SyncPackagesLists( + _enabledPackages.ToImmutableArray(), + regularPackages.Select(p => p as ContentPackage) + .Union(corePackages.Select(p => p as ContentPackage)) + .ToImmutableArray() + ).GetAwaiter().GetResult(); + } + + public async Task> Lookup(string packageName) + { + ((IService)this).CheckDisposed(); + if(packageName.IsNullOrWhiteSpace()) + return FluentResults.Result.Fail($"Name is null or empty."); + return await LookupInternal(packageName); + } + + public async Task> Lookup(string packageName, ulong steamWorkshopId) + { + ((IService)this).CheckDisposed(); + if (packageName.IsNullOrWhiteSpace() || steamWorkshopId == 0) + return FluentResults.Result.Fail($"Name or steam id is null or empty."); + return await LookupInternal((packageName, steamWorkshopId)); + } + + public async Task> Lookup(ulong steamWorkshopId) + { + ((IService)this).CheckDisposed(); + if (steamWorkshopId is 0) + return FluentResults.Result.Fail($"SteamId is 0."); + return await LookupInternal(steamWorkshopId); + } + + public async Task> Lookup(ContentPackage package) + { + ((IService)this).CheckDisposed(); + if (package is null) + return FluentResults.Result.Fail($"Package is null."); + + if (package.TryExtractSteamWorkshopId(out var steamWorkshopId) && steamWorkshopId.Value != 0) + { + if (!package.Name.IsNullOrWhiteSpace()) + return await LookupInternal((package.Name, steamWorkshopId.Value)); + else + return await LookupInternal(steamWorkshopId.Value); + } + + if (!package.Name.IsNullOrWhiteSpace()) + return await LookupInternal(package.Name); + + return FluentResults.Result.Fail($"Package name is null and steamid is 0."); + } + + public void RefreshPackageLists() + { + ((IService)this).CheckDisposed(); + if (Thread.CurrentThread != GameMain.MainThread) + throw new InvalidOperationException($"{nameof(ContentPackageInfoLookup)}: {nameof(RefreshPackageLists)} must be run on the main thread."); + var enabledPackages = _packageListRetrievalService.GetEnabledContentPackages().ToImmutableArray(); + var allPackages = _packageListRetrievalService.GetAllContentPackages().ToImmutableArray(); + SyncPackagesLists(enabledPackages, allPackages).GetAwaiter().GetResult(); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs index c0cde07dc..c237015a7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs @@ -1,22 +1,12 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; -using System.Collections.Specialized; -using System.Dynamic; using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Threading; using Barotrauma.Extensions; using Barotrauma.LuaCs.Events; using Barotrauma.LuaCs.Services.Compatibility; -using Barotrauma.LuaCs.Services.Safe; -using Dynamitey; using FluentResults; using FluentResults.LuaCs; -using HarmonyLib; -using ImpromptuInterface; using OneOf; namespace Barotrauma.LuaCs.Services; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LocalizationService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LocalizationService.cs new file mode 100644 index 000000000..e3a51d638 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LocalizationService.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Services; + +public interface LocalizationService +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaGame.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaGame.cs index 3a767a3b0..a97beb235 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaGame.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaGame.cs @@ -272,8 +272,9 @@ namespace Barotrauma.LuaCs.Services public LuaGame() { - LuaUserData.MakeFieldAccessible(UserData.RegisterType(typeof(GameSettings)), "currentConfig"); - Settings = UserData.CreateStatic(typeof(GameSettings)); + throw new NotImplementedException(); + /*LuaUserData.MakeFieldAccessible(UserData.RegisterType(typeof(GameSettings)), "currentConfig"); + Settings = UserData.CreateStatic(typeof(GameSettings));*/ } public void OverrideTraitors(bool o) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs index 971661eec..97d0baed2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs @@ -3,7 +3,7 @@ using Barotrauma.Networking; using System; using System.Collections.Generic; -namespace Barotrauma.LuaCs.Networking; +namespace Barotrauma.LuaCs.Services; internal partial class NetworkingService : INetworkingService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageListRetrievalService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageListRetrievalService.cs new file mode 100644 index 000000000..37e1c2bfc --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageListRetrievalService.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +namespace Barotrauma.LuaCs.Services; + +public sealed class PackageListRetrievalService : IPackageListRetrievalService +{ + public void Dispose() + { + // stateless service + return; + } + + public void CheckDisposed() + { + // stateless service + return; + } + + public bool IsDisposed => false; + + public IEnumerable GetEnabledContentPackages() + { + return ContentPackageManager.EnabledPackages.All; + } + + public IEnumerable GetAllContentPackages() + { + return ContentPackageManager.AllPackages; + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index c9d31605e..23f646bb6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -1,13 +1,391 @@  +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; +using System.Threading; using System.Threading.Tasks; using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Services.Processing; +using Barotrauma.Steam; using FluentResults; +using OneOf; + +// ReSharper disable UseCollectionExpression namespace Barotrauma.LuaCs.Services; -public class PackageManagementService : IPackageManagementService +public partial class PackageManagementService : IPackageManagementService { + private int _isDisposed; + private readonly ConcurrentDictionary _modInfos = new(); + // lookup caches + private readonly IPackageInfoLookupService _packageInfoLookupService; + // processors + private readonly IConverterServiceAsync _modConfigParserService; + private readonly IProcessorService, IAssembliesResourcesInfo> _assemblyInfoConverter; + private readonly IProcessorService, IConfigsResourcesInfo> _configsInfoConverter; + private readonly IProcessorService, IConfigProfilesResourcesInfo> _configProfilesConverter; + private readonly IProcessorService, ILocalizationsResourcesInfo> _localizationsConverter; + private readonly IProcessorService, ILuaScriptsResourcesInfo> _luaScriptsConverter; + + + public void Dispose() + { + IsDisposed = true; + _modInfos.Clear(); + } + + public bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } + + public FluentResults.Result Reset() + { + try + { + ((IService)this).CheckDisposed(); + _modInfos.Clear(); + } + catch (Exception e) + { + return FluentResults.Result.Fail(new ExceptionalError(e)); + } + return FluentResults.Result.Ok(); + } + + public ImmutableArray Localizations => _modInfos.IsEmpty ? ImmutableArray.Empty + : _modInfos.SelectMany(kvp => kvp.Value.Localizations).ToImmutableArray(); + public ImmutableArray Configs => _modInfos.IsEmpty ? ImmutableArray.Empty + : _modInfos.SelectMany(kvp => kvp.Value.Configs).ToImmutableArray(); + public ImmutableArray ConfigProfiles => _modInfos.IsEmpty ? ImmutableArray.Empty + : _modInfos.SelectMany(kvp => kvp.Value.ConfigProfiles).ToImmutableArray(); + public ImmutableArray LuaScripts => _modInfos.IsEmpty ? ImmutableArray.Empty + : _modInfos.SelectMany(kvp => kvp.Value.LuaScripts).ToImmutableArray(); + public ImmutableArray Assemblies => _modInfos.IsEmpty ? ImmutableArray.Empty + : _modInfos.SelectMany(kvp => kvp.Value.Assemblies).ToImmutableArray(); + + + public async Task LoadPackageInfosAsync(ContentPackage package) + { + ((IService)this).CheckDisposed(); + if (package is null) + return FluentResults.Result.Fail(new ExceptionalError(new NullReferenceException($"{nameof(LoadPackageInfosAsync)}: ContentPackage is null."))); + var result = await _modConfigParserService.TryParseResourceAsync(package); + if (result.IsFailed) + return FluentResults.Result.Fail($"$Could not parse package mod config.").WithErrors(result.Errors); + if (!_modInfos.TryAdd(package, result.Value)) + return FluentResults.Result.Fail($"Failed to add ModInfo for {package.Name}."); + return FluentResults.Result.Ok(); + } + + public async Task> LoadPackagesInfosAsync(IReadOnlyList packages) + { + ((IService)this).CheckDisposed(); + if (packages is null || packages.Count == 0) + throw new ArgumentNullException(nameof(LoadPackagesInfosAsync)); + ConcurrentQueue<(ContentPackage, FluentResults.Result)> results = new(); + await packages.ParallelForEachAsync(async package => + { + var res = await LoadPackageInfosAsync(package); + results.Enqueue((package, res)); + }, Environment.ProcessorCount); + return results.ToImmutableArray(); + } + + public IReadOnlyList GetAllLoadedPackages() + { + ((IService)this).CheckDisposed(); + return _modInfos.IsEmpty ? ImmutableArray.Empty + : _modInfos.Select(kvp => kvp.Key).ToImmutableArray(); + } + + public void DisposePackageInfos(ContentPackage package) + { + _modInfos.TryRemove(package, out _); + } + + public void DisposePackagesInfos(IReadOnlyList packages) + { + if (packages is null || packages.Count == 0) + return; + + foreach (var package in packages) + { + DisposePackageInfos(package); + } + } + + public Result GetPackageDependencyInfo(ContentPackage ownerPackage, string packageName, + ulong steamWorkshopId) + { + ((IService)this).CheckDisposed(); + + if (ownerPackage is null) + return FluentResults.Result.Fail($"OwnerPackage is null."); + var nameGood = !packageName.IsNullOrWhiteSpace(); + + if (!nameGood && steamWorkshopId == 0) + FluentResults.Result.Fail($"PackageName and SteamId cannot both be invalid."); + + IPackageInfo depInfo = null; + + // complex key + if (nameGood && steamWorkshopId != 0 + && _packageInfoLookupService.Lookup(packageName, steamWorkshopId).GetAwaiter().GetResult() is + { IsSuccess: true, Value: {} dep1 }) + { + depInfo = dep1; + } + // name key + else if (nameGood && _packageInfoLookupService.Lookup(packageName).GetAwaiter().GetResult() is + { IsSuccess: true, Value: { } dep2 }) + { + depInfo = dep2; + } + // steamid key + else if (_packageInfoLookupService.Lookup(steamWorkshopId).GetAwaiter().GetResult() is + { IsSuccess: true, Value: { } dep3 }) + { + depInfo = dep3; + } + // this should never be null so we return an exception + else + { + return FluentResults.Result.Fail($"Package Dependency for {ownerPackage.Name} was not found."); + } + + return FluentResults.Result.Ok(new PackageDependency(ownerPackage, depInfo, ownerPackage.Name)); + } + + public Result GetAssembliesInfos(ContentPackage package, bool onlySupportedResources = true) + { + ((IService)this).CheckDisposed(); + if (package is null) + return FluentResults.Result.Fail($"{nameof(GetAssembliesInfos)}: ContentPackage is null."); + if (_modInfos.TryGetValue(package, out var result)) + return FluentResults.Result.Ok(_assemblyInfoConverter.Process(onlySupportedResources? + result.Assemblies.Where(r => + (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 + && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() + : result.Assemblies + )); + return FluentResults.Result.Fail( + $"{nameof(GetAssembliesInfos)}: ContentPackage {package.Name} is not registered."); + } + + public Result GetConfigsInfos(ContentPackage package, bool onlySupportedResources = true) + { + ((IService)this).CheckDisposed(); + if (package is null) + return FluentResults.Result.Fail($"{nameof(GetConfigsInfos)}: ContentPackage is null."); + + if (_modInfos.TryGetValue(package, out var result)) + { + return FluentResults.Result.Ok(_configsInfoConverter.Process(onlySupportedResources? + result.Configs.Where(r => + (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 + && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() + : result.Configs + )); + } + + return FluentResults.Result.Fail( + $"{nameof(GetConfigsInfos)}: ContentPackage {package.Name} is not registered."); + } + + public Result GetConfigProfilesInfos(ContentPackage package, bool onlySupportedResources = true) + { + ((IService)this).CheckDisposed(); + if (package is null) + return FluentResults.Result.Fail($"{nameof(GetConfigProfilesInfos)}: ContentPackage is null."); + + if (_modInfos.TryGetValue(package, out var result)) + { + return FluentResults.Result.Ok(_configProfilesConverter.Process(onlySupportedResources? + result.ConfigProfiles.Where(r => + (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 + && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() + : result.ConfigProfiles + )); + } + + return FluentResults.Result.Fail( + $"{nameof(GetConfigProfilesInfos)}: ContentPackage {package.Name} is not registered."); + } + + public Result GetLocalizationsInfos(ContentPackage package, bool onlySupportedResources = true) + { + ((IService)this).CheckDisposed(); + if (package is null) + return FluentResults.Result.Fail($"{nameof(GetLocalizationsInfos)}: ContentPackage is null."); + + if (_modInfos.TryGetValue(package, out var result)) + { + return FluentResults.Result.Ok(_localizationsConverter.Process(onlySupportedResources? + result.Localizations.Where(r => + (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 + && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() + : result.Localizations + )); + } + + return FluentResults.Result.Fail( + $"{nameof(GetLocalizationsInfos)}: ContentPackage {package.Name} is not registered."); + } + + public Result GetLuaScriptsInfos(ContentPackage package, bool onlySupportedResources = true) + { + ((IService)this).CheckDisposed(); + if (package is null) + return FluentResults.Result.Fail($"{nameof(GetLuaScriptsInfos)}: ContentPackage is null."); + + if (_modInfos.TryGetValue(package, out var result)) + { + return FluentResults.Result.Ok(_luaScriptsConverter.Process(onlySupportedResources? + result.LuaScripts.Where(r => + (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 + && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() + : result.LuaScripts + )); + } + + return FluentResults.Result.Fail( + $"{nameof(GetLuaScriptsInfos)}: ContentPackage {package.Name} is not registered."); + } + + public Result GetAssembliesInfos(IReadOnlyList packages, bool onlySupportedResources = true) + { + ((IService)this).CheckDisposed(); + if (packages is null || packages.Count == 0) + return FluentResults.Result.Fail($"{nameof(GetAssembliesInfos)}: ContentPackage list is null or empty."); + var builder = ImmutableArray.CreateBuilder(); + foreach (var package in packages) + { + if (_modInfos.TryGetValue(package, out var result) && result.Assemblies is { IsEmpty: false }) + { + builder.AddRange(onlySupportedResources? + result.Assemblies.Where(r => + (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 + && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() + : result.Assemblies); + } + } + + return FluentResults.Result.Ok(_assemblyInfoConverter.Process(builder.MoveToImmutable())); + } + + public Result GetConfigsInfos(IReadOnlyList packages, bool onlySupportedResources = true) + { + ((IService)this).CheckDisposed(); + if (packages is null || packages.Count == 0) + return FluentResults.Result.Fail($"{nameof(GetConfigsInfos)}: ContentPackage list is null or empty."); + var builder = ImmutableArray.CreateBuilder(); + foreach (var package in packages) + { + if (_modInfos.TryGetValue(package, out var result) && result.Configs is { IsEmpty: false }) + { + builder.AddRange(onlySupportedResources? + result.Configs.Where(r => + (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 + && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() + : result.Configs); + } + } + + return FluentResults.Result.Ok(_configsInfoConverter.Process(builder.MoveToImmutable())); + } + + public Result GetConfigProfilesInfos(IReadOnlyList packages, bool onlySupportedResources = true) + { + ((IService)this).CheckDisposed(); + if (packages is null || packages.Count == 0) + return FluentResults.Result.Fail($"{nameof(GetConfigProfilesInfos)}: ContentPackage list is null or empty."); + var builder = ImmutableArray.CreateBuilder(); + foreach (var package in packages) + { + if (_modInfos.TryGetValue(package, out var result) && result.ConfigProfiles is { IsEmpty: false }) + { + builder.AddRange(onlySupportedResources? + result.ConfigProfiles.Where(r => + (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 + && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() + : result.ConfigProfiles); + } + } + + return FluentResults.Result.Ok(_configProfilesConverter.Process(builder.MoveToImmutable())); + } + + public Result GetLocalizationsInfos(IReadOnlyList packages, bool onlySupportedResources = true) + { + ((IService)this).CheckDisposed(); + if (packages is null || packages.Count == 0) + return FluentResults.Result.Fail($"{nameof(GetLocalizationsInfos)}: ContentPackage list is null or empty."); + var builder = ImmutableArray.CreateBuilder(); + foreach (var package in packages) + { + if (_modInfos.TryGetValue(package, out var result) && result.Localizations is { IsEmpty: false }) + { + builder.AddRange(onlySupportedResources? + result.Localizations.Where(r => + (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 + && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() + : result.Localizations); + } + } + + return FluentResults.Result.Ok(_localizationsConverter.Process(builder.MoveToImmutable())); + } + + public Result GetLuaScriptsInfos(IReadOnlyList packages, bool onlySupportedResources = true) + { + ((IService)this).CheckDisposed(); + if (packages is null || packages.Count == 0) + return FluentResults.Result.Fail($"{nameof(GetLuaScriptsInfos)}: ContentPackage list is null or empty."); + var builder = ImmutableArray.CreateBuilder(); + foreach (var package in packages) + { + if (_modInfos.TryGetValue(package, out var result) && result.LuaScripts is { IsEmpty: false }) + { + builder.AddRange(onlySupportedResources? + result.LuaScripts.Where(r => + (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 + && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() + : result.LuaScripts); + } + } + + return FluentResults.Result.Ok(_luaScriptsConverter.Process(builder.MoveToImmutable())); + } + + public async Task> GetAssembliesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true) + { + return await Task.Run(() => GetAssembliesInfos(packages, onlySupportedResources)); + } + + public async Task> GetConfigsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true) + { + return await Task.Run(() => GetConfigsInfos(packages, onlySupportedResources)); + } + + public async Task> GetConfigProfilesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true) + { + return await Task.Run(() => GetConfigProfilesInfos(packages, onlySupportedResources)); + } + + public async Task> GetLocalizationsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true) + { + return await Task.Run(() => GetLocalizationsInfos(packages, onlySupportedResources)); + } + + public async Task> GetLuaScriptsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true) + { + return await Task.Run(() => GetLuaScriptsInfos(packages, onlySupportedResources)); + } + } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs index a71445714..c64ffdb02 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading.Tasks; using System.Xml.Linq; using Barotrauma.LuaCs.Data; @@ -6,18 +7,24 @@ using FluentResults; namespace Barotrauma.LuaCs.Services.Processing; -#region TypeDef - -public interface IConverterService : IReusableService +public interface IConverterService : IService { Result TryParseResource(TSrc src); - Result TryParseResources(IEnumerable sources); + ImmutableArray> TryParseResources(IEnumerable sources); } -public interface IConverterServiceAsync : IReusableService +public interface IConverterServiceAsync : IService { Task> TryParseResourceAsync(TSrc src); - Task> TryParseResourcesAsync(IEnumerable sources); + Task>> TryParseResourcesAsync(IEnumerable sources); } -#endregion +public interface IProcessorService : IService +{ + TOut Process(TSrc src); +} + +public interface IProcessorServiceAsync : IService +{ + Task ProcessAsync(TSrc src); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IModConfigCreatorService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IModConfigCreatorService.cs deleted file mode 100644 index f91bdba34..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IModConfigCreatorService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Barotrauma.LuaCs.Data; - -namespace Barotrauma.LuaCs.Services.Processing; - -public interface IModConfigCreatorService : IService -{ - FluentResults.Result BuildConfigForPackage(ContentPackage package); - FluentResults.Result BuildConfigFromManifest(string manifestPath); -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs new file mode 100644 index 000000000..b4f39989a --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs @@ -0,0 +1,661 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using Barotrauma.LuaCs.Data; +using FluentResults; + +namespace Barotrauma.LuaCs.Services.Processing; + +public partial class ModConfigService : IConverterServiceAsync, IConverterService +{ + private readonly IStorageService _storageService; + private readonly Lazy _packageManagementService; + private int _isDisposed; + + private const string ModConfigFileName = "ModConfig.xml"; + private const string ModConfigRootName = "ModConfig"; + + public ModConfigService(IStorageService storageService, Lazy pms) + { + _storageService = storageService; + _packageManagementService = pms; + } + + public void Dispose() + { + throw new System.NotImplementedException(); + } + + public bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } + + public async Task> TryParseResourceAsync(ContentPackage src) + { + ((IService)this).CheckDisposed(); + + // validate package + if (src is null) + return FluentResults.Result.Fail("ContentPackage is null"); + if (_storageService.DirectoryExists(src.Path) is { } res && (res.IsFailed || !res.Value)) + return FluentResults.Result.Fail($"ContentPackage does not exist or cannot be accessed: {src.Path}"); + + // find ModConfig.xml or deep scan on fail (legacy) + if (await _storageService.LoadPackageXmlAsync(src, ModConfigFileName) is + { IsSuccess: true, Value: var modConfigXml } + && modConfigXml.Root is { Name.LocalName: ModConfigRootName } root) + { + return await GetModConfigInfoAsync(src, root); + } + + // legacy mode + try + { + // we only supported assemblies and lua scripts + var asm = GetAssembliesLegacy(src); + var lua = GetLuaScriptsLegacy(src); + + return new ModConfigInfo() + { + Assemblies = asm, + LuaScripts = lua, + Configs = ImmutableArray.Empty, + ConfigProfiles = ImmutableArray.Empty, + Localizations = ImmutableArray.Empty, + Package = src, + PackageName = src.Name +#if CLIENT + ,Styles = ImmutableArray.Empty +#endif + }; + } + catch (Exception e) + { + return FluentResults.Result.Fail($"Unable to parse legacy content package: {src.Name}: {src.Path}"); + } + } + + private partial Task> GetModConfigInfoAsync(ContentPackage package, XElement root); + + private ImmutableArray GetLocalizations(ContentPackage src, IEnumerable elements) + { + var builder = ImmutableArray.CreateBuilder(); + + if (GetXmlFilesList(src, elements, "Localizations") + is not { IsSuccess: true, Value: { } xmlFiles }) + return ImmutableArray.Empty; + + foreach (var file in xmlFiles) + { + // get dependencies + var deps = GetElementsDependenciesData(file.Item1, src); + // get platform, culture and target architecture + var info = GetElementsAttributesData(file.Item1, file.Item2.First()); + + builder.Add(new LocalizationResourceInfo() + { + Dependencies = deps, + Optional = info.IsOptional, + FilePaths = file.Item2, + InternalName = info.Name, + LoadPriority = info.LoadPriority, + OwnerPackage = src, + SupportedCultures = info.SupportedCultures, + SupportedPlatforms = info.SupportedPlatforms, + SupportedTargets = info.SupportedTargets + }); + } + + return builder.Count > 0 + ? builder.ToImmutable() + : ImmutableArray.Empty; + } + + private ImmutableArray GetAssemblies(ContentPackage src, IEnumerable elements) + { + var builder = ImmutableArray.CreateBuilder(); + var elementsList = elements.ToImmutableArray(); + + if (GetFilesList(src, elementsList, "Assembly", "*.dll") + is not { IsSuccess: true, Value: { } xmlFiles }) + return ImmutableArray.Empty; + + foreach (var file in xmlFiles) + { + // get dependencies + var deps = GetElementsDependenciesData(file.Item1, src); + // get platform, culture and target architecture + var info = GetElementsAttributesData(file.Item1, file.Item2.First()); + + builder.Add(new AssemblyResourceInfo() + { + Dependencies = deps, + Optional = info.IsOptional, + FilePaths = file.Item2, + InternalName = info.Name, + LoadPriority = info.LoadPriority, + OwnerPackage = src, + SupportedCultures = info.SupportedCultures, + SupportedPlatforms = info.SupportedPlatforms, + SupportedTargets = info.SupportedTargets, + FriendlyName = file.Item1.GetAttributeString("Name", info.Name), + IsScript = false, + LazyLoad = !file.Item1.GetAttributeBool("RunFile", true) + }); + } + + if (GetFilesList(src, elementsList, "Assembly", "*.cs") + is not { IsSuccess: true, Value: { } xmlFiles2 }) + return ImmutableArray.Empty; + + foreach (var file in xmlFiles2) + { + // get dependencies + var deps = GetElementsDependenciesData(file.Item1, src); + // get platform, culture and target architecture + var info = GetElementsAttributesData(file.Item1, file.Item2.First()); + + builder.Add(new AssemblyResourceInfo() + { + Dependencies = deps, + Optional = info.IsOptional, + FilePaths = file.Item2, + InternalName = info.Name, + LoadPriority = info.LoadPriority, + OwnerPackage = src, + SupportedCultures = info.SupportedCultures, + SupportedPlatforms = info.SupportedPlatforms, + SupportedTargets = info.SupportedTargets, + FriendlyName = file.Item1.GetAttributeString("Name", info.Name), + IsScript = true, + LazyLoad = !file.Item1.GetAttributeBool("RunFile", true) + }); + } + + return builder.Count > 0 + ? builder.ToImmutable() + : ImmutableArray.Empty; + } + + private ImmutableArray GetConfigs(ContentPackage src, IEnumerable elements) + { + var builder = ImmutableArray.CreateBuilder(); + if (GetXmlFilesList(src, elements, "Config") + is not { IsSuccess: true, Value: { } xmlFiles }) + return ImmutableArray.Empty; + + foreach (var file in xmlFiles) + { + // get dependencies + var deps = GetElementsDependenciesData(file.Item1, src); + // get platform, culture and target architecture + var info = GetElementsAttributesData(file.Item1, file.Item2.First()); + + builder.Add(new ConfigResourceInfo() + { + Dependencies = deps, + Optional = info.IsOptional, + FilePaths = file.Item2, + InternalName = info.Name, + LoadPriority = info.LoadPriority, + OwnerPackage = src, + SupportedCultures = info.SupportedCultures, + SupportedPlatforms = info.SupportedPlatforms, + SupportedTargets = info.SupportedTargets + }); + } + + return builder.Count > 0 + ? builder.ToImmutable() + : ImmutableArray.Empty; + } + + private ImmutableArray GetConfigProfiles(ContentPackage src, IEnumerable elements) + { + var builder = ImmutableArray.CreateBuilder(); + if (GetXmlFilesList(src, elements, "Config") + is not { IsSuccess: true, Value: { } xmlFiles }) + return ImmutableArray.Empty; + + foreach (var file in xmlFiles) + { + // get dependencies + var deps = GetElementsDependenciesData(file.Item1, src); + // get platform, culture and target architecture + var info = GetElementsAttributesData(file.Item1, file.Item2.First()); + + builder.Add(new ConfigProfileResourceInfo() + { + Dependencies = deps, + Optional = info.IsOptional, + FilePaths = file.Item2, + InternalName = info.Name, + LoadPriority = info.LoadPriority, + OwnerPackage = src, + SupportedCultures = info.SupportedCultures, + SupportedPlatforms = info.SupportedPlatforms, + SupportedTargets = info.SupportedTargets + }); + } + + return builder.Count > 0 + ? builder.ToImmutable() + : ImmutableArray.Empty; + } + + private ImmutableArray GetLuaScripts(ContentPackage src, IEnumerable elements) + { + var builder = ImmutableArray.CreateBuilder(); + if (GetXmlFilesList(src, elements, "Config") + is not { IsSuccess: true, Value: { } xmlFiles }) + return ImmutableArray.Empty; + + foreach (var file in xmlFiles) + { + // get dependencies + var deps = GetElementsDependenciesData(file.Item1, src); + // get platform, culture and target architecture + var info = GetElementsAttributesData(file.Item1, file.Item2.First()); + + builder.Add(new LuaScriptScriptResourceInfo() + { + Dependencies = deps, + Optional = info.IsOptional, + FilePaths = file.Item2, + InternalName = info.Name, + LoadPriority = info.LoadPriority, + OwnerPackage = src, + SupportedCultures = info.SupportedCultures, + SupportedPlatforms = info.SupportedPlatforms, + SupportedTargets = info.SupportedTargets, + IsAutorun = file.Item1.GetAttributeBool("RunFile", true) + }); + } + + return builder.Count > 0 + ? builder.ToImmutable() + : ImmutableArray.Empty; + } + + private Result)>> GetXmlFilesList(ContentPackage src, + IEnumerable elements, string elementNameCheck) => + GetFilesList(src, elements, elementNameCheck, "*.xml"); + + private Result)>> GetFilesList(ContentPackage src, + IEnumerable elements, string elementNameCheck, string filter) + { + var builder = ImmutableArray.CreateBuilder<(XElement, ImmutableArray)>(); + + if (elementNameCheck.IsNullOrWhiteSpace()) + throw new ArgumentNullException($"{nameof(GetXmlFilesList)}: The element check is null."); + + foreach (var element in elements) + { + if (element.Name.LocalName != elementNameCheck) + throw new ArgumentException("Element is not a Localization element"); + + if (element.GetAttributeString("Folder", string.Empty) is { } str + && !string.IsNullOrWhiteSpace(str)) + { + if (_storageService.FindFilesInPackage(src, str, filter, true) + is not { IsSuccess: true, Value: var fpList } || !fpList.Any()) + { + continue; + } + + foreach (var fileP in fpList) + builder.Add((element, fpList.ToImmutableArray())); + } + else if (element.GetAttributeString("File", string.Empty) is { } fileStr + && !string.IsNullOrWhiteSpace(fileStr) + && _storageService.GetAbsFromPackage(src, fileStr) is { IsSuccess: true, Value: var fp } + && _storageService.FileExists(fp) is { IsSuccess: true, Value: true }) + { + builder.Add((element, new [] { fileStr }.ToImmutableArray())); + } + } + + return builder.Count > 0 + ? FluentResults.Result.Ok(builder.ToImmutable()) + : FluentResults.Result.Fail($"No files found"); + } + + private ResourceAdditionalInfo GetElementsAttributesData(XElement element, string localPath) + { + return new ResourceAdditionalInfo( + element.GetAttributeString("Name", localPath), + GetSupportedPlatforms(element.GetAttributeString("Platform", "any")), + GetSupportedTargets(element.GetAttributeString("Target", "any")), + GetSupportedCultures(element), + element.GetAttributeBool("Optional", false), + element.GetAttributeInt("Priority", 0)); + + Platform GetSupportedPlatforms(string platformName) => platformName.ToLowerInvariant().Trim() switch + { + "windows" => Platform.Windows, + "linux" => Platform.Linux, + "osx" => Platform.OSX, + _ => Platform.Windows | Platform.Linux | Platform.OSX + }; + + Target GetSupportedTargets(string targetName) => targetName.ToLowerInvariant().Trim() switch + { + "client" => Target.Client, + "server" => Target.Server, + _ => Target.Client | Target.Server, + }; + + ImmutableArray GetSupportedCultures(XElement element) + { + var culture = element.GetAttributeString("Culture", string.Empty); + if (string.IsNullOrWhiteSpace(culture)) + return new[] { CultureInfo.InvariantCulture }.ToImmutableArray(); + var builder = ImmutableArray.CreateBuilder(); + var arr = culture.Split(','); + if (arr.Length == 0) + return new[] { CultureInfo.InvariantCulture }.ToImmutableArray(); + foreach (var culstr in arr) + { + if (string.IsNullOrWhiteSpace(culstr)) + continue; + try + { + builder.Add( + culstr.ToLowerInvariant().Trim() == "default" + ? CultureInfo.InvariantCulture + : CultureInfo.GetCultureInfo(culstr)); + } + catch (CultureNotFoundException e) + { + // This is the case if a culture is specified by the package that is not supported by the OS/.NET ENV. + // We ignore it since we can never use it. + continue; + } + } + + return builder.Count > 0 + ? builder.ToImmutable() + : new[] { CultureInfo.InvariantCulture }.ToImmutableArray(); + } + } + + private ImmutableArray GetElementsDependenciesData(XElement element, ContentPackage src) + { + if (element.GetChildElement("Dependencies") is not {} dependencies + || dependencies.GetChildElements("Dependency").ToImmutableArray() is not { Length: >0 } depsList) + return ImmutableArray.Empty; + var builder = ImmutableArray.CreateBuilder(); + foreach (var dep in depsList) + { + var packName = dep.GetAttributeString("PackageName", string.Empty); + var packId = dep.GetAttributeUInt64("PackageId", 0); + + // invalid entry + if (packName.IsNullOrWhiteSpace() && packId == 0) + continue; + + if (_packageManagementService.Value.GetPackageDependencyInfo(src, packName, packId) is + { IsSuccess: true, Value: { } depsInfo }) + { + builder.Add(depsInfo); + } + } + return builder.ToImmutable(); + } + + private ImmutableArray GetAssembliesLegacy(ContentPackage src) + { + var builder = ImmutableArray.CreateBuilder(); + // server, linux + if (_storageService.FindFilesInPackage(src, "bin/Server/Linux", "*.dll", true) + is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesSrvLin}) + { + builder.Add(new AssemblyResourceInfo() + { + Dependencies = ImmutableArray.Empty, + FilePaths = filesSrvLin, + FriendlyName = "AssembliesServerLinux", + InternalName = "AssembliesServerLinux", + IsScript = false, + LazyLoad = false, + LoadPriority = 1, + Optional = false, + OwnerPackage = src, + SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), + SupportedPlatforms = Platform.Linux, + SupportedTargets = Target.Server + }); + } + + // server, osx + if (_storageService.FindFilesInPackage(src, "bin/Server/OSX", "*.dll", true) + is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesSrvOsx}) + { + builder.Add(new AssemblyResourceInfo() + { + Dependencies = ImmutableArray.Empty, + FilePaths = filesSrvOsx, + FriendlyName = "AssembliesServerOSX", + InternalName = "AssembliesServerOSX", + IsScript = false, + LazyLoad = false, + LoadPriority = 1, + Optional = false, + OwnerPackage = src, + SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), + SupportedPlatforms = Platform.OSX, + SupportedTargets = Target.Server + }); + } + + // server, osx + if (_storageService.FindFilesInPackage(src, "bin/Server/Windows", "*.dll", true) + is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesSrvWin}) + { + builder.Add(new AssemblyResourceInfo() + { + Dependencies = ImmutableArray.Empty, + FilePaths = filesSrvWin, + FriendlyName = "AssembliesServerWin", + InternalName = "AssembliesServerWin", + IsScript = false, + LazyLoad = false, + LoadPriority = 1, + Optional = false, + OwnerPackage = src, + SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), + SupportedPlatforms = Platform.Windows, + SupportedTargets = Target.Server + }); + } + + // client, linux + if (_storageService.FindFilesInPackage(src, "bin/Client/Linux", "*.dll", true) + is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesCliLin}) + { + builder.Add(new AssemblyResourceInfo() + { + Dependencies = ImmutableArray.Empty, + FilePaths = filesCliLin, + FriendlyName = "AssembliesClientLinux", + InternalName = "AssembliesClientLinux", + IsScript = false, + LazyLoad = false, + LoadPriority = 1, + Optional = false, + OwnerPackage = src, + SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), + SupportedPlatforms = Platform.Linux, + SupportedTargets = Target.Client + }); + } + + // server, osx + if (_storageService.FindFilesInPackage(src, "bin/Client/OSX", "*.dll", true) + is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesCliOsx}) + { + builder.Add(new AssemblyResourceInfo() + { + Dependencies = ImmutableArray.Empty, + FilePaths = filesCliOsx, + FriendlyName = "AssembliesClientOSX", + InternalName = "AssembliesClientOSX", + IsScript = false, + LazyLoad = false, + LoadPriority = 1, + Optional = false, + OwnerPackage = src, + SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), + SupportedPlatforms = Platform.OSX, + SupportedTargets = Target.Client + }); + } + + // server, osx + if (_storageService.FindFilesInPackage(src, "bin/Client/Windows", "*.dll", true) + is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesCliWin}) + { + builder.Add(new AssemblyResourceInfo() + { + Dependencies = ImmutableArray.Empty, + FilePaths = filesCliWin, + FriendlyName = "AssembliesClientWin", + InternalName = "AssembliesClientWin", + IsScript = false, + LazyLoad = false, + LoadPriority = 1, + Optional = false, + OwnerPackage = src, + SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), + SupportedPlatforms = Platform.Windows, + SupportedTargets = Target.Client + }); + } + + var sharedFound = _storageService.FindFilesInPackage(src, "CSharp/Shared", "*.cs", true) + is { IsSuccess: true, Value: { IsDefaultOrEmpty: false } filesCssShared }; + + // source files legacy: server + if (_storageService.FindFilesInPackage(src, "CSharp/Server", "*.cs", true) + is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesCssServer}) + { + builder.Add(new AssemblyResourceInfo() + { + Dependencies = ImmutableArray.Empty, + FilePaths = sharedFound ? filesCssServer.Concat(filesCssShared).ToImmutableArray() : filesCssServer, + FriendlyName = "CssServer", + InternalName = "CssServer", + IsScript = true, + LazyLoad = false, + LoadPriority = 1, + Optional = false, + OwnerPackage = src, + SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), + SupportedPlatforms = Platform.Linux | Platform.OSX | Platform.Windows, + SupportedTargets = Target.Server + }); + } + + // source files legacy: client + if (_storageService.FindFilesInPackage(src, "CSharp/Client", "*.cs", true) + is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesCssClient}) + { + builder.Add(new AssemblyResourceInfo() + { + Dependencies = ImmutableArray.Empty, + FilePaths = sharedFound ? filesCssClient.Concat(filesCssShared).ToImmutableArray() : filesCssClient, + FriendlyName = "CssClient", + InternalName = "CssClient", + IsScript = true, + LazyLoad = false, + LoadPriority = 1, + Optional = false, + OwnerPackage = src, + SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), + SupportedPlatforms = Platform.Linux | Platform.OSX | Platform.Windows, + SupportedTargets = Target.Client + }); + } + + return builder.MoveToImmutable(); + } + private ImmutableArray GetLuaScriptsLegacy(ContentPackage src) + { + var builder = ImmutableArray.CreateBuilder(); + + if (_storageService.FindFilesInPackage(src, "Lua", "*.lua", true) + is { IsSuccess: true, Value: { IsDefaultOrEmpty: false } fileAll }) + { + builder.Add(new LuaScriptScriptResourceInfo() + { + Dependencies = ImmutableArray.Empty, + FilePaths = fileAll.Where(path => !path.Contains("Autorun")).ToImmutableArray(), + InternalName = "LuaScriptsNormal", + Optional = false, + IsAutorun = false, + OwnerPackage = src, + SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), + SupportedPlatforms = Platform.Linux | Platform.OSX | Platform.Windows, + SupportedTargets = Target.Client | Target.Server + }); + + builder.Add(new LuaScriptScriptResourceInfo() + { + Dependencies = ImmutableArray.Empty, + FilePaths = fileAll.Where(path => path.Contains("Autorun")).ToImmutableArray(), + InternalName = "LuaScriptsAutorun", + Optional = false, + IsAutorun = true, + OwnerPackage = src, + SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), + SupportedPlatforms = Platform.Linux | Platform.OSX | Platform.Windows, + SupportedTargets = Target.Client | Target.Server + }); + } + + return builder.MoveToImmutable(); + } + + public async Task>> TryParseResourcesAsync(IEnumerable sources) + { + ((IService)this).CheckDisposed(); + + var srcs = sources.ToImmutableArray(); + var results = new AsyncLocal>>(); + await srcs.ParallelForEachAsync(async pkg => + { + try + { + results.Value.Enqueue(await TryParseResourceAsync(pkg)); + } + catch (Exception e) + { + // this should never happen but this is to stop partial execution exit. + results.Value.Enqueue( + FluentResults.Result.Fail($"Failed to parse package {pkg?.Name}: {e.Message}")); + } + }); + return results.Value.ToImmutableArray(); + } + + public Result TryParseResource(ContentPackage src) => + TryParseResourceAsync(src).GetAwaiter().GetResult(); + public ImmutableArray> TryParseResources(IEnumerable sources) => + TryParseResourcesAsync(sources.ToImmutableArray()).GetAwaiter().GetResult(); + + private record ResourceAdditionalInfo( + string Name, + Platform SupportedPlatforms, + Target SupportedTargets, + ImmutableArray SupportedCultures, + bool IsOptional, + int LoadPriority); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ResourceInfoProcessors.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ResourceInfoProcessors.cs new file mode 100644 index 000000000..c1151a649 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ResourceInfoProcessors.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using Barotrauma.LuaCs.Data; + +namespace Barotrauma.LuaCs.Services.Processing; + +public partial class ResourceInfoArrayPacker : + IProcessorService, IAssembliesResourcesInfo>, + IProcessorService, IConfigsResourcesInfo>, + IProcessorService, IConfigProfilesResourcesInfo>, + IProcessorService, ILocalizationsResourcesInfo>, + IProcessorService, ILuaScriptsResourcesInfo> +{ + private bool _isDisposed; + public IAssembliesResourcesInfo Process(IReadOnlyList src) + { + return new AssemblyResourcesInfo(src.ToImmutableArray()); + } + + public IConfigsResourcesInfo Process(IReadOnlyList src) + { + return new ConfigResourcesInfo(src.ToImmutableArray()); + } + + public IConfigProfilesResourcesInfo Process(IReadOnlyList src) + { + return new ConfigProfilesResourcesInfo(src.ToImmutableArray()); + } + + public ILocalizationsResourcesInfo Process(IReadOnlyList src) + { + return new LocalizationResourcesInfo(src.ToImmutableArray()); + } + + public ILuaScriptsResourcesInfo Process(IReadOnlyList src) + { + return new LuaScriptsResourcesInfo(src.ToImmutableArray()); + } + + public void Dispose() + { + // Stateless class + GC.SuppressFinalize(this); + IsDisposed = true; + } + + public bool IsDisposed + { + get => _isDisposed; + set => _isDisposed = value; + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs index d812e767d..bc7bd0bad 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs @@ -5,10 +5,11 @@ using System.IO; using System.Reflection; using System.Security; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using Barotrauma.LuaCs.Configuration; -using Barotrauma.LuaCs.Networking; +using Barotrauma.LuaCs.Services; using Barotrauma.Steam; using FluentResults; using FluentResults.LuaCs; @@ -293,12 +294,31 @@ public class StorageService : IStorageService }); } + public FluentResults.Result DirectoryExists(string directoryPath) + { + ((IService)this).CheckDisposed(); + try + { + var di = new DirectoryInfo(directoryPath); + return di.Exists; + } + catch (Exception ex) + { + return new FluentResults.Result().WithError(ex.Message); + } + } + public async Task> TryLoadXmlAsync(string filePath, Encoding encoding = null) { - var r = await TryLoadTextAsync(filePath, encoding); - if (r is { IsSuccess: true, Value: {} value } && !value.IsNullOrWhiteSpace()) - return XDocument.Parse(value); - return FluentResults.Result.Fail(GetGeneralError(nameof(TryLoadXml), filePath)); + try + { + await using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); + return await XDocument.LoadAsync(fs, LoadOptions.PreserveWhitespace, CancellationToken.None); + } + catch (Exception e) + { + return FluentResults.Result.Fail(GetGeneralError(nameof(TryLoadXmlAsync), filePath)); + } } public async Task> TryLoadTextAsync(string filePath, Encoding encoding = null) @@ -601,7 +621,7 @@ public class StorageService : IStorageService localFilePath))); } - private FluentResults.Result GetAbsFromPackage(ContentPackage package, string localFilePath) + public FluentResults.Result GetAbsFromPackage(ContentPackage package, string localFilePath) { if (package is null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs index c540f3b78..5e5a19118 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs @@ -5,7 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Networking; +using Barotrauma.LuaCs.Services; using Barotrauma.LuaCs.Services.Safe; using Barotrauma.Networking; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs index e5c94a1d3..835176d95 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs @@ -1,6 +1,6 @@ using System; using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Networking; +using Barotrauma.LuaCs.Services; using Barotrauma.LuaCs.Services.Compatibility; using Barotrauma.Networking; @@ -8,7 +8,7 @@ namespace Barotrauma.LuaCs.Services; internal delegate void NetMessageReceived(IReadMessage netMessage); -internal interface INetworkingService : IReusableService, ILuaCsNetworking +internal partial interface INetworkingService : IReusableService, ILuaCsNetworking { bool IsActive { get; } bool IsSynchronized { get; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageInfoLookupService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageInfoLookupService.cs new file mode 100644 index 000000000..36dabf384 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageInfoLookupService.cs @@ -0,0 +1,15 @@ +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Events; + +namespace Barotrauma.LuaCs.Services; + +public interface IPackageInfoLookupService : IReusableService +{ + Task> Lookup(string packageName); + Task> Lookup(string packageName, ulong steamWorkshopId); + Task> Lookup(ulong steamWorkshopId); + Task> Lookup(ContentPackage package); + void RefreshPackageLists(); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageListRetrievalService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageListRetrievalService.cs new file mode 100644 index 000000000..c534047a5 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageListRetrievalService.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Barotrauma.LuaCs.Services; + +public interface IPackageListRetrievalService : IService +{ + IEnumerable GetEnabledContentPackages(); + IEnumerable GetAllContentPackages(); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs index e3013afe9..72bf528d0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs @@ -17,20 +17,22 @@ public interface IPackageManagementService : IReusableService, ILocalizationsRes { /// /// Loads and parses the provided for supported by the current runtime environment. + /// Will overwrite any existing package data. /// - /// + /// Package to load. /// - Task LoadPackageInfosAsync(ContentPackage packages); + Task LoadPackageInfosAsync(ContentPackage package); /// /// Loads and parses the provided collection for supported by the current runtime environment. + /// Will overwrite any existing package data. /// - /// + /// List of packages to load. /// Task> LoadPackagesInfosAsync(IReadOnlyList packages); IReadOnlyList GetAllLoadedPackages(); void DisposePackageInfos(ContentPackage package); void DisposePackagesInfos(IReadOnlyList packages); - void DisposeAllPackagesInfos(); + FluentResults.Result GetPackageDependencyInfo(ContentPackage ownerPackage, string packageName, ulong steamWorkshopId); // single FluentResults.Result GetAssembliesInfos(ContentPackage package, bool onlySupportedResources = true); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IService.cs index 680f51fc2..3e8457e19 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IService.cs @@ -18,6 +18,7 @@ public interface IReusableService : IService /// /// Base interface inherited by all services. /// +/// Throws exception if `IsDisposed` return true. public interface IService : IDisposable { bool IsDisposed { get; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs index bbcac52a4..281c39a9b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs @@ -32,6 +32,7 @@ public interface IStorageService : IService ImmutableArray<(string, FluentResults.Result)> LoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePaths); ImmutableArray<(string, FluentResults.Result)> LoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePaths); FluentResults.Result> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively); + FluentResults.Result GetAbsFromPackage(ContentPackage package, string localFilePath); // async // singles Task> LoadPackageXmlAsync(ContentPackage package, string localFilePath); @@ -50,6 +51,8 @@ public interface IStorageService : IService FluentResults.Result TrySaveText(string filePath, in string text, Encoding encoding = null); FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes); FluentResults.Result FileExists(string filePath); + FluentResults.Result DirectoryExists(string directoryPath); + //async Task> TryLoadXmlAsync(string filePath, Encoding encoding = null); Task> TryLoadTextAsync(string filePath, Encoding encoding = null); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/CsPackageManager.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/CsPackageManager.cs index d323ac849..9e7060756 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/CsPackageManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/CsPackageManager.cs @@ -19,7 +19,7 @@ using MonoMod.Utils; namespace Barotrauma; -public sealed class CsPackageManager : IDisposable +/*public sealed class CsPackageManager : IDisposable { #region PRIVATE_FUNCDATA @@ -632,8 +632,6 @@ public sealed class CsPackageManager : IDisposable bool ShouldRunPackage(ContentPackage package, RunConfig config) { throw new NotImplementedException(); - /*return (!_luaCsSetup.Config.TreatForcedModsAsNormal && config.IsForced()) - || (ContentPackageManager.EnabledPackages.All.Contains(package) && config.IsForcedOrStandard());*/ } void UpdatePackagesToDisable(ref HashSet set, @@ -1098,3 +1096,4 @@ public sealed class CsPackageManager : IDisposable #endregion } +*/ diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/MemoryFileAssemblyContextLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/MemoryFileAssemblyContextLoader.cs index d5ccc6a86..dc570158b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/MemoryFileAssemblyContextLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/MemoryFileAssemblyContextLoader.cs @@ -1,4 +1,5 @@ -using System; +/* +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; @@ -339,3 +340,4 @@ public class MemoryFileAssemblyContextLoader : AssemblyLoadContext this.IsDisposed = true; } } +*/ diff --git a/Barotrauma/BarotraumaTest/LuaCs/HookPatchHelpers.cs b/Barotrauma/BarotraumaTest/LuaCs/HookPatchHelpers.cs index cdc4ed6f1..49037a3c3 100644 --- a/Barotrauma/BarotraumaTest/LuaCs/HookPatchHelpers.cs +++ b/Barotrauma/BarotraumaTest/LuaCs/HookPatchHelpers.cs @@ -73,7 +73,8 @@ namespace TestProject.LuaCs LuaCsHook.HookMethodType.After => "Hook.HookMethodType.After", _ => throw new NotImplementedException(), }); - return luaCs.Lua.DoString($"return Hook.Patch({string.Join(", ", args)})"); + throw new NotImplementedException(); + //return luaCs.Lua.DoString($"return Hook.Patch({string.Join(", ", args)})"); } private static DynValue DoHookRemovePatch( @@ -91,7 +92,8 @@ namespace TestProject.LuaCs LuaCsHook.HookMethodType.After => "Hook.HookMethodType.After", _ => throw new NotImplementedException(), }); - return luaCs.Lua.DoString($"return Hook.RemovePatch({string.Join(", ", args)})"); + throw new NotImplementedException(); + //return luaCs.Lua.DoString($"return Hook.RemovePatch({string.Join(", ", args)})"); } public static PatchHandle AddPrefix(this LuaCsSetup luaCs, string body, string methodName, string[]? parameters = null, string? patchId = null) diff --git a/Barotrauma/BarotraumaTest/LuaCs/HookPatchTests.cs b/Barotrauma/BarotraumaTest/LuaCs/HookPatchTests.cs index b9366b239..9608b804a 100644 --- a/Barotrauma/BarotraumaTest/LuaCs/HookPatchTests.cs +++ b/Barotrauma/BarotraumaTest/LuaCs/HookPatchTests.cs @@ -1,4 +1,5 @@ -extern alias Client; +/* +extern alias Client; using Client::Barotrauma; using Microsoft.Xna.Framework; @@ -7,6 +8,8 @@ using System; using Xunit; using Xunit.Abstractions; +// TODO: Rewrite all of this. + namespace TestProject.LuaCs { [Collection("LuaCs")] @@ -16,6 +19,7 @@ namespace TestProject.LuaCs public HookPatchTests(LuaCsFixture luaCsFixture, ITestOutputHelper output) { + // XXX: we can't have multiple instances of LuaCs patching the // same methods, otherwise we get script ownership exceptions. luaCs = luaCsFixture.LuaCs; @@ -36,10 +40,12 @@ namespace TestProject.LuaCs UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); - - luaCs.Initialize(); - luaCs.Lua.Globals["TestValueType"] = UserData.CreateStatic(); - luaCs.Lua.Globals["InterfaceImplementingType"] = UserData.CreateStatic(); + + luaCs.ForceRunState(RunState.Running); + throw new NotImplementedException(); + //luaCs.Initialize(); + //luaCs.Lua.Globals["TestValueType"] = UserData.CreateStatic(); + //luaCs.Lua.Globals["InterfaceImplementingType"] = UserData.CreateStatic(); } private class PatchTargetSimple @@ -664,3 +670,4 @@ namespace TestProject.LuaCs } } } +*/ diff --git a/Barotrauma/BarotraumaTest/LuaCs/LuaCsFixture.cs b/Barotrauma/BarotraumaTest/LuaCs/LuaCsFixture.cs index 70c2d9c1e..61fdc58a6 100644 --- a/Barotrauma/BarotraumaTest/LuaCs/LuaCsFixture.cs +++ b/Barotrauma/BarotraumaTest/LuaCs/LuaCsFixture.cs @@ -1,9 +1,12 @@ -extern alias Client; +/* +extern alias Client; using Client::Barotrauma; using System; using System.Runtime.ExceptionServices; +// TODO: Rewrite all of this. + namespace TestProject.LuaCs { /// @@ -31,3 +34,4 @@ namespace TestProject.LuaCs void IDisposable.Dispose() => LuaCs.Stop(); } } +*/ From c6713f37bbe21405c2ba88c4d5165abbfa043b8e Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Sun, 30 Mar 2025 06:20:45 -0400 Subject: [PATCH 014/288] IT BUILDS!!! - Removed LocalizationServices and other sus things. - Rewrote AssemblyLoader [In Progress] SafeStorageService [In Progress] LuaScriptLoader --- .../LuaCs/Configuration/IDisplayableConfig.cs | 142 ++++ .../LuaCs/Data/DataInterfaceDefinitions.cs | 23 - .../ClientSource/LuaCs/Data/IConfigInfo.cs | 16 +- .../LuaCs/Data/IResourceInfoDeclarations.cs | 15 - .../ClientSource/LuaCs/Data/IStylesInfo.cs | 6 - .../ClientSource/LuaCs/LuaCsSetup.cs | 3 - .../LuaCs/Services/ConfigService.cs | 26 + .../LuaCs/Services/IConfigService.cs | 28 +- .../Services/IStylesManagementService.cs | 13 - .../LuaCs/Services/IStylesService.cs | 58 -- .../LuaCs/Services/NetworkingService.cs | 5 +- .../Services/PackageManagementService.cs | 81 --- .../Services/Processing/ModConfigService.cs | 11 +- .../Processing/StylesManagementService.cs | 6 - .../LuaCs/Services/StylesService.cs | 120 --- .../LuaCs/Services/UIStyleProcessor.cs | 93 --- .../ClientSource/Screens/SubEditorScreen.cs | 3 +- .../Services/PackageManagementService.cs | 2 - .../Services/Processing/ModConfigService.cs | 2 - .../Lua/Content/ModConfig.xml | 0 .../Lua/Content/Schemas/IModConfig.xsd | 50 ++ .../LuaCs/Configuration/ConfigEntry.cs | 103 +++ .../LuaCs/Configuration/ConfigList.cs | 111 +++ .../LuaCs/Configuration/IConfigBase.cs | 19 +- .../LuaCs/Configuration/IConfigEntry.cs | 4 +- .../LuaCs/Configuration/IConfigEnum.cs | 9 + .../LuaCs/Configuration/IConfigList.cs | 7 +- ...ons.cs => DataInterfaceImplementations.cs} | 53 +- .../SharedSource/LuaCs/Data/IConfigInfo.cs | 50 +- .../LuaCs/Data/IConfigProfileInfo.cs | 9 +- .../LuaCs/Data/ILocalizationInfo.cs | 12 - .../SharedSource/LuaCs/Data/IModConfigInfo.cs | 2 +- .../LuaCs/Data/IResourceInfoDeclarations.cs | 6 - .../LuaCs/Data/ServicesConfigData.cs | 206 ++++++ .../SharedSource/LuaCs/IEvents.cs | 6 + .../SharedSource/LuaCs/Lua/LuaScriptLoader.cs | 28 - .../SharedSource/LuaCs/LuaCsSetup.cs | 127 +--- .../SharedSource/LuaCs/ModUtils.cs | 9 + .../SharedSource/LuaCs/Networking/INetVar.cs | 27 - .../LuaCs/Networking/INetworkSyncEntity.cs | 66 ++ .../Services/Compatibility/ILuaCsUtility.cs | 4 + .../LuaCs/Services/ConfigInitializers.cs | 340 +++++++++ .../LuaCs/Services/ConfigService.cs | 688 +++++++++++++++++- .../Services/ContentPackageInfoLookup.cs | 12 +- .../LuaCs/Services/LocalizationService.cs | 6 - .../Services/LuaScriptManagementService.cs | 205 +++++- .../LuaCs/Services/NetworkingService.cs | 6 +- .../Services/PackageManagementService.cs | 70 +- .../Services/Processing/ConfigIOService.cs | 278 +++++++ .../Services/Processing/IConfigIOService.cs | 15 + .../Services/Processing/ModConfigService.cs | 59 +- .../Processing/ResourceInfoProcessors.cs | 6 - .../LuaCs/Services/Safe/ILuaConfigService.cs | 28 +- .../LuaCs/Services/Safe/ILuaDataService.cs | 2 + .../LuaCs/Services/Safe/ILuaScriptLoader.cs | 8 + .../Services/Safe/ISafeStorageService.cs | 56 ++ .../LuaCs/Services/Safe/LuaScriptLoader.cs | 118 +++ .../LuaCs/Services/Safe/SafeStorageService.cs | 123 ++++ .../LuaCs/Services/StorageService.cs | 427 +++++------ .../Services/_Interfaces/IConfigService.cs | 85 +-- .../_Interfaces/ILocalizationService.cs | 34 - .../ILuaScriptManagementService.cs | 52 +- .../_Interfaces/INetworkingService.cs | 11 +- .../_Interfaces/IPackageInfoLookupService.cs | 1 + .../_Interfaces/IPackageManagementService.cs | 37 +- .../Services/_Interfaces/IStorageService.cs | 23 +- .../LuaCs/_Plugins/AssemblyLoader.cs | 368 +++++++--- 67 files changed, 3336 insertions(+), 1283 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayableConfig.cs delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IStylesInfo.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesManagementService.cs delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageManagementService.cs delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/StylesManagementService.cs delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStyleProcessor.cs create mode 100644 Barotrauma/BarotraumaShared/Lua/Content/ModConfig.xml create mode 100644 Barotrauma/BarotraumaShared/Lua/Content/Schemas/IModConfig.xsd create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ConfigEntry.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ConfigList.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEnum.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/{DataInterfaceDefinitions.cs => DataInterfaceImplementations.cs} (86%) delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ILocalizationInfo.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaScriptLoader.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetworkSyncEntity.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigInitializers.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LocalizationService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigIOService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConfigIOService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ISafeStorageService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILocalizationService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayableConfig.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayableConfig.cs new file mode 100644 index 000000000..789c00ac0 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayableConfig.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using Barotrauma.LuaCs.Data; + +namespace Barotrauma.LuaCs.Configuration; + + +/// +/// Base type of all menu displayable types. +/// +public interface IDisplayableConfigBase : IDataInfo, IConfigDisplayInfo +{ + /// + /// Whether the current config is editable. + /// + bool IsEditable { get; } + /// + /// Used to indicate the implemented interface and targeted display logic. + /// + static virtual DisplayType DisplayOption => DisplayType.Undefined; +} + +public interface IDisplayableConfigBase : IDisplayableConfigBase +{ + void SetValue(TValue value); + TDisplay GetDisplayValue(); +} + +public interface IDisplayableConfigBool : IDisplayableConfigBase +{ + static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.Boolean; +} + +public interface IDisplayableConfigText : IDisplayableConfigBase +{ + static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.Text; +} + +public interface IDisplayableConfigInt : IDisplayableConfigBase +{ + static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.Integer; +} + +public interface IDisplayableConfigFloat : IDisplayableConfigBase +{ + static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.Float; +} + +public interface IDisplayableConfigSliderInt : IDisplayableConfigBase<(int Min, int Max, int Value, int Steps), int> +{ + static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.SliderInt; +} + +public interface IDisplayableConfigSliderFloat : IDisplayableConfigBase<(float Min, float Max, float Value, int Steps), float> +{ + static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.SliderFloat; +} + +public interface IDisplayableConfigDropdown : IDisplayableConfigBase, string> +{ + static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.Dropdown; +} + +/// +/// Allows completely custom-designed UI for this configuration component. +/// +public interface IDisplayableConfigCustom : IDisplayableConfigBase +{ + static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.Custom; + /// + /// Draw your menu settings option. + /// + /// Parent layout component. + void DrawComponent(GUILayoutGroup layoutGroup); + /// + /// Called when the config element is set to be disposed to allow for cleanup. + /// + void DisposeGUI(); + /// + /// Called when the UI indicates to save the current value as permanent. + /// + void OnValueSaved(); + /// + /// Called when the UI indicates to discard the currently displayed value and revert to the last saved value. + /// + void OnValueDiscarded(); +} + + + +/// +/// Indicates the intended display and feedback logic to be used by the . +///
[Important] +///
The type must implement the indicated interface for the selected option, or it will not be displayed. +///
+public enum DisplayType +{ + /// + /// Will not be displayed in menus. + /// + Undefined, + /// + /// Will be shown as a checkbox. + ///
[Requires()] + ///
+ Boolean, + /// + /// Shown as an editable text input. + ///
[Requires()] + ///
+ Text, + /// + /// Shown as number input (no decimal input). + ///
[Requires()] + ///
+ Integer, + /// + /// Shown as a number input. + ///
[Requires()] + ///
+ Float, + /// + /// Shown as a slider, values parsed as integers. + ///
[Requires()] + ///
+ SliderInt, + /// + /// Shown as a slider, values parsed as single-precision decimal numbers. + ///
[Requires()] + ///
+ SliderFloat, + /// + /// Shown as a menu, values parsed as strings. + ///
[Requires()] + ///
+ Dropdown, + /// + /// UI Display is implemented by inheritor and actioned by a call to . + ///
[Requires()] + ///
+ Custom +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs deleted file mode 100644 index 570f48f9b..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/DataInterfaceDefinitions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Immutable; -using System.Globalization; - -namespace Barotrauma.LuaCs.Data; - -public partial record ModConfigInfo : IModConfigInfo -{ - public ImmutableArray Styles { get; init; } -} - -public record StylesResourceInfo : IStylesResourceInfo -{ - public Platform SupportedPlatforms { get; init; } - public Target SupportedTargets { get; init; } - public int LoadPriority { get; init; } - public ImmutableArray FilePaths { get; init; } - public bool Optional { get; init; } - public ImmutableArray SupportedCultures { get; init; } - public string InternalName { get; init; } - public ContentPackage OwnerPackage { get; init; } - public string FallbackPackageName { get; init; } - public ImmutableArray Dependencies { get; init; } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs index 576d18118..801bc5ac4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs @@ -2,8 +2,22 @@ using Barotrauma.LuaCs.Configuration; namespace Barotrauma.LuaCs.Data; -public partial interface IConfigInfo +public partial interface IConfigInfo : IConfigDisplayInfo { } + +public interface IConfigDisplayInfo { + /// + /// User-friendly name or Localization Token. + /// + string DisplayName { get; } + /// + /// User-friendly description or Localization Token. + /// + string Description { get; } + /// + /// The menu category to display under. Used for filtering. + /// + string DisplayCategory { get; } /// /// Should this config be displayed in end-user menus. /// diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs deleted file mode 100644 index de47ef225..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IResourceInfoDeclarations.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Immutable; - -namespace Barotrauma.LuaCs.Data; - -public partial interface IModConfigInfo : IStylesResourcesInfo { } - -public interface IStylesResourceInfo : IResourceInfo, IResourceCultureInfo, IDataInfo, IPackageDependenciesInfo { } - -public interface IStylesResourcesInfo -{ - /// - /// Collection of loadable styles data. - /// - ImmutableArray Styles { get; } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IStylesInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IStylesInfo.cs deleted file mode 100644 index b3e2a456b..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IStylesInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Barotrauma.LuaCs.Data; - -public interface IStylesInfo -{ - -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index 19649aea8..894641c17 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -22,9 +22,6 @@ namespace Barotrauma } private partial bool ShouldRunCs() => IsCsEnabled.Value; - - public IStylesManagementService StylesManagementService => _servicesProvider.TryGetService(out var svc) - ? svc : throw new NullReferenceException("Networking Manager service not found!"); public void CheckCsEnabled() { diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs new file mode 100644 index 000000000..a628969b2 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Immutable; +using Barotrauma.LuaCs.Configuration; +using Barotrauma.LuaCs.Data; +using Barotrauma.Networking; +using FluentResults; + +namespace Barotrauma.LuaCs.Services; + +public partial class ConfigService +{ + public ImmutableArray GetDisplayableConfigs() + { + throw new NotImplementedException(); + } + + public ImmutableArray GetDisplayableConfigsForPackage(ContentPackage package) + { + throw new NotImplementedException(); + } + + public Result AddConfigControl(IConfigInfo configInfo) + { + throw new NotImplementedException(); + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs index 89b2316a3..cbe7bf94d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using Barotrauma.LuaCs.Configuration; +using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Services; using Barotrauma.Networking; @@ -8,28 +10,8 @@ namespace Barotrauma.LuaCs.Services; public partial interface IConfigService { - /* - * Immediate mode - */ - FluentResults.Result> AddConfigEntry(IDisplayableData data, - T defaultValue, - NetSync syncMode = NetSync.None, - ClientPermissions permissions = ClientPermissions.None, - Func valueChangePredicate = null, - Action> onValueChanged = null) where T : IConvertible, IEquatable; - - FluentResults.Result AddConfigList(IDisplayableData data, - int defaultIndex, IReadOnlyList values, - NetSync syncMode = NetSync.None, - ClientPermissions permissions = ClientPermissions.None, - Func valueChangePredicate = null, - Action onValueChanged = null); + ImmutableArray GetDisplayableConfigs(); + ImmutableArray GetDisplayableConfigsForPackage(ContentPackage package); - FluentResults.Result> AddConfigRangeEntry(IDisplayableData data, - T defaultValue, T minValue, T maxValue, - Func, int> getStepCount, - NetSync syncMode = NetSync.None, - ClientPermissions permissions = ClientPermissions.None, - Func valueChangePredicate = null, - Action> onValueChanged = null) where T : IConvertible, IEquatable; + FluentResults.Result AddConfigControl(IConfigInfo configInfo); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesManagementService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesManagementService.cs deleted file mode 100644 index dc5dfc69a..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesManagementService.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Immutable; -using System.Threading.Tasks; -using Barotrauma.LuaCs.Data; - -namespace Barotrauma.LuaCs.Services; - -public interface IStylesManagementService : IReusableService -{ - Task LoadStylesAsync(ImmutableArray styles); - FluentResults.Result GetStylesService(ContentPackage package); - Task DisposeAllStyles(); - Task DisposeStylesForPackage(ContentPackage package); -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs deleted file mode 100644 index 26c8b2404..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IStylesService.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Threading.Tasks; - -namespace Barotrauma.LuaCs.Services; - -// TODO: Rework interface to support resource infos. -/// -/// Loads XML Style assets from the given content package. -/// -public interface IStylesService : IService -{ - /// - /// Tries to load the styles file for the given and path into a new instance. - /// - /// - /// - /// - Task LoadStylesFileAsync(ContentPackage package, ContentPath path); - - /// - /// Unloads all styles assets and instances. - /// - FluentResults.Result UnloadAllStyles(); - - /// - /// Tries to the get the asset by xml asset name, returns null on failure. - /// - /// XML Name of the asset. - /// The asset or null if none are found. - GUIFont GetFont(string fontName); - - /// - /// Tries to the get the asset by xml asset name, returns null on failure. - /// - /// XML Name of the asset. - /// The asset or null if none are found. - GUISprite GetSprite(string spriteName); - - /// - /// Tries to the get the asset by xml asset name, returns null on failure. - /// - /// XML Name of the asset. - /// The asset or null if none are found. - GUISpriteSheet GetSpriteSheet(string spriteSheetName); - - /// - /// Tries to the get the asset by xml asset name, returns null on failure. - /// - /// XML Name of the asset. - /// The asset or null if none are found. - GUICursor GetCursor(string cursorName); - - /// - /// Tries to the get the asset by xml asset name, returns null on failure. - /// - /// XML Name of the asset. - /// The asset or null if none are found. - GUIColor GetColor(string colorName); -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs index 597709cc5..654e2c817 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs @@ -1,13 +1,14 @@ using Barotrauma.LuaCs.Services; using Barotrauma.Networking; using System; +using System.Collections.Concurrent; using System.Collections.Generic; namespace Barotrauma.LuaCs.Services; partial class NetworkingService : INetworkingService { - private Dictionary> receiveQueue = new Dictionary>(); + private ConcurrentDictionary> receiveQueue = new(); public void SendSyncMessage() { @@ -99,7 +100,7 @@ partial class NetworkingService : INetworkingService } else { - if (!receiveQueue.ContainsKey(id)) { receiveQueue[id] = new Queue(); } + if (!receiveQueue.ContainsKey(id)) { receiveQueue[id] = new ConcurrentQueue(); } receiveQueue[id].Enqueue(netMessage); if (GameSettings.CurrentConfig.VerboseLogging) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageManagementService.cs deleted file mode 100644 index 7b722751a..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/PackageManagementService.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading.Tasks; -using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services.Processing; -using FluentResults; -// ReSharper disable UseCollectionExpression - -namespace Barotrauma.LuaCs.Services; - -public partial class PackageManagementService : IPackageManagementService -{ - public PackageManagementService( - IConverterServiceAsync modConfigParserService, - IProcessorService, IAssembliesResourcesInfo> assemblyInfoConverter, - IProcessorService, IConfigsResourcesInfo> configsInfoConverter, - IProcessorService, IConfigProfilesResourcesInfo> configProfilesConverter, - IProcessorService, ILocalizationsResourcesInfo> localizationsConverter, - IProcessorService, ILuaScriptsResourcesInfo> luaScriptsConverter, - IPackageInfoLookupService packageInfoLookupService, Func, IStylesResourcesInfo> stylesInfoConverter) - { - _stylesInfoConverter = stylesInfoConverter; - _modConfigParserService = modConfigParserService; - _assemblyInfoConverter = assemblyInfoConverter; - _configsInfoConverter = configsInfoConverter; - _configProfilesConverter = configProfilesConverter; - _localizationsConverter = localizationsConverter; - _luaScriptsConverter = luaScriptsConverter; - _packageInfoLookupService = packageInfoLookupService; - } - - private readonly Func, IStylesResourcesInfo> _stylesInfoConverter; - - public ImmutableArray Styles => _modInfos.IsEmpty ? ImmutableArray.Empty - : _modInfos.SelectMany(kvp => kvp.Value.Styles).ToImmutableArray(); - - public Result GetStylesInfos(ContentPackage package, bool onlySupportedResources = true) - { - ((IService)this).CheckDisposed(); - if (package is null) - return FluentResults.Result.Fail($"{nameof(GetStylesInfos)}: ContentPackage is null."); - if (_modInfos.TryGetValue(package, out var result)) - return FluentResults.Result.Ok(_stylesInfoConverter(onlySupportedResources? - result.Styles.Where(r => - (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 - && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() - : result.Styles - )); - return FluentResults.Result.Fail( - $"{nameof(GetStylesInfos)}: ContentPackage {package.Name} is not registered."); - } - - public Result GetStylesInfos(IReadOnlyList packages, bool onlySupportedResources = true) - { - ((IService)this).CheckDisposed(); - if (packages is null || packages.Count == 0) - return FluentResults.Result.Fail($"{nameof(GetStylesInfos)}: ContentPackage list is null or empty."); - var builder = ImmutableArray.CreateBuilder(); - foreach (var package in packages) - { - if (_modInfos.TryGetValue(package, out var result) && result.Styles is { IsEmpty: false }) - { - builder.AddRange(onlySupportedResources? - result.Styles.Where(r => - (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 - && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() - : result.Styles); - } - } - - return FluentResults.Result.Ok(_stylesInfoConverter(builder.MoveToImmutable())); - } - - public async Task> GetStylesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true) - { - return await Task.Run(() => GetStylesInfos(packages, onlySupportedResources)); - - } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/ModConfigService.cs index 386a66940..0e46339e7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/ModConfigService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/ModConfigService.cs @@ -14,26 +14,17 @@ public partial class ModConfigService private partial async Task> GetModConfigInfoAsync(ContentPackage package, XElement root) { var asm = root.GetChildElements("Assembly").ToImmutableArray(); - var loc = root.GetChildElements("Localization").ToImmutableArray(); var cfg = root.GetChildElements("Config").ToImmutableArray(); var lua = root.GetChildElements("Lua").ToImmutableArray(); - var stl = root.GetChildElements("Style").ToImmutableArray(); return FluentResults.Result.Ok(new ModConfigInfo() { Package = package, PackageName = package.Name, Assemblies = asm.Any() ? GetAssemblies(package, asm) : ImmutableArray.Empty, - Localizations = loc.Any() ? GetLocalizations(package, loc) : ImmutableArray.Empty, Configs = cfg.Any() ? GetConfigs(package, cfg) : ImmutableArray.Empty, ConfigProfiles = cfg.Any() ? GetConfigProfiles(package, cfg) : ImmutableArray.Empty, - LuaScripts = lua.Any() ? GetLuaScripts(package, lua) : ImmutableArray.Empty, - Styles = stl.Any() ? GetStyles(package, stl) : ImmutableArray.Empty + LuaScripts = lua.Any() ? GetLuaScripts(package, lua) : ImmutableArray.Empty }); } - - private ImmutableArray GetStyles(ContentPackage src, IEnumerable elements) - { - throw new NotImplementedException(); - } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/StylesManagementService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/StylesManagementService.cs deleted file mode 100644 index 49bd12eb2..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/StylesManagementService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Barotrauma.LuaCs.Services.Processing; - -public class StylesManagementService : IStylesManagementService -{ - -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs deleted file mode 100644 index 6e608a5fc..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/StylesService.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using System.Xml.Linq; -using FluentResults; -using FluentResults.LuaCs; - -namespace Barotrauma.LuaCs.Services; - -// TODO: Complete rewrite -public class StylesService : IStylesService -{ - private readonly ConcurrentDictionary _loadedProcessors = new(); - private readonly IStorageService _storageService; - private readonly ILoggerService _loggerService; - - public StylesService(IStorageService storageService, ILoggerService loggerService) - { - _storageService = storageService; - _loggerService = loggerService; - } - - - public async Task LoadStylesFileAsync(ContentPackage package, ContentPath path) - { - throw new NotImplementedException(); - } - - public FluentResults.Result UnloadAllStyles() - { - if (NoProcessorsLoaded) - return FluentResults.Result.Fail(new Error($"{nameof(StylesService)}.{nameof(UnloadAllStyles)}: No processors have been loaded.") - .WithMetadata(MetadataType.ExceptionObject, this)); - - foreach (var processor in _loadedProcessors) - { - processor.Value.UnloadFile(); - } - _loadedProcessors.Clear(); - return FluentResults.Result.Ok(); - } - - public GUIFont GetFont(string fontName) - { - if (NoProcessorsLoaded) - return null; - foreach (var processor in _loadedProcessors.Values) - { - if (processor.Fonts.TryGetValue(fontName, out var asset)) - return asset; - } - - return null; - } - - public GUISprite GetSprite(string spriteName) - { - if (NoProcessorsLoaded) - return null; - foreach (var processor in _loadedProcessors.Values) - { - if (processor.Sprites.TryGetValue(spriteName, out var asset)) - return asset; - } - - return null; - } - - public GUISpriteSheet GetSpriteSheet(string spriteSheetName) - { - if (NoProcessorsLoaded) - return null; - foreach (var processor in _loadedProcessors.Values) - { - if (processor.SpriteSheets.TryGetValue(spriteSheetName, out var asset)) - return asset; - } - - return null; - } - - public GUICursor GetCursor(string cursorName) - { - if (NoProcessorsLoaded) - return null; - foreach (var processor in _loadedProcessors.Values) - { - if (processor.Cursors.TryGetValue(cursorName, out var asset)) - return asset; - } - - return null; - } - - public GUIColor GetColor(string colorName) - { - if (NoProcessorsLoaded) - return null; - foreach (var processor in _loadedProcessors.Values) - { - if (processor.Colors.TryGetValue(colorName, out var asset)) - return asset; - } - - return null; - } - - private bool NoProcessorsLoaded => _loadedProcessors.IsEmpty; - - public void Dispose() - { - UnloadAllStyles(); - GC.SuppressFinalize(this); - } - - public bool IsDisposed { get; private set; } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStyleProcessor.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStyleProcessor.cs deleted file mode 100644 index b90276e7a..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStyleProcessor.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Xml.Linq; -using Barotrauma.Extensions; - -namespace Barotrauma.LuaCs.Services; - -public class UIStyleProcessor : HashlessFile -{ - private readonly UIStyleFile _fake; - public readonly Dictionary Fonts = new(); - public readonly Dictionary Sprites = new(); - public readonly Dictionary SpriteSheets = new(); - public readonly Dictionary Cursors = new(); - public readonly Dictionary Colors = new(); - - public UIStyleProcessor(ContentPackage contentPackage, ContentPath path) : base(contentPackage, path) - { - _fake = new UIStyleFile(contentPackage, path); - } - - public override void LoadFile() - { - var element = XMLExtensions.TryLoadXml(path: Path)?.Root?.FromPackage(ContentPackage); - if (element is null) - throw new InvalidDataException($"UIStyleProcessor: Failed to load UI style file: {Path}"); - - var styleElement = element.Name.LocalName.ToLowerInvariant() == "style" ? element : element.GetChildElement("style"); - if (styleElement is null) - throw new InvalidDataException($"UIStyleProcessor: no 'style' XmlElement found in file: {Path}"); - - var childElements = styleElement.GetChildElements("Font"); - if (childElements is not null) - AddToList(Fonts, childElements, _fake); - - childElements = styleElement.GetChildElements("Sprite"); - if (childElements is not null) - AddToList(Sprites, childElements, _fake); - - childElements = styleElement.GetChildElements("Spritesheet"); - if (childElements is not null) - AddToList(SpriteSheets, childElements, _fake); - - childElements = styleElement.GetChildElements("Cursor"); - if (childElements is not null) - AddToList(Cursors, childElements, _fake); - - childElements = styleElement.GetChildElements("Color"); - if (childElements is not null) - AddToList(Colors, childElements, _fake); - - - void AddToList(Dictionary dict, IEnumerable ele, UIStyleFile file) where T1 : GUISelector where T2 : GUIPrefab - { - foreach (ContentXElement prefabElement in ele) - { - string name = prefabElement.GetAttributeString("name", string.Empty); - if (name != string.Empty) - { - var prefab = (T2)Activator.CreateInstance(typeof(T2), new object[]{ prefabElement, file })!; - if (!dict.ContainsKey(name)) - dict[name] = (T1)Activator.CreateInstance(typeof(T1), new object[] { name })!; - dict[name].Prefabs.Add(prefab, false); - } - } - } - } - - public override void UnloadFile() - { - Fonts.Values.ForEach(p => p.Prefabs.RemoveByFile(_fake)); - Sprites.Values.ForEach(p => p.Prefabs.RemoveByFile(_fake)); - SpriteSheets.Values.ForEach(p => p.Prefabs.RemoveByFile(_fake)); - Cursors.Values.ForEach(p => p.Prefabs.RemoveByFile(_fake)); - Colors.Values.ForEach(p => p.Prefabs.RemoveByFile(_fake)); - - Fonts.Clear(); - Sprites.Clear(); - SpriteSheets.Clear(); - Cursors.Clear(); - Colors.Clear(); - } - - public override void Sort() - { - Fonts.Values.ForEach(p => p.Prefabs.Sort()); - Sprites.Values.ForEach(p => p.Prefabs.Sort()); - SpriteSheets.Values.ForEach(p => p.Prefabs.Sort()); - Cursors.Values.ForEach(p => p.Prefabs.Sort()); - Colors.Values.ForEach(p => p.Prefabs.Sort()); - } -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index 37b5d6e5c..d7b368980 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -12,6 +12,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Xml.Linq; +using Barotrauma.LuaCs.Events; using Barotrauma.Sounds; namespace Barotrauma @@ -1532,7 +1533,7 @@ namespace Barotrauma { Select(enableAutoSave: true); - GameMain.LuaCs.CheckInitialize(); + GameMain.LuaCs.EventService.PublishEvent(sub => sub.OnScreenSelected(this)); } public void Select(bool enableAutoSave = true) diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageManagementService.cs index 435facc4d..99e66743e 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageManagementService.cs @@ -11,7 +11,6 @@ public partial class PackageManagementService IProcessorService, IAssembliesResourcesInfo> assemblyInfoConverter, IProcessorService, IConfigsResourcesInfo> configsInfoConverter, IProcessorService, IConfigProfilesResourcesInfo> configProfilesConverter, - IProcessorService, ILocalizationsResourcesInfo> localizationsConverter, IProcessorService, ILuaScriptsResourcesInfo> luaScriptsConverter, IPackageInfoLookupService packageInfoLookupService) { @@ -19,7 +18,6 @@ public partial class PackageManagementService _assemblyInfoConverter = assemblyInfoConverter; _configsInfoConverter = configsInfoConverter; _configProfilesConverter = configProfilesConverter; - _localizationsConverter = localizationsConverter; _luaScriptsConverter = luaScriptsConverter; _packageInfoLookupService = packageInfoLookupService; } diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/Processing/ModConfigService.cs index ee93eccf5..3c59ac10e 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/Processing/ModConfigService.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/Processing/ModConfigService.cs @@ -15,7 +15,6 @@ public partial class ModConfigService private partial async Task> GetModConfigInfoAsync(ContentPackage package, XElement root) { var asm = root.GetChildElements("Assembly").ToImmutableArray(); - var loc = root.GetChildElements("Localization").ToImmutableArray(); var cfg = root.GetChildElements("Config").ToImmutableArray(); var lua = root.GetChildElements("Lua").ToImmutableArray(); @@ -24,7 +23,6 @@ public partial class ModConfigService Package = package, PackageName = package.Name, Assemblies = asm.Any() ? GetAssemblies(package, asm) : ImmutableArray.Empty, - Localizations = loc.Any() ? GetLocalizations(package, loc) : ImmutableArray.Empty, Configs = cfg.Any() ? GetConfigs(package, cfg) : ImmutableArray.Empty, ConfigProfiles = cfg.Any() ? GetConfigProfiles(package, cfg) : ImmutableArray.Empty, LuaScripts = lua.Any() ? GetLuaScripts(package, lua) : ImmutableArray.Empty diff --git a/Barotrauma/BarotraumaShared/Lua/Content/ModConfig.xml b/Barotrauma/BarotraumaShared/Lua/Content/ModConfig.xml new file mode 100644 index 000000000..e69de29bb diff --git a/Barotrauma/BarotraumaShared/Lua/Content/Schemas/IModConfig.xsd b/Barotrauma/BarotraumaShared/Lua/Content/Schemas/IModConfig.xsd new file mode 100644 index 000000000..1908c9960 --- /dev/null +++ b/Barotrauma/BarotraumaShared/Lua/Content/Schemas/IModConfig.xsd @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ConfigEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ConfigEntry.cs new file mode 100644 index 000000000..4fd22a1cb --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ConfigEntry.cs @@ -0,0 +1,103 @@ +using System; +using System.Xml.Linq; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Services; +using Barotrauma.Networking; +using OneOf; + +namespace Barotrauma.LuaCs.Configuration; + +public class ConfigEntry : IConfigEntry where T : IEquatable +{ + + private readonly Action, INetReadMessage> _readMessageHandler; + private readonly Action, INetWriteMessage> _writeMessageHandler; + + public ConfigEntry(IConfigInfo configInfo, Action, INetReadMessage> readMessageHandler, + Action, INetWriteMessage> writeMessageHandler) + { + _readMessageHandler = readMessageHandler; + _writeMessageHandler = writeMessageHandler; + } + + public string InternalName { get; init; } + public ContentPackage OwnerPackage { get; init; } + + public bool Equals(IConfigBase other) + { + if (ReferenceEquals(this, other)) + return true; + throw new NotImplementedException(); + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + public Type GetValueType() + { + throw new NotImplementedException(); + } + + public string GetValue() + { + throw new NotImplementedException(); + } + + public bool TrySetValue(OneOf value) + { + throw new NotImplementedException(); + } + + public bool IsAssignable(OneOf value) + { + throw new NotImplementedException(); + } + + private event Action> _onValueChanged; + public event Action> OnValueChanged + { + add => _onValueChanged += value; + remove => _onValueChanged -= value; + } + + event Action IConfigBase.OnValueChanged + { + add => _onValueChanged += value; + remove => _onValueChanged -= value; + } + + public OneOf GetSerializableValue() + { + throw new NotImplementedException(); + } + + public Guid InstanceId => throw new NotImplementedException(); + + public NetSync SyncType => throw new NotImplementedException(); + + public ClientPermissions WritePermissions => throw new NotImplementedException(); + + public void ReadNetMessage(INetReadMessage message) + { + throw new NotImplementedException(); + } + + public void WriteNetMessage(INetWriteMessage message) + { + throw new NotImplementedException(); + } + + public T Value => throw new NotImplementedException(); + + public bool TrySetValue(T value) + { + throw new NotImplementedException(); + } + + public bool IsAssignable(T value) + { + throw new NotImplementedException(); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ConfigList.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ConfigList.cs new file mode 100644 index 000000000..d67d09e7d --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ConfigList.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Xml.Linq; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Services; +using Barotrauma.Networking; +using OneOf; + +namespace Barotrauma.LuaCs.Configuration; + +public class ConfigList : IConfigList where T : IEquatable +{ + private readonly Action, INetReadMessage> _readMessageHandler; + private readonly Action, INetWriteMessage> _writeMessageHandler; + + public ConfigList(IConfigInfo configInfo, Action, INetReadMessage> readMessageHandler, + Action, INetWriteMessage> writeMessageHandler) + { + _readMessageHandler = readMessageHandler; + _writeMessageHandler = writeMessageHandler; + } + + public string InternalName => throw new NotImplementedException(); + + public ContentPackage OwnerPackage => throw new NotImplementedException(); + + public bool Equals(IConfigBase other) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + public Type GetValueType() + { + throw new NotImplementedException(); + } + + public string GetValue() + { + throw new NotImplementedException(); + } + + public bool TrySetValue(OneOf value) + { + throw new NotImplementedException(); + } + + public bool IsAssignable(OneOf value) + { + throw new NotImplementedException(); + } + + private event Action> _onValueChanged; + + public event Action> OnValueChanged + { + add => _onValueChanged += value; + remove => _onValueChanged -= value; + } + + event Action> IConfigEntry.OnValueChanged + { + add => _onValueChanged += value; + remove => _onValueChanged -= value; + } + + event Action IConfigBase.OnValueChanged + { + add => _onValueChanged += value; + remove => _onValueChanged -= value; + } + + public T Value => throw new NotImplementedException(); + + public bool TrySetValue(T value) + { + throw new NotImplementedException(); + } + + public bool IsAssignable(T value) + { + throw new NotImplementedException(); + } + + public OneOf GetSerializableValue() + { + throw new NotImplementedException(); + } + + public Guid InstanceId => throw new NotImplementedException(); + + public NetSync SyncType => throw new NotImplementedException(); + + public ClientPermissions WritePermissions => throw new NotImplementedException(); + + public void ReadNetMessage(INetReadMessage message) + { + throw new NotImplementedException(); + } + + public void WriteNetMessage(INetWriteMessage message) + { + throw new NotImplementedException(); + } + + public IReadOnlyList Options => throw new NotImplementedException(); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigBase.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigBase.cs index 87334e592..8e65f68b5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigBase.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigBase.cs @@ -1,20 +1,17 @@ using System; +using System.Xml.Linq; using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Services; using Barotrauma.Networking; namespace Barotrauma.LuaCs.Configuration; -public partial interface IConfigBase : IVarId +public partial interface IConfigBase : IDataInfo, IEquatable, IDisposable { - bool IsInitialized { get; } - string GetValue(); - bool TrySetValue(string value); - bool IsAssignable(string value); Type GetValueType(); - void Initialize(IVarId id, string defaultValue); -} - -public interface IVarId : IDataInfo -{ - Guid InstanceId { get; } + string GetValue(); + bool TrySetValue(OneOf.OneOf value); + bool IsAssignable(OneOf.OneOf value); + event Action OnValueChanged; + OneOf.OneOf GetSerializableValue(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs index a39032258..9b173233a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs @@ -3,10 +3,10 @@ using Barotrauma.LuaCs.Services; namespace Barotrauma.LuaCs.Configuration; -public interface IConfigEntry : IConfigBase, INetVar where T : IConvertible, IEquatable +public interface IConfigEntry : IConfigBase, INetworkSyncEntity where T : IEquatable { T Value { get; } bool TrySetValue(T value); bool IsAssignable(T value); - void Initialize(IVarId id, T defaultValue); + new event Action> OnValueChanged; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEnum.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEnum.cs new file mode 100644 index 000000000..742f80a46 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEnum.cs @@ -0,0 +1,9 @@ +using System; +using Barotrauma.LuaCs.Services; + +namespace Barotrauma.LuaCs.Configuration; + +public interface IConfigEnum : IConfigBase, INetworkSyncEntity +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigList.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigList.cs index 0d291f215..d78b5779d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigList.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigList.cs @@ -1,8 +1,11 @@ +using System; +using System.Collections.Generic; using Barotrauma.LuaCs.Services; namespace Barotrauma.LuaCs.Configuration; -public interface IConfigList : IConfigBase, INetVar +public interface IConfigList : IConfigEntry, INetworkSyncEntity where T : IEquatable { - + IReadOnlyList Options { get; } + new event Action> OnValueChanged; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs similarity index 86% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs index 6dd98c625..790c7c9cd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceDefinitions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs @@ -5,7 +5,10 @@ using System.Globalization; using System.Linq; using System.Runtime.InteropServices; using System.Text; +using System.Xml.Linq; +using Barotrauma.LuaCs.Services; using Barotrauma.Steam; +using OneOf; namespace Barotrauma.LuaCs.Data; @@ -16,7 +19,6 @@ public partial record ModConfigInfo : IModConfigInfo public ContentPackage Package { get; init; } public string PackageName { get; init; } public ImmutableArray Assemblies { get; init; } - public ImmutableArray Localizations { get; init; } public ImmutableArray LuaScripts { get; init; } public ImmutableArray Configs { get; init; } public ImmutableArray ConfigProfiles { get; init; } @@ -24,10 +26,9 @@ public partial record ModConfigInfo : IModConfigInfo #endregion -#region DataContracts +#region DataContracts_Resources public record AssemblyResourcesInfo(ImmutableArray Assemblies) : IAssembliesResourcesInfo; -public record LocalizationResourcesInfo(ImmutableArray Localizations) : ILocalizationsResourcesInfo; public record LuaScriptsResourcesInfo(ImmutableArray LuaScripts) : ILuaScriptsResourcesInfo; public record ConfigResourcesInfo(ImmutableArray Configs) : IConfigsResourcesInfo; public record ConfigProfilesResourcesInfo(ImmutableArray ConfigProfiles) : IConfigProfilesResourcesInfo; @@ -161,20 +162,7 @@ public record ConfigProfileResourceInfo : IConfigProfileResourceInfo public ContentPackage OwnerPackage { get; init; } } -public record LocalizationResourceInfo : ILocalizationResourceInfo -{ - public string InternalName { get; init; } - public ContentPackage OwnerPackage { get; init; } - public Platform SupportedPlatforms { get; init; } - public Target SupportedTargets { get; init; } - public int LoadPriority { get; init; } - public ImmutableArray FilePaths { get; init; } - public ImmutableArray SupportedCultures { get; init; } - public ImmutableArray Dependencies { get; init; } - public bool Optional { get; init; } -} - -public readonly struct LuaScriptScriptResourceInfo : ILuaScriptResourceInfo +public readonly struct LuaScriptsResourceInfo : ILuaScriptResourceInfo { public ContentPackage OwnerPackage { get; init; } public Platform SupportedPlatforms { get; init; } @@ -189,3 +177,34 @@ public readonly struct LuaScriptScriptResourceInfo : ILuaScriptResourceInfo } #endregion + +#region DataContracts_ParsedInfo + +public record ConfigInfo : IConfigInfo +{ + public string InternalName { get; init; } + public ContentPackage OwnerPackage { get; init; } + public Type DataType { get; init; } + public OneOf DefaultValue { get; init; } + public OneOf Value { get; init; } + public RunState EditableStates { get; init; } + public NetSync NetSync { get; init; } + +#if CLIENT // IConfigDisplayInfo + public string DisplayName { get; init; } + public string Description { get; init; } + public string DisplayCategory { get; init; } + public bool ShowInMenus { get; init; } + public string Tooltip { get; init; } + public string ImageIconPath { get; init; } +#endif +} + +public record ConfigProfileInfo : IConfigProfileInfo +{ + public string InternalName { get; init; } + public ContentPackage OwnerPackage { get; init; } + public IReadOnlyList<(string ConfigName, OneOf Value)> ProfileValues { get; init; } +} + +#endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs index cfa46a629..4d8a5e5ea 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs @@ -1,51 +1,43 @@ using System; +using System.Xml.Linq; using Barotrauma.LuaCs.Services; using Barotrauma.Networking; namespace Barotrauma.LuaCs.Data; -// TODO: Finish +/// +/// Parsed data from a configuration xml. +/// public partial interface IConfigInfo : IDataInfo { /// - /// Specifies the data type this should be initialized to (ie. string, int, vector, etc.) - /// Custom types can be registered by mods. + /// Specifies the type initializer that will be used to instantiate the config var. /// Type DataType { get; } /// - /// String version of the default value. + /// The default value. /// - string DefaultValue { get; } + OneOf.OneOf DefaultValue { get; } /// - /// The value the last time this config was saved, if found in /data/. + /// The value the last time this config was saved. If not found, returns the default value. + ///
[If(Type='')]
+ /// The value is from the 'Value' Attribute. Typically used for types with single/simple values, such as primitives. + ///
[If(Type='')]
+ /// The value is from the first 'Value' child element. Typically used with complex config types, such as range and list. ///
- string StoredValue { get; } + OneOf.OneOf Value { get; } /// - /// Custom data storage for other type-specific information needed. IE. Used to store the min, - /// max and step values for the IConfigRangeEntry(T). + /// In what (s) is this config editable. Will be editable in the selected state, and lower value states. + ///

+ /// [Important]
Setting this to value lower than 'Configuration` will render this config read-only. + ///

Expected Behaviour: + ///
[|]: Read-Only. + ///
[]: Can only be changed at the Main Menu (not in a lobby). + ///
[]: Can be changed at the Main Menu and while a lobby is active. ///
- string CustomParameters { get; } - /// - /// [Multiplayer]
- /// What permissions do clients require to change this setting. - ///
- ClientPermissions RequiredPermissions { get; } - /// - /// In what s is this config editable. - ///
- /// Note: Setting this to value lower than 'Configuration` will render this config read-only. - ///
- RunState CanEditStates { get; } + RunState EditableStates { get; } /// /// Network synchronization rules for this config. /// NetSync NetSync { get; } - /// - /// User friendly name or Localization Token. - /// - string DisplayName { get; } - /// - /// User friendly description or Localization Token. - /// - string Description { get; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs index 7be1db56c..d60fb2772 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs @@ -1,6 +1,9 @@ -namespace Barotrauma.LuaCs.Data; +using System.Collections.Generic; +using System.Xml.Linq; -public interface IConfigProfileInfo +namespace Barotrauma.LuaCs.Data; + +public interface IConfigProfileInfo : IDataInfo { - + IReadOnlyList<(string ConfigName, OneOf.OneOf Value)> ProfileValues { get; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ILocalizationInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ILocalizationInfo.cs deleted file mode 100644 index b74b1f275..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ILocalizationInfo.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; -using System.Globalization; - -namespace Barotrauma.LuaCs.Data; - -public interface ILocalizationInfo : IDataInfo -{ - string Symbol { get; } - IReadOnlyDictionary LocalizedValues { get; } - RawLString GetLocalizedString(CultureInfo locale); - RawLString GetLocalizedString(string cultureCode); -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs index 7d60bf562..cdecd9037 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs @@ -3,7 +3,7 @@ namespace Barotrauma.LuaCs.Data; public partial interface IModConfigInfo : IAssembliesResourcesInfo, - ILocalizationsResourcesInfo, ILuaScriptsResourcesInfo, IConfigsResourcesInfo, + ILuaScriptsResourcesInfo, IConfigsResourcesInfo, IConfigProfilesResourcesInfo { // package info diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs index ad6bb45d3..777bd24d1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs @@ -6,7 +6,6 @@ namespace Barotrauma.LuaCs.Data; public interface IConfigResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IDataInfo { } public interface IConfigProfileResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IDataInfo { } -public interface ILocalizationResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IDataInfo { } /// /// Represents loadable Lua files. @@ -40,11 +39,6 @@ public interface IAssembliesResourcesInfo ImmutableArray Assemblies { get; } } -public interface ILocalizationsResourcesInfo -{ - ImmutableArray Localizations { get; } -} - public interface ILuaScriptsResourcesInfo { ImmutableArray LuaScripts { get; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs new file mode 100644 index 000000000..f3e3bab2d --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security.AccessControl; +using Barotrauma.Networking; +using FluentResults; + +namespace Barotrauma.LuaCs.Data; + + +// --- Storage Service +public interface IStorageServiceConfig +{ + string LocalModsDirectory { get; } + string WorkshopModsDirectory { get; } + string GameSettingsConfigPath { get; } +#if CLIENT + string TempDownloadsDirectory { get; } +#endif + + //ReadOnlyCollection SafeIOReadDirectories { get; } + //ReadOnlyCollection SafeIOWriteDirectories { get; } + IEnumerable GlobalIOReadWhitelist(); + IEnumerable GlobalIOWriteWhitelist(); + + bool IOReadWhiteListContains(string filePath); + bool IOWriteWhiteListContains(string filePath); + + string LocalDataSavePath { get; } + string LocalDataPathRegex { get; } + string LocalPackageDataPath { get; } + public string RunLocation { get; } + bool GlobalSafeIOEnabled { get; } +} + +internal interface IStorageServiceConfigUpdate +{ + public FluentResults.Result SetSafeReadFilePaths(string[] filePaths); + public FluentResults.Result SetSafeWriteFilePaths(string[] filePaths); +} + +public record StorageServiceConfig : IStorageServiceConfig, IStorageServiceConfigUpdate +{ + private static readonly string ExecutionLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location.CleanUpPath()); + + public string LocalModsDirectory { get; init; } = System.IO.Path.GetFullPath(ContentPackage.LocalModsDir).CleanUpPath(); + public string WorkshopModsDirectory { get; init; } = System.IO.Path.GetFullPath(ContentPackage.WorkshopModsDir).CleanUpPath(); + public string GameSettingsConfigPath { get; init; } = System.IO.Path.GetFullPath( + string.IsNullOrEmpty(GameSettings.CurrentConfig.SavePath) + ? SaveUtil.DefaultSaveFolder + : GameSettings.CurrentConfig.SavePath).CleanUpPath(); +#if CLIENT + public string TempDownloadsDirectory { get; init; } = System.IO.Path.GetFullPath(ModReceiver.DownloadFolder).CleanUpPath(); +#endif + + private readonly AsyncReaderWriterLock _safeIOReadLock = new(); + private readonly AsyncReaderWriterLock _safeIOWriteLock = new(); + private readonly ConcurrentDictionary _safeIOReadFilePaths = new(); + + private readonly ConcurrentDictionary _safeIOWriteFilePaths = new(); + + public IEnumerable GlobalIOReadWhitelist() + { + using var lck = _safeIOReadLock.AcquireReaderLock().GetAwaiter().GetResult(); + + if (_safeIOReadFilePaths.Count == 0) + { + yield break; + } + + foreach (var path in _safeIOReadFilePaths) + { + yield return path.Key; + } + } + + public IEnumerable GlobalIOWriteWhitelist() + { + using var lck = _safeIOWriteLock.AcquireReaderLock().GetAwaiter().GetResult(); + + if (_safeIOWriteFilePaths.Count == 0) + { + yield break; + } + + foreach (var path in _safeIOWriteFilePaths) + { + yield return path.Key; + } + } + + public bool IOReadWhiteListContains(string filePath) + { + if (filePath.IsNullOrWhiteSpace()) + return false; + return _safeIOReadFilePaths.ContainsKey(filePath); + } + + public bool IOWriteWhiteListContains(string filePath) + { + if (filePath.IsNullOrWhiteSpace()) + return false; + return _safeIOWriteFilePaths.ContainsKey(filePath); + } + + public string LocalDataSavePath => Path.Combine(ExecutionLocation, "/Data/Mods/"); + + public string LocalDataPathRegex => ""; + + public string RunLocation => ExecutionLocation; + public bool GlobalSafeIOEnabled => false; + + public string LocalPackageDataPath + { + get + { + return ContainsIllegalPaths(LocalDataSavePath) ? $"/Data/Mods/{LocalDataPathRegex}" + : Path.Combine(LocalDataSavePath, LocalDataPathRegex); + + bool ContainsIllegalPaths(string path) + { + throw new NotImplementedException(); + } + } + } + + + public FluentResults.Result SetSafeReadFilePaths(string[] filePaths) + { + using var lck = _safeIOReadLock.AcquireWriterLock().GetAwaiter().GetResult(); + return SetSafeDirectory(_safeIOReadFilePaths, filePaths); + } + + public FluentResults.Result SetSafeWriteFilePaths(string[] filePaths) + { + using var lck = _safeIOWriteLock.AcquireWriterLock().GetAwaiter().GetResult(); + return SetSafeDirectory(_safeIOWriteFilePaths, filePaths); + } + + private FluentResults.Result SetSafeDirectory(ConcurrentDictionary target, string[] filePaths) + { + if (filePaths is null || filePaths.Length < 1) + { + target.Clear(); + return FluentResults.Result.Ok(); + } + + FluentResults.Result result = new(); + + target.Clear(); + foreach (string path in filePaths) + { + if (path.IsNullOrWhiteSpace()) + { + result = result.WithError($"ServicesConfigData: A supplied whitelist path was null."); + continue; + } + + try + { + var path2 = Path.GetFullPath(path); + target.TryAdd(path2, 0); + } + catch (Exception e) + { + result = result.WithError( + new ExceptionalError(e).WithMetadata(FluentResults.LuaCs.MetadataType.ExceptionObject, this)); + continue; + } + } + + return result.WithSuccess($"Whitelist updated."); + } +} + +// --- Config Service +public interface IConfigServiceConfig +{ + string LocalConfigPathPartial { get; } + string FileNamePattern { get; } +} + +public record ConfigServiceConfig : IConfigServiceConfig +{ + public string LocalConfigPathPartial => $"/Config/{FileNamePattern}.xml"; + public string FileNamePattern => ""; +} + + +// --- Lua Scripts Service +public interface ILuaScriptServicesConfig +{ + bool SafeLuaIOEnabled { get; } + bool UseCaching { get; } +} + +public record LuaScriptServicesConfig : ILuaScriptServicesConfig +{ + public bool SafeLuaIOEnabled => true; + public bool UseCaching => true; +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index 36d8d9b82..ffefc003c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Services; using Barotrauma.Networking; using Dynamitey; @@ -60,6 +61,11 @@ internal interface IEventReloadAllPackages : IEvent void OnReloadAllPackages(); } +internal interface IEventConfigVarInstanced : IEvent +{ + void OnConfigCreated(IConfigBase config); +} + #endregion #region GameEvents diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaScriptLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaScriptLoader.cs deleted file mode 100644 index 212c70ae5..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaScriptLoader.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.IO; -using MoonSharp.Interpreter; -using MoonSharp.Interpreter.Loaders; -using System.Linq; - -namespace Barotrauma -{ - class LuaScriptLoader : ScriptLoaderBase - { - - public override object LoadFile(string file, Table globalContext) - { - if (!LuaCsFile.IsPathAllowedLuaException(file, false)) return null; - - return File.ReadAllText(file); - } - - public override bool ScriptFileExists(string file) - { - if (!LuaCsFile.IsPathAllowedLuaException(file, false)) return false; - - return File.Exists(file); - } - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index c409ac4d3..ae24a2efc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -44,15 +44,11 @@ namespace Barotrauma _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); -#if CLIENT - _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); -#endif _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - // TODO: ILocalizationService + // TODO: IConfigService // TODO: INetworkingService // TODO: [Resource Converter/Parser Services] @@ -61,13 +57,12 @@ namespace Barotrauma _servicesProvider.RegisterServiceType, IAssembliesResourcesInfo>, ResourceInfoArrayPacker>(ServiceLifetime.Transient); _servicesProvider.RegisterServiceType, IConfigsResourcesInfo>, ResourceInfoArrayPacker>(ServiceLifetime.Transient); _servicesProvider.RegisterServiceType, IConfigProfilesResourcesInfo>, ResourceInfoArrayPacker>(ServiceLifetime.Transient); - _servicesProvider.RegisterServiceType, ILocalizationsResourcesInfo>, ResourceInfoArrayPacker>(ServiceLifetime.Transient); _servicesProvider.RegisterServiceType, ILuaScriptsResourcesInfo>, ResourceInfoArrayPacker>(ServiceLifetime.Transient); + // Loaders and Processors (yes the naming is reversed, oops). _servicesProvider.RegisterServiceType, ModConfigService>(ServiceLifetime.Transient); _servicesProvider.RegisterServiceType, ModConfigService>(ServiceLifetime.Transient); - - + _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); _servicesProvider.Compile(); } @@ -121,8 +116,6 @@ namespace Barotrauma ? svc : throw new NullReferenceException("Plugin Manager service not found!"); public ILuaScriptManagementService LuaScriptManagementService => _servicesProvider.TryGetService(out var svc) ? svc : throw new NullReferenceException("Lua Script Manager service not found!"); - public ILocalizationService LocalizationService => _servicesProvider.TryGetService(out var svc) - ? svc : throw new NullReferenceException("Localization Manager service not found!"); public INetworkingService NetworkingService => _servicesProvider.TryGetService(out var svc) ? svc : throw new NullReferenceException("Networking Manager service not found!"); public IEventService EventService => _servicesProvider.TryGetService(out var svc) @@ -179,6 +172,11 @@ namespace Barotrauma /// TODO: @evilfactory@users.noreply.github.com /// public IConfigEntry RestrictMessageSize { get; private set; } + + /// + /// The local save path for all local data storage for mods. + /// + public IConfigEntry LocalDataSavePath { get; private set; } /** * == Ops Vars @@ -287,11 +285,7 @@ namespace Barotrauma PluginManagementService.Dispose(); LuaScriptManagementService.Dispose(); -#if CLIENT - StylesManagementService.Dispose(); -#endif ConfigService.Dispose(); - LocalizationService.Dispose(); PackageManagementService.Dispose(); // TODO: Add all missing services. //NetworkingService.Dispose(); @@ -372,12 +366,7 @@ namespace Barotrauma while (_toUnload.TryDequeue(out var cp)) { LuaScriptManagementService.DisposePackageResources(cp); - ConfigService.DisposeConfigsProfiles(cp); - ConfigService.DisposeConfigs(cp); -#if CLIENT - StylesManagementService.DisposeStylesForPackage(cp); -#endif - LocalizationService.DisposePackage(cp); + ConfigService.DisposePackageData(cp); PackageManagementService.DisposePackageInfos(cp); } @@ -515,23 +504,22 @@ namespace Barotrauma void LoadLuaCsConfig() { - IsCsEnabled = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "IsCsEnabled") - ?? throw new NullReferenceException($"{nameof(IsCsEnabled)} cannot be loaded."); - TreatForcedModsAsNormal = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "TreatForcedModsAsNormal") - ?? throw new NullReferenceException($"{nameof(TreatForcedModsAsNormal)} cannot be loaded."); - DisableErrorGUIOverlay = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "DisableErrorGUIOverlay") - ?? throw new NullReferenceException($"{nameof(DisableErrorGUIOverlay)} cannot be loaded."); - HideUserNamesInLogs = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "HideUserNamesInLogs") - ?? throw new NullReferenceException($"{nameof(HideUserNamesInLogs)} cannot be loaded."); - LuaForBarotraumaSteamId = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "LuaForBarotraumaSteamId") - ?? throw new NullReferenceException($"{nameof(LuaForBarotraumaSteamId)} cannot be loaded."); - CsForBarotraumaSteamId = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "CsForBarotraumaSteamId") - ?? throw new NullReferenceException($"{nameof(CsForBarotraumaSteamId)} cannot be loaded."); - RestrictMessageSize = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "RestrictMessageSize") - ?? throw new NullReferenceException($"{nameof(RestrictMessageSize)} cannot be loaded."); - ReloadPackagesOnLobbyStart = ConfigService.GetConfig>(ContentPackageManager.VanillaCorePackage, "ReloadPackagesOnLobbyStart") - ?? throw new NullReferenceException($"{nameof(ReloadPackagesOnLobbyStart)} cannot be loaded."); - + IsCsEnabled = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "IsCsEnabled", out var val1) ? val1 + : throw new NullReferenceException($"{nameof(IsCsEnabled)} cannot be loaded."); + TreatForcedModsAsNormal = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "TreatForcedModsAsNormal", out var val2) ? val2 + : throw new NullReferenceException($"{nameof(TreatForcedModsAsNormal)} cannot be loaded."); + DisableErrorGUIOverlay = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "DisableErrorGUIOverlay", out var val3) ? val3 + : throw new NullReferenceException($"{nameof(DisableErrorGUIOverlay)} cannot be loaded."); + HideUserNamesInLogs = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "HideUserNamesInLogs", out var val4) ? val4 + : throw new NullReferenceException($"{nameof(HideUserNamesInLogs)} cannot be loaded."); + LuaForBarotraumaSteamId = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "LuaForBarotraumaSteamId", out var val5) ? val5 + : throw new NullReferenceException($"{nameof(LuaForBarotraumaSteamId)} cannot be loaded."); + CsForBarotraumaSteamId = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "CsForBarotraumaSteamId", out var val6) ? val6 + : throw new NullReferenceException($"{nameof(CsForBarotraumaSteamId)} cannot be loaded."); + RestrictMessageSize = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "RestrictMessageSize", out var val7) ? val7 + : throw new NullReferenceException($"{nameof(RestrictMessageSize)} cannot be loaded."); + ReloadPackagesOnLobbyStart = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "ReloadPackagesOnLobbyStart", out var val8) ? val8 + : throw new NullReferenceException($"{nameof(ReloadPackagesOnLobbyStart)} cannot be loaded."); } void DisposeLuaCsConfig() @@ -548,27 +536,14 @@ namespace Barotrauma async Task LoadStaticAssetsAsync(IReadOnlyList packages) { - var locRes = ImmutableArray.Empty; var cfgRes = ImmutableArray.Empty; var cfpRes = ImmutableArray.Empty; var luaRes = ImmutableArray.Empty; - -#if CLIENT - var styleRes = ImmutableArray.Empty; -#endif var tasksBuilder = ImmutableArray.CreateBuilder(); //---- get resource infos - tasksBuilder.AddRange(new Func(async () => - { - var res = await PackageManagementService.GetLocalizationsInfosAsync(packages); - if (res.IsSuccess) - locRes = res.Value.Localizations; - if (res.Errors.Any()) - ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), - res.ToResult()); - })(), + tasksBuilder.AddRange( new Func(async () => { var res = await PackageManagementService.GetConfigsInfosAsync(packages); @@ -596,18 +571,7 @@ namespace Barotrauma ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), res.ToResult()); })()); - -#if CLIENT - tasksBuilder.Add(new Func(async () => - { - var res = await PackageManagementService.GetStylesInfosAsync(packages); - if (res.IsSuccess) - styleRes = res.Value.Styles; - if (res.Errors.Any()) - ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), - res.ToResult()); - })()); -#endif + await Task.WhenAll(tasksBuilder.MoveToImmutable()); tasksBuilder.Clear(); @@ -628,23 +592,6 @@ namespace Barotrauma Logger.LogResults(res); })()); -#if CLIENT - tasksBuilder.Add(new Func(async () => - { - var res = await StylesManagementService.LoadStylesAsync(styleRes); - if (res.Errors.Any()) - Logger.LogResults(res); - })()); -#endif - - // load localizations first - if (!locRes.IsDefaultOrEmpty) - { - var res = await LocalizationService.LoadLocalizations(locRes); - if (res.Errors.Any()) - Logger.LogResults(res); - } - await Task.WhenAll(tasksBuilder.MoveToImmutable()); } @@ -706,22 +653,18 @@ namespace Barotrauma } //lua - var luaRes = PackageManagementService.GetLuaScriptsInfos(PackageManagementService - .GetAllLoadedPackages() + var luaRes = PackageManagementService.LuaScripts + .Select(ls => ls.OwnerPackage) + .Where(p => p is not null) .Where(ContentPackageManager.EnabledPackages.All.Contains) - .ToList()); - if (luaRes.IsFailed) + .ToImmutableArray(); + if (luaRes.IsDefaultOrEmpty) { Logger.LogError($"{nameof(RunScripts)}: Failed to get enabled lua script resources!"); - Logger.LogResults(luaRes.ToResult()); return; } - if (luaRes.Errors.Any()) - Logger.LogResults(luaRes.ToResult()); - - - LuaScriptManagementService.ExecuteLoadedScripts(luaRes.Value.LuaScripts); + LuaScriptManagementService.ExecuteLoadedScriptsForPackages(luaRes); if (CurrentRunState < RunState.Running) _runState = RunState.Running; @@ -748,10 +691,6 @@ namespace Barotrauma PluginManagementService.Reset(); LuaScriptManagementService.Reset(); ConfigService.Reset(); -#if CLIENT - StylesManagementService.Reset(); -#endif - LocalizationService.Reset(); if (CurrentRunState >= RunState.Configuration) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs index ad34c723d..fc30ce5db 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs @@ -567,8 +567,17 @@ namespace FluentResults.LuaCs public static class MetadataType { public static string ExceptionDetails = nameof(ExceptionDetails); + /// + /// The object that threw the exception. + /// public static string ExceptionObject = nameof(ExceptionObject); + /// + /// The parameter-object responsible for the exception thrown (not the exception thrower). + /// public static string RootObject = nameof(RootObject); + /// + /// Additional exception sources. + /// public static string Sources = nameof(Sources); public static string StackTrace = nameof(StackTrace); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs deleted file mode 100644 index a907b18fa..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetVar.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using Barotrauma.LuaCs.Configuration; -using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services; -using Barotrauma.Networking; - -namespace Barotrauma.LuaCs.Services; - -public interface INetVar : IVarId -{ - /// - /// Synchronization type - /// - NetSync SyncType { get; } - /// - /// Permissions needed by clients to send net-events or receive net messages. - /// - ClientPermissions WritePermissions { get; } - - void ReadNetMessage(INetReadMessage message); - void WriteNetMessage(INetWriteMessage message); -} - -public enum NetSync -{ - None, TwoWay, ServerAuthority, ClientOneWay -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetworkSyncEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetworkSyncEntity.cs new file mode 100644 index 000000000..e0cb5713d --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetworkSyncEntity.cs @@ -0,0 +1,66 @@ +using System; +using Barotrauma.LuaCs.Configuration; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Services; +using Barotrauma.Networking; + +namespace Barotrauma.LuaCs.Services; + +public interface INetworkSyncEntity +{ + /// + /// Network-synchronized object ID. Used for networking send/receive message events. + /// + Guid InstanceId { get; } + + /// + /// Synchronization type. See for more information. + /// + NetSync SyncType { get; } + + /// + /// Permissions needed by clients to send net-events and/or receive net messages. + /// + ClientPermissions WritePermissions { get; } + + /// + /// Called when an incoming net message has data for this network object, typically from the same entity on another + /// machine. + /// + /// Wrapper for the internal type: + void ReadNetMessage(INetReadMessage message); + + /// + /// Called when a network send-event involving this entity is triggered. Any data expected to be read by the recipient + /// network object on the other instance(s) should be written to the packet. + /// + /// Wrapper for the internal type: + void WriteNetMessage(INetWriteMessage message); +} + +/// +/// Specifies the networking send/receive relationship for network object. Objects implementing this interface are +/// expected to adhere to the contract or de-sync may occur. +/// +public enum NetSync +{ + /// + /// No network synchronization. + /// + None, + /// + /// Both the client and the server have 'send' and 'receive' permissions (limited by ). Can also be used to allow two-way communication + /// with the server. + /// + TwoWay, + /// + /// Only the host/server has the authority to change this value. + /// + ServerAuthority, + /// + /// Only clients (with the required by ) may change the value and all value changes are communicated to the server/host. + ///

[Important] The host/server will not send the value to other connected clients.
+ /// Intended to allow clients to send one-way messages to the server. + ///
+ ClientOneWay +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsUtility.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsUtility.cs index b5db0066c..4e1bde042 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsUtility.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsUtility.cs @@ -2,5 +2,9 @@ public interface ILuaCsUtility : ILuaCsShim { + public bool CanReadFromPath(string file); + public bool CanWriteToPath(string file); + internal bool IsPathAllowedException(string path, bool write = true, + LuaCsMessageOrigin origin = LuaCsMessageOrigin.Unknown); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigInitializers.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigInitializers.cs new file mode 100644 index 000000000..d3bad9e9d --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigInitializers.cs @@ -0,0 +1,340 @@ +using System; +using System.Numerics; +using Barotrauma.LuaCs.Configuration; +using Barotrauma.LuaCs.Data; +using FluentResults; +using Microsoft.Xna.Framework; +using Vector2 = Microsoft.Xna.Framework.Vector2; +using Vector3 = Microsoft.Xna.Framework.Vector3; +using Vector4 = Microsoft.Xna.Framework.Vector4; + +namespace Barotrauma.LuaCs.Services; + +public class ConfigInitializers : IService +{ + // parameterless .ctor + public ConfigInitializers() + { + } + + public void Dispose() + { + // stateless service + return; + } + + // stateless service + public bool IsDisposed => false; + + private Result> CreateConfigEntry(IConfigInfo configInfo, + Action, INetReadMessage> readHandler, + Action, INetWriteMessage> writeHandler) + where T : IEquatable + { + try + { + var ice = new ConfigEntry(configInfo, readHandler, writeHandler); + return FluentResults.Result.Ok>(ice); + } + catch (Exception e) + { + return FluentResults.Result.Fail($"Error while initializing config var: {configInfo?.OwnerPackage} - {configInfo?.InternalName}") + .WithError(new ExceptionalError(e)); + } + } + + private Result> CreateConfigList(IConfigInfo configInfo, + Action, INetReadMessage> readHandler, Action, INetWriteMessage> writeHandler) + where T : IEquatable + { + try + { + var icl = new ConfigList(configInfo, readHandler, writeHandler); + return FluentResults.Result.Ok>(icl); + } + catch (Exception e) + { + return FluentResults.Result.Fail($"Error while initializing config var: {configInfo?.OwnerPackage} - {configInfo?.InternalName}") + .WithError(new ExceptionalError(e)); + } + } + + public void RegisterTypeInitializers(IConfigService configService) + { + if (configService == null) + throw new ArgumentNullException($"{nameof(RegisterTypeInitializers)}: {nameof(IConfigService)} is null."); + + configService.RegisterTypeInitializer>(this.CreateConfigBool); + configService.RegisterTypeInitializer>(this.CreateConfigSbyte); + configService.RegisterTypeInitializer>(this.CreateConfigByte); + configService.RegisterTypeInitializer>(this.CreateConfigShort); + configService.RegisterTypeInitializer>(this.CreateConfigUShort); + configService.RegisterTypeInitializer>(this.CreateConfigInt32); + configService.RegisterTypeInitializer>(this.CreateConfigUInt32); + configService.RegisterTypeInitializer>(this.CreateConfigInt64); + configService.RegisterTypeInitializer>(this.CreateConfigUInt64); + configService.RegisterTypeInitializer>(this.CreateConfigFloat32); + configService.RegisterTypeInitializer>(this.CreateConfigFloat64); + configService.RegisterTypeInitializer>(this.CreateConfigFloat128); + configService.RegisterTypeInitializer>(this.CreateConfigChar); + configService.RegisterTypeInitializer>(this.CreateConfigString); + configService.RegisterTypeInitializer>(this.CreateConfigColor); + configService.RegisterTypeInitializer>(this.CreateConfigVector2); + configService.RegisterTypeInitializer>(this.CreateConfigVector3); + configService.RegisterTypeInitializer>(this.CreateConfigVector4); + } + + + #region InitializerWrappers_NetworkInjected + + private void AssignValueConditional(T val, IConfigEntry inst) where T : IEquatable + { +#if SERVER + if (inst.SyncType is NetSync.None or NetSync.ServerAuthority) + throw new InvalidOperationException($"[Server] Tried to assign a net value to a type that does not support sync: {inst.SyncType}. Name: {inst.InternalName}, Package: {inst.OwnerPackage.Name}"); + inst.TrySetValue(val); +#else + if (inst.SyncType is NetSync.None or NetSync.ClientOneWay) + throw new InvalidOperationException($"[Client] Tried to assign a net value to a type that does not support sync: {inst.SyncType}. Name: {inst.InternalName}, Package: {inst.OwnerPackage.Name}"); + inst.TrySetValue(val); +#endif + } + + private Result> CreateConfigBool(IConfigInfo configInfo) + { + return CreateConfigEntry(configInfo, (inst, readMsg) => + { + AssignValueConditional(readMsg.ReadBoolean(), inst); + + }, (inst, writeMsg) => + { + writeMsg.WriteBoolean(inst.Value); + }); + } + + private Result> CreateConfigSbyte(IConfigInfo configInfo) + { + return CreateConfigEntry(configInfo, (inst, readMsg) => + { + AssignValueConditional((sbyte)readMsg.ReadInt16(), inst); + + }, (inst, writeMsg) => + { + writeMsg.WriteInt16((short)inst.Value); + }); + } + + private Result> CreateConfigByte(IConfigInfo configInfo) + { + return CreateConfigEntry(configInfo, (inst, readMsg) => + { + AssignValueConditional((byte)readMsg.ReadUInt16(), inst); + + }, (inst, writeMsg) => + { + writeMsg.WriteUInt16((byte)inst.Value); + }); + } + + private Result> CreateConfigShort(IConfigInfo configInfo) + { + return CreateConfigEntry(configInfo, (inst, readMsg) => + { + AssignValueConditional(readMsg.ReadInt16(), inst); + + }, (inst, writeMsg) => + { + writeMsg.WriteInt16(inst.Value); + }); + } + + private Result> CreateConfigUShort(IConfigInfo configInfo) + { + return CreateConfigEntry(configInfo, (inst, readMsg) => + { + AssignValueConditional(readMsg.ReadUInt16(), inst); + + }, (inst, writeMsg) => + { + writeMsg.WriteUInt16(inst.Value); + }); + } + + private Result> CreateConfigInt32(IConfigInfo configInfo) + { + return CreateConfigEntry(configInfo, (inst, readMsg) => + { + AssignValueConditional(readMsg.ReadInt32(), inst); + + }, (inst, writeMsg) => + { + writeMsg.WriteInt32(inst.Value); + }); + } + + private Result> CreateConfigUInt32(IConfigInfo configInfo) + { + return CreateConfigEntry(configInfo, (inst, readMsg) => + { + AssignValueConditional(readMsg.ReadUInt32(), inst); + + }, (inst, writeMsg) => + { + writeMsg.WriteUInt32(inst.Value); + }); + } + + private Result> CreateConfigInt64(IConfigInfo configInfo) + { + return CreateConfigEntry(configInfo, (inst, readMsg) => + { + AssignValueConditional(readMsg.ReadInt64(), inst); + + }, (inst, writeMsg) => + { + writeMsg.WriteInt64(inst.Value); + }); + } + + private Result> CreateConfigUInt64(IConfigInfo configInfo) + { + return CreateConfigEntry(configInfo, (inst, readMsg) => + { + AssignValueConditional(readMsg.ReadUInt64(), inst); + + }, (inst, writeMsg) => + { + writeMsg.WriteUInt64(inst.Value); + }); + } + + private Result> CreateConfigFloat32(IConfigInfo configInfo) + { + return CreateConfigEntry(configInfo, (inst, readMsg) => + { + AssignValueConditional(readMsg.ReadSingle(), inst); + + }, (inst, writeMsg) => + { + writeMsg.WriteSingle(inst.Value); + }); + } + + private Result> CreateConfigFloat64(IConfigInfo configInfo) + { + return CreateConfigEntry(configInfo, (inst, readMsg) => + { + AssignValueConditional(readMsg.ReadDouble(), inst); + + }, (inst, writeMsg) => + { + writeMsg.WriteDouble(inst.Value); + }); + } + + private Result> CreateConfigFloat128(IConfigInfo configInfo) + { + return CreateConfigEntry(configInfo, (inst, readMsg) => + { + var decimalArr = new int[4]; + decimalArr[0] = readMsg.ReadInt32(); + decimalArr[1] = readMsg.ReadInt32(); + decimalArr[2] = readMsg.ReadInt32(); + decimalArr[3] = readMsg.ReadInt32(); + AssignValueConditional(new decimal(decimalArr), inst); + + }, (inst, writeMsg) => + { + var decimalArr = Decimal.GetBits(inst.Value); + writeMsg.WriteInt32(decimalArr[0]); + writeMsg.WriteInt32(decimalArr[1]); + writeMsg.WriteInt32(decimalArr[2]); + writeMsg.WriteInt32(decimalArr[3]); + }); + } + + private Result> CreateConfigChar(IConfigInfo configInfo) + { + return CreateConfigEntry(configInfo, (inst, readMsg) => + { + AssignValueConditional((char)readMsg.ReadUInt16(), inst); + + }, (inst, writeMsg) => + { + writeMsg.WriteUInt16(inst.Value); + }); + } + + private Result> CreateConfigString(IConfigInfo configInfo) + { + return CreateConfigEntry(configInfo, (inst, readMsg) => + { + AssignValueConditional(readMsg.ReadString(), inst); + + }, (inst, writeMsg) => + { + writeMsg.WriteString(inst.Value); + }); + } + + private Result> CreateConfigColor(IConfigInfo configInfo) + { + return CreateConfigEntry(configInfo, (inst, readMsg) => + { + AssignValueConditional(readMsg.ReadColorR8G8B8A8(), inst); + + }, (inst, writeMsg) => + { + writeMsg.WriteColorR8G8B8A8(inst.Value); + }); + } + + private Result> CreateConfigVector2(IConfigInfo configInfo) + { + return CreateConfigEntry(configInfo, (inst, readMsg) => + { + AssignValueConditional(new Vector2(readMsg.ReadSingle(), readMsg.ReadSingle()), inst); + + }, (inst, writeMsg) => + { + writeMsg.WriteSingle(inst.Value.X); + writeMsg.WriteSingle(inst.Value.Y); + }); + } + + private Result> CreateConfigVector3(IConfigInfo configInfo) + { + return CreateConfigEntry(configInfo, (inst, readMsg) => + { + AssignValueConditional(new Vector3(readMsg.ReadSingle(), readMsg.ReadSingle(), readMsg.ReadSingle()), inst); + + }, (inst, writeMsg) => + { + writeMsg.WriteSingle(inst.Value.X); + writeMsg.WriteSingle(inst.Value.Y); + writeMsg.WriteSingle(inst.Value.Z); + }); + } + + private Result> CreateConfigVector4(IConfigInfo configInfo) + { + return CreateConfigEntry(configInfo, (inst, readMsg) => + { + AssignValueConditional(new Vector4( + readMsg.ReadSingle(), + readMsg.ReadSingle(), + readMsg.ReadSingle(), + readMsg.ReadSingle()), inst); + + }, (inst, writeMsg) => + { + writeMsg.WriteSingle(inst.Value.X); + writeMsg.WriteSingle(inst.Value.Y); + writeMsg.WriteSingle(inst.Value.Z); + writeMsg.WriteSingle(inst.Value.W); + }); + } + + + #endregion +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs index f6962300d..2b31d3da4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs @@ -1,6 +1,690 @@ -namespace Barotrauma.LuaCs.Services; +using System; +using System.Collections.Concurrent; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Xml.Linq; +using Barotrauma.LuaCs.Configuration; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Events; +using Barotrauma.LuaCs.Services.Processing; +using Barotrauma.Networking; +using Dynamitey.DynamicObjects; +using FluentResults; +using Microsoft.Xna.Framework; +using OneOf; +using Path = Barotrauma.IO.Path; -public class ConfigService : IConfigService +namespace Barotrauma.LuaCs.Services; + +public partial class ConfigService : IConfigService { + //--- Internals + public ConfigService(IConverterServiceAsync> configProfileResourceConverter, + IConverterServiceAsync> configResourceConverter, IEventService eventService, System.Lazy storageService) + { + _configProfileResourceConverter = configProfileResourceConverter; + _configResourceConverter = configResourceConverter; + _eventService = eventService; + _storageService = storageService; + this._base = this; + } + + // data, states + private readonly IService _base; + private int _isDisposed = 0; + private readonly ConcurrentDictionary>> _configTypeInitializers = new(); + private readonly ConcurrentDictionary<(ContentPackage Package, string ConfigName), IConfigBase> _configs = new(); + private readonly ConcurrentDictionary> _packageConfigReverseLookup = new(); + private readonly ConcurrentDictionary<(ContentPackage Package, string ProfileName), ImmutableArray<(string ConfigName, OneOf.OneOf Value)>> _configProfiles = new(); + private readonly ConcurrentDictionary> _packageProfilesReverseLookup = new(); + private readonly ConcurrentDictionary _packageNameMap= new(); + + private readonly AsyncReaderWriterLock _disposeOpsLock = new(); + + // extern services + private readonly IConverterServiceAsync> _configResourceConverter; + private readonly IConverterServiceAsync> _configProfileResourceConverter; + private readonly IEventService _eventService; + private readonly System.Lazy _storageService; + + //--- GC + public bool IsDisposed => ModUtils.Threading.GetBool(ref _isDisposed); + + public void Dispose() + { + // stop all ops + using var lck = _disposeOpsLock.AcquireWriterLock().GetAwaiter().GetResult(); + // set flag + ModUtils.Threading.SetBool(ref _isDisposed, true); + + _configTypeInitializers.Clear(); + if (!_configs.IsEmpty) + { + foreach (var config in _configs) + { + if (config.Value is IDisposable disposable) + disposable.Dispose(); + config.Value.OnValueChanged -= this.SaveConfigEvent; + } + _configs.Clear(); + } + + _configProfiles.Clear(); + _packageConfigReverseLookup.Clear(); + _packageNameMap.Clear(); + _packageProfilesReverseLookup.Clear(); + + GC.SuppressFinalize(this); + } + + public FluentResults.Result Reset() + { + using var lck = _disposeOpsLock.AcquireWriterLock().GetAwaiter().GetResult(); + _base.CheckDisposed(); + + _configTypeInitializers.Clear(); + _configs.Clear(); + _configProfiles.Clear(); + _packageConfigReverseLookup.Clear(); + _packageNameMap.Clear(); + _packageProfilesReverseLookup.Clear(); + + return FluentResults.Result.Ok(); + } + + //--- API contracts + // Notes: + // -- Lua Interface uses strong types due to lua limitations. May be required to move API to an adapter class + // to allow testing abstraction. + // -- Lua interface should not propagate errors. + #region LuaInterface + + private bool TryGetConfigValue(string packageName, string configName, out T value) where T : IEquatable + { + value = default; + using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); + if (ModUtils.Threading.GetBool(ref _isDisposed)) + return false; + if (!_packageNameMap.TryGetValue(packageName, out var package)) + return false; + if (!_configs.TryGetValue((package, configName), out var config)) + return false; + if (config is not IConfigEntry entry) + return false; + value = entry.Value; + return true; + } + + public bool TryGetConfigBool(string packageName, string configName, out bool value) + { + return TryGetConfigValue(packageName, configName, out value); + } + + public bool TryGetConfigInt(string packageName, string configName, out int value) + { + return TryGetConfigValue(packageName, configName, out value); + } + + public bool TryGetConfigFloat(string packageName, string configName, out float value) + { + return TryGetConfigValue(packageName, configName, out value); + } + + public bool TryGetConfigNumber(string packageName, string configName, out double value) + { + return TryGetConfigValue(packageName, configName, out value); + } + + public bool TryGetConfigString(string packageName, string configName, out string value) + { + return TryGetConfigValue(packageName, configName, out value); + } + + public bool TryGetConfigVector2(string packageName, string configName, out Vector2 value) + { + return TryGetConfigValue(packageName, configName, out value); + } + + public bool TryGetConfigVector3(string packageName, string configName, out Vector3 value) + { + return TryGetConfigValue(packageName, configName, out value); + } + + public bool TryGetConfigColor(string packageName, string configName, out Color value) + { + return TryGetConfigValue(packageName, configName, out value); + } + + public bool TryGetConfigList(string packageName, string configName, out IReadOnlyList value) + { + value = null; + using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); + if (ModUtils.Threading.GetBool(ref _isDisposed)) + return false; + if (!_packageNameMap.TryGetValue(packageName, out var package)) + return false; + if (!_configs.TryGetValue((package, configName), out var config)) + return false; + if (config is not IConfigList entry) + return false; + value = entry.Options; + return value is not null && value.Count > 0; + } + + private void SetConfigValue(string packageName, string configName, T value) where T : IEquatable + { + using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); + if (ModUtils.Threading.GetBool(ref _isDisposed)) + return; + if (!_packageNameMap.TryGetValue(packageName, out var package)) + return; + if (!_configs.TryGetValue((package, configName), out var config)) + return; + if (config is not IConfigEntry entry) + return; + entry.TrySetValue(value); + } + + public void SetConfigBool(string packageName, string configName, bool value) + { + SetConfigValue(packageName, configName, value); + } + + public void SetConfigInt(string packageName, string configName, int value) + { + SetConfigValue(packageName, configName, value); + } + + public void SetConfigFloat(string packageName, string configName, float value) + { + SetConfigValue(packageName, configName, value); + } + + public void SetConfigNumber(string packageName, string configName, double value) + { + SetConfigValue(packageName, configName, value); + } + + public void SetConfigString(string packageName, string configName, string value) + { + SetConfigValue(packageName, configName, value); + } + + public void SetConfigVector2(string packageName, string configName, Vector2 value) + { + SetConfigValue(packageName, configName, value); + } + + public void SetConfigVector3(string packageName, string configName, Vector3 value) + { + SetConfigValue(packageName, configName, value); + } + + public void SetConfigColor(string packageName, string configName, Color value) + { + SetConfigValue(packageName, configName, value); + } + + public void SetConfigList(string packageName, string configName, string value) + { + using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); + if (ModUtils.Threading.GetBool(ref _isDisposed)) + return; + if (!_packageNameMap.TryGetValue(packageName, out var package)) + return; + if (!_configs.TryGetValue((package, configName), out var config)) + return; + if (config is not IConfigList entry) + return; + entry.TrySetValue(value); + } + + public bool TryApplyProfileSettings(string packageName, string profileName) + { + + if (ModUtils.Threading.GetBool(ref _isDisposed)) + return false; + if (packageName.IsNullOrWhiteSpace() || profileName.IsNullOrWhiteSpace()) + return false; + ContentPackage package = null; + using (var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult()) + { + if (!_packageNameMap.TryGetValue(packageName, out package) || package == null) + return false; + } + // exit semaphore before invocation. Note: Race condition, may require copy implementation. + return this.ApplyProfileSettings(package, profileName).IsSuccess; + } + + #endregion + + public void RegisterTypeInitializer(Func> initializer, bool replaceIfExists = false) + where TData : IEquatable where TConfig : IConfigBase + { + using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); + _base.CheckDisposed(); + + Type dataType = typeof(TData); + if (_configTypeInitializers.ContainsKey(dataType) && !replaceIfExists) + return; + _configTypeInitializers[dataType] = (info => + { + var res = initializer(info); + if (res.IsFailed) + return FluentResults.Result.Fail($"Failed to initialize config type {dataType.Name}").WithErrors(res.Errors); + return res.Value; + }); + } + + private void AddConfigInstance((ContentPackage Package, string ConfigName) key, IConfigBase instance) + { + _configs[key] = instance; + if (!_packageNameMap.ContainsKey(key.Package.Name)) + _packageNameMap[key.Package.Name] = key.Package; + if (!_packageConfigReverseLookup.TryGetValue(key.Package, out var list)) + { + list = new ConcurrentBag<(ContentPackage Package, string ConfigName)>(); + _packageConfigReverseLookup[key.Package] = list; + } + list.Add(key); + // save hook + instance.OnValueChanged += this.SaveConfigEvent; + _eventService.PublishEvent(sub => sub.OnConfigCreated(instance)); + } + + private void AddProfileInstance((ContentPackage Package, string ProfileName) key, IConfigProfileInfo profile) + { + _configProfiles[key] = profile.ProfileValues.ToImmutableArray(); + if (!_packageNameMap.ContainsKey(key.Package.Name)) + _packageNameMap[key.Package.Name] = key.Package; + if (!_packageProfilesReverseLookup.TryGetValue(key.Package, out var list)) + { + list = new ConcurrentBag<(ContentPackage Package, string ProfileName)>(); + _packageProfilesReverseLookup[key.Package] = list; + } + list.Add(key); + } + + public async Task LoadConfigsAsync(ImmutableArray configResources) + { + using var lck = await _disposeOpsLock.AcquireReaderLock(); + _base.CheckDisposed(); + + if (configResources.IsDefaultOrEmpty) + return FluentResults.Result.Fail($"{nameof(LoadConfigsAsync)}: Array is empty."); + + var results = await _configResourceConverter.TryParseResourcesAsync(configResources); + var ret = new FluentResults.Result(); + + foreach (var result in results) + { + if (result.Errors.Any()) + ret.Errors.AddRange(result.Errors); + if (result.IsFailed || result.Value is not { Count: > 0 } res) + continue; + + foreach (var configInfo in res) + { + if (_configs.ContainsKey((configInfo.OwnerPackage, configInfo.InternalName))) + { + ret.Errors.Add(new Error($"{nameof(LoadConfigsAsync)}: Config already exists for the compound key {configInfo.OwnerPackage.Name} | {configInfo.InternalName}")); + continue; + } + + if (!_configTypeInitializers.TryGetValue(configInfo.DataType, out var initializer)) + { + ret.Errors.Add(new Error($"{nameof(LoadConfigsAsync)} No type initializer for {configInfo.DataType}")); + continue; + } + + + + var cfg = initializer(configInfo); + if (cfg.Errors.Any()) + ret.Errors.AddRange(cfg.Errors); + if (cfg.IsFailed || cfg.Value is not {} val) + continue; + + AddConfigInstance((configInfo.OwnerPackage, configInfo.InternalName), val); + } + } + + return ret; + } + + public async Task LoadConfigsProfilesAsync(ImmutableArray configProfileResources) + { + using var lck = await _disposeOpsLock.AcquireReaderLock(); + _base.CheckDisposed(); + + if (configProfileResources.IsDefaultOrEmpty) + return FluentResults.Result.Fail($"{nameof(LoadConfigsProfilesAsync)}: Array is empty."); + + var results = await _configProfileResourceConverter.TryParseResourcesAsync(configProfileResources); + var ret = new FluentResults.Result(); + + foreach (var result in results) + { + if (result.Errors.Any()) + ret.Errors.AddRange(result.Errors); + if (result.IsFailed || result.Value is not { Count: > 0 } res) + continue; + + foreach (var profileInfo in res) + { + if (_configProfiles.ContainsKey((profileInfo.OwnerPackage, profileInfo.InternalName))) + { + ret.Errors.Add(new Error($"{nameof(LoadConfigsProfilesAsync)}: Config already exists for the compound key {profileInfo.OwnerPackage.Name} | {profileInfo.InternalName}")); + continue; + } + + AddProfileInstance((profileInfo.OwnerPackage, profileInfo.InternalName), profileInfo); + } + } + + return ret; + } + + public FluentResults.Result AddConfig(IConfigInfo configInfo) where TConfig : IConfigBase + { + using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); + _base.CheckDisposed(); + + if (configInfo is null) + return FluentResults.Result.Fail($"{nameof(AddConfig)}: Config is null."); + + if (!_configTypeInitializers.TryGetValue(configInfo.DataType, out var initializer)) + return FluentResults.Result.Fail($"{nameof(AddConfig)}: No type initializer for {configInfo.DataType}"); + + var errList = new List(); + + try + { + var cfg = initializer(configInfo); + if (cfg.Errors.Any()) + errList.AddRange(cfg.Errors); + if (cfg.IsFailed || cfg.Value is null) + return FluentResults.Result.Fail($"Failed to initialize {configInfo.DataType}").WithErrors(errList); + AddConfigInstance((configInfo.OwnerPackage, configInfo.InternalName), cfg.Value); + return (TConfig)cfg.Value; + } + catch(Exception ex) + { + return FluentResults.Result.Fail($"Failed to initialize {configInfo.DataType}").WithError(new ExceptionalError(ex)); + } + } + + public FluentResults.Result ApplyProfileSettings(ContentPackage package, string profileName) + { + using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); + _base.CheckDisposed(); + + if (package == null || string.IsNullOrEmpty(profileName)) + return FluentResults.Result.Fail($"{nameof(ApplyProfileSettings)}: ContentPackage and/or name were null or empty."); + + if (!_configProfiles.TryGetValue((package, profileName), out var list)) + return FluentResults.Result.Fail($"No profiles found for package {package.Name} with name {profileName}"); + + if (list.IsDefaultOrEmpty) + return FluentResults.Result.Fail($"{nameof(ApplyProfileSettings)}: No stored values for profile {profileName}."); + + var errList = new List(); + + foreach (var profileVal in list) + { + if (!_configs.TryGetValue((package, profileVal.ConfigName), out var val)) + continue; + + if (!val.TrySetValue(profileVal.Value)) + errList.Add(new Error($"Failed to apply value from profile named {profileName} to {val.InternalName}")); + // continue + } + + return FluentResults.Result.Ok().WithErrors(errList); + } + + public FluentResults.Result DisposePackageData(ContentPackage package) + { + // stop regular ops during deletion ops + using var lck = _disposeOpsLock.AcquireWriterLock().GetAwaiter().GetResult(); + _base.CheckDisposed(); + + if (package is null) + return FluentResults.Result.Fail($"{nameof(DisposePackageData)}: Package was null."); + + var errList = new List(); + + if (_packageConfigReverseLookup.Remove(package, out var cfgKeys)) + { + if (cfgKeys.Any()) + { + foreach (var key in cfgKeys) + { + try + { + _configs.Remove(key, out var cfg); + cfg?.Dispose(); + } + catch (Exception e) + { + errList.Add(new ExceptionalError(e)); + } + } + } + } + + if (_packageProfilesReverseLookup.Remove(package, out var profileKeys)) + { + if (profileKeys.Any()) + { + foreach (var key in profileKeys) + { + _configProfiles.Remove(key, out _); + } + } + } + + _packageNameMap.Remove(package.Name, out _); + + return FluentResults.Result.Ok().WithErrors(errList); + } + + public Result> GetConfigsForPackage(ContentPackage package) + { + using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); + _base.CheckDisposed(); + + if (!_packageConfigReverseLookup.TryGetValue(package, out var keys) || keys.IsEmpty) + return FluentResults.Result.Fail($"No configs found for package {package.Name}"); + + return _configs.Where(kvp => keys.Contains(kvp.Key)).ToFrozenDictionary(kvp => kvp.Key, kvp => kvp.Value); + } + + public Result Value)>>> GetProfilesForPackage(ContentPackage package) + { + using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); + _base.CheckDisposed(); + + if (!_packageProfilesReverseLookup.TryGetValue(package, out var keys) || keys.IsEmpty) + return FluentResults.Result.Fail($"No profiles found for package {package.Name}"); + + return _configProfiles.Where(kvp => keys.Contains(kvp.Key)).ToFrozenDictionary(kvp => kvp.Key, kvp => kvp.Value); + } + + public IReadOnlyDictionary<(ContentPackage Package, string Name), IConfigBase> GetAllConfigs() + { + using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); + _base.CheckDisposed(); + + return _configs.ToFrozenDictionary(kvp => kvp.Key, kvp => kvp.Value); + } + + public bool TryGetConfig(ContentPackage package, string name, out T config) where T : IConfigBase + { + using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); + _base.CheckDisposed(); + + config = default; + if (!_configs.TryGetValue((package, name), out var value)) + return false; + try + { + config = (T)value; + return true; + } + catch + { + return false; + } + } + + public async Task SaveAllConfigs() + { + using var lck = await _disposeOpsLock.AcquireReaderLock(); + _base.CheckDisposed(); + if (_configs.IsEmpty) + return FluentResults.Result.Ok(); + var toSave = _configs.Where(kvp => kvp.Value is not null).Select(kvp => kvp.Value) + .ToImmutableArray(); + var errList = ImmutableArray.CreateBuilder(); + foreach (var config in toSave) + { + var res = await SaveConfigInternal(config); + if (res.Errors.Any()) + errList.AddRange(res.Errors); + } + return FluentResults.Result.Ok().WithErrors(errList.MoveToImmutable()); + } + + public async Task SaveConfigsForPackage(ContentPackage package) + { + if (package is null) + return FluentResults.Result.Fail($"{nameof(SaveConfigsForPackage)}: Package was null."); + using var lck = await _disposeOpsLock.AcquireReaderLock(); + _base.CheckDisposed(); + if (!_packageConfigReverseLookup.TryGetValue(package, out var keys) || keys.IsEmpty) + return FluentResults.Result.Fail($"No configs found for package {package.Name}"); + ConcurrentQueue toSave = new(); + foreach (var key in keys) + { + if (_configs.TryGetValue(key, out var config)) + toSave.Enqueue(config); + } + if (toSave.IsEmpty) + return FluentResults.Result.Fail($"No configs found for package {package.Name}"); + var errList = ImmutableArray.CreateBuilder(); + while (toSave.TryDequeue(out var config)) + { + var res = await SaveConfigInternal(config); + if (res.Errors.Any()) + errList.AddRange(res.Errors); + } + return FluentResults.Result.Ok().WithErrors(errList.MoveToImmutable()); + } + + public async Task SaveConfig((ContentPackage Package, string ConfigName) config) + { + if (config.Package is null || config.ConfigName.IsNullOrWhiteSpace()) + return FluentResults.Result.Fail($"{nameof(SaveConfig)}: Config properties were null or empty."); + using var lck = await _disposeOpsLock.AcquireReaderLock(); + _base.CheckDisposed(); + if (!_configs.TryGetValue(config, out var instance)) + return FluentResults.Result.Fail($"{nameof(SaveConfig)}: No config found for package {config.Package.Name} and name {config.ConfigName}"); + return await SaveConfigInternal(instance); + } + + private void SaveConfigEvent(IConfigBase instance) + { + using var lck = _disposeOpsLock.AcquireWriterLock().GetAwaiter().GetResult(); + _base.CheckDisposed(); + SaveConfigInternal(instance).GetAwaiter().GetResult(); + } + + private async Task SaveConfigInternal(IConfigBase instance) + { + var localStorePath = Path.Combine("Config", SanitizedFileName($"{instance.OwnerPackage.Name}.xml)")); + // Locking and checks must be handled by the caller. + var val = instance.GetSerializableValue(); + var docRes = await _storageService.Value.LoadLocalXmlAsync(instance.OwnerPackage, localStorePath); + XDocument doc; + XElement cfgElement; + XElement valueElement; + + // structure is + /* + * + * <[instance.InternalName]> + * + * <--Contents Here-> + * + * + * + */ + + if (docRes.IsFailed || docRes.Value is null) + { + doc = new XDocument( + new XElement("Config", new XAttribute("ContentPackage", instance.OwnerPackage.Name), + cfgElement = new XElement(instance.InternalName, valueElement = new XElement("Value")))); + } + else + { + doc = docRes.Value; + var e1 = doc.GetChildElement("Config"); + if (e1 is null) + { + e1 = new XElement("Config"); + doc.Add(e1); + } + + cfgElement = e1.GetChildElement(instance.InternalName); + if (cfgElement is null) + { + cfgElement = new XElement(instance.InternalName); + e1.Add(cfgElement); + } + + valueElement = cfgElement.GetChildElement("Value"); + if (valueElement is null) + { + valueElement = new XElement("Value"); + cfgElement.Add(valueElement); + } + } + + valueElement.Remove(); // remove from cfg + + // get potential updated element + var updatedElement = val.Match(str => + { + valueElement.RemoveAll(); + valueElement.Value = str; + return valueElement; + }, element => + { + valueElement.RemoveAll(); + valueElement.Add(element); + return valueElement; + }); + + // (re) add updated element. + cfgElement.Add(updatedElement); + + return await _storageService.Value.SaveLocalXmlAsync(instance.OwnerPackage, localStorePath, doc); + } + + private static readonly Regex RemoveInvalidChars = new Regex($"[{Regex.Escape(new string(System.IO.Path.GetInvalidFileNameChars()))}]", + RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant); + + private string SanitizedFileName(string fileName, string replacement = "_") + { + return RemoveInvalidChars.Replace(fileName, replacement); + } + } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ContentPackageInfoLookup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ContentPackageInfoLookup.cs index 4a2f8e594..e8f906079 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ContentPackageInfoLookup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ContentPackageInfoLookup.cs @@ -326,7 +326,17 @@ public sealed class ContentPackageInfoLookup : IPackageInfoLookupService, IEvent .ToImmutableArray() ).GetAwaiter().GetResult(); } - + + public bool IsPackageEnabled(ContentPackage package) + { + if (package is null) + return false; + using (_packageSetsLock.AcquireReaderLock().GetAwaiter().GetResult()) + { + return _enabledPackages.Contains(package); + } + } + public async Task> Lookup(string packageName) { ((IService)this).CheckDisposed(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LocalizationService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LocalizationService.cs deleted file mode 100644 index e3a51d638..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LocalizationService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Barotrauma.LuaCs.Services; - -public interface LocalizationService -{ - -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs index aa0c74610..f8531acef 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs @@ -2,13 +2,216 @@ using MoonSharp.Interpreter; using MoonSharp.Interpreter.Interop; using System; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Globalization; +using System.Linq; using System.Reflection; +using System.Text; +using System.Threading; using System.Threading.Tasks; +using Barotrauma.LuaCs.Services.Safe; +using FluentResults; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using MoonSharp.Interpreter.Loaders; namespace Barotrauma.LuaCs.Services; -public class LuaScriptManagementService : ILuaScriptManagementService +public class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService { + public LuaScriptManagementService(ILuaScriptLoader loader, ILuaScriptServicesConfig luaScriptServicesConfig) + { + _luaScriptLoader = loader; + _luaScriptServicesConfig = luaScriptServicesConfig; + } + private readonly ILuaScriptLoader _luaScriptLoader; + private readonly ILuaScriptServicesConfig _luaScriptServicesConfig; + + public void Dispose() + { + _luaScriptLoader.Dispose(); + } + + public bool IsDisposed + { + get => throw new NotImplementedException(); + } + + public FluentResults.Result Reset() + { + throw new NotImplementedException(); + } + + public Result GetGlobalTableValue(string tableName) + { + throw new NotImplementedException(); + } + + public async Task LoadScriptResourcesAsync(ImmutableArray resourcesInfo) + { + throw new NotImplementedException(); + } + + public FluentResults.Result ExecuteLoadedScriptsForPackage(ContentPackage package) + { + throw new NotImplementedException(); + } + + public FluentResults.Result ExecuteLoadedScriptsForPackages(IEnumerable packages) + { + throw new NotImplementedException(); + } + + public FluentResults.Result ExecuteLoadedScripts() + { + throw new NotImplementedException(); + } + + public FluentResults.Result DisposePackageResources(ContentPackage package) + { + throw new NotImplementedException(); + } + + public FluentResults.Result UnloadActiveScripts() + { + throw new NotImplementedException(); + } + + public FluentResults.Result DisposeAllPackageResources() + { + throw new NotImplementedException(); + } + + public IUserDataDescriptor RegisterType(Type type) + { + throw new NotImplementedException(); + } + + public IUserDataDescriptor RegisterGenericType(Type type) + { + throw new NotImplementedException(); + } + + public IUserDataDescriptor GetTypeInfo(string typeName) + { + throw new NotImplementedException(); + } + + public IUserDataDescriptor GetGenericTypeInfo(string typeName, params string[] typeNameArgs) + { + throw new NotImplementedException(); + } + + public void UnregisterType(Type type) + { + throw new NotImplementedException(); + } + + public bool IsRegistered(Type type) + { + throw new NotImplementedException(); + } + + public bool IsTargetType(object obj, string typeName) + { + throw new NotImplementedException(); + } + + public string TypeOf(object obj) + { + throw new NotImplementedException(); + } + + public object CreateStatic(string typeName) + { + throw new NotImplementedException(); + } + + public object CreateEnumTable(string typeName) + { + throw new NotImplementedException(); + } + + public FieldInfo FindFieldRecursively(Type type, string fieldName) + { + throw new NotImplementedException(); + } + + public void MakeFieldAccessible(IUserDataDescriptor descriptor, string fieldName) + { + throw new NotImplementedException(); + } + + public MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null) + { + throw new NotImplementedException(); + } + + public void MakeMethodAccessible(IUserDataDescriptor descriptor, string methodName, string[] parameters = null) + { + throw new NotImplementedException(); + } + + public PropertyInfo FindPropertyRecursively(Type type, string propertyName) + { + throw new NotImplementedException(); + } + + public void MakePropertyAccessible(IUserDataDescriptor descriptor, string propertyName) + { + throw new NotImplementedException(); + } + + public void AddMethod(IUserDataDescriptor descriptor, string methodName, object function) + { + throw new NotImplementedException(); + } + + public void AddField(IUserDataDescriptor descriptor, string fieldName, DynValue value) + { + throw new NotImplementedException(); + } + + public void RemoveMember(IUserDataDescriptor descriptor, string memberName) + { + throw new NotImplementedException(); + } + + public bool HasMember(object obj, string memberName) + { + throw new NotImplementedException(); + } + + public DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor descriptor) + { + throw new NotImplementedException(); + } + + public DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType) + { + throw new NotImplementedException(); + } + + public Table GetObjectTable(object obj, string tableName) + { + throw new NotImplementedException(); + } + + public Table GetTable(string tableName) + { + throw new NotImplementedException(); + } + + public Table GetOrCreateObjectTable(object obj, string tableName) + { + throw new NotImplementedException(); + } + + public Table GetOrCreateTable(string tableName) + { + throw new NotImplementedException(); + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs index 97d0baed2..f4abb97d7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs @@ -22,7 +22,7 @@ internal partial class NetworkingService : INetworkingService ReceiveIds } - private Dictionary netVars = new Dictionary(); + private Dictionary netVars = new Dictionary(); private Dictionary netReceives = new Dictionary(); private Dictionary packetToId = new Dictionary(); private Dictionary idToPacket = new Dictionary(); @@ -46,7 +46,7 @@ internal partial class NetworkingService : INetworkingService #endif } - public void RegisterNetVar(INetVar netVar) + public void RegisterNetVar(INetworkSyncEntity netVar) { netVars[netVar.InstanceId] = netVar; @@ -58,7 +58,7 @@ internal partial class NetworkingService : INetworkingService }; } - public void SendNetVar(INetVar netVar) + public void SendNetVar(INetworkSyncEntity netVar) { if (netVars.ContainsKey(netVar.InstanceId)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index 23f646bb6..318da7814 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -27,7 +27,6 @@ public partial class PackageManagementService : IPackageManagementService private readonly IProcessorService, IAssembliesResourcesInfo> _assemblyInfoConverter; private readonly IProcessorService, IConfigsResourcesInfo> _configsInfoConverter; private readonly IProcessorService, IConfigProfilesResourcesInfo> _configProfilesConverter; - private readonly IProcessorService, ILocalizationsResourcesInfo> _localizationsConverter; private readonly IProcessorService, ILuaScriptsResourcesInfo> _luaScriptsConverter; @@ -56,9 +55,7 @@ public partial class PackageManagementService : IPackageManagementService } return FluentResults.Result.Ok(); } - - public ImmutableArray Localizations => _modInfos.IsEmpty ? ImmutableArray.Empty - : _modInfos.SelectMany(kvp => kvp.Value.Localizations).ToImmutableArray(); + public ImmutableArray Configs => _modInfos.IsEmpty ? ImmutableArray.Empty : _modInfos.SelectMany(kvp => kvp.Value.Configs).ToImmutableArray(); public ImmutableArray ConfigProfiles => _modInfos.IsEmpty ? ImmutableArray.Empty @@ -103,6 +100,25 @@ public partial class PackageManagementService : IPackageManagementService : _modInfos.Select(kvp => kvp.Key).ToImmutableArray(); } + public bool IsPackageLoaded(ContentPackage package) + { + return package is not null && _modInfos.ContainsKey(package); + } + + public ImmutableArray FilterUnloadableResources(IReadOnlyList resources, bool enabledPackagesOnly = false) + where T : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo + { + return resources + .Where(r => r is not null) + .Where(r => (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0) + .Where(r => (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0) + .Where(r => !r.Dependencies.Any() || r.Dependencies.All(d => + d.Dependency.GetPackage() is {} p // cp is valid + && _modInfos.ContainsKey(p) // cp is parsed + && (!enabledPackagesOnly || _packageInfoLookupService.IsPackageEnabled(p)))) // cp is enabled + .ToImmutableArray(); + } + public void DisposePackageInfos(ContentPackage package) { _modInfos.TryRemove(package, out _); @@ -217,26 +233,6 @@ public partial class PackageManagementService : IPackageManagementService $"{nameof(GetConfigProfilesInfos)}: ContentPackage {package.Name} is not registered."); } - public Result GetLocalizationsInfos(ContentPackage package, bool onlySupportedResources = true) - { - ((IService)this).CheckDisposed(); - if (package is null) - return FluentResults.Result.Fail($"{nameof(GetLocalizationsInfos)}: ContentPackage is null."); - - if (_modInfos.TryGetValue(package, out var result)) - { - return FluentResults.Result.Ok(_localizationsConverter.Process(onlySupportedResources? - result.Localizations.Where(r => - (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 - && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() - : result.Localizations - )); - } - - return FluentResults.Result.Fail( - $"{nameof(GetLocalizationsInfos)}: ContentPackage {package.Name} is not registered."); - } - public Result GetLuaScriptsInfos(ContentPackage package, bool onlySupportedResources = true) { ((IService)this).CheckDisposed(); @@ -320,27 +316,6 @@ public partial class PackageManagementService : IPackageManagementService return FluentResults.Result.Ok(_configProfilesConverter.Process(builder.MoveToImmutable())); } - public Result GetLocalizationsInfos(IReadOnlyList packages, bool onlySupportedResources = true) - { - ((IService)this).CheckDisposed(); - if (packages is null || packages.Count == 0) - return FluentResults.Result.Fail($"{nameof(GetLocalizationsInfos)}: ContentPackage list is null or empty."); - var builder = ImmutableArray.CreateBuilder(); - foreach (var package in packages) - { - if (_modInfos.TryGetValue(package, out var result) && result.Localizations is { IsEmpty: false }) - { - builder.AddRange(onlySupportedResources? - result.Localizations.Where(r => - (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 - && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() - : result.Localizations); - } - } - - return FluentResults.Result.Ok(_localizationsConverter.Process(builder.MoveToImmutable())); - } - public Result GetLuaScriptsInfos(IReadOnlyList packages, bool onlySupportedResources = true) { ((IService)this).CheckDisposed(); @@ -377,11 +352,6 @@ public partial class PackageManagementService : IPackageManagementService return await Task.Run(() => GetConfigProfilesInfos(packages, onlySupportedResources)); } - public async Task> GetLocalizationsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true) - { - return await Task.Run(() => GetLocalizationsInfos(packages, onlySupportedResources)); - } - public async Task> GetLuaScriptsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true) { return await Task.Run(() => GetLuaScriptsInfos(packages, onlySupportedResources)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigIOService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigIOService.cs new file mode 100644 index 000000000..f07a01e8c --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigIOService.cs @@ -0,0 +1,278 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Xml.Linq; +using Barotrauma.LuaCs.Data; +using FarseerPhysics.Common; +using FluentResults; +using OneOf; + +namespace Barotrauma.LuaCs.Services.Processing; + +public class ConfigIOService : IConfigIOService +{ + private readonly IStorageService _storageService; + private readonly IConfigServiceConfig _configServiceConfig; + + public ConfigIOService(IStorageService storageService, IConfigServiceConfig configServiceConfig) + { + this._storageService = storageService; + storageService.UseCaching = true; + _configServiceConfig = configServiceConfig; + } + + public void Dispose() + { + // stateless service + return; + } + + // stateless service + public bool IsDisposed => false; + public FluentResults.Result Reset() + { + _storageService.PurgeCache(); + return FluentResults.Result.Ok(); + } + + public async Task>> TryParseResourceAsync(IConfigResourceInfo src) + { + if (src?.OwnerPackage is null || src.FilePaths.IsDefaultOrEmpty) + return FluentResults.Result.Fail($"{nameof(TryParseResourceAsync)}: Config resource and/or components were null."); + + try + { + var infos = await _storageService.LoadPackageXmlFilesAsync(src.OwnerPackage, src.FilePaths); + if (infos.IsDefaultOrEmpty) + return FluentResults.Result.Fail($"{nameof(TryParseResourceAsync)}: No resources found."); + + var errList = new List(); + + var resList = infos.Select(info => + { + if (info.Item2.Errors.Any()) + errList.AddRange(info.Item2.Errors); + if (info.Item2.IsFailed || info.Item2.Value is not { } configXDoc) + { + errList.Add(new Error($"Unable to parse file: {info.Item1}")); + return default; + } + + return (info.Item1, configXDoc); + }) + .Where(doc => !doc.Item1.IsNullOrWhiteSpace() && doc.configXDoc != null) + .SelectMany(doc => doc.configXDoc.Root.GetChildElements("Configuration")) + .SelectMany(cfgContainer => cfgContainer.GetChildElements("Configs")) + .SelectMany(cfgContainer => cfgContainer.GetChildElements("Config")) + .Select(async cfgElement => + { + try + { + OneOf.OneOf defaultValue = cfgElement.GetChildElement("Value"); + if (defaultValue.AsT1 is null) + defaultValue = cfgElement.GetAttributeString("Value", string.Empty); + + var internalName = cfgElement.GetAttributeString("Name", string.Empty); + if (internalName.IsNullOrWhiteSpace()) + return null; + + return new ConfigInfo() + { + DataType = Type.GetType(cfgElement.GetAttributeString("Type", "string")), + OwnerPackage = src.OwnerPackage, + DefaultValue = defaultValue, + Value = await LoadConfigDataFromLocal(src.OwnerPackage, internalName) is { IsSuccess: true } res + ? res.Value : defaultValue, + EditableStates = cfgElement.GetAttributeBool("ReadOnly", false) + ? RunState.Unloaded // read-only + : RunState.Running, // editable at runtime + InternalName = internalName, + NetSync = Enum.Parse( + cfgElement.GetAttributeString("NetSync", nameof(NetSync.None))), +#if CLIENT + DisplayName = cfgElement.GetAttributeString("DisplayName", null), + Description = cfgElement.GetAttributeString("Description", null), + DisplayCategory = cfgElement.GetAttributeString("Category", null), + ShowInMenus = cfgElement.GetAttributeBool("ShowInMenus", true), + Tooltip = cfgElement.GetAttributeString("Tooltip", null), + ImageIconPath = cfgElement.GetAttributeString("Image", null) +#endif + }; + } + catch (Exception e) + { + errList.Add(new Error($"Failed to parse config var for package {src.OwnerPackage}")); + errList.Add(new ExceptionalError(e)); + return null; + } + }) + .Where(task => task is not null) + .ToImmutableArray(); + + var result = (await Task.WhenAll(resList)).ToImmutableArray(); + + var ret = FluentResults.Result.Ok((IReadOnlyList)result); + if (errList.Any()) + ret.Errors.AddRange(errList); + return ret; + } + catch(Exception e) + { + return FluentResults.Result.Fail($"Failed to parse config resource for package {src.OwnerPackage}"); + } + } + + public async Task>>> TryParseResourcesAsync(IEnumerable sources) + { + var results = new ConcurrentQueue>>(); + + var src = sources.ToImmutableArray(); + if (!src.Any()) + return ImmutableArray>>.Empty; + + await src.ParallelForEachAsync(async cfg => + { + var res = await TryParseResourceAsync(cfg); + results.Enqueue(res); + }, 2); // we only need 2 parallels to buffer against disk loading. + + return results.ToImmutableArray(); + } + + public async Task>> TryParseResourceAsync(IConfigProfileResourceInfo src) + { + if (src?.OwnerPackage is null || src.FilePaths.IsDefaultOrEmpty) + return FluentResults.Result.Fail($"{nameof(TryParseResourceAsync)}: Profile resource and/or components were null."); + + try + { + var infos = await _storageService.LoadPackageXmlFilesAsync(src.OwnerPackage, src.FilePaths); + if (infos.IsDefaultOrEmpty) + return FluentResults.Result.Fail($"{nameof(TryParseResourceAsync)}: No resources found."); + + var errList = new List(); + + var resList = infos.Select(info => + { + if (info.Item2.Errors.Any()) + errList.AddRange(info.Item2.Errors); + if (info.Item2.IsFailed || info.Item2.Value is not { } configXDoc) + { + errList.Add(new Error($"Unable to parse file: {info.Item1}")); + return null; + } + + return configXDoc; + }) + .Where(doc => doc is not null) + .SelectMany(doc => doc.Root.GetChildElements("Configuration")) + .SelectMany(cfgContainer => cfgContainer.GetChildElements("Profiles")) + .SelectMany(cfgContainer => cfgContainer.GetChildElements("Profile")) + .Select(cfgElement => + { + try + { + return new ConfigProfileInfo() + { + OwnerPackage = src.OwnerPackage, + InternalName = cfgElement.GetAttributeString("Name", null), + ProfileValues = cfgElement.GetChildElements("ConfigValue") + .Select Value)>(element => + { + if (element.GetAttributeString("Name", null) is not { } name) + return default; + if (element.GetAttributeString("Value", null) is { } value) + return (name, value); + if (element.GetChildElement("Value") is { } xValue) + return (name, xValue); + return default; + }) + .Where(val => val.ConfigName is not null && val.Value.Match( + s => !s.IsNullOrWhiteSpace(), + element => element is not null)) + .ToList() + }; + } + catch (Exception e) + { + errList.Add(new Error($"Failed to parse profile var for package {src.OwnerPackage}")); + errList.Add(new ExceptionalError(e)); + return null; + } + }) + .Where(cfgInfo => cfgInfo != null && !cfgInfo.InternalName.IsNullOrWhiteSpace()) + .ToImmutableArray(); + + var ret = FluentResults.Result.Ok((IReadOnlyList)resList); + if (errList.Any()) + ret.Errors.AddRange(errList); + return ret; + } + catch(Exception e) + { + return FluentResults.Result.Fail($"Failed to parse profile resource for package {src.OwnerPackage}"); + } + } + + public async Task>>> TryParseResourcesAsync(IEnumerable sources) + { + var results = new ConcurrentQueue>>(); + + var src = sources.ToImmutableArray(); + if (!src.Any()) + return ImmutableArray>>.Empty; + + await src.ParallelForEachAsync(async cfg => + { + var res = await TryParseResourceAsync(cfg); + results.Enqueue(res); + }, 2); // we only need 2 parallels to buffer against disk loading. + + return results.ToImmutableArray(); + } + + private static readonly Regex RemoveInvalidChars = new Regex($"[{Regex.Escape(new string(System.IO.Path.GetInvalidFileNameChars()))}]", + RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant); + + private string SanitizedFileName(string fileName, string replacement = "_") + { + return RemoveInvalidChars.Replace(fileName, replacement); + } + + public async Task SaveConfigDataLocal(ContentPackage package, string configName, XElement serializedValue) + { + if (package is null || package.Name.IsNullOrWhiteSpace() || configName.IsNullOrWhiteSpace() || serializedValue is null) + return FluentResults.Result.Fail($"{nameof(SaveConfigDataLocal)}: Argument(s) were null"); + + var res = await LoadPackageConfigDocInternal(package); + + throw new NotImplementedException(); //TODO: Complete once the locally saved data structure is finalized. + } + + public async Task>> LoadConfigDataFromLocal(ContentPackage package, string configName) + { + if (package is null || package.Name.IsNullOrWhiteSpace() || configName.IsNullOrWhiteSpace()) + return FluentResults.Result.Fail($"{nameof(LoadConfigDataFromLocal)}: Argument(s) were null"); + + var filePath = _configServiceConfig.LocalConfigPathPartial.Replace( + _configServiceConfig.FileNamePattern, + $"{SanitizedFileName(package.Name)}.xml"); + + var res = await _storageService.LoadLocalXmlAsync(package, filePath); + + throw new NotImplementedException(); //TODO: Complete once the locally saved data structure is finalized. + } + + private async Task> LoadPackageConfigDocInternal(ContentPackage package) + { + var filePath = _configServiceConfig.LocalConfigPathPartial.Replace( + _configServiceConfig.FileNamePattern, + $"{SanitizedFileName(package.Name)}.xml"); + + throw new NotImplementedException(); //TODO: Complete once the locally saved data structure is finalized. + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConfigIOService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConfigIOService.cs new file mode 100644 index 000000000..7361b6575 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConfigIOService.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Xml.Linq; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Services.Processing; + +namespace Barotrauma.LuaCs.Services.Processing; + +public interface IConfigIOService : IReusableService, + IConverterServiceAsync>, + IConverterServiceAsync> +{ + Task SaveConfigDataLocal(ContentPackage package, string configName, XElement serializedValue); + Task>> LoadConfigDataFromLocal(ContentPackage package, string configName); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs index b4f39989a..afb5c3ec1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs @@ -69,12 +69,8 @@ public partial class ModConfigService : IConverterServiceAsync.Empty, ConfigProfiles = ImmutableArray.Empty, - Localizations = ImmutableArray.Empty, Package = src, PackageName = src.Name -#if CLIENT - ,Styles = ImmutableArray.Empty -#endif }; } catch (Exception e) @@ -84,40 +80,6 @@ public partial class ModConfigService : IConverterServiceAsync> GetModConfigInfoAsync(ContentPackage package, XElement root); - - private ImmutableArray GetLocalizations(ContentPackage src, IEnumerable elements) - { - var builder = ImmutableArray.CreateBuilder(); - - if (GetXmlFilesList(src, elements, "Localizations") - is not { IsSuccess: true, Value: { } xmlFiles }) - return ImmutableArray.Empty; - - foreach (var file in xmlFiles) - { - // get dependencies - var deps = GetElementsDependenciesData(file.Item1, src); - // get platform, culture and target architecture - var info = GetElementsAttributesData(file.Item1, file.Item2.First()); - - builder.Add(new LocalizationResourceInfo() - { - Dependencies = deps, - Optional = info.IsOptional, - FilePaths = file.Item2, - InternalName = info.Name, - LoadPriority = info.LoadPriority, - OwnerPackage = src, - SupportedCultures = info.SupportedCultures, - SupportedPlatforms = info.SupportedPlatforms, - SupportedTargets = info.SupportedTargets - }); - } - - return builder.Count > 0 - ? builder.ToImmutable() - : ImmutableArray.Empty; - } private ImmutableArray GetAssemblies(ContentPackage src, IEnumerable elements) { @@ -265,7 +227,7 @@ public partial class ModConfigService : IConverterServiceAsync(); + if (_storageService.FindFilesInPackage(src, "CSharp/Shared", "*.cs", true) + is { IsSuccess: true, Value: { IsDefaultOrEmpty: false } files }) + { + sharedCsBuilder.AddRange(files); + } + + var filesCssShared = sharedCsBuilder.MoveToImmutable(); + var sharedFound = !filesCssShared.IsDefaultOrEmpty; // source files legacy: server if (_storageService.FindFilesInPackage(src, "CSharp/Server", "*.cs", true) @@ -550,7 +519,7 @@ public partial class ModConfigService : IConverterServiceAsync.Empty, - FilePaths = sharedFound ? filesCssServer.Concat(filesCssShared).ToImmutableArray() : filesCssServer, + FilePaths = sharedFound ? filesCssServer.Concat(filesCssShared).ToImmutableArray() : filesCssServer, FriendlyName = "CssServer", InternalName = "CssServer", IsScript = true, @@ -594,7 +563,7 @@ public partial class ModConfigService : IConverterServiceAsync.Empty, FilePaths = fileAll.Where(path => !path.Contains("Autorun")).ToImmutableArray(), @@ -607,7 +576,7 @@ public partial class ModConfigService : IConverterServiceAsync.Empty, FilePaths = fileAll.Where(path => path.Contains("Autorun")).ToImmutableArray(), diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ResourceInfoProcessors.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ResourceInfoProcessors.cs index c1151a649..d6fb94b32 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ResourceInfoProcessors.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ResourceInfoProcessors.cs @@ -9,7 +9,6 @@ public partial class ResourceInfoArrayPacker : IProcessorService, IAssembliesResourcesInfo>, IProcessorService, IConfigsResourcesInfo>, IProcessorService, IConfigProfilesResourcesInfo>, - IProcessorService, ILocalizationsResourcesInfo>, IProcessorService, ILuaScriptsResourcesInfo> { private bool _isDisposed; @@ -28,11 +27,6 @@ public partial class ResourceInfoArrayPacker : return new ConfigProfilesResourcesInfo(src.ToImmutableArray()); } - public ILocalizationsResourcesInfo Process(IReadOnlyList src) - { - return new LocalizationResourcesInfo(src.ToImmutableArray()); - } - public ILuaScriptsResourcesInfo Process(IReadOnlyList src) { return new LuaScriptsResourcesInfo(src.ToImmutableArray()); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs index 781827ced..182f4fc4e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs @@ -1,8 +1,32 @@ -using Barotrauma.LuaCs.Configuration; +using System.Collections.Generic; +using Barotrauma.LuaCs.Configuration; +using Microsoft.Xna.Framework; namespace Barotrauma.LuaCs.Services.Safe; public interface ILuaConfigService : ILuaService { - + // get values + bool TryGetConfigBool(string packageName, string configName, out bool value); + bool TryGetConfigInt(string packageName, string configName, out int value); + bool TryGetConfigFloat(string packageName, string configName, out float value); + bool TryGetConfigNumber(string packageName, string configName, out double value); + bool TryGetConfigString(string packageName, string configName, out string value); + bool TryGetConfigVector2(string packageName, string configName, out Vector2 value); + bool TryGetConfigVector3(string packageName, string configName, out Vector3 value); + bool TryGetConfigColor(string packageName, string configName, out Color value); + bool TryGetConfigList(string packageName, string configName, out IReadOnlyList value); + // set values + void SetConfigBool(string packageName, string configName, bool value); + void SetConfigInt(string packageName, string configName, int value); + void SetConfigFloat(string packageName, string configName, float value); + void SetConfigNumber(string packageName, string configName, double value); + void SetConfigString(string packageName, string configName, string value); + void SetConfigVector2(string packageName, string configName, Vector2 value); + void SetConfigVector3(string packageName, string configName, Vector3 value); + void SetConfigColor(string packageName, string configName, Color value); + void SetConfigList(string packageName, string configName, string value); + // profiles + bool TryApplyProfileSettings(string packageName, string profileName); + } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaDataService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaDataService.cs index 04f4893bc..88cc45bdf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaDataService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaDataService.cs @@ -25,6 +25,8 @@ public interface ILuaDataService : ILuaService /// /// Returns stored table data for the given object or creates a new table if one doesn't exist. /// + /// Note: tables are stored using weak references and will be automatically deleted when the object is + /// garbage collected. /// /// /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs new file mode 100644 index 000000000..4f6475d06 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs @@ -0,0 +1,8 @@ +using MoonSharp.Interpreter.Loaders; + +namespace Barotrauma.LuaCs.Services.Safe; + +public interface ILuaScriptLoader : IService, IScriptLoader +{ + void ClearCaches(); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ISafeStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ISafeStorageService.cs new file mode 100644 index 000000000..bad6520eb --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ISafeStorageService.cs @@ -0,0 +1,56 @@ +using System.Collections.Immutable; + +namespace Barotrauma.LuaCs.Services.Safe; + +public interface ISafeStorageService : IStorageService +{ + /// + /// Checks the given file path to see if it can be read. This includes any permissions, whitelists and OS checks. + /// + /// The absolute path to the file. + /// Whether to only check for read permissions only, or full RWM if false. + /// Whether to only check if the file is safe to access, without checking accessibility at the OS level. + /// Whether the file is accessible. + bool IsFileAccessible(string path, bool readOnly, bool checkWhitelistOnly = true); + + /// + /// Adds the given path to the specified whitelists. + /// + /// Either the fully-qualified or local reference path to the given file. + /// + void AddFileToWhitelist(string path, bool readOnly = true); + + /// + /// Removes the given path from all whitelists (Read|Write). + /// + /// + void RemoveFileFromAllWhitelists(string path); + + /// + /// Sets the whitelist filtering for read-only file permissions for the instance. + /// + /// List of absolute file paths allowed. + FluentResults.Result SetReadOnlyWhitelist(ImmutableArray filePaths); + + /// + /// Sets the whitelist filtering for read & write file permissions for the instance. + /// + /// List of absolute file paths allowed. + FluentResults.Result SetReadWriteWhitelist(ImmutableArray filePaths); + + /// + /// Deletes all paths from all white lists. + /// + void ClearAllWhitelists(); + + /// + /// Whether the service instance is in file read-only mode. + /// + bool IsReadOnlyMode { get; } + + /// + /// Sets the service into file read-only mode. Cannot be undone. + /// + /// + bool EnableReadOnlyMode(); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs new file mode 100644 index 000000000..ab1e19553 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Loaders; +using System.Linq; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Services.Safe; + +namespace Barotrauma.LuaCs.Services.Safe +{ + public class LuaScriptLoader : ScriptLoaderBase, ILuaScriptLoader + { + public LuaScriptLoader(IStorageService storageService, Lazy loggerService, ILuaScriptServicesConfig luaScriptServicesConfig) + { + this._storageService = storageService; + this._loggerService = loggerService; + this._luaScriptServicesConfig = luaScriptServicesConfig; + _storageService.UseCaching = _luaScriptServicesConfig.UseCaching; + if (_luaScriptServicesConfig.SafeLuaIOEnabled) + { + //_storageService.EnableWhitelistOnly(); + } + } + + private readonly IStorageService _storageService; + private readonly Lazy _loggerService; + private readonly ILuaScriptServicesConfig _luaScriptServicesConfig; + + public override object LoadFile(string file, Table globalContext) + { + ((IService)this).CheckDisposed(); + + if (!CanReadFromPath(file)) + { + LogErrors($"File access to \"{file}\" is not allowed."); + return null; + } + + if (_storageService.TryLoadText(file) is not { IsSuccess: true, Value: not null } script) + { + LogErrors($"Failed to load file \"{file}\"."); + return null; + } + + if (script.Value.IsNullOrWhiteSpace()) + { + LogErrors($"The file \"{file}\" was empty."); + return null; + } + + return script.Value; + } + + public void ClearCaches() + { + ((IService)this).CheckDisposed(); + _storageService?.PurgeCache(); + } + + public override bool ScriptFileExists(string file) + { + ((IService)this).CheckDisposed(); + + if (!CanReadFromPath(file)) + { + LogErrors($"File access to \"{file}\" is not allowed."); + return false; + } + + var result = _storageService.FileExists(file); + + if (result is { IsFailed: true }) + { + LogErrors($"Unable to find and load file \"{file}\"."); + return false; + } + + return result.IsSuccess; + } + + private bool CanReadFromPath(string file) + { + throw new NotImplementedException(); + } + + private bool CanWriteToPath(string file) + { + throw new NotImplementedException(); + } + + private void LogErrors(string message, FluentResults.Result result = null) + { + _loggerService.Value.LogError($"{nameof(LuaScriptLoader)}: {message}"); + + if (result is null || result.Errors.Count <= 0) + return; + + foreach (var error in result.Errors) + { + _loggerService.Value.LogError($"{nameof(LuaScriptLoader)}: Error: {error.Message}."); + } + } + + public void Dispose() + { + if (IsDisposed) + return; + IsDisposed = true; + + _storageService?.Dispose(); + _loggerService?.Value.Dispose(); + } + + public bool IsDisposed { get; private set; } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs new file mode 100644 index 000000000..328705184 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using Barotrauma.IO; +using Barotrauma.LuaCs.Data; +using FarseerPhysics.Common; +using FluentResults; + +namespace Barotrauma.LuaCs.Services.Safe; + +public class SafeStorageService : StorageService, ISafeStorageService +{ + private ConcurrentDictionary _fileListRead = new (), _fileListReadWrite = new(); + + public SafeStorageService(IStorageServiceConfig configData) : base(configData) + { + + } + + private string GetFullPath(string path) => System.IO.Path.GetFullPath(path).CleanUpPathCrossPlatform(); + + public bool IsFileAccessible(string path, bool readOnly, bool checkWhitelistOnly = true) + { + ((IService)this).CheckDisposed(); + + try + { + path = GetFullPath(path); + if (!readOnly && IsReadOnlyMode) + return false; + if (readOnly) + { + if (!_fileListRead.ContainsKey(path)) + return false; + } + else + { + if (!_fileListReadWrite.ContainsKey(path)) + return false; + } + if (checkWhitelistOnly) + return true; + + using var fs = System.IO.File.Open( + path, FileMode.Open, readOnly ? FileAccess.Read : FileAccess.ReadWrite, FileShare.ReadWrite); + + return true; + } + catch + { + return false; + } + } + + public void AddFileToWhitelist(string path, bool readOnly = true) + { + ((IService)this).CheckDisposed(); + try + { + path = GetFullPath(path); + _fileListRead.AddOrUpdate(path, s => 0, (s, b) => 0); + if (!readOnly && !IsReadOnlyMode) + _fileListRead.AddOrUpdate(path, s => 0, (s, b) => 0); + } + catch + { + return; + } + } + + public void RemoveFileFromAllWhitelists(string path) + { + ((IService)this).CheckDisposed(); + try + { + path = GetFullPath(path); + _fileListRead.TryRemove(path, out _); + _fileListReadWrite.TryRemove(path, out _); + } + catch + { + return; + } + } + + public FluentResults.Result SetReadOnlyWhitelist(ImmutableArray filePaths) + { + ((IService)this).CheckDisposed(); + if (filePaths.IsDefaultOrEmpty) + return FluentResults.Result.Fail($"{nameof(SetReadOnlyWhitelist)}: FilePaths cannot be empty."); + var res = new FluentResults.Result(); + foreach (var path in filePaths) + { + // TODO: Cleanup path and add it. + } + + throw new NotImplementedException(); + } + + public FluentResults.Result SetReadWriteWhitelist(ImmutableArray filePaths) + { + ((IService)this).CheckDisposed(); + throw new System.NotImplementedException(); + } + + public void ClearAllWhitelists() + { + throw new System.NotImplementedException(); + } + + private int _isReadOnlyMode = 0; + public bool IsReadOnlyMode => ModUtils.Threading.GetBool(ref _isReadOnlyMode); + + public bool EnableReadOnlyMode() + { + ModUtils.Threading.SetBool(ref _isReadOnlyMode, true); + return ModUtils.Threading.GetBool(ref _isReadOnlyMode); + } + + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs index bc7bd0bad..54730328f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs @@ -1,117 +1,151 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; +using System.Collections.ObjectModel; using System.IO; +using System.Linq; using System.Reflection; using System.Security; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; -using Barotrauma.LuaCs.Configuration; -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs.Data; using Barotrauma.Steam; using FluentResults; using FluentResults.LuaCs; -using Microsoft.CodeAnalysis; -using OneOf.Types; using Error = FluentResults.Error; -using File = Barotrauma.IO.File; using Path = Barotrauma.IO.Path; -using Success = OneOf.Types.Success; namespace Barotrauma.LuaCs.Services; public class StorageService : IStorageService { - public StorageService(Lazy configService) + public StorageService(IStorageServiceConfig configData) { - _configService = configService; + _configData = configData; } - private readonly Lazy _configService; - private IConfigEntry _kLocalStoragePath = null; - private IConfigEntry _kLocalFilePathRules = null; - private const string _packagePathKeyword = ""; - private readonly string _runLocation = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location.CleanUpPath()); - - // TODO: Rewrite the config to get info from .ctor. - private IConfigEntry LocalStoragePath => _kLocalStoragePath ??= GetOrCreateConfig(nameof(LocalStoragePath), "/Data/Mods"); - private IConfigEntry LocalFilePathRule => _kLocalFilePathRules ??= GetOrCreateConfig(nameof(LocalFilePathRule), _packagePathKeyword); - private IConfigEntry GetOrCreateConfig(string name, string defaultValue) - { - var c = _configService.Value - .GetConfig>(ModUtils.Definitions.LuaCsForBarotrauma, name); - if (c is not null) - return c; - - var c1 = _configService.Value.AddConfigEntry( - ModUtils.Definitions.LuaCsForBarotrauma, - name, defaultValue, NetSync.None, valueChangePredicate: (value) => false); - if (c1.IsSuccess) - return c1.Value; - - throw new KeyNotFoundException("Cannot find storage value for key: " + name); - - } - public bool IsDisposed { get; private set; } + private readonly ConcurrentDictionary> _fsCache = new(); + protected readonly IStorageServiceConfig _configData; + + public bool IsDisposed => ModUtils.Threading.GetBool(ref _isDisposed); + private int _isDisposed = 0; public void Dispose() { - if (IsDisposed) - return; - IsDisposed = true; - _kLocalStoragePath = null; - _kLocalFilePathRules = null; + ModUtils.Threading.SetBool(ref _isDisposed, true); } - public FluentResults.Result LoadLocalXml(ContentPackage package, string localFilePath) => + public void PurgeCache() + { + ((IService)this).CheckDisposed(); + _fsCache.Clear(); + } + + public void PurgeFileFromCache(string absolutePath) + { + ((IService)this).CheckDisposed(); + + if (absolutePath.IsNullOrWhiteSpace()) + return; + + try + { + //sanitation pass + absolutePath = System.IO.Path.GetFullPath(absolutePath).CleanUpPath(); + _fsCache.Remove(absolutePath, out _); + } + catch + { + // ignored + return; + } + } + + public void PurgeFilesFromCache(params string[] absolutePaths) + { + ((IService)this).CheckDisposed(); + + if (absolutePaths.Length < 1) + return; + + foreach (var path in absolutePaths) + { + try + { + if (path.IsNullOrWhiteSpace()) + continue; + + //sanitation pass + var path2 = System.IO.Path.GetFullPath(path).CleanUpPath(); + _fsCache.Remove(path2, out _); + } + catch + { + // ignored + continue; + } + } + } + + private int _useCaching; + public bool UseCaching + { + get => ModUtils.Threading.GetBool(ref _useCaching); + set => ModUtils.Threading.SetBool(ref _useCaching, value); + } + + public virtual FluentResults.Result LoadLocalXml(ContentPackage package, string localFilePath) => GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? TryLoadXml(r.Value) : r.ToResult(); - public FluentResults.Result LoadLocalBinary(ContentPackage package, string localFilePath) => + public virtual FluentResults.Result LoadLocalBinary(ContentPackage package, string localFilePath) => GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? TryLoadBinary(r.Value) : r.ToResult(); - public FluentResults.Result LoadLocalText(ContentPackage package, string localFilePath) => + public virtual FluentResults.Result LoadLocalText(ContentPackage package, string localFilePath) => GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? TryLoadText(r.Value) : r.ToResult(); - public FluentResults.Result SaveLocalXml(ContentPackage package, string localFilePath, XDocument document) => + public virtual FluentResults.Result SaveLocalXml(ContentPackage package, string localFilePath, XDocument document) => GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? TrySaveXml(r.Value, document) : r.ToResult(); - public FluentResults.Result SaveLocalBinary(ContentPackage package, string localFilePath, in byte[] bytes) => + public virtual FluentResults.Result SaveLocalBinary(ContentPackage package, string localFilePath, in byte[] bytes) => GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? TrySaveBinary(r.Value, bytes) : r.ToResult(); - public FluentResults.Result SaveLocalText(ContentPackage package, string localFilePath, in string text) => + public virtual FluentResults.Result SaveLocalText(ContentPackage package, string localFilePath, in string text) => GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? TrySaveText(r.Value, text) : r.ToResult(); - public async Task> LoadLocalXmlAsync(ContentPackage package, string localFilePath) => + public virtual async Task> LoadLocalXmlAsync(ContentPackage package, string localFilePath) => GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? await TryLoadXmlAsync(r.Value) : r.ToResult(); - public async Task> LoadLocalBinaryAsync(ContentPackage package, string localFilePath) => + public virtual async Task> LoadLocalBinaryAsync(ContentPackage package, string localFilePath) => GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? await TryLoadBinaryAsync(r.Value) : r.ToResult(); - public async Task> LoadLocalTextAsync(ContentPackage package, string localFilePath) => + public virtual async Task> LoadLocalTextAsync(ContentPackage package, string localFilePath) => GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? await TryLoadTextAsync(r.Value) : r.ToResult(); - public async Task SaveLocalXmlAsync(ContentPackage package, string localFilePath, XDocument document) => + public virtual async Task SaveLocalXmlAsync(ContentPackage package, string localFilePath, XDocument document) => GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? await TrySaveXmlAsync(r.Value, document) : r.ToResult(); - public async Task SaveLocalBinaryAsync(ContentPackage package, string localFilePath, byte[] bytes) => + public virtual async Task SaveLocalBinaryAsync(ContentPackage package, string localFilePath, byte[] bytes) => GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? await TrySaveBinaryAsync(r.Value, bytes) : r.ToResult(); - public async Task SaveLocalTextAsync(ContentPackage package, string localFilePath, string text) => + public virtual async Task SaveLocalTextAsync(ContentPackage package, string localFilePath, string text) => GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? await TrySaveTextAsync(r.Value, text) : r.ToResult(); - public FluentResults.Result LoadPackageXml(ContentPackage package, string localFilePath) => + public virtual FluentResults.Result LoadPackageXml(ContentPackage package, string localFilePath) => GetAbsFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? TryLoadXml(r.Value) : r.ToResult(); - public FluentResults.Result LoadPackageBinary(ContentPackage package, string localFilePath) => + public virtual FluentResults.Result LoadPackageBinary(ContentPackage package, string localFilePath) => GetAbsFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? TryLoadBinary(r.Value) : r.ToResult(); - public FluentResults.Result LoadPackageText(ContentPackage package, string localFilePath) => + public virtual FluentResults.Result LoadPackageText(ContentPackage package, string localFilePath) => GetAbsFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? TryLoadText(r.Value) : r.ToResult(); - public ImmutableArray<(string, FluentResults.Result)> LoadPackageXmlFiles(ContentPackage package, ImmutableArray localFilePaths) + + + public virtual ImmutableArray<(string, FluentResults.Result)> LoadPackageXmlFiles(ContentPackage package, ImmutableArray localFilePaths) { ((IService)this).CheckDisposed(); if (localFilePaths.IsDefaultOrEmpty) @@ -122,7 +156,7 @@ public class StorageService : IStorageService return builder.MoveToImmutable(); } - public ImmutableArray<(string, FluentResults.Result)> LoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePaths) + public virtual ImmutableArray<(string, FluentResults.Result)> LoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePaths) { ((IService)this).CheckDisposed(); if (localFilePaths.IsDefaultOrEmpty) @@ -133,7 +167,7 @@ public class StorageService : IStorageService return builder.MoveToImmutable(); } - public ImmutableArray<(string, FluentResults.Result)> LoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePaths) + public virtual ImmutableArray<(string, FluentResults.Result)> LoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePaths) { ((IService)this).CheckDisposed(); if (localFilePaths.IsDefaultOrEmpty) @@ -144,7 +178,7 @@ public class StorageService : IStorageService return builder.MoveToImmutable(); } - public FluentResults.Result> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively) + public virtual FluentResults.Result> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively) { ((IService)this).CheckDisposed(); var r = GetAbsFromPackage(package, localSubfolder); @@ -157,53 +191,60 @@ public class StorageService : IStorageService .WithValue(arr.ToImmutableArray()); } - public async Task> LoadPackageXmlAsync(ContentPackage package, string localFilePath) => + public virtual async Task> LoadPackageXmlAsync(ContentPackage package, string localFilePath) => GetAbsFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? await TryLoadXmlAsync(r.Value) : r.ToResult(); - public async Task> LoadPackageBinaryAsync(ContentPackage package, string localFilePath) => + public virtual async Task> LoadPackageBinaryAsync(ContentPackage package, string localFilePath) => GetAbsFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? await TryLoadBinaryAsync(r.Value) : r.ToResult(); - public async Task> LoadPackageTextAsync(ContentPackage package, string localFilePath) => + public virtual async Task> LoadPackageTextAsync(ContentPackage package, string localFilePath) => GetAbsFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? await TryLoadTextAsync(r.Value) : r.ToResult(); - public async Task)>> LoadPackageXmlFilesAsync(ContentPackage package, ImmutableArray localFilePaths) + public virtual async Task)>> LoadPackageXmlFilesAsync(ContentPackage package, ImmutableArray localFilePaths) { ((IService)this).CheckDisposed(); if (localFilePaths.IsDefaultOrEmpty) return ImmutableArray<(string, FluentResults.Result)>.Empty; var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result)>(localFilePaths.Length); - foreach (var path in localFilePaths) + + await localFilePaths.ParallelForEachAsync(async path => + { builder.Add((path, await LoadPackageXmlAsync(package, path))); + }, maxDegreeOfParallelism: 2); return builder.MoveToImmutable(); } - public async Task)>> LoadPackageBinaryFilesAsync(ContentPackage package, ImmutableArray localFilePaths) + public virtual async Task)>> LoadPackageBinaryFilesAsync(ContentPackage package, ImmutableArray localFilePaths) { ((IService)this).CheckDisposed(); if (localFilePaths.IsDefaultOrEmpty) return ImmutableArray<(string, FluentResults.Result)>.Empty; var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result)>(localFilePaths.Length); - foreach (var path in localFilePaths) + await localFilePaths.ParallelForEachAsync(async path => + { builder.Add((path, await LoadPackageBinaryAsync(package, path))); + }, maxDegreeOfParallelism: 2); return builder.MoveToImmutable(); } - public async Task)>> LoadPackageTextFilesAsync(ContentPackage package, ImmutableArray localFilePaths) + public virtual async Task)>> LoadPackageTextFilesAsync(ContentPackage package, ImmutableArray localFilePaths) { ((IService)this).CheckDisposed(); if (localFilePaths.IsDefaultOrEmpty) return ImmutableArray<(string, FluentResults.Result)>.Empty; var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result)>(localFilePaths.Length); - foreach (var path in localFilePaths) + await localFilePaths.ParallelForEachAsync(async path => + { builder.Add((path, await LoadPackageTextAsync(package, path))); + }, maxDegreeOfParallelism: 2); return builder.MoveToImmutable(); } - public FluentResults.Result TryLoadXml(string filePath, Encoding encoding = null) + public virtual FluentResults.Result TryLoadXml(string filePath, Encoding encoding = null) { ((IService)this).CheckDisposed(); var r = TryLoadText(filePath, encoding); @@ -216,32 +257,48 @@ public class StorageService : IStorageService } } - public FluentResults.Result TryLoadText(string filePath, Encoding encoding = null) + public virtual FluentResults.Result TryLoadText(string filePath, Encoding encoding = null) { ((IService)this).CheckDisposed(); + if (UseCaching && _fsCache.TryGetValue(filePath, out var result) + && result.TryPickT1(out var cachedVal, out _)) + { + return FluentResults.Result.Ok(cachedVal); + } + return IOExceptionsOperationRunner(nameof(TryLoadText), filePath, () => { var fp = filePath.CleanUpPath(); fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); var fileText = encoding is null ? System.IO.File.ReadAllText(fp) : System.IO.File.ReadAllText(fp, encoding); + if (UseCaching) + _fsCache[filePath] = fileText; return new FluentResults.Result().WithSuccess($"Loaded file successfully").WithValue(fileText); }); } - public FluentResults.Result TryLoadBinary(string filePath) + public virtual FluentResults.Result TryLoadBinary(string filePath) { ((IService)this).CheckDisposed(); + if (UseCaching && _fsCache.TryGetValue(filePath, out var result) + && result.TryPickT0(out var cachedVal, out _)) + { + return FluentResults.Result.Ok(cachedVal); + } + return IOExceptionsOperationRunner(nameof(TryLoadBinary), filePath, () => { var fp = filePath.CleanUpPath(); fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); var fileData = System.IO.File.ReadAllBytes(fp); + if (UseCaching) + _fsCache[filePath] = fileData; return new FluentResults.Result().WithSuccess($"Loaded file successfully").WithValue(fileData); }); } - public FluentResults.Result TrySaveXml(string filePath, in XDocument document, Encoding encoding = null) => TrySaveText(filePath, document.ToString(), encoding); - public FluentResults.Result TrySaveText(string filePath, in string text, Encoding encoding = null) + public virtual FluentResults.Result TrySaveXml(string filePath, in XDocument document, Encoding encoding = null) => TrySaveText(filePath, document.ToString(), encoding); + public virtual FluentResults.Result TrySaveText(string filePath, in string text, Encoding encoding = null) { ((IService)this).CheckDisposed(); if (text.IsNullOrWhiteSpace()) @@ -251,18 +308,19 @@ public class StorageService : IStorageService .WithMetadata(MetadataType.ExceptionObject, this) .WithMetadata(MetadataType.Sources, filePath)); } - string t = text; //copy return IOExceptionsOperationRunner(nameof(TrySaveText), filePath, () => { var fp = filePath.CleanUpPath(); fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); System.IO.File.WriteAllText(fp, t, encoding); + if (UseCaching) + _fsCache[filePath] = t; return new FluentResults.Result().WithSuccess($"Saved to file successfully"); }); } - public FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes) + public virtual FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes) { ((IService)this).CheckDisposed(); if (bytes is null || bytes.Length == 0) @@ -279,11 +337,13 @@ public class StorageService : IStorageService var fp = filePath.CleanUpPath(); fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); System.IO.File.WriteAllBytes(fp, b); + if (UseCaching) + _fsCache[filePath] = b; return new FluentResults.Result().WithSuccess($"Saved to file successfully"); }); } - public FluentResults.Result FileExists(string filePath) + public virtual FluentResults.Result FileExists(string filePath) { ((IService)this).CheckDisposed(); return IOExceptionsOperationRunner(nameof(FileExists), filePath, () => @@ -294,7 +354,7 @@ public class StorageService : IStorageService }); } - public FluentResults.Result DirectoryExists(string directoryPath) + public virtual FluentResults.Result DirectoryExists(string directoryPath) { ((IService)this).CheckDisposed(); try @@ -308,12 +368,19 @@ public class StorageService : IStorageService } } - public async Task> TryLoadXmlAsync(string filePath, Encoding encoding = null) + public virtual async Task> TryLoadXmlAsync(string filePath, Encoding encoding = null) { + ((IService)this).CheckDisposed(); + if (UseCaching && _fsCache.TryGetValue(filePath, out var cachedVal) + && cachedVal.TryPickT2(out var cachedDoc, out _)) + return FluentResults.Result.Ok(cachedDoc); try { await using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); - return await XDocument.LoadAsync(fs, LoadOptions.PreserveWhitespace, CancellationToken.None); + var doc = await XDocument.LoadAsync(fs, LoadOptions.PreserveWhitespace, CancellationToken.None); + if (UseCaching) + _fsCache[filePath] = doc; + return FluentResults.Result.Ok(doc); } catch (Exception e) { @@ -321,20 +388,33 @@ public class StorageService : IStorageService } } - public async Task> TryLoadTextAsync(string filePath, Encoding encoding = null) + public virtual async Task> TryLoadTextAsync(string filePath, Encoding encoding = null) { ((IService)this).CheckDisposed(); + if (UseCaching && _fsCache.TryGetValue(filePath, out var cachedVal) + && cachedVal.TryPickT1(out var cachedTxt, out _)) + return FluentResults.Result.Ok(cachedTxt); + return await IOExceptionsOperationRunnerAsync(nameof(TryLoadTextAsync), filePath, async () => { var fp = filePath.CleanUpPath(); fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); - return await System.IO.File.ReadAllTextAsync(fp); + var txt = await System.IO.File.ReadAllTextAsync(fp); + if (UseCaching) + _fsCache[filePath] = txt; + return FluentResults.Result.Ok(txt); }); } - public async Task> TryLoadBinaryAsync(string filePath) + public virtual async Task> TryLoadBinaryAsync(string filePath) { ((IService)this).CheckDisposed(); + if (UseCaching && _fsCache.TryGetValue(filePath, out var cachedVal) + && cachedVal.TryPickT0(out var cachedBin, out _)) + { + return cachedBin; + } + return await IOExceptionsOperationRunnerAsync(nameof(TryLoadTextAsync), filePath, async () => { var fp = filePath.CleanUpPath(); @@ -343,8 +423,8 @@ public class StorageService : IStorageService }); } - public async Task TrySaveXmlAsync(string filePath, XDocument document, Encoding encoding = null) => await TrySaveTextAsync(filePath, document.ToString(), encoding); - public async Task TrySaveTextAsync(string filePath, string text, Encoding encoding = null) + public virtual async Task TrySaveXmlAsync(string filePath, XDocument document, Encoding encoding = null) => await TrySaveTextAsync(filePath, document.ToString(), encoding); + public virtual async Task TrySaveTextAsync(string filePath, string text, Encoding encoding = null) { ((IService)this).CheckDisposed(); if (text.IsNullOrWhiteSpace()) @@ -361,11 +441,13 @@ public class StorageService : IStorageService var fp = filePath.CleanUpPath(); fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); await System.IO.File.WriteAllTextAsync(fp, t, encoding); + if (UseCaching) + _fsCache[filePath] = t; return new FluentResults.Result().WithSuccess($"Saved to file successfully"); }); } - public async Task TrySaveBinaryAsync(string filePath, byte[] bytes) + public virtual async Task TrySaveBinaryAsync(string filePath, byte[] bytes) { ((IService)this).CheckDisposed(); if (bytes is null || bytes.Length == 0) @@ -382,6 +464,8 @@ public class StorageService : IStorageService var fp = filePath.CleanUpPath(); fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); await System.IO.File.WriteAllBytesAsync(fp, b); + if (UseCaching) + _fsCache[filePath] = b; return new FluentResults.Result().WithSuccess($"Saved to file successfully"); }); } @@ -392,41 +476,9 @@ public class StorageService : IStorageService { return await operation?.Invoke()!; } - catch (ArgumentNullException ane) + catch (Exception e) { - return ReturnException(ane, filepath).WithError(GetGeneralError(funcName, filepath)); - } - catch (ArgumentException ae) - { - return ReturnException(ae, filepath).WithError(GetGeneralError(funcName, filepath)); - } - catch (PathTooLongException ptle) - { - return ReturnException(ptle, filepath).WithError(GetGeneralError(funcName, filepath)); - } - catch (NotSupportedException nse) - { - return ReturnException(nse, filepath).WithError(GetGeneralError(funcName, filepath)); - } - catch (UnauthorizedAccessException uae) - { - return ReturnException(uae, filepath).WithError(GetGeneralError(funcName, filepath)); - } - catch (DirectoryNotFoundException dnfe) - { - return ReturnException(dnfe, filepath).WithError(GetGeneralError(funcName, filepath)); - } - catch (FileNotFoundException fnfe) - { - return ReturnException(fnfe, filepath).WithError(GetGeneralError(funcName, filepath)); - } - catch (SecurityException se) - { - return ReturnException(se, filepath).WithError(GetGeneralError(funcName, filepath)); - } - catch (IOException ioe) - { - return ReturnException(ioe, filepath).WithError(GetGeneralError(nameof(SaveLocalXml), filepath)); + return ReturnException(e, filepath).WithError(GetGeneralError(funcName, filepath)); } } @@ -436,41 +488,9 @@ public class StorageService : IStorageService { return await operation?.Invoke()!; } - catch (ArgumentNullException ane) + catch (Exception e) { - return ReturnException(ane, filepath).WithError(GetGeneralError(funcName, filepath)); - } - catch (ArgumentException ae) - { - return ReturnException(ae, filepath).WithError(GetGeneralError(funcName, filepath)); - } - catch (PathTooLongException ptle) - { - return ReturnException(ptle, filepath).WithError(GetGeneralError(funcName, filepath)); - } - catch (NotSupportedException nse) - { - return ReturnException(nse, filepath).WithError(GetGeneralError(funcName, filepath)); - } - catch (UnauthorizedAccessException uae) - { - return ReturnException(uae, filepath).WithError(GetGeneralError(funcName, filepath)); - } - catch (DirectoryNotFoundException dnfe) - { - return ReturnException(dnfe, filepath).WithError(GetGeneralError(funcName, filepath)); - } - catch (FileNotFoundException fnfe) - { - return ReturnException(fnfe, filepath).WithError(GetGeneralError(funcName, filepath)); - } - catch (SecurityException se) - { - return ReturnException(se, filepath).WithError(GetGeneralError(funcName, filepath)); - } - catch (IOException ioe) - { - return ReturnException(ioe, filepath).WithError(GetGeneralError(nameof(SaveLocalXml), filepath)); + return ReturnException(e, filepath).WithError(GetGeneralError(funcName, filepath)); } } @@ -480,50 +500,9 @@ public class StorageService : IStorageService { return operation?.Invoke(); } - catch (ArgumentNullException ane) + catch (Exception e) { - return ReturnException(ane, filepath) - .WithError(GetGeneralError(funcName, filepath)); - } - catch (ArgumentException ae) - { - return ReturnException(ae, filepath) - .WithError(GetGeneralError(funcName, filepath)); - } - catch (PathTooLongException ptle) - { - return ReturnException(ptle, filepath) - .WithError(GetGeneralError(funcName, filepath)); - } - catch (NotSupportedException nse) - { - return ReturnException(nse, filepath) - .WithError(GetGeneralError(funcName, filepath)); - } - catch (UnauthorizedAccessException uae) - { - return ReturnException(uae, filepath) - .WithError(GetGeneralError(funcName, filepath)); - } - catch (DirectoryNotFoundException dnfe) - { - return ReturnException(dnfe, filepath) - .WithError(GetGeneralError(funcName, filepath)); - } - catch (FileNotFoundException fnfe) - { - return ReturnException(fnfe, filepath) - .WithError(GetGeneralError(funcName, filepath)); - } - catch (SecurityException se) - { - return ReturnException(se, filepath) - .WithError(GetGeneralError(funcName, filepath)); - } - catch (IOException ioe) - { - return ReturnException(ioe, filepath) - .WithError(GetGeneralError(nameof(SaveLocalXml), filepath)); + return ReturnException(e, filepath).WithError(GetGeneralError(funcName, filepath)); } } @@ -533,50 +512,9 @@ public class StorageService : IStorageService { return operation?.Invoke(); } - catch (ArgumentNullException ane) + catch (Exception e) { - return ReturnException(ane, filepath) - .WithError(GetGeneralError(funcName, filepath)); - } - catch (ArgumentException ae) - { - return ReturnException(ae, filepath) - .WithError(GetGeneralError(funcName, filepath)); - } - catch (PathTooLongException ptle) - { - return ReturnException(ptle, filepath) - .WithError(GetGeneralError(funcName, filepath)); - } - catch (NotSupportedException nse) - { - return ReturnException(nse, filepath) - .WithError(GetGeneralError(funcName, filepath)); - } - catch (UnauthorizedAccessException uae) - { - return ReturnException(uae, filepath) - .WithError(GetGeneralError(funcName, filepath)); - } - catch (DirectoryNotFoundException dnfe) - { - return ReturnException(dnfe, filepath) - .WithError(GetGeneralError(funcName, filepath)); - } - catch (FileNotFoundException fnfe) - { - return ReturnException(fnfe, filepath) - .WithError(GetGeneralError(funcName, filepath)); - } - catch (SecurityException se) - { - return ReturnException(se, filepath) - .WithError(GetGeneralError(funcName, filepath)); - } - catch (IOException ioe) - { - return ReturnException(ioe, filepath) - .WithError(GetGeneralError(nameof(SaveLocalXml), filepath)); + return ReturnException(e, filepath).WithError(GetGeneralError(funcName, filepath)); } } @@ -610,14 +548,11 @@ public class StorageService : IStorageService } return new FluentResults.Result().WithSuccess($"Path constructed") - .WithValue( System.IO.Path.GetFullPath(System.IO.Path.Combine( - _runLocation, - LocalStoragePath.Value, - LocalFilePathRule.Value.Replace(_packagePathKeyword, package.Name.IsNullOrWhiteSpace() - ? package.TryExtractSteamWorkshopId(out var id) - ? id.Value.ToString() - : "_fallbackFolder" - : package.Name), + .WithValue(System.IO.Path.GetFullPath(System.IO.Path.Combine( + _configData.RunLocation, + _configData.LocalPackageDataPath.Replace( + _configData.LocalDataPathRegex, + package.TryExtractSteamWorkshopId(out var id) ? id.Value.ToString() : package.Name), localFilePath))); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs index 5e5a19118..b26cc724b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs @@ -3,77 +3,44 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; +using System.Xml.Linq; using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Services; using Barotrauma.LuaCs.Services.Safe; using Barotrauma.Networking; +using FluentResults; namespace Barotrauma.LuaCs.Services; public partial interface IConfigService : IReusableService, ILuaConfigService { - /* - * Resource Files. - */ + /// + /// Registers a type initializer from instancing config types by indicated type from config. + /// + /// + /// + /// The as parsed from the configuration info. + /// The resulting configuration instance. + void RegisterTypeInitializer(Func> initializer, bool replaceIfExists = false) + where TData : IEquatable where TConfig : IConfigBase; + + // Config Files/Resources Task LoadConfigsAsync(ImmutableArray configResources); Task LoadConfigsProfilesAsync(ImmutableArray configProfileResources); - FluentResults.Result DisposeConfigs(ImmutableArray configResources); - FluentResults.Result DisposeConfigsProfiles(ImmutableArray configProfilesResources); - FluentResults.Result DisposeConfigs(ContentPackage package); - FluentResults.Result DisposeConfigsProfiles(ContentPackage package); + // Immediate Mode + FluentResults.Result AddConfig(IConfigInfo configInfo) where TConfig : IConfigBase; - /* - * Immediate mode - */ - FluentResults.Result> AddConfigEntry(ContentPackage package, string name, - T defaultValue, - NetSync syncMode = NetSync.None, - ClientPermissions permissions = ClientPermissions.None, - Func valueChangePredicate = null, - Action> onValueChanged = null) where T : IConvertible, IEquatable; - - FluentResults.Result AddConfigList(ContentPackage package, string name, - int defaultIndex, IReadOnlyList values, - NetSync syncMode = NetSync.None, - ClientPermissions permissions = ClientPermissions.None, - Func valueChangePredicate = null, - Action onValueChanged = null); - - FluentResults.Result> AddConfigRangeEntry(ContentPackage package, string name, - T defaultValue, T minValue, T maxValue, - Func, int> getStepCount, - NetSync syncMode = NetSync.None, - ClientPermissions permissions = ClientPermissions.None, - Func valueChangePredicate = null, - Action> onValueChanged = null) where T : IConvertible, IEquatable; - - FluentResults.Result> AddConfigEntry(string packageName, string name, - T defaultValue, - NetSync syncMode = NetSync.None, - ClientPermissions permissions = ClientPermissions.None, - Func valueChangePredicate = null, - Action> onValueChanged = null) where T : IConvertible, IEquatable; - - FluentResults.Result AddConfigList(string packageName, string name, - int defaultIndex, IReadOnlyList values, - NetSync syncMode = NetSync.None, - ClientPermissions permissions = ClientPermissions.None, - Func valueChangePredicate = null, - Action onValueChanged = null); - - FluentResults.Result> AddConfigRangeEntry(string packageName, string name, - T defaultValue, T minValue, T maxValue, - Func, int> getStepCount, - NetSync syncMode = NetSync.None, - ClientPermissions permissions = ClientPermissions.None, - Func valueChangePredicate = null, - Action> onValueChanged = null) where T : IConvertible, IEquatable; - - FluentResults.Result> GetConfigsForPackage(ContentPackage package); - FluentResults.Result> GetConfigsForPackage(string packageName); - IReadOnlyDictionary<(ContentPackage, string), IConfigBase> GetAllConfigs(); - T GetConfig(ContentPackage package, string name) where T : IConfigBase; - T GetConfig(string packageName, string name) where T : IConfigBase; + // Utility + FluentResults.Result ApplyProfileSettings(ContentPackage package, string profileName); + FluentResults.Result DisposePackageData(ContentPackage package); + FluentResults.Result> GetConfigsForPackage(ContentPackage package); + FluentResults.Result Value)>>> + GetProfilesForPackage(ContentPackage package); + IReadOnlyDictionary<(ContentPackage Package, string Name), IConfigBase> GetAllConfigs(); + bool TryGetConfig(ContentPackage package, string name, out T config) where T : IConfigBase; + Task SaveAllConfigs(); + Task SaveConfigsForPackage(ContentPackage package); + Task SaveConfig((ContentPackage Package, string ConfigName) config); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILocalizationService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILocalizationService.cs deleted file mode 100644 index 59abcfe90..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILocalizationService.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Globalization; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Threading.Tasks; -using Barotrauma.LuaCs.Data; - -namespace Barotrauma.LuaCs.Services; - -public interface ILocalizationService : IReusableService -{ - IReadOnlyCollection GetLoadedLocales(); - void Remove(ImmutableArray localizations); - void DisposePackage(ContentPackage package); - FluentResults.Result SetCurrentCulture(CultureInfo culture); - FluentResults.Result SetCurrentCulture(string cultureName); - Task LoadLocalizations(ImmutableArray localizationResources); - - /// - /// Tries to get a localized string without a fallback. Returns success/failure and associated data. - /// - /// Neutral localization key. - /// - FluentResults.Result GetLocalizedString(string key); - FluentResults.Result GetLocalizedString(string key, CultureInfo targetCulture); - string GetLocalizedString(string key, string fallback); - string GetLocalizedString(string key, string fallback, CultureInfo targetCulture); - FluentResults.Result GetLocalizedStringForPackage(ContentPackage package, string key); - FluentResults.Result GetLocalizedStringForPackage(ContentPackage package, string key, CultureInfo targetCulture); - string GetLocalizedStringForPackage(ContentPackage package, string key, string fallback); - string GetLocalizedStringForPackage(ContentPackage package, string key, string fallback, CultureInfo targetCulture); - FluentResults.Result RegisterLocalizationResolver(CultureInfo targetCulture, Func factoryResolver); - bool IsCurrentCultureSupported(IResourceCultureInfo culturesInfo); -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs index 88e432400..d8acb5544 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.Reflection; using System.Threading.Tasks; using Barotrauma.LuaCs.Data; +using FluentResults; using MoonSharp.Interpreter; using MoonSharp.Interpreter.Interop; @@ -13,13 +14,58 @@ public interface ILuaScriptManagementService : IReusableService { #region Script_Ops + Result GetGlobalTableValue(string tableName); + + /// + /// Parses and loads script sources (code) into a memory cache without executing it. + /// + /// + /// + // [Required] Task LoadScriptResourcesAsync(ImmutableArray resourcesInfo); - FluentResults.Result ExecuteLoadedScripts(ContentPackage package, bool pauseExecutionOnError = false, bool verboseLogging = false); - FluentResults.Result ExecuteLoadedScripts(ImmutableArray scripts, bool pauseExecutionOnError = false, bool verboseLogging = false); - FluentResults.Result ExecuteLoadedScripts(bool pauseExecutionOnError = false, bool verboseLogging = false); + /// + /// Executes cached scripts (code) for the given . + /// + /// + /// + // [Required] + FluentResults.Result ExecuteLoadedScriptsForPackage(ContentPackage package); + + /// + /// Executes cached scripts (code) for the given collection . + /// + /// + /// + // [Required] + FluentResults.Result ExecuteLoadedScriptsForPackages(IEnumerable packages); + + /// + /// + /// + /// + // [Required] + FluentResults.Result ExecuteLoadedScripts(); + + /// + /// + /// + /// + /// + // [Required] FluentResults.Result DisposePackageResources(ContentPackage package); + + /// + /// Calls dispose on, and clears active refs for, currently running scripts. Does not clear caches. + /// + /// FluentResults.Result UnloadActiveScripts(); + + /// + /// Unloads all scripts and clears all caches/references. + /// + /// + /// May be functionally equivalent to FluentResults.Result DisposeAllPackageResources(); #endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs index 835176d95..c89b5e63e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs @@ -8,7 +8,7 @@ namespace Barotrauma.LuaCs.Services; internal delegate void NetMessageReceived(IReadMessage netMessage); -internal partial interface INetworkingService : IReusableService, ILuaCsNetworking +internal partial interface INetworkingService : IReusableService, ILuaCsNetworking, IEntityNetworkingService { bool IsActive { get; } bool IsSynchronized { get; } @@ -20,6 +20,11 @@ internal partial interface INetworkingService : IReusableService, ILuaCsNetworki #elif CLIENT public void Send(IWriteMessage netMessage, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable); #endif - public void RegisterNetVar(INetVar netVar); - public void SendNetVar(INetVar netVar); + +} + +public interface IEntityNetworkingService +{ + public void RegisterNetVar(INetworkSyncEntity netVar); + public void SendNetVar(INetworkSyncEntity netVar); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageInfoLookupService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageInfoLookupService.cs index 36dabf384..836103c25 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageInfoLookupService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageInfoLookupService.cs @@ -7,6 +7,7 @@ namespace Barotrauma.LuaCs.Services; public interface IPackageInfoLookupService : IReusableService { + bool IsPackageEnabled(ContentPackage package); Task> Lookup(string packageName); Task> Lookup(string packageName, ulong steamWorkshopId); Task> Lookup(ulong steamWorkshopId); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs index 72bf528d0..c342ff9f4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs @@ -9,11 +9,7 @@ using Barotrauma.LuaCs.Data; namespace Barotrauma.LuaCs.Services; -public interface IPackageManagementService : IReusableService, ILocalizationsResourcesInfo, IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo, IAssembliesResourcesInfo -#if CLIENT - ,IStylesResourcesInfo -#endif - +public interface IPackageManagementService : IReusableService, IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo, IAssembliesResourcesInfo { /// /// Loads and parses the provided for supported by the current runtime environment. @@ -30,6 +26,20 @@ public interface IPackageManagementService : IReusableService, ILocalizationsRes /// Task> LoadPackagesInfosAsync(IReadOnlyList packages); IReadOnlyList GetAllLoadedPackages(); + bool IsPackageLoaded(ContentPackage package); + + /// + /// Filters out resources not suitable for the current environment using the following criteria:
+ /// - Platform (Operating System)
+ /// - Target (Client|Server)
+ /// - Null/Invalid
+ /// - Dependency Package Registered in PMS
+ ///
+ /// + /// + /// + ImmutableArray FilterUnloadableResources(IReadOnlyList resources, bool enabledPackagesOnly = false) + where T : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo; void DisposePackageInfos(ContentPackage package); void DisposePackagesInfos(IReadOnlyList packages); FluentResults.Result GetPackageDependencyInfo(ContentPackage ownerPackage, string packageName, ulong steamWorkshopId); @@ -38,28 +48,15 @@ public interface IPackageManagementService : IReusableService, ILocalizationsRes FluentResults.Result GetAssembliesInfos(ContentPackage package, bool onlySupportedResources = true); FluentResults.Result GetConfigsInfos(ContentPackage package, bool onlySupportedResources = true); FluentResults.Result GetConfigProfilesInfos(ContentPackage package, bool onlySupportedResources = true); - FluentResults.Result GetLocalizationsInfos(ContentPackage package, bool onlySupportedResources = true); FluentResults.Result GetLuaScriptsInfos(ContentPackage package, bool onlySupportedResources = true); -#if CLIENT - FluentResults.Result GetStylesInfos(ContentPackage package, bool onlySupportedResources = true); -#endif // collection FluentResults.Result GetAssembliesInfos(IReadOnlyList packages, bool onlySupportedResources = true); FluentResults.Result GetConfigsInfos(IReadOnlyList packages, bool onlySupportedResources = true); FluentResults.Result GetConfigProfilesInfos(IReadOnlyList packages, bool onlySupportedResources = true); - FluentResults.Result GetLocalizationsInfos(IReadOnlyList packages, bool onlySupportedResources = true); - FluentResults.Result GetLuaScriptsInfos(IReadOnlyList packages, bool onlySupportedResources = true); -#if CLIENT - FluentResults.Result GetStylesInfos(IReadOnlyList packages, bool onlySupportedResources = true); -#endif + FluentResults.Result GetLuaScriptsInfos(IReadOnlyList packages, bool onlySupportedResources = true); Task> GetAssembliesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); Task> GetConfigsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); Task> GetConfigProfilesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); - Task> GetLocalizationsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); - Task> GetLuaScriptsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); -#if CLIENT - Task> GetStylesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); -#endif - + Task> GetLuaScriptsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs index 281c39a9b..860a8eb6e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs @@ -1,4 +1,5 @@ -using System.Collections.Immutable; +using System; +using System.Collections.Immutable; using System.Text; using System.Threading.Tasks; using System.Xml.Linq; @@ -7,6 +8,26 @@ namespace Barotrauma.LuaCs.Services; public interface IStorageService : IService { + + bool UseCaching { get; set; } + + /// + /// Deletes all cached file data. + /// + void PurgeCache(); + + /// + /// Deletes the data for the supplied file path from the data cache. + /// + /// + void PurgeFileFromCache(string absolutePath); + + /// + /// Deletes the data from the supplied file paths from the data cache. + /// + /// + void PurgeFilesFromCache(params string[] absolutePaths); + // -- local game folder storage FluentResults.Result LoadLocalXml(ContentPackage package, string localFilePath); FluentResults.Result LoadLocalBinary(ContentPackage package, string localFilePath); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs index ebfc6269e..ce6334379 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Loader; using System.Threading; using Barotrauma.Extensions; @@ -55,22 +56,131 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService //internal private readonly IAssemblyManagementService _assemblyManagementService; private readonly Action _onUnload; + private readonly Func _onResolvingManaged; + private readonly Func _onResolvingUnmanagedDll; private readonly ConcurrentDictionary _dependencyResolvers = new(); private readonly ConcurrentDictionary _loadedAssemblyData = new(); private readonly ThreadLocal _isResolving = new(static()=>false); // cyclic resolution exit + private readonly ThreadLocal _isResolvingNative = new(static () => false); - public AssemblyLoader(IAssemblyManagementService assemblyManagementService, + public AssemblyLoader( + IAssemblyManagementService assemblyManagementService, Guid id, string name, - bool isReferenceOnlyMode, Action onUnload) + bool isReferenceOnlyMode, + Action onUnload = null) : base(isCollectible: true, name: name) { _assemblyManagementService = assemblyManagementService; Id = id; IsReferenceOnlyMode = isReferenceOnlyMode; - _onUnload = onUnload; - if (_onUnload is not null) - base.Unloading += OnUnload; + base.Unloading += OnUnload; + base.Resolving += OnResolvingManagedAssembly; + base.ResolvingUnmanagedDll += OnResolvingUnmanagedDll; + } + + private IntPtr OnResolvingUnmanagedDll(Assembly invokingAssembly, string assemblyName) + { + if (IsDisposed) + return 0; + + if (_isResolvingNative.Value) + return 0; + + AreOperationRunning = true; + _isResolvingNative.Value = true; + try + { + if (!_dependencyResolvers.IsEmpty) + { + foreach (var resolver in _dependencyResolvers) + { + try + { + var path = resolver.Value.ResolveUnmanagedDllToPath(assemblyName); + if (path.IsNullOrWhiteSpace()) + continue; + return base.LoadUnmanagedDllFromPath(path); + } + catch + { + // ignored + continue; + } + } + } + + if (_onResolvingUnmanagedDll is not null) + { + try + { + return _onResolvingUnmanagedDll(invokingAssembly, assemblyName); + } + catch + { + // ignored + } + } + + return 0; + } + finally + { + AreOperationRunning = false; + _isResolvingNative.Value = false; + } + } + + private Assembly OnResolvingManagedAssembly(AssemblyLoadContext assemblyLoadContext, AssemblyName assemblyName) + { + if (IsDisposed) + return null; + + if (_isResolving.Value) + return null; + + AreOperationRunning = true; + _isResolving.Value = true; + try + { + if (!_dependencyResolvers.IsEmpty) + { + foreach (var resolver in _dependencyResolvers) + { + try + { + var path = resolver.Value.ResolveAssemblyToPath(assemblyName); + if (path.IsNullOrWhiteSpace()) + continue; + return assemblyLoadContext.LoadFromAssemblyPath(path); + } + catch + { + // ignored + continue; + } + } + } + + if (_onResolvingManaged is not null) + { + try + { + return _onResolvingManaged(assemblyLoadContext, assemblyName); + } + catch + { + // ignored + } + } + + return null; + } + finally + { + AreOperationRunning = false; + _isResolving.Value = false; + } } public IEnumerable AssemblyReferences @@ -107,10 +217,13 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService } catch (Exception ex) { - return res.WithError(new ExceptionalError(ex) + res = res.WithError(new ExceptionalError(ex) .WithMetadata(MetadataType.Sources, path)); } } + + if (res.Errors.Any()) + return FluentResults.Result.Fail(res.Errors); return FluentResults.Result.Ok(); } finally @@ -140,13 +253,16 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService if (_loadedAssemblyData.ContainsKey(assemblyName)) { - return new Result().WithError(new Error($"The name provided is already assigned to an assembly!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, syntaxTrees)); + return new Result().WithError( + new Error($"The name provided is already assigned to an assembly!") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, syntaxTrees)); } - - var compilationAssemblyName = compileWithInternalAccess ? IAssemblyLoaderService.InternalsAwareAssemblyName : assemblyName; - + + var compilationAssemblyName = compileWithInternalAccess + ? IAssemblyLoaderService.InternalsAwareAssemblyName + : assemblyName; + compilationOptions ??= new CSharpCompilationOptions( outputKind: OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release, @@ -158,7 +274,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService { typeof(CSharpCompilationOptions) .GetProperty("TopLevelBinderFlags", BindingFlags.Instance | BindingFlags.NonPublic) - ?.SetValue(compilationOptions, + ?.SetValue(compilationOptions, (uint)1 << 25 // CSharp.BinderFlags.AllowAwaitInUnsafeContext | (uint)1 << 22 // CSharp.BinderFlags.IgnoreAccessibility | (uint)1 << 1 // CSharp.BinderFlags.SuppressObsoleteChecks @@ -166,34 +282,39 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService } using var asmMemoryStream = new MemoryStream(); - var result = CSharpCompilation.Create(compilationAssemblyName, syntaxTrees, metadataReferences, compilationOptions).Emit(asmMemoryStream); + var result = CSharpCompilation + .Create(compilationAssemblyName, syntaxTrees, metadataReferences, compilationOptions) + .Emit(asmMemoryStream); if (!result.Success) { var res = new FluentResults.Result().WithError( new Error($"Compilation failed for assembly {assemblyName}!") .WithMetadata(MetadataType.ExceptionObject, this) .WithMetadata(MetadataType.RootObject, syntaxTrees)); - var failuresDiag = result.Diagnostics.Where(d => d.IsWarningAsError || d.Severity == DiagnosticSeverity.Error); + 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; } - + asmMemoryStream.Seek(0, SeekOrigin.Begin); - try - { - var data = new AssemblyData(LoadFromStream(asmMemoryStream), asmMemoryStream.ToArray()); - _loadedAssemblyData[data.Assembly] = data; - return new Result().WithSuccess($"Compiled assembly {assemblyName} successful.").WithValue(data.Assembly); - } - catch (Exception ex) - { - return new FluentResults.Result().WithError(new ExceptionalError(ex)); - } + var data = new AssemblyData(LoadFromStream(asmMemoryStream), asmMemoryStream.ToArray()); + _loadedAssemblyData[data.Assembly] = data; + return new Result().WithSuccess($"Compiled assembly {assemblyName} successful.") + .WithValue(data.Assembly); + } + catch (Exception ex) + { + return new FluentResults.Result().WithError(new ExceptionalError(ex) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, assemblyName) + .WithMetadata(MetadataType.Sources, syntaxTrees)); } finally { @@ -211,7 +332,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService try { if (assemblyFilePath.IsNullOrWhiteSpace()) - return new Result().WithError(new Error($"The path provided is null!")); + return new Result().WithError(new Error($"The path provided is empty.")); if (additionalDependencyPaths.Any()) { @@ -219,7 +340,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService if (!r.IsFailed) { // we have errors, loading may not work. - return FluentResults.Result.Fail(new Error($"Failed to load dependency paths") + return FluentResults.Result.Fail(new Error($"Failed to load dependency paths.") .WithMetadata(MetadataType.ExceptionObject, this) .WithMetadata(MetadataType.RootObject, assemblyFilePath)) .WithErrors(r.Errors); @@ -240,61 +361,51 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService { var assembly = LoadFromAssemblyPath(sanitizedFilePath); _loadedAssemblyData[assembly] = new AssemblyData(assembly, sanitizedFilePath); - return new Result().WithSuccess($"Loaded assembly'{assembly.GetName()}'").WithValue(assembly); - } - catch (ArgumentNullException ane) - { - return FluentResults.Result.Fail(new ExceptionalError(ane) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, assemblyFilePath) - .WithMetadata(MetadataType.ExceptionDetails, ane.Message) - .WithMetadata(MetadataType.StackTrace, ane.StackTrace)); - } - catch (ArgumentException ae) - { - return FluentResults.Result.Fail(new ExceptionalError(ae) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, assemblyFilePath) - .WithMetadata(MetadataType.ExceptionDetails, ae.Message) - .WithMetadata(MetadataType.StackTrace, ae.StackTrace)); - } - catch (FileLoadException fle) - { - return FluentResults.Result.Fail(new ExceptionalError(fle) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, assemblyFilePath) - .WithMetadata(MetadataType.ExceptionDetails, fle.Message) - .WithMetadata(MetadataType.StackTrace, fle.StackTrace)); + return new Result().WithSuccess($"Loaded assembly '{assembly.GetName()}'").WithValue(assembly); } catch (FileNotFoundException fnfe) { - return FluentResults.Result.Fail(new ExceptionalError(fnfe) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, assemblyFilePath) - .WithMetadata(MetadataType.ExceptionDetails, fnfe.Message) - .WithMetadata(MetadataType.StackTrace, fnfe.StackTrace)); - } - catch (BadImageFormatException bife) - { - return FluentResults.Result.Fail(new ExceptionalError(bife) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, assemblyFilePath) - .WithMetadata(MetadataType.ExceptionDetails, bife.Message) - .WithMetadata(MetadataType.StackTrace, bife.StackTrace)); + // last attempt + try + { + var assemblyName = new AssemblyName(System.IO.Path.GetFileName(sanitizedFilePath)); + foreach (var resolver in _dependencyResolvers) + { + try + { + var path = resolver.Value.ResolveAssemblyToPath(assemblyName); + return base.LoadFromAssemblyPath(path); + } + catch + { + continue; + } + } + return GenerateExceptionReturn(fnfe); + } + catch (Exception e) + { + return GenerateExceptionReturn(fnfe); + } } catch (Exception e) { - return FluentResults.Result.Fail(new ExceptionalError(e) - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, assemblyFilePath) - .WithMetadata(MetadataType.ExceptionDetails, e.Message) - .WithMetadata(MetadataType.StackTrace, e.StackTrace)); + return GenerateExceptionReturn(e); } } finally { AreOperationRunning = false; } + + FluentResults.Result GenerateExceptionReturn(T exception) where T : Exception + { + return FluentResults.Result.Fail(new ExceptionalError(exception) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, assemblyFilePath) + .WithMetadata(MetadataType.ExceptionDetails, exception.Message) + .WithMetadata(MetadataType.StackTrace, exception.StackTrace)); + } } public FluentResults.Result GetAssemblyByName(string assemblyName) @@ -303,7 +414,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService return FluentResults.Result.Fail(new Error($"Loader is disposed!")); if (assemblyName.IsNullOrWhiteSpace()) { - return FluentResults.Result.Fail(new Error($"Assembly name is null") + return FluentResults.Result.Fail(new Error($"Assembly name is empty.") .WithMetadata(MetadataType.ExceptionObject, this)); } AreOperationRunning = true; @@ -311,7 +422,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService { if (_loadedAssemblyData.TryGetValue(assemblyName, out var data)) { - return new Result().WithSuccess(new Success($"Assembly found")).WithValue(data.Assembly); + return new Result().WithSuccess(new Success($"Assembly found.")).WithValue(data.Assembly); } // search any assemblies that were background loaded and we're unaware of. @@ -332,11 +443,11 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService // ignored } - return new Result().WithSuccess(new Success($"Assembly found")).WithValue(assembly1); + return new Result().WithSuccess(new Success($"Assembly found.")).WithValue(assembly1); } } - return FluentResults.Result.Fail(new Error($"Assembly named { assemblyName } not found!")); + return FluentResults.Result.Fail(new Error($"Assembly named '{ assemblyName }' not found!")); } finally { @@ -420,60 +531,97 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService return; // we don't want to invoke events twice nor cause strong GC handles. IsDisposed = true; this.Unload(); + this.DisposeInternal(); + } + + ~AssemblyLoader() + { + this.DisposeInternal(); + } + + private void OnUnload(AssemblyLoadContext context) + { + // Try to wait for loading ops on other threads if they happen to occur with a timeout. + // This should be an edge, should it even occur. + DateTime timeout = DateTime.Now.AddSeconds(2); + while (timeout > DateTime.Now) + { + if (!AreOperationRunning) + break; + Thread.Sleep(1000/Timing.FixedUpdateRate-1); + } + + var wf = new WeakReference(this); + _onUnload?.Invoke(this); + } + + private void DisposeInternal() + { + IsDisposed = true; + base.Resolving -= OnResolvingManagedAssembly; + base.ResolvingUnmanagedDll -= OnResolvingUnmanagedDll; + base.Unloading -= OnUnload; + this._dependencyResolvers.Clear(); + this._loadedAssemblyData.Clear(); GC.SuppressFinalize(this); } protected override Assembly Load(AssemblyName assemblyName) { - if (_isResolving.Value) + if (IsDisposed) return null; - - _isResolving.Value = true; + AreOperationRunning = true; try { - if (_loadedAssemblyData.TryGetValue(assemblyName.FullName, out var data)) - return data.Assembly; - var ids = new[] { this.Id }; - if (_assemblyManagementService.GetLoadedAssembly(assemblyName, in ids) is { IsSuccess: true } ret) - return ret.Value; + if (_loadedAssemblyData.TryGetValue(assemblyName.FullName, out var assembly)) + return assembly.Assembly; return null; } - catch (ArgumentNullException _) + catch { return null; } finally { - _isResolving.Value = false; + AreOperationRunning = false; } } - // Use the default import resolver since native libraries are niche and not blocking for unloading. - // Implement if conflicts become an issue. - /*protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) { - // Implement NativeLibrary::InternalLoadUnmanagedDll() - throw new NotImplementedException(); - }*/ - - private void OnUnload(AssemblyLoadContext context) - { - IsDisposed = true; - - // Try to wait for loading ops on other threads if they happen to occur. - // Minor race condition on the loop exit but this loader is not intended to be thread-safe by design, this is just to cover edge cases. - DateTime timeout = DateTime.Now.AddSeconds(5); - while (timeout > DateTime.Now) + if (IsDisposed) + return 0; + + GCHandle? handle = null; + AreOperationRunning = true; + try { - if (!AreOperationRunning) - break; + if (_loadedAssemblyData.TryGetValue(unmanagedDllName, out var assemblyData)) + { + handle = GCHandle.Alloc(assemblyData.Assembly, GCHandleType.Pinned); + nint asmPtr = GCHandle.ToIntPtr(handle.Value); + return asmPtr; + } } - - base.Unloading -= OnUnload; - var wf = new WeakReference(this); - _onUnload?.Invoke(this); - this._dependencyResolvers.Clear(); - this._loadedAssemblyData.Clear(); + catch + { + return 0; + } + finally + { + AreOperationRunning = false; + try + { + if (handle.HasValue) + handle.Value.Free(); + } + catch + { + // ignored. We just want to ensure that free is called. + } + } + + return 0; } private readonly record struct AssemblyData @@ -538,7 +686,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService public int GetHashCode(AssemblyOrStringKey obj) { - return obj.HashCode; + return this.HashCode; } public static implicit operator AssemblyOrStringKey(Assembly assembly) => new AssemblyOrStringKey(assembly); From 2778df0fe7d7d79ab42c2785b5040de72d67a733 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 26 Dec 2025 19:10:34 -0500 Subject: [PATCH 015/288] - Changes to the Lua ScriptSystem spec. --- .../LuaCs/Services/Safe/ISafeStorageService.cs | 11 ----------- .../_Interfaces/ILuaScriptManagementService.cs | 16 ---------------- 2 files changed, 27 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ISafeStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ISafeStorageService.cs index bad6520eb..93247a023 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ISafeStorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ISafeStorageService.cs @@ -42,15 +42,4 @@ public interface ISafeStorageService : IStorageService /// Deletes all paths from all white lists. ///
void ClearAllWhitelists(); - - /// - /// Whether the service instance is in file read-only mode. - /// - bool IsReadOnlyMode { get; } - - /// - /// Sets the service into file read-only mode. Cannot be undone. - /// - /// - bool EnableReadOnlyMode(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs index d8acb5544..aad63020f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs @@ -24,22 +24,6 @@ public interface ILuaScriptManagementService : IReusableService // [Required] Task LoadScriptResourcesAsync(ImmutableArray resourcesInfo); - /// - /// Executes cached scripts (code) for the given . - /// - /// - /// - // [Required] - FluentResults.Result ExecuteLoadedScriptsForPackage(ContentPackage package); - - /// - /// Executes cached scripts (code) for the given collection . - /// - /// - /// - // [Required] - FluentResults.Result ExecuteLoadedScriptsForPackages(IEnumerable packages); - /// /// /// From aa7e825e702411730b6fde90af6bb02865c3e515 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 26 Dec 2025 19:19:14 -0500 Subject: [PATCH 016/288] - Deleted old assembly loader. --- .../MemoryFileAssemblyContextLoader.cs | 343 ------------------ 1 file changed, 343 deletions(-) delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/MemoryFileAssemblyContextLoader.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/MemoryFileAssemblyContextLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/MemoryFileAssemblyContextLoader.cs deleted file mode 100644 index dc570158b..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/MemoryFileAssemblyContextLoader.cs +++ /dev/null @@ -1,343 +0,0 @@ -/* -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.Loader; -using Barotrauma.LuaCs.Services; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -// ReSharper disable ConditionIsAlwaysTrueOrFalse - -[assembly: InternalsVisibleTo("CompiledAssembly")] - -namespace Barotrauma.LuaCs.Services; - -/// -/// AssemblyLoadContext to compile from syntax trees in memory and to load from disk/file. Provides dependency resolution. -/// [IMPORTANT] Only supports 1 in-memory compiled assembly at a time. Use more instances if you need more. -/// [IMPORTANT] All file assemblies required for the compilation of syntax trees should be loaded first. -/// -public class MemoryFileAssemblyContextLoader : AssemblyLoadContext -{ - // public - public string FriendlyName { get; set; } - // ReSharper disable MemberCanBePrivate.Global - public Assembly CompiledAssembly { get; private set; } - public byte[] CompiledAssemblyImage { get; private set; } - // ReSharper restore MemberCanBePrivate.Global - // internal - private readonly Dictionary _dependencyResolvers = new(); // path-folder, resolver - protected bool IsResolving; //this is to avoid circular dependency lookup. - private IAssemblyManagementService _assemblyManager; - public bool IsTemplateMode { get; set; } - public bool IsDisposed { get; private set; } - - public MemoryFileAssemblyContextLoader(IAssemblyManagementService assemblyManager) : base(isCollectible: true) - { - this._assemblyManager = assemblyManager; - this.IsDisposed = false; - base.Unloading += OnUnload; - } - - - /// - /// Try to load the list of disk-file assemblies. - /// - /// Operation success or failure reason. - public AssemblyLoadingSuccessState LoadFromFiles([NotNull] IEnumerable assemblyFilePaths) - { - if (assemblyFilePaths is null) - throw new ArgumentNullException( - $"{nameof(MemoryFileAssemblyContextLoader)}::{nameof(LoadFromFiles)}() | The supplied filepath list is null."); - - foreach (string filepath in assemblyFilePaths) - { - // path verification - if (filepath.IsNullOrWhiteSpace()) - continue; - string sanitizedFilePath = System.IO.Path.GetFullPath(filepath.CleanUpPath()); - string directoryKey = System.IO.Path.GetDirectoryName(sanitizedFilePath); - - if (directoryKey is null) - return AssemblyLoadingSuccessState.BadFilePath; - - // setup dep resolver if not available - if (!_dependencyResolvers.ContainsKey(directoryKey) || _dependencyResolvers[directoryKey] is null) - { - _dependencyResolvers[directoryKey] = new AssemblyDependencyResolver(sanitizedFilePath); // supply the first assembly to be loaded - } - - // try loading the assemblies - try - { - LoadFromAssemblyPath(sanitizedFilePath); - } - // on fail of any we're done because we assume that loaded files are related. This ACL needs to be unloaded and collected. - catch (ArgumentNullException ane) - { - ModUtils.Logging.PrintError($"MemFileACL::{nameof(LoadFromFiles)}() | Error loading file path {sanitizedFilePath}. Details: {ane.Message} | {ane.StackTrace}"); - return AssemblyLoadingSuccessState.BadFilePath; - } - catch (ArgumentException ae) - { - ModUtils.Logging.PrintError($"MemFileACL::{nameof(LoadFromFiles)}() | Error loading file path {sanitizedFilePath}. Details: {ae.Message} | {ae.StackTrace}"); - return AssemblyLoadingSuccessState.BadFilePath; - } - catch (FileLoadException fle) - { - ModUtils.Logging.PrintError($"MemFileACL::{nameof(LoadFromFiles)}() | Error loading file path {sanitizedFilePath}. Details: {fle.Message} | {fle.StackTrace}"); - return AssemblyLoadingSuccessState.CannotLoadFile; - } - catch (FileNotFoundException fnfe) - { - ModUtils.Logging.PrintError($"MemFileACL::{nameof(LoadFromFiles)}() | Error loading file path {sanitizedFilePath}. Details: {fnfe.Message} | {fnfe.StackTrace}"); - return AssemblyLoadingSuccessState.NoAssemblyFound; - } - catch (BadImageFormatException bife) - { - ModUtils.Logging.PrintError($"MemFileACL::{nameof(LoadFromFiles)}() | Error loading file path {sanitizedFilePath}. Details: {bife.Message} | {bife.StackTrace}"); - return AssemblyLoadingSuccessState.InvalidAssembly; - } - catch (Exception e) - { -#if SERVER - LuaCsLogger.LogError($"Unable to load dependency assembly file at {filepath.CleanUpPath()} for the assembly named {CompiledAssembly?.FullName}. | Data: {e.Message} | InnerException: {e.InnerException}"); -#elif CLIENT - LuaCsLogger.ShowErrorOverlay($"Unable to load dependency assembly file at {filepath} for the assembly named {CompiledAssembly?.FullName}. | Data: {e.Message} | InnerException: {e.InnerException}"); -#endif - return AssemblyLoadingSuccessState.ACLLoadFailure; - } - } - - return AssemblyLoadingSuccessState.Success; - } - - - /// - /// Compiles the supplied syntaxtrees and options into an in-memory assembly image. - /// Builds metadata from loaded assemblies, only supply your own if you have in-memory images not managed by the - /// AssemblyManager class. - /// - /// Name of the assembly. Must be supplied for in-memory assemblies. - /// Syntax trees to compile into the assembly. - /// Metadata to be used for compilation. - /// [IMPORTANT] This method builds metadata from loaded assemblies, only supply your own if you have in-memory - /// images not managed by the AssemblyManager class. - /// CSharp compilation options. This method automatically adds the 'IgnoreAccessChecks' property for compilation. - /// Will contain any diagnostic messages for compilation failure. - /// Additional assemblies located in the FileSystem to build metadata references from. - /// Assemblies here will have duplicates by the same name that are currently loaded filtered out. - /// Success state of the operation. - /// Throws exception if any of the required arguments are null. - public AssemblyLoadingSuccessState CompileAndLoadScriptAssembly( - [NotNull] string assemblyName, - [NotNull] IEnumerable syntaxTrees, - IEnumerable externMetadataReferences, - [NotNull] CSharpCompilationOptions compilationOptions, - out string compilationMessages, - IEnumerable externFileAssemblyReferences = null) - { - compilationMessages = ""; - - if (this.CompiledAssembly is not null) - { - return AssemblyLoadingSuccessState.AlreadyLoaded; - } - - var externAssemblyRefs = externFileAssemblyReferences is not null ? externFileAssemblyReferences.ToImmutableList() : ImmutableList.Empty; - var externAssemblyNames = externAssemblyRefs.Any() ? externAssemblyRefs - .Where(a => a.FullName is not null) - .Select(a => a.FullName).ToImmutableHashSet() - : ImmutableHashSet.Empty; - - // verifications - if (assemblyName.IsNullOrWhiteSpace()) - throw new ArgumentNullException( - $"{nameof(MemoryFileAssemblyContextLoader)}::{nameof(CompileAndLoadScriptAssembly)}() | The supplied assembly name is null!"); - - if (syntaxTrees is null) - throw new ArgumentNullException( - $"{nameof(MemoryFileAssemblyContextLoader)}::{nameof(CompileAndLoadScriptAssembly)}() | The supplied syntax tree is null!"); - - // add external references - List metadataReferences = new(); - if (externMetadataReferences is not null) - metadataReferences.AddRange(externMetadataReferences); - - // build metadata refs from default where not an in-memory compiled assembly and not the same assembly as supplied. - metadataReferences.AddRange(AssemblyLoadContext.Default.Assemblies - .Where(a => - { - if (a.IsDynamic || string.IsNullOrWhiteSpace(a.Location) || a.Location.Contains("xunit")) - return false; - if (a.FullName is null) - return true; - return !externAssemblyNames.Contains(a.FullName); // exclude duplicates - }) - .Select(a => MetadataReference.CreateFromFile(a.Location) as MetadataReference) - .Union(externAssemblyRefs // add custom supplied assemblies - .Where(a => !(a.IsDynamic || string.IsNullOrEmpty(a.Location) || a.Location.Contains("xunit"))) - .Select(a => MetadataReference.CreateFromFile(a.Location) as MetadataReference) - ).ToList()); - - ImmutableList loadedAcls = _assemblyManager.GetAllLoadedACLs().ToImmutableList(); - if (loadedAcls.Any()) - { - // build metadata refs from ACL assemblies from files/disk. - foreach (AssemblyManager.LoadedACL loadedAcl in loadedAcls) - { - if(loadedAcl?.Acl is null || loadedAcl.Acl.IsTemplateMode || loadedAcl.Acl.IsDisposed) - continue; - metadataReferences.AddRange(loadedAcl.Acl.Assemblies - .Where(a => - { - if (a.IsDynamic || string.IsNullOrWhiteSpace(a.Location) || a.Location.Contains("xunit")) - return false; - if (a.FullName is null) - return true; - return !externAssemblyNames.Contains(a.FullName); // exclude duplicates - }) - .Select(a => MetadataReference.CreateFromFile(a.Location) as MetadataReference) - .Union(externAssemblyRefs // add custom supplied assemblies - .Where(a => !(a.IsDynamic || string.IsNullOrEmpty(a.Location) || a.Location.Contains("xunit"))) - .Select(a => MetadataReference.CreateFromFile(a.Location) as MetadataReference) - ).ToList()); - } - - // build metadata refs from in-memory images - foreach (var loadedAcl in loadedAcls) - { - if (loadedAcl?.Acl?.CompiledAssemblyImage is null || loadedAcl.Acl.CompiledAssemblyImage.Length == 0) - continue; - metadataReferences.Add(MetadataReference.CreateFromImage(loadedAcl.Acl.CompiledAssemblyImage)); - } - } - - // Change inaccessible options to allow public access to restricted members - var topLevelBinderFlagsProperty = typeof(CSharpCompilationOptions).GetProperty("TopLevelBinderFlags", BindingFlags.Instance | BindingFlags.NonPublic); - topLevelBinderFlagsProperty?.SetValue(compilationOptions, (uint)1 << 22); - - // begin compilation - using var memoryCompilation = new MemoryStream(); - // compile, emit - var result = CSharpCompilation.Create(assemblyName, syntaxTrees, metadataReferences, compilationOptions).Emit(memoryCompilation); - // check for errors - if (!result.Success) - { - IEnumerable failures = result.Diagnostics.Where(d => d.IsWarningAsError || d.Severity == DiagnosticSeverity.Error); - foreach (Diagnostic diagnostic in failures) - { - compilationMessages += $"\n{diagnostic}"; - } - - return AssemblyLoadingSuccessState.InvalidAssembly; - } - - // read compiled assembly from memory stream into an in-memory assembly & image - memoryCompilation.Seek(0, SeekOrigin.Begin); // reset - try - { - CompiledAssembly = LoadFromStream(memoryCompilation); - CompiledAssemblyImage = memoryCompilation.ToArray(); - } - catch (Exception e) - { -#if SERVER - LuaCsLogger.LogError($"Unable to load memory assembly from stream. | Data: {e.Message} | InnerException: {e.InnerException}"); -#elif CLIENT - LuaCsLogger.ShowErrorOverlay($"Unable to load memory assembly from stream. | Data: {e.Message} | InnerException: {e.InnerException}"); -#endif - return AssemblyLoadingSuccessState.CannotLoadFromStream; - } - - return AssemblyLoadingSuccessState.Success; - } - - [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract")] - protected override Assembly Load(AssemblyName assemblyName) - { - if (IsResolving) - return null; //circular resolution fast exit. - - try - { - IsResolving = true; - - // resolve self collection - Assembly ass = this.Assemblies.FirstOrDefault(a => - a.FullName is not null && a.FullName.Equals(assemblyName.FullName), null); - if (ass is not null) - return ass; - - // resolve to local folders - foreach (KeyValuePair pair in _dependencyResolvers) - { - var asspath = pair.Value.ResolveAssemblyToPath(assemblyName); - if (asspath is null) - continue; - ass = LoadFromAssemblyPath(asspath); - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (ass is not null) - return ass; - } - - //try resolve against other loaded alcs - ImmutableList list; - try - { - list = _assemblyManager.UnsafeGetAllLoadedACLs(); - } - catch - { - list = ImmutableList.Empty; - } - - if (!list.IsEmpty) - { - foreach (var loadedAcL in list) - { - if (loadedAcL.Acl is null || loadedAcL.Acl.IsTemplateMode || loadedAcL.Acl.IsDisposed) - continue; - - try - { - ass = loadedAcL.Acl.LoadFromAssemblyName(assemblyName); - if (ass is not null) - return ass; - } - catch - { - // LoadFromAssemblyName throws, no need to propagate - } - } - } - - ass = AssemblyLoadContext.Default.LoadFromAssemblyName(assemblyName); - if (ass is not null) - return ass; - } - finally - { - IsResolving = false; - } - - return null; - } - - - private void OnUnload(AssemblyLoadContext alc) - { - CompiledAssembly = null; - CompiledAssemblyImage = null; - _dependencyResolvers.Clear(); - _assemblyManager = null; - base.Unloading -= OnUnload; - this.IsDisposed = true; - } -} -*/ From 4d97a427f9e8abb62fccf5374f1c0728183d5231 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 27 Dec 2025 00:08:53 -0300 Subject: [PATCH 017/288] WIP Lua script management service --- .../SharedSource/LuaCs/LuaCsSetup.cs | 2 +- .../Services/LuaScriptManagementService.cs | 278 ++++++++---------- .../LuaCs/Services/Safe/ILuaDataService.cs | 30 -- .../ILuaScriptManagementService.cs | 61 +--- 4 files changed, 121 insertions(+), 250 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index ae24a2efc..4996dcc00 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -664,7 +664,7 @@ namespace Barotrauma return; } - LuaScriptManagementService.ExecuteLoadedScriptsForPackages(luaRes); + LuaScriptManagementService.ExecuteLoadedScripts(); if (CurrentRunState < RunState.Running) _runState = RunState.Running; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs index f8531acef..8dda54b32 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs @@ -1,217 +1,171 @@ -using Barotrauma.LuaCs.Data; +#nullable enable + +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Services.Safe; +using FluentResults; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; using MoonSharp.Interpreter; using MoonSharp.Interpreter.Interop; +using MoonSharp.Interpreter.Loaders; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; -using Barotrauma.LuaCs.Services.Safe; -using FluentResults; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using MoonSharp.Interpreter.Loaders; namespace Barotrauma.LuaCs.Services; public class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService { - public LuaScriptManagementService(ILuaScriptLoader loader, ILuaScriptServicesConfig luaScriptServicesConfig) + private Script? _script; + private bool _isRunning; + [MemberNotNullWhen(true, nameof(_script))] + public bool IsRunning => _isRunning; + private List _resourcesInfo = new List(); + private readonly ILuaScriptLoader _luaScriptLoader; + private readonly ILuaScriptServicesConfig _luaScriptServicesConfig; + private readonly ILoggerService _loggerService; + + public LuaScriptManagementService(ILoggerService loggerService, ILuaScriptLoader loader, ILuaScriptServicesConfig luaScriptServicesConfig) { _luaScriptLoader = loader; _luaScriptServicesConfig = luaScriptServicesConfig; - } - - private readonly ILuaScriptLoader _luaScriptLoader; - private readonly ILuaScriptServicesConfig _luaScriptServicesConfig; - - public void Dispose() - { - _luaScriptLoader.Dispose(); + _loggerService = loggerService; } - public bool IsDisposed + public bool IsDisposed { get; private set; } + + public Task LoadScriptResourcesAsync(ImmutableArray resourcesInfo) { - get => throw new NotImplementedException(); + _resourcesInfo.AddRange(resourcesInfo.OrderBy(static r => r.LoadPriority)); + + // TODO disk caching + + return Task.FromResult(FluentResults.Result.Ok()); } - public FluentResults.Result Reset() + private void SetupEnvironment() { - throw new NotImplementedException(); - } + _script = new Script(CoreModules.Preset_SoftSandbox | CoreModules.Debug | CoreModules.IO | CoreModules.OS_System); + _script.Options.DebugPrint = (string msg) => + { + _loggerService.Log(msg); + }; + _script.Options.ScriptLoader = _luaScriptLoader; + _script.Options.CheckThreadAccess = false; - public Result GetGlobalTableValue(string tableName) - { - throw new NotImplementedException(); - } - - public async Task LoadScriptResourcesAsync(ImmutableArray resourcesInfo) - { - throw new NotImplementedException(); - } - - public FluentResults.Result ExecuteLoadedScriptsForPackage(ContentPackage package) - { - throw new NotImplementedException(); - } - - public FluentResults.Result ExecuteLoadedScriptsForPackages(IEnumerable packages) - { - throw new NotImplementedException(); + Script.GlobalOptions.ShouldPCallCatchException = (Exception ex) => { return true; }; } public FluentResults.Result ExecuteLoadedScripts() { - throw new NotImplementedException(); + if (_isRunning) + { + return FluentResults.Result.Fail("Tried to execute Lua scripts without unloading first."); + } + + SetupEnvironment(); + + _isRunning = true; + + var result = FluentResults.Result.Ok(); + + foreach (var resource in _resourcesInfo) + { + foreach (var filePath in resource.FilePaths) + { + try + { + _script?.Call(_script.LoadFile(filePath)); + } + catch(Exception e) + { + result = result.WithError(new ExceptionalError(e)); + } + } + } + + return result; } - public FluentResults.Result DisposePackageResources(ContentPackage package) + public DynValue? CallFunction(DynValue luaFunction, params object[] args) { - throw new NotImplementedException(); + if (!IsRunning) { return null; } + + lock (_script) + { + try + { + return _script.Call(luaFunction, args); + } + catch (Exception e) + { + _loggerService.HandleException(e); + } + return null; + } } public FluentResults.Result UnloadActiveScripts() { - throw new NotImplementedException(); + _isRunning = false; + + _script = null; + + // todo unregister everything + + return FluentResults.Result.Ok(); + } + + public FluentResults.Result DisposePackageResources(ContentPackage package) + { + return FluentResults.Result.Fail("Not supported for Lua"); } public FluentResults.Result DisposeAllPackageResources() { - throw new NotImplementedException(); + if (IsRunning) + { + UnloadActiveScripts(); + } + + _resourcesInfo.Clear(); + + return FluentResults.Result.Ok(); + } + + public FluentResults.Result Reset() + { + return DisposeAllPackageResources(); + } + + public void Dispose() + { + _luaScriptLoader.Dispose(); + IsDisposed = true; } public IUserDataDescriptor RegisterType(Type type) { - throw new NotImplementedException(); + return UserData.RegisterType(type); } - - public IUserDataDescriptor RegisterGenericType(Type type) - { - throw new NotImplementedException(); - } - - public IUserDataDescriptor GetTypeInfo(string typeName) - { - throw new NotImplementedException(); - } - - public IUserDataDescriptor GetGenericTypeInfo(string typeName, params string[] typeNameArgs) - { - throw new NotImplementedException(); - } - public void UnregisterType(Type type) { - throw new NotImplementedException(); + UserData.UnregisterType(type, true); } - public bool IsRegistered(Type type) + public object? GetGlobalTableValue(string tableName) { - throw new NotImplementedException(); - } + if (!IsRunning) { return null; } - public bool IsTargetType(object obj, string typeName) - { - throw new NotImplementedException(); - } - - public string TypeOf(object obj) - { - throw new NotImplementedException(); - } - - public object CreateStatic(string typeName) - { - throw new NotImplementedException(); - } - - public object CreateEnumTable(string typeName) - { - throw new NotImplementedException(); - } - - public FieldInfo FindFieldRecursively(Type type, string fieldName) - { - throw new NotImplementedException(); - } - - public void MakeFieldAccessible(IUserDataDescriptor descriptor, string fieldName) - { - throw new NotImplementedException(); - } - - public MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null) - { - throw new NotImplementedException(); - } - - public void MakeMethodAccessible(IUserDataDescriptor descriptor, string methodName, string[] parameters = null) - { - throw new NotImplementedException(); - } - - public PropertyInfo FindPropertyRecursively(Type type, string propertyName) - { - throw new NotImplementedException(); - } - - public void MakePropertyAccessible(IUserDataDescriptor descriptor, string propertyName) - { - throw new NotImplementedException(); - } - - public void AddMethod(IUserDataDescriptor descriptor, string methodName, object function) - { - throw new NotImplementedException(); - } - - public void AddField(IUserDataDescriptor descriptor, string fieldName, DynValue value) - { - throw new NotImplementedException(); - } - - public void RemoveMember(IUserDataDescriptor descriptor, string memberName) - { - throw new NotImplementedException(); - } - - public bool HasMember(object obj, string memberName) - { - throw new NotImplementedException(); - } - - public DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor descriptor) - { - throw new NotImplementedException(); - } - - public DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType) - { - throw new NotImplementedException(); - } - - public Table GetObjectTable(object obj, string tableName) - { - throw new NotImplementedException(); - } - - public Table GetTable(string tableName) - { - throw new NotImplementedException(); - } - - public Table GetOrCreateObjectTable(object obj, string tableName) - { - throw new NotImplementedException(); - } - - public Table GetOrCreateTable(string tableName) - { - throw new NotImplementedException(); + return _script.Globals[tableName]; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaDataService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaDataService.cs index 88cc45bdf..050a9329b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaDataService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaDataService.cs @@ -7,35 +7,5 @@ namespace Barotrauma.LuaCs.Services.Safe; /// public interface ILuaDataService : ILuaService { - /// - /// Returns stored table for the given object if it exists. - /// - /// - /// - /// The table data or null if none exists. - Table GetObjectTable(object obj, string tableName); - /// - /// Returns stored table data under the given name if it exists. - /// - /// - /// The table data or null if none exists. - Table GetTable(string tableName); - - /// - /// Returns stored table data for the given object or creates a new table if one doesn't exist. - /// - /// Note: tables are stored using weak references and will be automatically deleted when the object is - /// garbage collected. - /// - /// - /// - Table GetOrCreateObjectTable(object obj, string tableName); - - /// - /// Returns stored table data or creates a new table if one doesn't exist. - /// - /// - /// - Table GetOrCreateTable(string tableName); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs index aad63020f..74be9298f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs @@ -1,4 +1,6 @@ -using System; +#nullable enable + +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Reflection; @@ -14,7 +16,7 @@ public interface ILuaScriptManagementService : IReusableService { #region Script_Ops - Result GetGlobalTableValue(string tableName); + object? GetGlobalTableValue(string tableName); /// /// Parses and loads script sources (code) into a memory cache without executing it. @@ -57,62 +59,7 @@ public interface ILuaScriptManagementService : IReusableService #region Type_Registration IUserDataDescriptor RegisterType(Type type); - /// - /// [Deprecated]
- /// Use () instead. - /// Gets the type information for an already registered type. - ///
- /// The fully qualified name of the type and namespace. - /// The for the type, if registered. Null if none is found. - [Obsolete($"Use {nameof(GetTypeInfo)} instead.")] - IUserDataDescriptor RegisterType(string typeName) => GetTypeInfo(typeName); - IUserDataDescriptor RegisterGenericType(Type type); - /// - /// [Deprecated]
- /// Use () instead. - /// Gets the generic type information for an already registered type. - ///
- /// The fully qualified name of the generic type and namespace. - /// The fully qualified name of the template types. - /// The for the type, if registered. Null if none is found. - [Obsolete($"Use {nameof(GetGenericTypeInfo)} instead.")] - IUserDataDescriptor RegisterGenericType(string typeName, params string[] typeNameArgs) => GetGenericTypeInfo(typeName, typeNameArgs); - /// - /// Gets the type information for an already registered type. - /// - /// The fully qualified name of the type and namespace. - /// The for the type, if registered. Null if none is found. - IUserDataDescriptor GetTypeInfo(string typeName); - /// - /// Gets the generic type information for an already registered type. - /// - /// The fully qualified name of the generic type and namespace. - /// The fully qualified name of the template types. - /// The for the type, if registered. Null if none is found. - IUserDataDescriptor GetGenericTypeInfo(string typeName, params string[] typeNameArgs); void UnregisterType(Type type); #endregion - - #region Type_Checks_&Utilities - - bool IsRegistered(Type type); - bool IsTargetType(object obj, string typeName); - string TypeOf(object obj); - object CreateStatic(string typeName); - object CreateEnumTable(string typeName); - FieldInfo FindFieldRecursively(Type type, string fieldName); - void MakeFieldAccessible(IUserDataDescriptor descriptor, string fieldName); - MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null); - void MakeMethodAccessible(IUserDataDescriptor descriptor, string methodName, string[] parameters = null); - PropertyInfo FindPropertyRecursively(Type type, string propertyName); - void MakePropertyAccessible(IUserDataDescriptor descriptor, string propertyName); - void AddMethod(IUserDataDescriptor descriptor, string methodName, object function); - void AddField(IUserDataDescriptor descriptor, string fieldName, DynValue value); - void RemoveMember(IUserDataDescriptor descriptor, string memberName); - bool HasMember(object obj, string memberName); - DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor descriptor); - DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType); - - #endregion } From cce5bf26c830453e7b60e140c1c34d98c42a45b8 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 28 Dec 2025 00:15:30 -0500 Subject: [PATCH 018/288] Completed SafeStorageService.cs --- .../LuaCs/Services/Safe/SafeStorageService.cs | 216 +++++++++++++++--- 1 file changed, 187 insertions(+), 29 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs index 328705184..77e9a2aee 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs @@ -3,20 +3,24 @@ using System.Collections.Concurrent; using System.Collections.Immutable; using System.IO; using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; using Barotrauma.IO; using Barotrauma.LuaCs.Data; using FarseerPhysics.Common; using FluentResults; +using FluentResults.LuaCs; +using Path = System.IO.Path; namespace Barotrauma.LuaCs.Services.Safe; public class SafeStorageService : StorageService, ISafeStorageService { - private ConcurrentDictionary _fileListRead = new (), _fileListReadWrite = new(); + private ConcurrentDictionary _fileListRead = new (), _fileListWrite = new(); public SafeStorageService(IStorageServiceConfig configData) : base(configData) { - } private string GetFullPath(string path) => System.IO.Path.GetFullPath(path).CleanUpPathCrossPlatform(); @@ -28,25 +32,15 @@ public class SafeStorageService : StorageService, ISafeStorageService try { path = GetFullPath(path); - if (!readOnly && IsReadOnlyMode) + if (!_fileListRead.ContainsKey(path)) + return false; + if (!readOnly && !_fileListWrite.ContainsKey(path)) return false; - if (readOnly) - { - if (!_fileListRead.ContainsKey(path)) - return false; - } - else - { - if (!_fileListReadWrite.ContainsKey(path)) - return false; - } if (checkWhitelistOnly) return true; - using var fs = System.IO.File.Open( path, FileMode.Open, readOnly ? FileAccess.Read : FileAccess.ReadWrite, FileShare.ReadWrite); - - return true; + return readOnly ? fs.CanRead : fs.CanWrite; } catch { @@ -61,8 +55,8 @@ public class SafeStorageService : StorageService, ISafeStorageService { path = GetFullPath(path); _fileListRead.AddOrUpdate(path, s => 0, (s, b) => 0); - if (!readOnly && !IsReadOnlyMode) - _fileListRead.AddOrUpdate(path, s => 0, (s, b) => 0); + if (!readOnly) + _fileListWrite.AddOrUpdate(path, s => 0, (s, b) => 0); } catch { @@ -77,7 +71,7 @@ public class SafeStorageService : StorageService, ISafeStorageService { path = GetFullPath(path); _fileListRead.TryRemove(path, out _); - _fileListReadWrite.TryRemove(path, out _); + _fileListWrite.TryRemove(path, out _); } catch { @@ -90,34 +84,198 @@ public class SafeStorageService : StorageService, ISafeStorageService ((IService)this).CheckDisposed(); if (filePaths.IsDefaultOrEmpty) return FluentResults.Result.Fail($"{nameof(SetReadOnlyWhitelist)}: FilePaths cannot be empty."); + _fileListRead.Clear(); var res = new FluentResults.Result(); foreach (var path in filePaths) { - // TODO: Cleanup path and add it. + try + { + var p = Path.GetFullPath(path.CleanUpPathCrossPlatform()); + if (_fileListRead.ContainsKey(p)) + { + res = res.WithReason(new Success($"Path already in whitelist: {p}")); + continue; + } + + if (_fileListRead.TryAdd(p, 0)) + { + res = res.WithSuccess($"Added path successfully: {p}"); + continue; + } + + res = res.WithError(new Error($"Failed to add path to list: {p}")); + } + catch (Exception e) + { + res = res.WithError(new ExceptionalError(e) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.ExceptionDetails, e.Message) + .WithMetadata(MetadataType.RootObject, path) + ); + continue; + } } - throw new NotImplementedException(); + return res; } public FluentResults.Result SetReadWriteWhitelist(ImmutableArray filePaths) { ((IService)this).CheckDisposed(); - throw new System.NotImplementedException(); + if (filePaths.IsDefaultOrEmpty) + return FluentResults.Result.Fail($"{nameof(SetReadOnlyWhitelist)}: FilePaths cannot be empty."); + _fileListRead.Clear(); + _fileListWrite.Clear(); + var res = new FluentResults.Result(); + foreach (var path in filePaths) + { + try + { + var p = Path.GetFullPath(path.CleanUpPathCrossPlatform()); + TryAddToList(_fileListRead, p); + TryAddToList(_fileListWrite, p); + res = res.WithError(new Error($"Failed to add path to list: {p}")); + } + catch (Exception e) + { + res = res.WithError(new ExceptionalError(e) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.ExceptionDetails, e.Message) + .WithMetadata(MetadataType.RootObject, path) + ); + continue; + } + } + + void TryAddToList(ConcurrentDictionary dict, string p) + { + if (dict.ContainsKey(p)) + { + res = res.WithReason(new Success($"Path already in whitelist: {p}")); + return; + } + + if (dict.TryAdd(p, 0)) + { + res = res.WithSuccess($"Added path successfully: {p}"); + return; + } + } + + return res; } public void ClearAllWhitelists() { - throw new System.NotImplementedException(); + ((IService)this).CheckDisposed(); + _fileListRead.Clear(); + _fileListWrite.Clear(); } - private int _isReadOnlyMode = 0; - public bool IsReadOnlyMode => ModUtils.Threading.GetBool(ref _isReadOnlyMode); - - public bool EnableReadOnlyMode() + #region Base_Overrides + + private bool ReadCheck(string path) { - ModUtils.Threading.SetBool(ref _isReadOnlyMode, true); - return ModUtils.Threading.GetBool(ref _isReadOnlyMode); + return IsFileAccessible(path, true, true); } + + private bool WriteCheck(string path) + { + return IsFileAccessible(path, false, true); + } + + public override Result FileExists(string filePath) + { + if (!ReadCheck(filePath)) + return FluentResults.Result.Fail("Cannot access file."); + return base.FileExists(filePath); + } + + public override Result TryLoadBinary(string filePath) + { + if (!ReadCheck(filePath)) + return FluentResults.Result.Fail("Cannot access file."); + return base.TryLoadBinary(filePath); + } + + public override async Task> TryLoadBinaryAsync(string filePath) + { + if (!ReadCheck(filePath)) + return FluentResults.Result.Fail("Cannot access file."); + return await base.TryLoadBinaryAsync(filePath); + } + + public override Result TryLoadText(string filePath, Encoding encoding = null) + { + if (!ReadCheck(filePath)) + return FluentResults.Result.Fail("Cannot access file."); + return base.TryLoadText(filePath, encoding); + } + + public override async Task> TryLoadTextAsync(string filePath, Encoding encoding = null) + { + if (!ReadCheck(filePath)) + return FluentResults.Result.Fail("Cannot access file."); + return await base.TryLoadTextAsync(filePath, encoding); + } + + public override Result TryLoadXml(string filePath, Encoding encoding = null) + { + if (!ReadCheck(filePath)) + return FluentResults.Result.Fail("Cannot access file."); + return base.TryLoadXml(filePath, encoding); + } + + public override async Task> TryLoadXmlAsync(string filePath, Encoding encoding = null) + { + if (!ReadCheck(filePath)) + return FluentResults.Result.Fail("Cannot access file."); + return await base.TryLoadXmlAsync(filePath, encoding); + } + + public override FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes) + { + if (!WriteCheck(filePath)) + return FluentResults.Result.Fail("Cannot write to file."); + return base.TrySaveBinary(filePath, in bytes); + } + + public override async Task TrySaveBinaryAsync(string filePath, byte[] bytes) + { + if (!WriteCheck(filePath)) + return FluentResults.Result.Fail("Cannot write to file."); + return await base.TrySaveBinaryAsync(filePath, bytes); + } + + public override FluentResults.Result TrySaveText(string filePath, in string text, Encoding encoding = null) + { + if (!WriteCheck(filePath)) + return FluentResults.Result.Fail("Cannot write to file."); + return base.TrySaveText(filePath, in text, encoding); + } + + public override async Task TrySaveTextAsync(string filePath, string text, Encoding encoding = null) + { + if (!WriteCheck(filePath)) + return FluentResults.Result.Fail("Cannot write to file."); + return await base.TrySaveTextAsync(filePath, text, encoding); + } + + public override FluentResults.Result TrySaveXml(string filePath, in XDocument document, Encoding encoding = null) + { + if (!WriteCheck(filePath)) + return FluentResults.Result.Fail("Cannot write to file."); + return base.TrySaveXml(filePath, in document, encoding); + } + + public override async Task TrySaveXmlAsync(string filePath, XDocument document, Encoding encoding = null) + { + if (!WriteCheck(filePath)) + return FluentResults.Result.Fail("Cannot write to file."); + return await base.TrySaveXmlAsync(filePath, document, encoding); + } + + #endregion } From 26b657a96fd942960b0d235f78bf853f9c437e0b Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 28 Dec 2025 07:23:58 -0500 Subject: [PATCH 019/288] [In-Progress] Plugin system rewrite. Game starts up, runs until unimplemented functions are reached without errors. --- .../LuaCs/Data/ServicesConfigData.cs | 26 ++- .../SharedSource/LuaCs/LuaCsSetup.cs | 10 +- .../LuaCs/Services/PluginManagementService.cs | 210 ++++++++---------- .../LuaCs/Services/ServicesProvider.cs | 2 +- .../_Interfaces/IAssemblyManagementService.cs | 28 +-- .../_Interfaces/IPluginManagementService.cs | 26 +-- .../LuaCs/_Plugins/AssemblyLoader.cs | 28 ++- .../LuaCs/_Plugins/IAssemblyLoaderService.cs | 30 ++- 8 files changed, 177 insertions(+), 183 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs index f3e3bab2d..b5ee25eb9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Security.AccessControl; +using Barotrauma.LuaCs.Services; using Barotrauma.Networking; using FluentResults; @@ -14,7 +15,8 @@ namespace Barotrauma.LuaCs.Data; // --- Storage Service -public interface IStorageServiceConfig +// TODO: Configs should not be services, add new registration path for them. +public interface IStorageServiceConfig : IService { string LocalModsDirectory { get; } string WorkshopModsDirectory { get; } @@ -176,10 +178,17 @@ public record StorageServiceConfig : IStorageServiceConfig, IStorageServiceConfi return result.WithSuccess($"Whitelist updated."); } + + public void Dispose() + { + // cannot be disposed. + } + + public bool IsDisposed => false; } // --- Config Service -public interface IConfigServiceConfig +public interface IConfigServiceConfig : IService { string LocalConfigPathPartial { get; } string FileNamePattern { get; } @@ -189,11 +198,16 @@ public record ConfigServiceConfig : IConfigServiceConfig { public string LocalConfigPathPartial => $"/Config/{FileNamePattern}.xml"; public string FileNamePattern => ""; + public void Dispose() + { + // ignored + } + public bool IsDisposed => false; } // --- Lua Scripts Service -public interface ILuaScriptServicesConfig +public interface ILuaScriptServicesConfig : IService { bool SafeLuaIOEnabled { get; } bool UseCaching { get; } @@ -203,4 +217,10 @@ public record LuaScriptServicesConfig : ILuaScriptServicesConfig { public bool SafeLuaIOEnabled => true; public bool UseCaching => true; + public void Dispose() + { + // ignored + } + + public bool IsDisposed => false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 4996dcc00..691486a54 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -12,6 +12,7 @@ using Barotrauma.LuaCs.Events; using Barotrauma.LuaCs.Services; using Barotrauma.LuaCs.Services.Compatibility; using Barotrauma.LuaCs.Services.Processing; +using Barotrauma.LuaCs.Services.Safe; using Barotrauma.Networking; using FluentResults; using ImpromptuInterface; @@ -47,6 +48,7 @@ namespace Barotrauma _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); // TODO: IConfigService @@ -64,6 +66,12 @@ namespace Barotrauma _servicesProvider.RegisterServiceType, ModConfigService>(ServiceLifetime.Transient); _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + // service config data + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + + // gen IL _servicesProvider.Compile(); } @@ -702,7 +710,7 @@ namespace Barotrauma { EventService.ClearAllSubscribers(); LuaScriptManagementService.UnloadActiveScripts(); - PluginManagementService.UnloadAllAssemblyResources(); + PluginManagementService.UnloadManagedAssemblies(); SubscribeToLuaCsEvents(); if (IsCodeRunning) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 5289e946a..d3e9d8a8b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -10,96 +10,116 @@ using System.Runtime.Loader; using System.Threading; using Barotrauma.Extensions; using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Events; using FluentResults; using Microsoft.CodeAnalysis; +using OneOf; namespace Barotrauma.LuaCs.Services; public class PluginManagementService : IPluginManagementService, IAssemblyManagementService { + private readonly Func _assemblyLoaderServiceFactory; + private readonly ConcurrentDictionary ResourceInfos, IAssemblyLoaderService Loader)> _packageAssemblyResources; + private readonly ConcurrentDictionary> _pluginInstances; + private readonly Lazy _eventService; + private readonly ConditionalWeakTable _unloadingAssemblyLoaders; + private readonly ConditionalWeakTable> _assemblyTypesCache; + + public PluginManagementService( + Func assemblyLoaderServiceFactory, + Lazy eventService) + { + _assemblyLoaderServiceFactory = assemblyLoaderServiceFactory; + _eventService = eventService; + AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoadedGlobal; + } + + private void OnAssemblyLoadedGlobal(object sender, AssemblyLoadEventArgs args) + { + // cache types by name + try + { + var context = AssemblyLoadContext.GetLoadContext(args.LoadedAssembly); + if (context is not IAssemblyLoaderService loaderService) + return; + _eventService.Value.PublishEvent(sub => sub.OnAssemblyLoaded(args.LoadedAssembly)); + var lookupDict = new ConcurrentDictionary(); + foreach (var type in args.LoadedAssembly.GetSafeTypes()) + { + lookupDict[type.FullName ?? type.Name] = type; + } + _assemblyTypesCache.AddOrUpdate(args.LoadedAssembly, lookupDict); + } + catch (Exception e) + { + // ignored + return; + } + } + + private int _isDisposed = 0; public bool IsDisposed { get => ModUtils.Threading.GetBool(ref _isDisposed); - set => ModUtils.Threading.SetBool(ref _isDisposed, value); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); } - private int _isDisposed; - - private readonly ReaderWriterLockSlim _operationsLock = new(LockRecursionPolicy.SupportsRecursion); - - private readonly ConcurrentDictionary _assemblyServices = new(); - private readonly ConcurrentDictionary _resourceData = new(); - private readonly Lazy _eventService; - private readonly Func _assemblyServiceFactory; - private ImmutableDictionary _cachedTypes = null; - private ImmutableDictionary DefaultTypeCache => _cachedTypes ??= AssemblyLoadContext.Default.Assemblies - .SelectMany(ass => ass.GetSafeTypes()).ToImmutableDictionary(type => type.FullName, type => type); - - public bool IsResourceLoaded(T resource) where T : IAssemblyResourceInfo + public void Dispose() { - ((IService)this).CheckDisposed(); - return _resourceData.ContainsKey(resource); + throw new NotImplementedException(); } - public Result> GetImplementingTypes(string namespacePrefix = null, bool includeInterfaces = false, + public FluentResults.Result Reset() + { + if (IsDisposed) + return FluentResults.Result.Fail($"{nameof(PluginManagementService)} is disposed!"); + + throw new NotImplementedException(); + } + + public Result> GetImplementingTypes(bool includeInterfaces = false, bool includeAbstractTypes = false, bool includeDefaultContext = true) { - ((IService)this).CheckDisposed(); - var types = ImmutableArray.CreateBuilder(); - _operationsLock.EnterReadLock(); - try - { - if (AssemblyLoaderServices.Any()) - { - types.AddRange(AssemblyLoaderServices - .SelectMany(als => als.UnsafeGetTypesInAssemblies()) - .Where(t => t is not null) - .Where(type => typeof(T).IsAssignableFrom(type)) - .Where(type => includeInterfaces || !type.IsInterface) - .Where(type => includeAbstractTypes || !type.IsAbstract) - .Where(type => namespacePrefix is not null && type.FullName is not null && type.FullName.StartsWith(namespacePrefix))); - } + var builder = ImmutableArray.CreateBuilder(); - if (includeDefaultContext) - { - types.AddRange(AssemblyLoadContext.Default.Assemblies - .SelectMany(ass => ass.GetSafeTypes()) - .Where(t => t is not null) - .Where(type => typeof(T).IsAssignableFrom(type)) - .Where(type => includeInterfaces || !type.IsInterface) - .Where(type => includeAbstractTypes || !type.IsAbstract) - .Where(type => namespacePrefix is not null && type.FullName is not null && type.FullName.StartsWith(namespacePrefix))); - } - - return types.MoveToImmutable(); - } - finally + if (this._packageAssemblyResources.Any()) { - _operationsLock.ExitReadLock(); + foreach (var resource in this._packageAssemblyResources + .Where(res => !res.Value.Loader.IsReferenceOnlyMode)) + { + builder.AddRange(resource.Value.Loader.Assemblies + .SelectMany(assembly => assembly.GetSafeTypes()) + .Where(type => type.IsAssignableTo(typeof(T))) + .Where(type => includeInterfaces || !type.IsInterface) + .Where(type => includeAbstractTypes || !type.IsAbstract)); + } } + + if (includeDefaultContext) + { + builder.AddRange(AssemblyLoadContext.Default.Assemblies + .SelectMany(assembly => assembly.GetSafeTypes()) + .Where(type => type.IsAssignableTo(typeof(T))) + .Where(type => includeInterfaces || !type.IsInterface) + .Where(type => includeAbstractTypes || !type.IsAbstract)); + } + + return builder.Count == 0 + ? FluentResults.Result.Fail($"Failed to find any types that implement {typeof(T).Name})") + : FluentResults.Result.Ok(builder.ToImmutable()); } - public Type GetType(string typeName) + public Type GetType(string typeName, bool isByRefType = false, bool includeInterfaces = false, + bool includeDefaultContext = true) { - ((IService)this).CheckDisposed(); - _operationsLock.EnterReadLock(); - try + if (includeDefaultContext) { - if (DefaultTypeCache.TryGetValue(typeName, out var type)) - return type; - if (AssemblyLoaderServices.None()) - return null; - foreach (var loaderService in AssemblyLoaderServices) - { - if (loaderService.GetTypeInAssemblies(typeName) is { IsSuccess: true, Value: not null } ret) - return ret.Value; - } - return null; - } - finally - { - _operationsLock.ExitReadLock(); + var type = Type.GetType(typeName, false); } + + // TODO: implement by-ref type resolution + throw new NotImplementedException(); } public Result> LoadAssemblyResources(ImmutableArray resource) @@ -113,72 +133,20 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage throw new NotImplementedException(); } - public FluentResults.Result UnloadHostedReferences() + public FluentResults.Result UnloadManagedAssemblies() { throw new NotImplementedException(); } - public FluentResults.Result UnloadAllAssemblyResources() + public Result GetLoadedAssembly(OneOf assemblyName, in Guid[] excludedContexts) { throw new NotImplementedException(); } - public Result GetLoadedAssembly(string assemblyName, in Guid[] excludedContexts) + public ImmutableArray GetDefaultMetadataReferences(bool includeDefaultContext = true) { - ((IService)this).CheckDisposed(); - _operationsLock.EnterReadLock(); - try - { - foreach (var (guid, context) in _assemblyServices) - { - if (excludedContexts.Length > 0 && excludedContexts.Contains(guid)) - continue; - if (context.GetAssemblyByName(assemblyName) is { IsSuccess: true, Value: not null } ret) - return ret.Value; - } - return FluentResults.Result.Fail($"Could not find assembly {assemblyName}"); - } - finally - { - _operationsLock.ExitReadLock(); - } - } - - public Result GetLoadedAssembly(AssemblyName assemblyName, in Guid[] excludedContexts) - => GetLoadedAssembly(assemblyName.FullName, excludedContexts); - - public ImmutableArray GetDefaultMetadataReferences() => - Basic.Reference.Assemblies.Net60.References.All.Select(Unsafe.As).ToImmutableArray(); - - public ImmutableArray GetAddInContextsMetadataReferences() - { - ((IService)this).CheckDisposed(); - _operationsLock.EnterReadLock(); - try - { - if (_assemblyServices.IsEmpty) - return ImmutableArray.Empty; - var builder = ImmutableArray.CreateBuilder(); - foreach (var context in _assemblyServices.Values) - builder.AddRange(context.AssemblyReferences); - return builder.ToImmutable(); - } - finally - { - _operationsLock.ExitReadLock(); - } + throw new NotImplementedException(); } public ImmutableArray AssemblyLoaderServices { get; } - - public void Dispose() - { - // TODO release managed resources here - throw new NotImplementedException(); - } - - public FluentResults.Result Reset() - { - throw new NotImplementedException(); - } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs index 49d45cb75..9b2c6420b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs @@ -134,7 +134,7 @@ public class ServicesProvider : IServicesProvider service = ServiceContainer.TryGetInstance(); return service is not null; } - catch + catch { service = null; return false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IAssemblyManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IAssemblyManagementService.cs index b347a301f..2499d55f8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IAssemblyManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IAssemblyManagementService.cs @@ -6,41 +6,33 @@ using System.Reflection; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using OneOf; + // ReSharper disable InconsistentNaming namespace Barotrauma.LuaCs.Services; public interface IAssemblyManagementService : IReusableService { - /// - /// Searches for an assembly given it's fully qualified name, while excluding the contexts with the given Guids, if supplied. - /// - /// The fully-qualified assembly name. - /// Guids of excluded contexts. - /// On Success: The assembly.
On Failure: nothing.
- FluentResults.Result GetLoadedAssembly(string assemblyName, in Guid[] excludedContexts); + /// /// Searches for an assembly given it's fully qualified name, while excluding the contexts with the given Guids, if supplied. /// /// The assembly info. /// Guids of excluded contexts. /// On Success: The assembly.
On Failure: nothing.
- FluentResults.Result GetLoadedAssembly(AssemblyName assemblyName, in Guid[] excludedContexts); + FluentResults.Result GetLoadedAssembly(OneOf assemblyName, in Guid[] excludedContexts); /// - /// Gets the assembly collection for the BCL and base game assemblies. + /// Gets all for all service-managed assemblies. /// - /// collection, if any are found. Returns an empty collection otherwise. - ImmutableArray GetDefaultMetadataReferences(); + /// collection for all service-managed, and default if selected, assemblies, if any are found. Returns an empty collection otherwise. + ImmutableArray GetDefaultMetadataReferences(bool includeDefaultContext = true); /// - /// Gets the assembly collection for all add-in assemblies loaded. - /// - /// collection, if any are found. Returns an empty collection otherwise. - ImmutableArray GetAddInContextsMetadataReferences(); - - /// - /// + /// Returns all active, managed assembly loaders. /// ImmutableArray AssemblyLoaderServices { get; } + + } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs index 6319573bb..dbb87bdf9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs @@ -10,33 +10,27 @@ namespace Barotrauma.LuaCs.Services; public interface IPluginManagementService : IReusableService { /// - /// Checks if the supplied resource is currently loaded. + /// Gets all types in searched that implement the type supplied. /// - /// The resource to check. - /// - bool IsResourceLoaded(T resource) where T : IAssemblyResourceInfo; - - /// - /// - /// - /// /// /// /// /// /// FluentResults.Result> GetImplementingTypes( - string namespacePrefix = null, bool includeInterfaces = false, bool includeAbstractTypes = false, bool includeDefaultContext = true); /// - /// Tries to get the Type given the fully qualified name. + /// Tries to find the type given the fully qualified name and filters. /// /// + /// + /// + /// /// - Type GetType(string typeName); + Type GetType(string typeName, bool isByRefType = false, bool includeInterfaces = false, bool includeDefaultContext = true); /// /// Loads the provided assembly resources in the order of their dependencies and intra-mod priority load order. @@ -56,11 +50,9 @@ public interface IPluginManagementService : IReusableService IReadOnlyList> ActivateTypeInstances(ImmutableArray types, bool serviceInjection = true, bool hostInstanceReference = false) where T : IDisposable; - FluentResults.Result UnloadHostedReferences(); - /// - /// Tries to gracefully unload all hosted plugin references + /// Unloads all managed , , and s. /// - /// - FluentResults.Result UnloadAllAssemblyResources(); + /// Success of the operation.
Note: does not guarantee .NET runtime assembly unloading success.
+ FluentResults.Result UnloadManagedAssemblies(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs index ce6334379..a7cebbd5c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs @@ -25,6 +25,7 @@ namespace Barotrauma.LuaCs.Services; public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService { public Guid Id { get; init; } + public ContentPackage OwnerPackage { get; private set; } public bool IsReferenceOnlyMode { get; init; } public bool IsDisposed { @@ -55,8 +56,8 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService //internal private readonly IAssemblyManagementService _assemblyManagementService; - private readonly Action _onUnload; - private readonly Func _onResolvingManaged; + private readonly Action _onUnload; + private readonly Func _onResolvingManaged; private readonly Func _onResolvingUnmanagedDll; private readonly ConcurrentDictionary _dependencyResolvers = new(); private readonly ConcurrentDictionary _loadedAssemblyData = new(); @@ -64,16 +65,16 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService private readonly ThreadLocal _isResolving = new(static()=>false); // cyclic resolution exit private readonly ThreadLocal _isResolvingNative = new(static () => false); - public AssemblyLoader( - IAssemblyManagementService assemblyManagementService, - Guid id, string name, - bool isReferenceOnlyMode, - Action onUnload = null) - : base(isCollectible: true, name: name) + public AssemblyLoader(IAssemblyLoaderService.LoaderInitData initData) + : base(isCollectible: true, name: initData.Name) { - _assemblyManagementService = assemblyManagementService; - Id = id; - IsReferenceOnlyMode = isReferenceOnlyMode; + _assemblyManagementService = initData.AssemblyManagementService; + Id = initData.InstanceId; + IsReferenceOnlyMode = initData.IsReferenceMode; + this._onUnload = initData.OnUnload; + this._onResolvingManaged = initData.OnResolvingManaged; + this._onResolvingUnmanagedDll = initData.OnResolvingUnmanagedDll; + this.OwnerPackage = initData.OwnerPackage; base.Unloading += OnUnload; base.Resolving += OnResolvingManagedAssembly; base.ResolvingUnmanagedDll += OnResolvingUnmanagedDll; @@ -138,6 +139,9 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService if (_isResolving.Value) return null; + + if (assemblyLoadContext != this) + return null; AreOperationRunning = true; _isResolving.Value = true; @@ -166,7 +170,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService { try { - return _onResolvingManaged(assemblyLoadContext, assemblyName); + return _onResolvingManaged(this, assemblyName); } catch { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs index 6f4dc1159..071effa1a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; @@ -13,17 +14,26 @@ namespace Barotrauma.LuaCs; public interface IAssemblyLoaderService : IService { /// - /// Assembly loader factory for DI registration. + /// Constructor record for instancing. /// - /// The assembly hosting management service. - /// The event service for publishing. - /// The referencing ID. Intended to be used to distinguish between instances. - /// The name of the friendly name instance, used for error messages. - /// Loaded assemblies are not intended for execution, just MetadataReferences. - delegate IAssemblyLoaderService AssemblyLoaderDelegate( - IAssemblyManagementService assemblyManagementService, - IEventService eventService, Guid id, string name, - bool isReferenceOnlyMode, Action onUnload); + /// + /// + /// + /// Assemblies and Types in this context are for only. + /// Execution of assembly data is forbidden. + /// + /// + /// + /// + public record LoaderInitData( + [Required][NotNull] IAssemblyManagementService AssemblyManagementService, + [Required] Guid InstanceId, + [Required][NotNull] string Name, + [Required] bool IsReferenceMode, + ContentPackage OwnerPackage, + Action OnUnload, + Func OnResolvingManaged, + Func OnResolvingUnmanagedDll); /// /// ID for this instance. From bd5d04f5ab99560b4cf7db8ef11873f9351fd030 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Mon, 29 Dec 2025 03:50:26 -0500 Subject: [PATCH 020/288] Work on plugin loader. --- .../LuaCs/Services/PluginManagementService.cs | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index d3e9d8a8b..2138961e7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -12,6 +12,7 @@ using Barotrauma.Extensions; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Events; using FluentResults; +using FluentResults.LuaCs; using Microsoft.CodeAnalysis; using OneOf; @@ -135,7 +136,58 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage public FluentResults.Result UnloadManagedAssemblies() { - throw new NotImplementedException(); + var res = new FluentResults.Result(); + + // cleanup managed plugins + if (_pluginInstances.Any()) + { + foreach (var packageInstances in _pluginInstances) + { + if (!packageInstances.Value.Any()) + continue; + + foreach (var disposable in packageInstances.Value) + { + try + { + disposable.Dispose(); + } + catch (Exception e) + { + res = res.WithError(new ExceptionalError(e) + .WithMetadata(MetadataType.ExceptionObject, this)); + } + } + } + _pluginInstances.Clear(); + } + + _assemblyTypesCache.Clear(); + + // cleanup running assembly contexts + if (_packageAssemblyResources.Any()) + { + foreach (var resource in _packageAssemblyResources.ToImmutableDictionary()) + { + if (resource.Value.Loader is not null) + { + try + { + resource.Value.Loader.Dispose(); + _unloadingAssemblyLoaders.AddOrUpdate(resource.Value.Loader, resource.Key); + _packageAssemblyResources.TryRemove(resource); + _packageAssemblyResources.TryAdd(resource.Key, (resource.Value.ResourceInfos, null)); + } + catch (Exception e) + { + res = res.WithError(new ExceptionalError(e) + .WithMetadata(MetadataType.ExceptionObject, this)); + } + } + } + } + + return res.WithSuccess($"Unloading of managed assemblies started successfully,"); } public Result GetLoadedAssembly(OneOf assemblyName, in Guid[] excludedContexts) From 71c2e54afd81f29d929e13224b2258b8fc48bf13 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Mon, 29 Dec 2025 11:52:35 -0300 Subject: [PATCH 021/288] Remove CheckUpdate --- .../BarotraumaClient/ClientSource/GameMain.cs | 3 - .../ClientSource/LuaCs/LuaCsInstaller.cs | 68 ------------------- 2 files changed, 71 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index 92b024ed0..874de92fc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -636,9 +636,6 @@ namespace Barotrauma HasLoaded = true; log("LOADING COROUTINE FINISHED"); -#if CLIENT - LuaCsInstaller.CheckUpdate(); -#endif contentLoaded = true; while (postContentLoadActions.TryDequeue(out Action action)) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs index 490ce38d1..4a7f9fdea 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs @@ -54,73 +54,5 @@ namespace Barotrauma return true; }; } - - public static void CheckUpdate() - { - throw new NotImplementedException(); - /*// TODO: Rewrite this to not rely on LuaCsSetup. - - if (!File.Exists(LuaCsSetup.VersionFile)) { return; } - - ContentPackage luaPackage = LuaCsSetup.GetPackage(new SteamWorkshopId(GameMain.LuaCs.LuaForBarotraumaSteamId?.Value ?? 0)); - - if (luaPackage == null) { return; } - - string luaCsPath = Path.GetDirectoryName(luaPackage.Path); - string clientVersion = File.ReadAllText(LuaCsSetup.VersionFile); - string workshopVersion = luaPackage.ModVersion; - - if (clientVersion == workshopVersion || File.Exists("debugsomething")) { return; } - - var msg = new GUIMessageBox($"LuaCs Update", $"Your LuaCs client version is different from the version found in the LuaCsForBarotrauma workshop files. Do you want to update?\n\n Client Version: {clientVersion}\n Workshop Version: {workshopVersion}", - new LocalizedString[2] { TextManager.Get("Yes"), TextManager.Get("Cancel") }); - - msg.Buttons[0].OnClicked = (GUIButton button, object obj) => - { - string[] filesToUpdate = trackingFiles.Concat(Directory.EnumerateFiles(luaCsPath, "*.dll", SearchOption.AllDirectories) - .Where(s => s.Contains("mscordaccore_amd64_amd64")).Select(s => Path.GetFileName(s))).ToArray(); - - try - { - CreateMissingDirectory(); - - foreach (string file in filesToUpdate) - { - try - { - File.Move(file, "Temp/Old/" + file, true); - File.Copy(Path.Combine(luaCsPath, "Binary", file), file, true); - } - catch (Exception e) - { - DebugConsole.ThrowError($"Failed to update file {e}"); - } - - } - - File.WriteAllText(LuaCsSetup.VersionFile, workshopVersion); - } - catch (Exception e) - { - new GUIMessageBox("Failed", $"Failed to update, error: {e}"); - - msg.Close(); - return true; - } - - new GUIMessageBox("Restart", $"LuaCs updated! Restart your game to apply the changes."); - - msg.Close(); - return true; - }; - - msg.Buttons[1].OnClicked = (GUIButton button, object obj) => - { - msg.Close(); - return true; - }; - */ - - } } } From 569e14f50f5f4448c6bde914c3f0d97e3668c91b Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 30 Dec 2025 12:00:14 -0500 Subject: [PATCH 022/288] - Removed generic exception from LuaCsSetup.cs services retrival, replaced with exception propagation. - Fixed `PerScopeLifetime` and `Transient` lifetimes for container services. --- .../SharedSource/LuaCs/LuaCsSetup.cs | 33 +++++++--------- .../LuaCs/Services/ServicesProvider.cs | 38 ++++++++++++------- .../Services/_Interfaces/IServicesProvider.cs | 7 ++++ 3 files changed, 46 insertions(+), 32 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 691486a54..030427c3f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -111,25 +111,20 @@ namespace Barotrauma */ private readonly IServicesProvider _servicesProvider; - - public PerformanceCounterService PerformanceCounter => _servicesProvider.TryGetService(out var svc) - ? svc : throw new NullReferenceException("Performance counter service not found!"); - public ILoggerService Logger => _servicesProvider.TryGetService(out var svc) - ? svc : throw new NullReferenceException("Logger service not found!"); - public IConfigService ConfigService => _servicesProvider.TryGetService(out var svc) - ? svc : throw new NullReferenceException("Config Manager service not found!"); - public IPackageManagementService PackageManagementService => _servicesProvider.TryGetService(out var svc) - ? svc : throw new NullReferenceException("Package Manager service not found!"); - public IPluginManagementService PluginManagementService => _servicesProvider.TryGetService(out var svc) - ? svc : throw new NullReferenceException("Plugin Manager service not found!"); - public ILuaScriptManagementService LuaScriptManagementService => _servicesProvider.TryGetService(out var svc) - ? svc : throw new NullReferenceException("Lua Script Manager service not found!"); - public INetworkingService NetworkingService => _servicesProvider.TryGetService(out var svc) - ? svc : throw new NullReferenceException("Networking Manager service not found!"); - public IEventService EventService => _servicesProvider.TryGetService(out var svc) - ? svc : throw new NullReferenceException("Networking Manager service not found!"); - public LuaGame Game => _servicesProvider.TryGetService(out var svc) - ? svc : throw new NullReferenceException("LuaGame service not found!"); + + public PerformanceCounterService PerformanceCounter => + _servicesProvider.GetService(); + public ILoggerService Logger => _servicesProvider.GetService(); + public IConfigService ConfigService => _servicesProvider.GetService(); + public IPackageManagementService PackageManagementService => + _servicesProvider.GetService(); + public IPluginManagementService PluginManagementService => + _servicesProvider.GetService(); + public ILuaScriptManagementService LuaScriptManagementService => + _servicesProvider.GetService(); + public INetworkingService NetworkingService => _servicesProvider.GetService(); + public IEventService EventService => _servicesProvider.GetService(); + public LuaGame Game => _servicesProvider.GetService(); /* * === Config Vars diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs index 9b2c6420b..17213a0cf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs @@ -11,18 +11,14 @@ namespace Barotrauma.LuaCs.Services; public class ServicesProvider : IServicesProvider { private ServiceContainer _serviceContainerInst; - private ServiceContainer ServiceContainer - { - get - { - // ReSharper disable once ConvertIfStatementToNullCoalescingExpression - if (_serviceContainerInst is null) - _serviceContainerInst = new ServiceContainer(); - return _serviceContainerInst; - } - } + private ServiceContainer ServiceContainer => _serviceContainerInst; private readonly ReaderWriterLockSlim _serviceLock = new(); + + public ServicesProvider() + { + _serviceContainerInst = new ServiceContainer(); + } public void RegisterServiceType(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface { @@ -39,9 +35,9 @@ public class ServicesProvider : IServicesProvider // treat these as transient case ServiceLifetime.Transient: case ServiceLifetime.Invalid: - case ServiceLifetime.Custom: // lifetime should not be null here + case ServiceLifetime.Custom: default: - lifetimeInstance = new PerRequestLifeTime(); + lifetimeInstance = null; break; } } @@ -49,7 +45,10 @@ public class ServicesProvider : IServicesProvider try { _serviceLock.EnterReadLock(); - ServiceContainer.Register(lifetimeInstance); + if (lifetimeInstance is not null) + ServiceContainer.Register(lifetimeInstance); + else + ServiceContainer.Register(); OnServiceRegistered?.Invoke(typeof(TSvcInterface), typeof(TService)); } finally @@ -145,6 +144,19 @@ public class ServicesProvider : IServicesProvider } } + public TSvcInterface GetService() where TSvcInterface : class, IService + { + try + { + _serviceLock.EnterReadLock(); + return ServiceContainer.GetInstance(); + } + finally + { + _serviceLock.ExitReadLock(); + } + } + public bool TryGetService(string name, out TSvcInterface service) where TSvcInterface : class, IService { try diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IServicesProvider.cs index 40dd31e6c..cce7a24c7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IServicesProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IServicesProvider.cs @@ -62,6 +62,13 @@ public interface IServicesProvider /// bool TryGetService(out TSvcInterface service) where TSvcInterface : class, IService; + /// + /// Tries to get a service for the given interface, throws an exception upon failure. + /// + /// + /// + TSvcInterface GetService() where TSvcInterface : class, IService; + /// /// Tries to get a service for the given name and interface, returns success/failure. /// From 6499e7608c3c0ff8850ed3f6a385563c1812c74a Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Tue, 30 Dec 2025 14:40:11 -0300 Subject: [PATCH 023/288] Fix package management service constructor and create [DebugOnlyTest]TestLuaMod --- .../LuaCs/Services/PackageManagementService.cs | 16 +--------------- .../Lua/Autorun/init.lua | 1 + .../[DebugOnlyTest]TestLuaMod/filelist.xml | 3 +++ .../LuaCs/Services/PackageManagementService.cs | 15 +++++++++++++++ 4 files changed, 20 insertions(+), 15 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/Autorun/init.lua create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/filelist.xml diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageManagementService.cs index 99e66743e..ef6ee88a6 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageManagementService.cs @@ -6,19 +6,5 @@ namespace Barotrauma.LuaCs.Services; public partial class PackageManagementService { - public PackageManagementService( - IConverterServiceAsync modConfigParserService, - IProcessorService, IAssembliesResourcesInfo> assemblyInfoConverter, - IProcessorService, IConfigsResourcesInfo> configsInfoConverter, - IProcessorService, IConfigProfilesResourcesInfo> configProfilesConverter, - IProcessorService, ILuaScriptsResourcesInfo> luaScriptsConverter, - IPackageInfoLookupService packageInfoLookupService) - { - _modConfigParserService = modConfigParserService; - _assemblyInfoConverter = assemblyInfoConverter; - _configsInfoConverter = configsInfoConverter; - _configProfilesConverter = configProfilesConverter; - _luaScriptsConverter = luaScriptsConverter; - _packageInfoLookupService = packageInfoLookupService; - } + } diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/Autorun/init.lua b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/Autorun/init.lua new file mode 100644 index 000000000..f8f41c492 --- /dev/null +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/Autorun/init.lua @@ -0,0 +1 @@ +print("Hello!") \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/filelist.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/filelist.xml new file mode 100644 index 000000000..0a9ea0fd3 --- /dev/null +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/filelist.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index 318da7814..077c148ff 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -29,6 +29,21 @@ public partial class PackageManagementService : IPackageManagementService private readonly IProcessorService, IConfigProfilesResourcesInfo> _configProfilesConverter; private readonly IProcessorService, ILuaScriptsResourcesInfo> _luaScriptsConverter; + public PackageManagementService( + IConverterServiceAsync modConfigParserService, + IProcessorService, IAssembliesResourcesInfo> assemblyInfoConverter, + IProcessorService, IConfigsResourcesInfo> configsInfoConverter, + IProcessorService, IConfigProfilesResourcesInfo> configProfilesConverter, + IProcessorService, ILuaScriptsResourcesInfo> luaScriptsConverter, + IPackageInfoLookupService packageInfoLookupService) + { + _modConfigParserService = modConfigParserService; + _assemblyInfoConverter = assemblyInfoConverter; + _configsInfoConverter = configsInfoConverter; + _configProfilesConverter = configProfilesConverter; + _luaScriptsConverter = luaScriptsConverter; + _packageInfoLookupService = packageInfoLookupService; + } public void Dispose() { From d70885711bed7180f061627b781c776d40b40de1 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Tue, 30 Dec 2025 14:59:53 -0300 Subject: [PATCH 024/288] Fix some runstate if checks --- .../SharedSource/LuaCs/LuaCsSetup.cs | 32 +++++++++++++------ .../LuaCs/Services/LoggerService.cs | 9 ++++-- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 030427c3f..d2596cd64 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -400,20 +400,30 @@ namespace Barotrauma return; if (runState > CurrentRunState) { - if (CurrentRunState < RunState.Parsed) + if (CurrentRunState <= RunState.Parsed) + { LoadCurrentContentPackageInfos(); - - if (runState <= CurrentRunState) - return; + } - if (CurrentRunState < RunState.Configuration) + if (runState <= CurrentRunState) + { + return; + } + + if (CurrentRunState <= RunState.Configuration) + { LoadStaticAssets(); + } if (runState <= CurrentRunState) + { return; + } - if (CurrentRunState < RunState.Running) + if (CurrentRunState <= RunState.Running) + { RunScripts(); + } } else if (runState < CurrentRunState) { @@ -423,18 +433,22 @@ namespace Barotrauma ProcessPackagesListDifferences(); _runState = RunState.Configuration; } - + if (runState >= CurrentRunState) + { return; + } if (CurrentRunState == RunState.Configuration) { UnloadStaticAssets(); _runState = RunState.Parsed; } - + if (runState >= CurrentRunState) + { return; + } if (CurrentRunState == RunState.Parsed) { @@ -487,7 +501,7 @@ namespace Barotrauma void LoadStaticAssets() { - if (CurrentRunState < RunState.Parsed) + if (CurrentRunState <= RunState.Parsed) { throw new InvalidOperationException($"{nameof(LoadStaticAssets)} cannot load assets in the '{CurrentRunState}' state."); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs index 7ad55c0e7..b5a016914 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs @@ -68,7 +68,12 @@ public partial class LoggerService : ILoggerService public void LogWarning(string message) { - throw new NotImplementedException(); + if (HideUserNames && !Environment.UserName.IsNullOrEmpty()) + { + message = message.Replace(Environment.UserName, "USERNAME"); + } + + Log($"{message}", Color.Yellow, ServerLog.MessageType.ServerMessage); } public void LogMessage(string message, Color? serverColor = null, Color? clientColor = null) @@ -131,7 +136,7 @@ public partial class LoggerService : ILoggerService public void LogResults(FluentResults.Result result) { - throw new NotImplementedException(); + LogError("LogResults not implemented"); } public void LogDebug(string message, Color? color = null) From 4f8531332e49e5c18b3915d8dbc92899eec6d107 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Tue, 30 Dec 2025 15:26:27 -0300 Subject: [PATCH 025/288] Implement LogResults --- .../LuaCs/Services/LoggerService.cs | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs index b5a016914..ed1e3c42e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs @@ -58,21 +58,11 @@ public partial class LoggerService : ILoggerService public void LogError(string message) { - if (HideUserNames && !Environment.UserName.IsNullOrEmpty()) - { - message = message.Replace(Environment.UserName, "USERNAME"); - } - Log($"{message}", Color.Red, ServerLog.MessageType.Error); } public void LogWarning(string message) { - if (HideUserNames && !Environment.UserName.IsNullOrEmpty()) - { - message = message.Replace(Environment.UserName, "USERNAME"); - } - Log($"{message}", Color.Yellow, ServerLog.MessageType.ServerMessage); } @@ -90,6 +80,11 @@ public partial class LoggerService : ILoggerService public void Log(string message, Color? color = null, ServerLog.MessageType messageType = ServerLog.MessageType.ServerMessage) { + if (HideUserNames && !Environment.UserName.IsNullOrEmpty()) + { + message = message.Replace(Environment.UserName, "USERNAME"); + } + DebugConsole.NewMessage(message, color); #if SERVER @@ -136,7 +131,29 @@ public partial class LoggerService : ILoggerService public void LogResults(FluentResults.Result result) { - LogError("LogResults not implemented"); + if (result == null) + { + LogError("Result is null"); + return; + } + + if (result.IsSuccess) + { + return; + } + + foreach (var error in result.Errors) + { + LogError(error.Message); + + if (error.Reasons != null) + { + foreach (var reason in error.Reasons) + { + LogError($" - {reason.Message}"); + } + } + } } public void LogDebug(string message, Color? color = null) From 1daf68dea12b675a7b18e2e2342ce82fd99ca7f7 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Tue, 30 Dec 2025 16:13:30 -0300 Subject: [PATCH 026/288] Oops --- Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index d2596cd64..d94311ee2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -501,7 +501,7 @@ namespace Barotrauma void LoadStaticAssets() { - if (CurrentRunState <= RunState.Parsed) + if (CurrentRunState < RunState.Parsed) { throw new InvalidOperationException($"{nameof(LoadStaticAssets)} cannot load assets in the '{CurrentRunState}' state."); } From 6a6be3caa4683b5bd28f112558b069723fc7dba3 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 30 Dec 2025 22:59:36 -0500 Subject: [PATCH 027/288] - Removed all package dependency lookup code. - Changed from absolute file paths to the upstream `ContentPath` system. --- .../Data/DataInterfaceImplementations.cs | 149 +------ .../LuaCs/Data/IBaseInfoDefinitions.cs | 13 +- .../LuaCs/Data/IPackageDependency.cs | 66 --- .../LuaCs/Data/IResourceInfoDeclarations.cs | 12 +- .../Services/ContentPackageInfoLookup.cs | 393 ------------------ .../Services/PackageManagementService.cs | 313 ++------------ .../Services/Processing/ModConfigService.cs | 78 +--- .../_Interfaces/IPackageInfoLookupService.cs | 16 - .../_Interfaces/IPackageManagementService.cs | 3 +- 9 files changed, 60 insertions(+), 983 deletions(-) delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependency.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ContentPackageInfoLookup.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageInfoLookupService.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs index 790c7c9cd..21b718d03 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs @@ -33,146 +33,29 @@ public record LuaScriptsResourcesInfo(ImmutableArray Lua public record ConfigResourcesInfo(ImmutableArray Configs) : IConfigsResourcesInfo; public record ConfigProfilesResourcesInfo(ImmutableArray ConfigProfiles) : IConfigProfilesResourcesInfo; -public record AssemblyResourceInfo : IAssemblyResourceInfo +public record BaseResourceInfo : IBaseResourceInfo { + public Platform SupportedPlatforms { get; init; } + public Target SupportedTargets { get; init; } + public int LoadPriority { get; init; } + public ImmutableArray FilePaths { get; init; } + public bool Optional { get; init; } + public string InternalName { get; init; } public ContentPackage OwnerPackage { get; init; } +} + +public record AssemblyResourceInfo : BaseResourceInfo, IAssemblyResourceInfo +{ public string FriendlyName { get; init; } public bool IsScript { get; init; } - public string InternalName { get; init; } - public bool LazyLoad { get; init; } - public Platform SupportedPlatforms { get; init; } - public Target SupportedTargets { get; init; } - public int LoadPriority { get; init; } - public ImmutableArray FilePaths { get; init; } - public ImmutableArray SupportedCultures { get; init; } - public ImmutableArray Dependencies { get; init; } - public bool Optional { get; init; } } -public record PackageDependency : IPackageDependency +public record ConfigResourceInfo : BaseResourceInfo, IConfigResourceInfo {} + +public record ConfigProfileResourceInfo : BaseResourceInfo, IConfigProfileResourceInfo {} + +public record LuaScriptsResourceInfo : BaseResourceInfo, ILuaScriptResourceInfo { - public PackageDependency(ContentPackage package, IPackageInfo dependencyInfo, string internalName) - { - Dependency = dependencyInfo ?? throw new ArgumentNullException(nameof(dependencyInfo)); - OwnerPackage = package ?? throw new ArgumentNullException(nameof(package)); - InternalName = internalName ?? throw new ArgumentNullException(nameof(internalName)); - } - public string InternalName { get; init; } - public ContentPackage OwnerPackage { get; init; } - public IPackageInfo Dependency { get; init; } - public override int GetHashCode() => Dependency.GetHashCode(); - -} - -public record PackageInfo : IPackageInfo -{ - public string Name { get; private set; } - public ulong SteamWorkshopId { get; private set; } - public uint Id { get; private set; } - - private readonly Func _getPackage; - - public ContentPackage GetPackage() => _getPackage?.Invoke(this) ?? null; - - public void UpdateInfo(string name, ulong steamId, uint packageId) - { - if (name.IsNullOrWhiteSpace() || steamId == 0 || packageId == 0) - { - throw new ArgumentException( - $"{nameof(PackageInfo)}: You cannot update a package with an invalid name or steam id with a valid id, or vice-versa."); - } - - Name = name; - SteamWorkshopId = steamId; - Id = packageId; - } - - public PackageInfo(ContentPackage package, uint id, Func getPackage) - { - if (package is null) - throw new ArgumentNullException($"{nameof(PackageInfo)}: package is null"); - if (id == 0) - throw new ArgumentNullException($"{nameof(PackageInfo)}: id is zero."); - - this.Name = package.Name; - this.SteamWorkshopId = package.TryExtractSteamWorkshopId(out var sId) ? sId.Value : 0; - this.Id = id; - this._getPackage = getPackage; - } - - public PackageInfo(string name, ulong steamWorkshopId, uint id, Func getPackage) - { - Name = !name.IsNullOrWhiteSpace() ? name : throw new ArgumentNullException($"{nameof(PackageInfo)}: name cannot be null or empty."); - SteamWorkshopId = steamWorkshopId != 0 ? steamWorkshopId : throw new ArgumentNullException($"{nameof(PackageInfo)}: steam id cannot be 0."); - this.Id = id; - this._getPackage = getPackage; - } - - public PackageInfo(string name, uint id, Func getPackage) - { - Name = name ?? throw new ArgumentNullException($"{nameof(PackageInfo)}: name cannot be null or empty."); - this.SteamWorkshopId = 0; - this.Id = id; - this._getPackage = getPackage; - } - - public PackageInfo(ulong steamWorkshopId, uint id, Func getPackage) - { - SteamWorkshopId = steamWorkshopId != 0 ? steamWorkshopId : throw new ArgumentNullException($"{nameof(PackageInfo)}: steamid cannot be 0."); - this.Id = id; - this._getPackage = getPackage; - } - - public override int GetHashCode() - { - return (int)Id; - } - - public virtual bool Equals(PackageInfo other) - { - return ((IEquatable)this).Equals(other); - } -} - - - -public record ConfigResourceInfo : IConfigResourceInfo -{ - public Platform SupportedPlatforms { get; init; } - public Target SupportedTargets { get; init; } - public int LoadPriority { get; init; } - public ImmutableArray FilePaths { get; init; } - public bool Optional { get; init; } - public ImmutableArray SupportedCultures { get; init; } - public ImmutableArray Dependencies { get; init; } - public string InternalName { get; init; } - public ContentPackage OwnerPackage { get; init; } -} - -public record ConfigProfileResourceInfo : IConfigProfileResourceInfo -{ - public Platform SupportedPlatforms { get; init; } - public Target SupportedTargets { get; init; } - public int LoadPriority { get; init; } - public ImmutableArray FilePaths { get; init; } - public bool Optional { get; init; } - public ImmutableArray SupportedCultures { get; init; } - public ImmutableArray Dependencies { get; init; } - public string InternalName { get; init; } - public ContentPackage OwnerPackage { get; init; } -} - -public readonly struct LuaScriptsResourceInfo : ILuaScriptResourceInfo -{ - public ContentPackage OwnerPackage { get; init; } - public Platform SupportedPlatforms { get; init; } - public Target SupportedTargets { get; init; } - public int LoadPriority { get; init; } - public ImmutableArray FilePaths { get; init; } - public ImmutableArray SupportedCultures { get; init; } - public ImmutableArray Dependencies { get; init; } - public bool Optional { get; init; } - public string InternalName { get; init; } public bool IsAutorun { get; init; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs index cc9387175..bf3525c0d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs @@ -37,7 +37,7 @@ public interface IResourceInfo : IPlatformInfo /// Resource absolute file paths. /// [Required] - ImmutableArray FilePaths { get; } + ImmutableArray FilePaths { get; } /// /// Marks this resource as optional (ie. Cross-CP content). Setting this to true will allow the dependency system to @@ -45,14 +45,3 @@ public interface IResourceInfo : IPlatformInfo /// bool Optional { get; } } - -/// -/// Information about supported cultures. It is intended to be ignored if the array is ImmutableArray.Empty . -/// -public interface IResourceCultureInfo -{ - /// - /// List of supported cultures by this resource. - /// - ImmutableArray SupportedCultures { get; } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependency.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependency.cs deleted file mode 100644 index a094d122f..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IPackageDependency.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Immutable; -using Barotrauma; -using Barotrauma.LuaCs.Data; - -namespace Barotrauma.LuaCs.Data; - -public interface IPackageDependency : IDataInfo, IEquatable -{ - public IPackageInfo Dependency { get; } - - bool IEquatable.Equals(IPackageDependency other) - { - return other is not null && Dependency.Equals(other.Dependency); - } -} - -public interface IPackageInfo : IEquatable -{ - /// - /// Name of the content package. - /// - public string Name { get; } - /// - /// Steam ID of the package. - /// - public ulong SteamWorkshopId { get; } - /// - /// The Guid for the runtime instance of the package. - /// - public uint Id { get; } - - /// - /// Gets the reference to the best-match target ContentPackage that meets the requirement. - /// - /// The reference, or null if none was found. - public ContentPackage GetPackage(); - - /// - /// Tries to retrieve the current best and returns true if none was found. - /// - public bool IsMissing => GetPackage() is null; - - bool IEquatable.Equals(IPackageInfo other) - { - if (other is null) - return false; - if (ReferenceEquals(other, this)) - return true; - if (!this.IsMissing && !other.IsMissing && ReferenceEquals(other.GetPackage, this.GetPackage)) - return true; - if (this.SteamWorkshopId != 0 && other.SteamWorkshopId == this.SteamWorkshopId) - return true; - return this.Name == other.Name; - } -} - -public interface IPackageDependenciesInfo -{ - /// - /// List of required packages. - /// - ImmutableArray Dependencies { get; } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs index 777bd24d1..1bc74e54a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs @@ -4,13 +4,17 @@ using System.Globalization; namespace Barotrauma.LuaCs.Data; -public interface IConfigResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IDataInfo { } -public interface IConfigProfileResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IDataInfo { } + +public interface IBaseResourceInfo : IResourceInfo, IDataInfo {} + +public interface IConfigResourceInfo : IBaseResourceInfo {} + +public interface IConfigProfileResourceInfo :IBaseResourceInfo {} /// /// Represents loadable Lua files. /// -public interface ILuaScriptResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IDataInfo +public interface ILuaScriptResourceInfo : IBaseResourceInfo { /// /// Should this script be run automatically. @@ -18,7 +22,7 @@ public interface ILuaScriptResourceInfo : IResourceInfo, IResourceCultureInfo, I public bool IsAutorun { get; } } -public interface IAssemblyResourceInfo : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo, IDataInfo +public interface IAssemblyResourceInfo : IBaseResourceInfo { /// /// The friendly name of the assembly. Script files belonging to the same assembly should all have the same name. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ContentPackageInfoLookup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ContentPackageInfoLookup.cs deleted file mode 100644 index e8f906079..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ContentPackageInfoLookup.cs +++ /dev/null @@ -1,393 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Events; -using Barotrauma.Steam; -using FluentResults; -using OneOf; - -namespace Barotrauma.LuaCs.Services; - -/// -/// Provides resolution for dynamically locating the best matching package at the time of consumption. -/// -public sealed class ContentPackageInfoLookup : IPackageInfoLookupService, IEventEnabledPackageListChanged, IEventAllPackageListChanged -{ - #region INTERNAL - - // packageinfo query data - private readonly ConcurrentDictionary, IPackageInfo> _packageInfoMap = new(); - // package query data - private readonly ConcurrentDictionary> _packageIdGroups = new(); - private readonly ConcurrentDictionary> _reversePackageIdGroups = new(); - private readonly HashSet _enabledPackages; - private readonly HashSet _allPackages; - // threading - private readonly AsyncReaderWriterLock _packageIdGroupsLock = new(); - private readonly AsyncReaderWriterLock _packageSetsLock = new(); - // services - private readonly IEventService _eventService; - private readonly IPackageListRetrievalService _packageListRetrievalService; - - private int _isDisposed = 0; - private uint _idCounter = 0; - - // returns ++_idCounter; - private uint GetNextId() => Interlocked.Increment(ref _idCounter); - - private ContentPackage GetBestMatchPackage(IPackageInfo packageInfo) - { - if (packageInfo is null) - return null; - if (!_packageIdGroups.TryGetValue(packageInfo.Id, out var packageGroup) - || packageGroup.IsDefaultOrEmpty) - return null; - if (packageGroup.Length == 1) - return packageGroup[0]; - - bool nameGood = !packageInfo.Name.IsNullOrWhiteSpace(); - - // try by enabled - var prev = packageGroup; - - var packList = packageGroup; - using (_packageSetsLock.AcquireReaderLock().GetAwaiter().GetResult()) - { - packList = packList - .Where(p => p is not null && _enabledPackages.Contains(p)) - .ToImmutableArray(); - } - - if (ReturnValue()) - return packList[0]; - - // try by steam id - if (packageInfo.SteamWorkshopId != 0) - { - packList = packList - .Where(p => p.TryExtractSteamWorkshopId(out var sId) && sId.Value == packageInfo.SteamWorkshopId) - .ToImmutableArray(); - - if (ReturnValue()) - return packList[0]; - } - - // try by name - if (nameGood) - { - packList = packList - .Where(p => p.Name == packageInfo.Name) - .ToImmutableArray(); - - if (ReturnValue()) - return packList[0]; - } - - // try by localmods - packList = packList.Where(p => p.Path.ToLowerInvariant().Contains("localmods")) - .ToImmutableArray(); - - if (ReturnValue()) - return packList[0]; - - // get the first in the list - return packList.First(); - - bool ReturnValue() - { - if (packList.IsDefaultOrEmpty) - packList = prev; - else if (packList.Length == 1) - return true; - else - prev = packList; - return false; - } - } - - private async Task SyncPackagesLists(IReadOnlyList enabledPackages, - IReadOnlyList allPackages) - { - if (enabledPackages is null || allPackages is null) - return; - - // take all locks - using var l1 = await _packageIdGroupsLock.AcquireWriterLock(); - using var l2 = await _packageSetsLock.AcquireWriterLock(); - - // calc diffs - var toAddAll = allPackages.Except(_allPackages).ToHashSet(); - var toAddEnabled = enabledPackages.Except(_enabledPackages).ToHashSet(); - var toRemoveAll = _allPackages.Except(allPackages).ToHashSet(); - var toRemoveEnabled = _enabledPackages.Except(enabledPackages).ToHashSet(); - - // remove old - if (toRemoveAll.Any()) - { - foreach (var package in toRemoveAll) - { - if (package is null) - continue; - - _allPackages.Remove(package); - - // try to find id lookup - if (!_reversePackageIdGroups.TryGetValue(package, out var idGroup)) - continue; - - // found packs - if (!idGroup.IsDefaultOrEmpty) - { - foreach (var id in idGroup) - { - if (!_packageIdGroups.TryGetValue(id, out var packageGroup) - || packageGroup.IsDefaultOrEmpty) - continue; - _packageIdGroups[id] = packageGroup.RemoveAll(p => toRemoveAll.Contains(p)); - } - } - - // remove ref - _reversePackageIdGroups.Remove(package, out _); - } - } - - if (toRemoveEnabled.Any()) - { - foreach (var package in toRemoveEnabled) - { - if (package is null) - continue; - _enabledPackages.Remove(package); - } - } - - // add new - if (toAddAll.Any()) - { - foreach (var package in toAddAll) - { - if (package is null) - continue; - - _allPackages.Add(package); - - var steamId = package.TryExtractSteamWorkshopId(out var id) ? id.Value : 0; - IPackageInfo packageInfo; - Queue idListsToAdd = new(); - if (!package.Name.IsNullOrWhiteSpace() && steamId > 0) - { - // combined key - packageInfo = GetOrCreateInfoForMap(package, (package.Name, steamId)); - AddToPackageIdGroups(packageInfo.Id, package); - // string key - packageInfo = GetOrCreateInfoForMap(package, package.Name); - AddToPackageIdGroups(packageInfo.Id, package); - // steamId key - packageInfo = GetOrCreateInfoForMap(package, steamId); - AddToPackageIdGroups(packageInfo.Id, package); - } - - // try find in the existing list, or make a new one - IPackageInfo GetOrCreateInfoForMap(ContentPackage package, OneOf.OneOf infoKey) - { - return _packageInfoMap.TryGetValue(infoKey, out var pInfo) - ? pInfo - : new PackageInfo(package, GetNextId(), GetBestMatchPackage); - } - - // add to package lookups - void AddToPackageIdGroups(uint id, ContentPackage package) - { - if (_packageIdGroups.TryGetValue(id, out var packageGroup)) - { - if (!packageGroup.Contains(package)) - _packageIdGroups[id] = packageGroup.Add(package); - } - else - _packageIdGroups[id] = new[] { package }.ToImmutableArray(); - - if (_reversePackageIdGroups.TryGetValue(package, out var idGroup)) - { - if (!idGroup.Contains(id)) - _reversePackageIdGroups[package] = idGroup.Add(id); - } - else - _reversePackageIdGroups[package] = new[] { id }.ToImmutableArray(); - } - } - } - - if (toAddEnabled.Any()) - { - foreach (var package in toAddEnabled) - { - if (package is null) - continue; - _enabledPackages.Add(package); - } - } - } - - private async Task> LookupInternal(OneOf.OneOf infoKey) - { - using (await _packageIdGroupsLock.AcquireReaderLock()) - { - if (_packageInfoMap.TryGetValue(infoKey, out var packageInfo)) - return FluentResults.Result.Ok(packageInfo); - } - - // change to write lock - using (await _packageIdGroupsLock.AcquireWriterLock()) - { - // create one - var packageInfo = infoKey.Match( - sPackName => new PackageInfo(sPackName, GetNextId(), GetBestMatchPackage), - uSteamId => new PackageInfo(uSteamId, GetNextId(), GetBestMatchPackage), - cKey => new PackageInfo(cKey.Item1, cKey.Item2, GetNextId(), GetBestMatchPackage) - ); - _packageInfoMap[infoKey] = packageInfo; - // empty array - _packageIdGroups[packageInfo.Id] = ImmutableArray.Empty; - return FluentResults.Result.Ok(packageInfo); - } - } - - #endregion - - public ContentPackageInfoLookup(IEventService eventService, IPackageListRetrievalService packageListRetrievalService) - { - _eventService = eventService ?? throw new ArgumentNullException( - $"{nameof(ContentPackageInfoLookup)}: {nameof(eventService)} cannot be null."); - _packageListRetrievalService = packageListRetrievalService ?? throw new ArgumentNullException(nameof(packageListRetrievalService)); - this._enabledPackages = new HashSet(); - this._allPackages = new HashSet(); - } - - public void Dispose() - { - IsDisposed = true; - // locks - using var l1 = _packageIdGroupsLock.AcquireWriterLock().GetAwaiter().GetResult(); - using var l2 = _packageSetsLock.AcquireWriterLock().GetAwaiter().GetResult(); - - _eventService.Unsubscribe(this); - _eventService.Unsubscribe(this); - - _packageIdGroups.Clear(); - _packageInfoMap.Clear(); - _reversePackageIdGroups.Clear(); - } - - public bool IsDisposed - { - get => ModUtils.Threading.GetBool(ref _isDisposed); - private set => ModUtils.Threading.SetBool(ref _isDisposed, value); - } - - public FluentResults.Result Reset() - { - if (IsDisposed) - return FluentResults.Result.Fail($"Service is disposed."); - - using var l1 = _packageIdGroupsLock.AcquireWriterLock().GetAwaiter().GetResult(); - using var l2 = _packageSetsLock.AcquireWriterLock().GetAwaiter().GetResult(); - - _packageIdGroups.Clear(); - _packageInfoMap.Clear(); - _reversePackageIdGroups.Clear(); - - RefreshPackageLists(); - - return FluentResults.Result.Ok(); - } - - public void OnEnabledPackageListChanged(CorePackage package, IEnumerable regularPackages) - { - ((IService)this).CheckDisposed(); - SyncPackagesLists( - regularPackages.Select(p => (ContentPackage)p).ToImmutableArray().Add(package), - _allPackages.ToImmutableArray()) - .GetAwaiter().GetResult(); - } - - public void OnAllPackageListChanged(IEnumerable corePackages, IEnumerable regularPackages) - { - ((IService)this).CheckDisposed(); - SyncPackagesLists( - _enabledPackages.ToImmutableArray(), - regularPackages.Select(p => p as ContentPackage) - .Union(corePackages.Select(p => p as ContentPackage)) - .ToImmutableArray() - ).GetAwaiter().GetResult(); - } - - public bool IsPackageEnabled(ContentPackage package) - { - if (package is null) - return false; - using (_packageSetsLock.AcquireReaderLock().GetAwaiter().GetResult()) - { - return _enabledPackages.Contains(package); - } - } - - public async Task> Lookup(string packageName) - { - ((IService)this).CheckDisposed(); - if(packageName.IsNullOrWhiteSpace()) - return FluentResults.Result.Fail($"Name is null or empty."); - return await LookupInternal(packageName); - } - - public async Task> Lookup(string packageName, ulong steamWorkshopId) - { - ((IService)this).CheckDisposed(); - if (packageName.IsNullOrWhiteSpace() || steamWorkshopId == 0) - return FluentResults.Result.Fail($"Name or steam id is null or empty."); - return await LookupInternal((packageName, steamWorkshopId)); - } - - public async Task> Lookup(ulong steamWorkshopId) - { - ((IService)this).CheckDisposed(); - if (steamWorkshopId is 0) - return FluentResults.Result.Fail($"SteamId is 0."); - return await LookupInternal(steamWorkshopId); - } - - public async Task> Lookup(ContentPackage package) - { - ((IService)this).CheckDisposed(); - if (package is null) - return FluentResults.Result.Fail($"Package is null."); - - if (package.TryExtractSteamWorkshopId(out var steamWorkshopId) && steamWorkshopId.Value != 0) - { - if (!package.Name.IsNullOrWhiteSpace()) - return await LookupInternal((package.Name, steamWorkshopId.Value)); - else - return await LookupInternal(steamWorkshopId.Value); - } - - if (!package.Name.IsNullOrWhiteSpace()) - return await LookupInternal(package.Name); - - return FluentResults.Result.Fail($"Package name is null and steamid is 0."); - } - - public void RefreshPackageLists() - { - ((IService)this).CheckDisposed(); - if (Thread.CurrentThread != GameMain.MainThread) - throw new InvalidOperationException($"{nameof(ContentPackageInfoLookup)}: {nameof(RefreshPackageLists)} must be run on the main thread."); - var enabledPackages = _packageListRetrievalService.GetEnabledContentPackages().ToImmutableArray(); - var allPackages = _packageListRetrievalService.GetAllContentPackages().ToImmutableArray(); - SyncPackagesLists(enabledPackages, allPackages).GetAwaiter().GetResult(); - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index 077c148ff..064813b9e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -1,376 +1,127 @@ - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; -using System.Threading; using System.Threading.Tasks; using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services.Processing; -using Barotrauma.Steam; using FluentResults; -using OneOf; - -// ReSharper disable UseCollectionExpression namespace Barotrauma.LuaCs.Services; public partial class PackageManagementService : IPackageManagementService { - private int _isDisposed; - private readonly ConcurrentDictionary _modInfos = new(); - // lookup caches - private readonly IPackageInfoLookupService _packageInfoLookupService; - // processors - private readonly IConverterServiceAsync _modConfigParserService; - private readonly IProcessorService, IAssembliesResourcesInfo> _assemblyInfoConverter; - private readonly IProcessorService, IConfigsResourcesInfo> _configsInfoConverter; - private readonly IProcessorService, IConfigProfilesResourcesInfo> _configProfilesConverter; - private readonly IProcessorService, ILuaScriptsResourcesInfo> _luaScriptsConverter; - - public PackageManagementService( - IConverterServiceAsync modConfigParserService, - IProcessorService, IAssembliesResourcesInfo> assemblyInfoConverter, - IProcessorService, IConfigsResourcesInfo> configsInfoConverter, - IProcessorService, IConfigProfilesResourcesInfo> configProfilesConverter, - IProcessorService, ILuaScriptsResourcesInfo> luaScriptsConverter, - IPackageInfoLookupService packageInfoLookupService) - { - _modConfigParserService = modConfigParserService; - _assemblyInfoConverter = assemblyInfoConverter; - _configsInfoConverter = configsInfoConverter; - _configProfilesConverter = configProfilesConverter; - _luaScriptsConverter = luaScriptsConverter; - _packageInfoLookupService = packageInfoLookupService; - } - public void Dispose() { - IsDisposed = true; - _modInfos.Clear(); + throw new System.NotImplementedException(); } + private int _isDisposed = 0; public bool IsDisposed { - get => ModUtils.Threading.GetBool(ref _isDisposed); - private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); } - + + public FluentResults.Result Reset() { - try - { - ((IService)this).CheckDisposed(); - _modInfos.Clear(); - } - catch (Exception e) - { - return FluentResults.Result.Fail(new ExceptionalError(e)); - } - return FluentResults.Result.Ok(); + throw new System.NotImplementedException(); } - - public ImmutableArray Configs => _modInfos.IsEmpty ? ImmutableArray.Empty - : _modInfos.SelectMany(kvp => kvp.Value.Configs).ToImmutableArray(); - public ImmutableArray ConfigProfiles => _modInfos.IsEmpty ? ImmutableArray.Empty - : _modInfos.SelectMany(kvp => kvp.Value.ConfigProfiles).ToImmutableArray(); - public ImmutableArray LuaScripts => _modInfos.IsEmpty ? ImmutableArray.Empty - : _modInfos.SelectMany(kvp => kvp.Value.LuaScripts).ToImmutableArray(); - public ImmutableArray Assemblies => _modInfos.IsEmpty ? ImmutableArray.Empty - : _modInfos.SelectMany(kvp => kvp.Value.Assemblies).ToImmutableArray(); - + public ImmutableArray Configs { get; } + public ImmutableArray ConfigProfiles { get; } + public ImmutableArray LuaScripts { get; } + public ImmutableArray Assemblies { get; } public async Task LoadPackageInfosAsync(ContentPackage package) { - ((IService)this).CheckDisposed(); - if (package is null) - return FluentResults.Result.Fail(new ExceptionalError(new NullReferenceException($"{nameof(LoadPackageInfosAsync)}: ContentPackage is null."))); - var result = await _modConfigParserService.TryParseResourceAsync(package); - if (result.IsFailed) - return FluentResults.Result.Fail($"$Could not parse package mod config.").WithErrors(result.Errors); - if (!_modInfos.TryAdd(package, result.Value)) - return FluentResults.Result.Fail($"Failed to add ModInfo for {package.Name}."); - return FluentResults.Result.Ok(); + throw new System.NotImplementedException(); } public async Task> LoadPackagesInfosAsync(IReadOnlyList packages) { - ((IService)this).CheckDisposed(); - if (packages is null || packages.Count == 0) - throw new ArgumentNullException(nameof(LoadPackagesInfosAsync)); - ConcurrentQueue<(ContentPackage, FluentResults.Result)> results = new(); - await packages.ParallelForEachAsync(async package => - { - var res = await LoadPackageInfosAsync(package); - results.Enqueue((package, res)); - }, Environment.ProcessorCount); - return results.ToImmutableArray(); + throw new System.NotImplementedException(); } public IReadOnlyList GetAllLoadedPackages() { - ((IService)this).CheckDisposed(); - return _modInfos.IsEmpty ? ImmutableArray.Empty - : _modInfos.Select(kvp => kvp.Key).ToImmutableArray(); + throw new System.NotImplementedException(); } public bool IsPackageLoaded(ContentPackage package) { - return package is not null && _modInfos.ContainsKey(package); + throw new System.NotImplementedException(); } - public ImmutableArray FilterUnloadableResources(IReadOnlyList resources, bool enabledPackagesOnly = false) - where T : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo + public ImmutableArray FilterUnloadableResources(IReadOnlyList resources, bool enabledPackagesOnly = false) where T : IResourceInfo { - return resources - .Where(r => r is not null) - .Where(r => (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0) - .Where(r => (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0) - .Where(r => !r.Dependencies.Any() || r.Dependencies.All(d => - d.Dependency.GetPackage() is {} p // cp is valid - && _modInfos.ContainsKey(p) // cp is parsed - && (!enabledPackagesOnly || _packageInfoLookupService.IsPackageEnabled(p)))) // cp is enabled - .ToImmutableArray(); + throw new System.NotImplementedException(); } public void DisposePackageInfos(ContentPackage package) { - _modInfos.TryRemove(package, out _); + throw new System.NotImplementedException(); } public void DisposePackagesInfos(IReadOnlyList packages) { - if (packages is null || packages.Count == 0) - return; - - foreach (var package in packages) - { - DisposePackageInfos(package); - } - } - - public Result GetPackageDependencyInfo(ContentPackage ownerPackage, string packageName, - ulong steamWorkshopId) - { - ((IService)this).CheckDisposed(); - - if (ownerPackage is null) - return FluentResults.Result.Fail($"OwnerPackage is null."); - var nameGood = !packageName.IsNullOrWhiteSpace(); - - if (!nameGood && steamWorkshopId == 0) - FluentResults.Result.Fail($"PackageName and SteamId cannot both be invalid."); - - IPackageInfo depInfo = null; - - // complex key - if (nameGood && steamWorkshopId != 0 - && _packageInfoLookupService.Lookup(packageName, steamWorkshopId).GetAwaiter().GetResult() is - { IsSuccess: true, Value: {} dep1 }) - { - depInfo = dep1; - } - // name key - else if (nameGood && _packageInfoLookupService.Lookup(packageName).GetAwaiter().GetResult() is - { IsSuccess: true, Value: { } dep2 }) - { - depInfo = dep2; - } - // steamid key - else if (_packageInfoLookupService.Lookup(steamWorkshopId).GetAwaiter().GetResult() is - { IsSuccess: true, Value: { } dep3 }) - { - depInfo = dep3; - } - // this should never be null so we return an exception - else - { - return FluentResults.Result.Fail($"Package Dependency for {ownerPackage.Name} was not found."); - } - - return FluentResults.Result.Ok(new PackageDependency(ownerPackage, depInfo, ownerPackage.Name)); + throw new System.NotImplementedException(); } public Result GetAssembliesInfos(ContentPackage package, bool onlySupportedResources = true) { - ((IService)this).CheckDisposed(); - if (package is null) - return FluentResults.Result.Fail($"{nameof(GetAssembliesInfos)}: ContentPackage is null."); - if (_modInfos.TryGetValue(package, out var result)) - return FluentResults.Result.Ok(_assemblyInfoConverter.Process(onlySupportedResources? - result.Assemblies.Where(r => - (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 - && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() - : result.Assemblies - )); - return FluentResults.Result.Fail( - $"{nameof(GetAssembliesInfos)}: ContentPackage {package.Name} is not registered."); + throw new System.NotImplementedException(); } public Result GetConfigsInfos(ContentPackage package, bool onlySupportedResources = true) { - ((IService)this).CheckDisposed(); - if (package is null) - return FluentResults.Result.Fail($"{nameof(GetConfigsInfos)}: ContentPackage is null."); - - if (_modInfos.TryGetValue(package, out var result)) - { - return FluentResults.Result.Ok(_configsInfoConverter.Process(onlySupportedResources? - result.Configs.Where(r => - (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 - && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() - : result.Configs - )); - } - - return FluentResults.Result.Fail( - $"{nameof(GetConfigsInfos)}: ContentPackage {package.Name} is not registered."); + throw new System.NotImplementedException(); } public Result GetConfigProfilesInfos(ContentPackage package, bool onlySupportedResources = true) { - ((IService)this).CheckDisposed(); - if (package is null) - return FluentResults.Result.Fail($"{nameof(GetConfigProfilesInfos)}: ContentPackage is null."); - - if (_modInfos.TryGetValue(package, out var result)) - { - return FluentResults.Result.Ok(_configProfilesConverter.Process(onlySupportedResources? - result.ConfigProfiles.Where(r => - (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 - && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() - : result.ConfigProfiles - )); - } - - return FluentResults.Result.Fail( - $"{nameof(GetConfigProfilesInfos)}: ContentPackage {package.Name} is not registered."); + throw new System.NotImplementedException(); } public Result GetLuaScriptsInfos(ContentPackage package, bool onlySupportedResources = true) { - ((IService)this).CheckDisposed(); - if (package is null) - return FluentResults.Result.Fail($"{nameof(GetLuaScriptsInfos)}: ContentPackage is null."); - - if (_modInfos.TryGetValue(package, out var result)) - { - return FluentResults.Result.Ok(_luaScriptsConverter.Process(onlySupportedResources? - result.LuaScripts.Where(r => - (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 - && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() - : result.LuaScripts - )); - } - - return FluentResults.Result.Fail( - $"{nameof(GetLuaScriptsInfos)}: ContentPackage {package.Name} is not registered."); + throw new System.NotImplementedException(); } public Result GetAssembliesInfos(IReadOnlyList packages, bool onlySupportedResources = true) { - ((IService)this).CheckDisposed(); - if (packages is null || packages.Count == 0) - return FluentResults.Result.Fail($"{nameof(GetAssembliesInfos)}: ContentPackage list is null or empty."); - var builder = ImmutableArray.CreateBuilder(); - foreach (var package in packages) - { - if (_modInfos.TryGetValue(package, out var result) && result.Assemblies is { IsEmpty: false }) - { - builder.AddRange(onlySupportedResources? - result.Assemblies.Where(r => - (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 - && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() - : result.Assemblies); - } - } - - return FluentResults.Result.Ok(_assemblyInfoConverter.Process(builder.MoveToImmutable())); + throw new System.NotImplementedException(); } public Result GetConfigsInfos(IReadOnlyList packages, bool onlySupportedResources = true) { - ((IService)this).CheckDisposed(); - if (packages is null || packages.Count == 0) - return FluentResults.Result.Fail($"{nameof(GetConfigsInfos)}: ContentPackage list is null or empty."); - var builder = ImmutableArray.CreateBuilder(); - foreach (var package in packages) - { - if (_modInfos.TryGetValue(package, out var result) && result.Configs is { IsEmpty: false }) - { - builder.AddRange(onlySupportedResources? - result.Configs.Where(r => - (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 - && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() - : result.Configs); - } - } - - return FluentResults.Result.Ok(_configsInfoConverter.Process(builder.MoveToImmutable())); + throw new System.NotImplementedException(); } public Result GetConfigProfilesInfos(IReadOnlyList packages, bool onlySupportedResources = true) { - ((IService)this).CheckDisposed(); - if (packages is null || packages.Count == 0) - return FluentResults.Result.Fail($"{nameof(GetConfigProfilesInfos)}: ContentPackage list is null or empty."); - var builder = ImmutableArray.CreateBuilder(); - foreach (var package in packages) - { - if (_modInfos.TryGetValue(package, out var result) && result.ConfigProfiles is { IsEmpty: false }) - { - builder.AddRange(onlySupportedResources? - result.ConfigProfiles.Where(r => - (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 - && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() - : result.ConfigProfiles); - } - } - - return FluentResults.Result.Ok(_configProfilesConverter.Process(builder.MoveToImmutable())); + throw new System.NotImplementedException(); } public Result GetLuaScriptsInfos(IReadOnlyList packages, bool onlySupportedResources = true) { - ((IService)this).CheckDisposed(); - if (packages is null || packages.Count == 0) - return FluentResults.Result.Fail($"{nameof(GetLuaScriptsInfos)}: ContentPackage list is null or empty."); - var builder = ImmutableArray.CreateBuilder(); - foreach (var package in packages) - { - if (_modInfos.TryGetValue(package, out var result) && result.LuaScripts is { IsEmpty: false }) - { - builder.AddRange(onlySupportedResources? - result.LuaScripts.Where(r => - (r.SupportedPlatforms & ModUtils.Environment.CurrentPlatform) > 0 - && (r.SupportedTargets & ModUtils.Environment.CurrentTarget) > 0).ToImmutableArray() - : result.LuaScripts); - } - } - - return FluentResults.Result.Ok(_luaScriptsConverter.Process(builder.MoveToImmutable())); + throw new System.NotImplementedException(); } public async Task> GetAssembliesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true) { - return await Task.Run(() => GetAssembliesInfos(packages, onlySupportedResources)); + throw new System.NotImplementedException(); } public async Task> GetConfigsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true) { - return await Task.Run(() => GetConfigsInfos(packages, onlySupportedResources)); + throw new System.NotImplementedException(); } public async Task> GetConfigProfilesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true) { - return await Task.Run(() => GetConfigProfilesInfos(packages, onlySupportedResources)); + throw new System.NotImplementedException(); } public async Task> GetLuaScriptsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true) { - return await Task.Run(() => GetLuaScriptsInfos(packages, onlySupportedResources)); + throw new System.NotImplementedException(); } - - } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs index afb5c3ec1..8604bbe64 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs @@ -92,25 +92,20 @@ public partial class ModConfigService : IConverterServiceAsync GetElementsDependenciesData(XElement element, ContentPackage src) - { - if (element.GetChildElement("Dependencies") is not {} dependencies - || dependencies.GetChildElements("Dependency").ToImmutableArray() is not { Length: >0 } depsList) - return ImmutableArray.Empty; - var builder = ImmutableArray.CreateBuilder(); - foreach (var dep in depsList) - { - var packName = dep.GetAttributeString("PackageName", string.Empty); - var packId = dep.GetAttributeUInt64("PackageId", 0); - - // invalid entry - if (packName.IsNullOrWhiteSpace() && packId == 0) - continue; - - if (_packageManagementService.Value.GetPackageDependencyInfo(src, packName, packId) is - { IsSuccess: true, Value: { } depsInfo }) - { - builder.Add(depsInfo); - } - } - return builder.ToImmutable(); - } private ImmutableArray GetAssembliesLegacy(ContentPackage src) { @@ -382,16 +336,13 @@ public partial class ModConfigService : IConverterServiceAsync.Empty, FilePaths = filesSrvLin, FriendlyName = "AssembliesServerLinux", InternalName = "AssembliesServerLinux", IsScript = false, - LazyLoad = false, LoadPriority = 1, Optional = false, OwnerPackage = src, - SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), SupportedPlatforms = Platform.Linux, SupportedTargets = Target.Server }); @@ -403,16 +354,13 @@ public partial class ModConfigService : IConverterServiceAsync.Empty, FilePaths = filesSrvOsx, FriendlyName = "AssembliesServerOSX", InternalName = "AssembliesServerOSX", IsScript = false, - LazyLoad = false, LoadPriority = 1, Optional = false, OwnerPackage = src, - SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), SupportedPlatforms = Platform.OSX, SupportedTargets = Target.Server }); @@ -424,16 +372,13 @@ public partial class ModConfigService : IConverterServiceAsync.Empty, FilePaths = filesSrvWin, FriendlyName = "AssembliesServerWin", InternalName = "AssembliesServerWin", IsScript = false, - LazyLoad = false, LoadPriority = 1, Optional = false, OwnerPackage = src, - SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), SupportedPlatforms = Platform.Windows, SupportedTargets = Target.Server }); @@ -445,16 +390,13 @@ public partial class ModConfigService : IConverterServiceAsync.Empty, FilePaths = filesCliLin, FriendlyName = "AssembliesClientLinux", InternalName = "AssembliesClientLinux", IsScript = false, - LazyLoad = false, LoadPriority = 1, Optional = false, OwnerPackage = src, - SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), SupportedPlatforms = Platform.Linux, SupportedTargets = Target.Client }); @@ -466,16 +408,13 @@ public partial class ModConfigService : IConverterServiceAsync.Empty, FilePaths = filesCliOsx, FriendlyName = "AssembliesClientOSX", InternalName = "AssembliesClientOSX", IsScript = false, - LazyLoad = false, LoadPriority = 1, Optional = false, OwnerPackage = src, - SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), SupportedPlatforms = Platform.OSX, SupportedTargets = Target.Client }); @@ -487,16 +426,13 @@ public partial class ModConfigService : IConverterServiceAsync.Empty, FilePaths = filesCliWin, FriendlyName = "AssembliesClientWin", InternalName = "AssembliesClientWin", IsScript = false, - LazyLoad = false, LoadPriority = 1, Optional = false, OwnerPackage = src, - SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), SupportedPlatforms = Platform.Windows, SupportedTargets = Target.Client }); @@ -518,16 +454,13 @@ public partial class ModConfigService : IConverterServiceAsync.Empty, FilePaths = sharedFound ? filesCssServer.Concat(filesCssShared).ToImmutableArray() : filesCssServer, FriendlyName = "CssServer", InternalName = "CssServer", IsScript = true, - LazyLoad = false, LoadPriority = 1, Optional = false, OwnerPackage = src, - SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), SupportedPlatforms = Platform.Linux | Platform.OSX | Platform.Windows, SupportedTargets = Target.Server }); @@ -539,16 +472,13 @@ public partial class ModConfigService : IConverterServiceAsync.Empty, FilePaths = sharedFound ? filesCssClient.Concat(filesCssShared).ToImmutableArray() : filesCssClient, FriendlyName = "CssClient", InternalName = "CssClient", IsScript = true, - LazyLoad = false, LoadPriority = 1, Optional = false, OwnerPackage = src, - SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), SupportedPlatforms = Platform.Linux | Platform.OSX | Platform.Windows, SupportedTargets = Target.Client }); @@ -565,26 +495,22 @@ public partial class ModConfigService : IConverterServiceAsync.Empty, FilePaths = fileAll.Where(path => !path.Contains("Autorun")).ToImmutableArray(), InternalName = "LuaScriptsNormal", Optional = false, IsAutorun = false, OwnerPackage = src, - SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), SupportedPlatforms = Platform.Linux | Platform.OSX | Platform.Windows, SupportedTargets = Target.Client | Target.Server }); builder.Add(new LuaScriptsResourceInfo() { - Dependencies = ImmutableArray.Empty, FilePaths = fileAll.Where(path => path.Contains("Autorun")).ToImmutableArray(), InternalName = "LuaScriptsAutorun", Optional = false, IsAutorun = true, OwnerPackage = src, - SupportedCultures = new CultureInfo[]{ CultureInfo.InvariantCulture }.ToImmutableArray(), SupportedPlatforms = Platform.Linux | Platform.OSX | Platform.Windows, SupportedTargets = Target.Client | Target.Server }); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageInfoLookupService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageInfoLookupService.cs deleted file mode 100644 index 836103c25..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageInfoLookupService.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; -using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Events; - -namespace Barotrauma.LuaCs.Services; - -public interface IPackageInfoLookupService : IReusableService -{ - bool IsPackageEnabled(ContentPackage package); - Task> Lookup(string packageName); - Task> Lookup(string packageName, ulong steamWorkshopId); - Task> Lookup(ulong steamWorkshopId); - Task> Lookup(ContentPackage package); - void RefreshPackageLists(); -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs index c342ff9f4..0f185aa8b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs @@ -39,10 +39,9 @@ public interface IPackageManagementService : IReusableService, IConfigsResources /// /// ImmutableArray FilterUnloadableResources(IReadOnlyList resources, bool enabledPackagesOnly = false) - where T : IResourceInfo, IResourceCultureInfo, IPackageDependenciesInfo; + where T : IResourceInfo; void DisposePackageInfos(ContentPackage package); void DisposePackagesInfos(IReadOnlyList packages); - FluentResults.Result GetPackageDependencyInfo(ContentPackage ownerPackage, string packageName, ulong steamWorkshopId); // single FluentResults.Result GetAssembliesInfos(ContentPackage package, bool onlySupportedResources = true); From 7d39c092c671ab57cd6fbd55a5d2b939c6eb3240 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 2 Jan 2026 18:02:01 -0500 Subject: [PATCH 028/288] [Save/Sync] Big If tru Rewrite in progress. - Removed IProcessors - Removed old ModConfigService format. - Converting to ContentPath from absolute paths where possible. - Added: Microsoft.Toolkit.Diagnostics package. --- .../Services/Processing/ModConfigService.cs | 30 - Barotrauma/BarotraumaShared/Luatrauma.props | 39 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 11 +- .../LuaCs/Services/ConfigService.cs | 16 +- .../Services/Processing/ConfigIOService.cs | 2 +- .../Services/Processing/IConfigIOService.cs | 4 +- ...itions.cs => IHelperServiceDefinitions.cs} | 14 +- .../Services/Processing/ModConfigService.cs | 566 ++---------------- .../Processing/ResourceInfoProcessors.cs | 47 -- .../LuaCs/Services/StorageService.cs | 59 +- .../Services/_Interfaces/IModConfigInfo.cs | 22 + .../Services/_Interfaces/IStorageService.cs | 2 +- 12 files changed, 132 insertions(+), 680 deletions(-) delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/ModConfigService.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/{IConverterServiceDefinitions.cs => IHelperServiceDefinitions.cs} (60%) delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ResourceInfoProcessors.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IModConfigInfo.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/ModConfigService.cs deleted file mode 100644 index 0e46339e7..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Processing/ModConfigService.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading.Tasks; -using System.Xml.Linq; -using Barotrauma.LuaCs.Data; -using FluentResults; - -namespace Barotrauma.LuaCs.Services.Processing; - -public partial class ModConfigService -{ - private partial async Task> GetModConfigInfoAsync(ContentPackage package, XElement root) - { - var asm = root.GetChildElements("Assembly").ToImmutableArray(); - var cfg = root.GetChildElements("Config").ToImmutableArray(); - var lua = root.GetChildElements("Lua").ToImmutableArray(); - - return FluentResults.Result.Ok(new ModConfigInfo() - { - Package = package, - PackageName = package.Name, - Assemblies = asm.Any() ? GetAssemblies(package, asm) : ImmutableArray.Empty, - Configs = cfg.Any() ? GetConfigs(package, cfg) : ImmutableArray.Empty, - ConfigProfiles = cfg.Any() ? GetConfigProfiles(package, cfg) : ImmutableArray.Empty, - LuaScripts = lua.Any() ? GetLuaScripts(package, lua) : ImmutableArray.Empty - }); - } -} diff --git a/Barotrauma/BarotraumaShared/Luatrauma.props b/Barotrauma/BarotraumaShared/Luatrauma.props index 5b7154571..ebc46700a 100644 --- a/Barotrauma/BarotraumaShared/Luatrauma.props +++ b/Barotrauma/BarotraumaShared/Luatrauma.props @@ -1,25 +1,24 @@ - - - - - - - - - - - - - - - - - - en - + --> + + en + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index d94311ee2..6b94165f4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -40,7 +40,6 @@ namespace Barotrauma void RegisterServices() { _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); @@ -55,15 +54,9 @@ namespace Barotrauma // TODO: INetworkingService // TODO: [Resource Converter/Parser Services] - // IResourceInfo wrappers and mutators. - _servicesProvider.RegisterServiceType, IAssembliesResourcesInfo>, ResourceInfoArrayPacker>(ServiceLifetime.Transient); - _servicesProvider.RegisterServiceType, IConfigsResourcesInfo>, ResourceInfoArrayPacker>(ServiceLifetime.Transient); - _servicesProvider.RegisterServiceType, IConfigProfilesResourcesInfo>, ResourceInfoArrayPacker>(ServiceLifetime.Transient); - _servicesProvider.RegisterServiceType, ILuaScriptsResourcesInfo>, ResourceInfoArrayPacker>(ServiceLifetime.Transient); - // Loaders and Processors (yes the naming is reversed, oops). - _servicesProvider.RegisterServiceType, ModConfigService>(ServiceLifetime.Transient); - _servicesProvider.RegisterServiceType, ModConfigService>(ServiceLifetime.Transient); + _servicesProvider.RegisterServiceType, ModConfigService>(ServiceLifetime.Transient); + _servicesProvider.RegisterServiceType, ModConfigService>(ServiceLifetime.Transient); _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); // service config data diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs index 2b31d3da4..47648cab3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs @@ -24,11 +24,11 @@ namespace Barotrauma.LuaCs.Services; public partial class ConfigService : IConfigService { //--- Internals - public ConfigService(IConverterServiceAsync> configProfileResourceConverter, - IConverterServiceAsync> configResourceConverter, IEventService eventService, System.Lazy storageService) + public ConfigService(IParserServiceAsync> configProfileResourceParser, + IParserServiceAsync> configResourceParser, IEventService eventService, System.Lazy storageService) { - _configProfileResourceConverter = configProfileResourceConverter; - _configResourceConverter = configResourceConverter; + _configProfileResourceParser = configProfileResourceParser; + _configResourceParser = configResourceParser; _eventService = eventService; _storageService = storageService; this._base = this; @@ -47,8 +47,8 @@ public partial class ConfigService : IConfigService private readonly AsyncReaderWriterLock _disposeOpsLock = new(); // extern services - private readonly IConverterServiceAsync> _configResourceConverter; - private readonly IConverterServiceAsync> _configProfileResourceConverter; + private readonly IParserServiceAsync> _configResourceParser; + private readonly IParserServiceAsync> _configProfileResourceParser; private readonly IEventService _eventService; private readonly System.Lazy _storageService; @@ -318,7 +318,7 @@ public partial class ConfigService : IConfigService if (configResources.IsDefaultOrEmpty) return FluentResults.Result.Fail($"{nameof(LoadConfigsAsync)}: Array is empty."); - var results = await _configResourceConverter.TryParseResourcesAsync(configResources); + var results = await _configResourceParser.TryParseResourcesAsync(configResources); var ret = new FluentResults.Result(); foreach (var result in results) @@ -365,7 +365,7 @@ public partial class ConfigService : IConfigService if (configProfileResources.IsDefaultOrEmpty) return FluentResults.Result.Fail($"{nameof(LoadConfigsProfilesAsync)}: Array is empty."); - var results = await _configProfileResourceConverter.TryParseResourcesAsync(configProfileResources); + var results = await _configProfileResourceParser.TryParseResourcesAsync(configProfileResources); var ret = new FluentResults.Result(); foreach (var result in results) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigIOService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigIOService.cs index f07a01e8c..b82098744 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigIOService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigIOService.cs @@ -46,7 +46,7 @@ public class ConfigIOService : IConfigIOService try { - var infos = await _storageService.LoadPackageXmlFilesAsync(src.OwnerPackage, src.FilePaths); + var infos = await _storageService.LoadPackageXmlFilesAsync(src.OwnerPackage, [..src.FilePaths.Select(fp => fp.FullPath)]); if (infos.IsDefaultOrEmpty) return FluentResults.Result.Fail($"{nameof(TryParseResourceAsync)}: No resources found."); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConfigIOService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConfigIOService.cs index 7361b6575..72d1dff10 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConfigIOService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConfigIOService.cs @@ -7,8 +7,8 @@ using Barotrauma.LuaCs.Services.Processing; namespace Barotrauma.LuaCs.Services.Processing; public interface IConfigIOService : IReusableService, - IConverterServiceAsync>, - IConverterServiceAsync> + IParserServiceAsync>, + IParserServiceAsync> { Task SaveConfigDataLocal(ContentPackage package, string configName, XElement serializedValue); Task>> LoadConfigDataFromLocal(ContentPackage package, string configName); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IHelperServiceDefinitions.cs similarity index 60% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IHelperServiceDefinitions.cs index c64ffdb02..a1ddb3f94 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConverterServiceDefinitions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IHelperServiceDefinitions.cs @@ -7,24 +7,14 @@ using FluentResults; namespace Barotrauma.LuaCs.Services.Processing; -public interface IConverterService : IService +public interface IParserService : IService { Result TryParseResource(TSrc src); ImmutableArray> TryParseResources(IEnumerable sources); } -public interface IConverterServiceAsync : IService +public interface IParserServiceAsync : IService { Task> TryParseResourceAsync(TSrc src); Task>> TryParseResourcesAsync(IEnumerable sources); } - -public interface IProcessorService : IService -{ - TOut Process(TSrc src); -} - -public interface IProcessorServiceAsync : IService -{ - Task ProcessAsync(TSrc src); -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs index 8604bbe64..60dcbd4e1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs @@ -12,545 +12,81 @@ using FluentResults; namespace Barotrauma.LuaCs.Services.Processing; -public partial class ModConfigService : IConverterServiceAsync, IConverterService +public sealed class ModConfigService : IModConfigService { - private readonly IStorageService _storageService; - private readonly Lazy _packageManagementService; - private int _isDisposed; + private IStorageService _storageService; + private IParserServiceAsync _assemblyParserService; + private IParserServiceAsync _luaScriptParserService; + private IParserServiceAsync _configParserService; + private IParserServiceAsync _configProfileParserService; - private const string ModConfigFileName = "ModConfig.xml"; - private const string ModConfigRootName = "ModConfig"; - - public ModConfigService(IStorageService storageService, Lazy pms) + public ModConfigService(IStorageService storageService, + IParserServiceAsync assemblyParserService, + IParserServiceAsync luaScriptParserService, + IParserServiceAsync configParserService, + IParserServiceAsync configProfileParserService) { _storageService = storageService; - _packageManagementService = pms; - } + _assemblyParserService = assemblyParserService; + _luaScriptParserService = luaScriptParserService; + _configParserService = configParserService; + _configProfileParserService = configProfileParserService; + } + #region Disposal + public void Dispose() { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } + private int _isDisposed = 0; public bool IsDisposed { - get => ModUtils.Threading.GetBool(ref _isDisposed); - private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + get => ModUtils.Threading.GetBool(ref _isDisposed); + protected set => ModUtils.Threading.SetBool(ref _isDisposed, value); } - public async Task> TryParseResourceAsync(ContentPackage src) + #endregion + + public async Task> CreateConfigAsync(ContentPackage src) { - ((IService)this).CheckDisposed(); - - // validate package if (src is null) - return FluentResults.Result.Fail("ContentPackage is null"); - if (_storageService.DirectoryExists(src.Path) is { } res && (res.IsFailed || !res.Value)) - return FluentResults.Result.Fail($"ContentPackage does not exist or cannot be accessed: {src.Path}"); + ArgumentNullException.ThrowIfNull($"{nameof(CreateConfigAsync)}: Source is null."); - // find ModConfig.xml or deep scan on fail (legacy) - if (await _storageService.LoadPackageXmlAsync(src, ModConfigFileName) is - { IsSuccess: true, Value: var modConfigXml } - && modConfigXml.Root is { Name.LocalName: ModConfigRootName } root) + if (await TryGetModConfigXmlAsync(src) is { IsSuccess: true, Value: { } config }) { - return await GetModConfigInfoAsync(src, root); + return await CreateFromConfigXmlAsync(config); } - // legacy mode - try - { - // we only supported assemblies and lua scripts - var asm = GetAssembliesLegacy(src); - var lua = GetLuaScriptsLegacy(src); + return await CreateFromLegacyAsync(src); + } - return new ModConfigInfo() - { - Assemblies = asm, - LuaScripts = lua, - Configs = ImmutableArray.Empty, - ConfigProfiles = ImmutableArray.Empty, - Package = src, - PackageName = src.Name - }; - } - catch (Exception e) + public async Task Config)>> CreateConfigsAsync(ImmutableArray src) + { + var builder = ImmutableArray.CreateBuilder<(ContentPackage Source, Result Config)>(); + + foreach (var package in src) { - return FluentResults.Result.Fail($"Unable to parse legacy content package: {src.Name}: {src.Path}"); + builder.Add((package, await CreateConfigAsync(package))); } + + return builder.ToImmutable(); + } + + //--- Helpers + private async Task> TryGetModConfigXmlAsync(ContentPackage src) + { + + } + + private async Task> CreateFromConfigXmlAsync(XElement src) + { + throw new NotImplementedException(); } - private partial Task> GetModConfigInfoAsync(ContentPackage package, XElement root); - - private ImmutableArray GetAssemblies(ContentPackage src, IEnumerable elements) + private async Task> CreateFromLegacyAsync(ContentPackage src) { - var builder = ImmutableArray.CreateBuilder(); - var elementsList = elements.ToImmutableArray(); - - if (GetFilesList(src, elementsList, "Assembly", "*.dll") - is not { IsSuccess: true, Value: { } xmlFiles }) - return ImmutableArray.Empty; - - foreach (var file in xmlFiles) - { - // get platform, culture and target architecture - var info = GetElementsAttributesData(file.Item1, file.Item2.First()); - - builder.Add(new AssemblyResourceInfo() - { - Optional = info.IsOptional, - FilePaths = file.Item2, - InternalName = info.Name, - LoadPriority = info.LoadPriority, - OwnerPackage = src, - SupportedPlatforms = info.SupportedPlatforms, - SupportedTargets = info.SupportedTargets, - FriendlyName = file.Item1.GetAttributeString("Name", info.Name), - IsScript = false - }); - } - - if (GetFilesList(src, elementsList, "Assembly", "*.cs") - is not { IsSuccess: true, Value: { } xmlFiles2 }) - return ImmutableArray.Empty; - - foreach (var file in xmlFiles2) - { - // get platform, culture and target architecture - var info = GetElementsAttributesData(file.Item1, file.Item2.First()); - - builder.Add(new AssemblyResourceInfo() - { - Optional = info.IsOptional, - FilePaths = file.Item2, - InternalName = info.Name, - LoadPriority = info.LoadPriority, - OwnerPackage = src, - SupportedPlatforms = info.SupportedPlatforms, - SupportedTargets = info.SupportedTargets, - FriendlyName = file.Item1.GetAttributeString("Name", info.Name), - IsScript = true - }); - } - - return builder.Count > 0 - ? builder.ToImmutable() - : ImmutableArray.Empty; + throw new NotImplementedException(); } - - private ImmutableArray GetConfigs(ContentPackage src, IEnumerable elements) - { - var builder = ImmutableArray.CreateBuilder(); - if (GetXmlFilesList(src, elements, "Config") - is not { IsSuccess: true, Value: { } xmlFiles }) - return ImmutableArray.Empty; - - foreach (var file in xmlFiles) - { - // get platform, culture and target architecture - var info = GetElementsAttributesData(file.Item1, file.Item2.First()); - - builder.Add(new ConfigResourceInfo() - { - Optional = info.IsOptional, - FilePaths = file.Item2, - InternalName = info.Name, - LoadPriority = info.LoadPriority, - OwnerPackage = src, - SupportedPlatforms = info.SupportedPlatforms, - SupportedTargets = info.SupportedTargets - }); - } - - return builder.Count > 0 - ? builder.ToImmutable() - : ImmutableArray.Empty; - } - - private ImmutableArray GetConfigProfiles(ContentPackage src, IEnumerable elements) - { - var builder = ImmutableArray.CreateBuilder(); - if (GetXmlFilesList(src, elements, "Config") - is not { IsSuccess: true, Value: { } xmlFiles }) - return ImmutableArray.Empty; - - foreach (var file in xmlFiles) - { - // get platform, culture and target architecture - var info = GetElementsAttributesData(file.Item1, file.Item2.First()); - - builder.Add(new ConfigProfileResourceInfo() - { - Optional = info.IsOptional, - FilePaths = file.Item2, - InternalName = info.Name, - LoadPriority = info.LoadPriority, - OwnerPackage = src, - SupportedPlatforms = info.SupportedPlatforms, - SupportedTargets = info.SupportedTargets - }); - } - - return builder.Count > 0 - ? builder.ToImmutable() - : ImmutableArray.Empty; - } - - private ImmutableArray GetLuaScripts(ContentPackage src, IEnumerable elements) - { - var builder = ImmutableArray.CreateBuilder(); - if (GetXmlFilesList(src, elements, "Config") - is not { IsSuccess: true, Value: { } xmlFiles }) - return ImmutableArray.Empty; - - foreach (var file in xmlFiles) - { - // get platform, culture and target architecture - var info = GetElementsAttributesData(file.Item1, file.Item2.First()); - - builder.Add(new LuaScriptsResourceInfo() - { - Optional = info.IsOptional, - FilePaths = file.Item2, - InternalName = info.Name, - LoadPriority = info.LoadPriority, - OwnerPackage = src, - SupportedPlatforms = info.SupportedPlatforms, - SupportedTargets = info.SupportedTargets, - IsAutorun = file.Item1.GetAttributeBool("RunFile", true) - }); - } - - return builder.Count > 0 - ? builder.ToImmutable() - : ImmutableArray.Empty; - } - - private Result)>> GetXmlFilesList(ContentPackage src, - IEnumerable elements, string elementNameCheck) => - GetFilesList(src, elements, elementNameCheck, "*.xml"); - - private Result)>> GetFilesList(ContentPackage src, - IEnumerable elements, string elementNameCheck, string filter) - { - var builder = ImmutableArray.CreateBuilder<(XElement, ImmutableArray)>(); - - if (elementNameCheck.IsNullOrWhiteSpace()) - throw new ArgumentNullException($"{nameof(GetXmlFilesList)}: The element check is null."); - - foreach (var element in elements) - { - if (element.Name.LocalName != elementNameCheck) - throw new ArgumentException("Element is not a Localization element"); - - if (element.GetAttributeString("Folder", string.Empty) is { } str - && !string.IsNullOrWhiteSpace(str)) - { - if (_storageService.FindFilesInPackage(src, str, filter, true) - is not { IsSuccess: true, Value: var fpList } || !fpList.Any()) - { - continue; - } - - foreach (var fileP in fpList) - builder.Add((element, fpList.ToImmutableArray())); - } - else if (element.GetAttributeString("File", string.Empty) is { } fileStr - && !string.IsNullOrWhiteSpace(fileStr) - && _storageService.GetAbsFromPackage(src, fileStr) is { IsSuccess: true, Value: var fp } - && _storageService.FileExists(fp) is { IsSuccess: true, Value: true }) - { - builder.Add((element, new [] { fileStr }.ToImmutableArray())); - } - } - - return builder.Count > 0 - ? FluentResults.Result.Ok(builder.ToImmutable()) - : FluentResults.Result.Fail($"No files found"); - } - - private ResourceAdditionalInfo GetElementsAttributesData(XElement element, string localPath) - { - return new ResourceAdditionalInfo( - element.GetAttributeString("Name", localPath), - GetSupportedPlatforms(element.GetAttributeString("Platform", "any")), - GetSupportedTargets(element.GetAttributeString("Target", "any")), - GetSupportedCultures(element), - element.GetAttributeBool("Optional", false), - element.GetAttributeInt("Priority", 0)); - - Platform GetSupportedPlatforms(string platformName) => platformName.ToLowerInvariant().Trim() switch - { - "windows" => Platform.Windows, - "linux" => Platform.Linux, - "osx" => Platform.OSX, - _ => Platform.Windows | Platform.Linux | Platform.OSX - }; - - Target GetSupportedTargets(string targetName) => targetName.ToLowerInvariant().Trim() switch - { - "client" => Target.Client, - "server" => Target.Server, - _ => Target.Client | Target.Server, - }; - - ImmutableArray GetSupportedCultures(XElement element) - { - var culture = element.GetAttributeString("Culture", string.Empty); - if (string.IsNullOrWhiteSpace(culture)) - return new[] { CultureInfo.InvariantCulture }.ToImmutableArray(); - var builder = ImmutableArray.CreateBuilder(); - var arr = culture.Split(','); - if (arr.Length == 0) - return new[] { CultureInfo.InvariantCulture }.ToImmutableArray(); - foreach (var culstr in arr) - { - if (string.IsNullOrWhiteSpace(culstr)) - continue; - try - { - builder.Add( - culstr.ToLowerInvariant().Trim() == "default" - ? CultureInfo.InvariantCulture - : CultureInfo.GetCultureInfo(culstr)); - } - catch (CultureNotFoundException e) - { - // This is the case if a culture is specified by the package that is not supported by the OS/.NET ENV. - // We ignore it since we can never use it. - continue; - } - } - - return builder.Count > 0 - ? builder.ToImmutable() - : new[] { CultureInfo.InvariantCulture }.ToImmutableArray(); - } - } - - private ImmutableArray GetAssembliesLegacy(ContentPackage src) - { - var builder = ImmutableArray.CreateBuilder(); - // server, linux - if (_storageService.FindFilesInPackage(src, "bin/Server/Linux", "*.dll", true) - is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesSrvLin}) - { - builder.Add(new AssemblyResourceInfo() - { - FilePaths = filesSrvLin, - FriendlyName = "AssembliesServerLinux", - InternalName = "AssembliesServerLinux", - IsScript = false, - LoadPriority = 1, - Optional = false, - OwnerPackage = src, - SupportedPlatforms = Platform.Linux, - SupportedTargets = Target.Server - }); - } - - // server, osx - if (_storageService.FindFilesInPackage(src, "bin/Server/OSX", "*.dll", true) - is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesSrvOsx}) - { - builder.Add(new AssemblyResourceInfo() - { - FilePaths = filesSrvOsx, - FriendlyName = "AssembliesServerOSX", - InternalName = "AssembliesServerOSX", - IsScript = false, - LoadPriority = 1, - Optional = false, - OwnerPackage = src, - SupportedPlatforms = Platform.OSX, - SupportedTargets = Target.Server - }); - } - - // server, osx - if (_storageService.FindFilesInPackage(src, "bin/Server/Windows", "*.dll", true) - is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesSrvWin}) - { - builder.Add(new AssemblyResourceInfo() - { - FilePaths = filesSrvWin, - FriendlyName = "AssembliesServerWin", - InternalName = "AssembliesServerWin", - IsScript = false, - LoadPriority = 1, - Optional = false, - OwnerPackage = src, - SupportedPlatforms = Platform.Windows, - SupportedTargets = Target.Server - }); - } - - // client, linux - if (_storageService.FindFilesInPackage(src, "bin/Client/Linux", "*.dll", true) - is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesCliLin}) - { - builder.Add(new AssemblyResourceInfo() - { - FilePaths = filesCliLin, - FriendlyName = "AssembliesClientLinux", - InternalName = "AssembliesClientLinux", - IsScript = false, - LoadPriority = 1, - Optional = false, - OwnerPackage = src, - SupportedPlatforms = Platform.Linux, - SupportedTargets = Target.Client - }); - } - - // server, osx - if (_storageService.FindFilesInPackage(src, "bin/Client/OSX", "*.dll", true) - is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesCliOsx}) - { - builder.Add(new AssemblyResourceInfo() - { - FilePaths = filesCliOsx, - FriendlyName = "AssembliesClientOSX", - InternalName = "AssembliesClientOSX", - IsScript = false, - LoadPriority = 1, - Optional = false, - OwnerPackage = src, - SupportedPlatforms = Platform.OSX, - SupportedTargets = Target.Client - }); - } - - // server, osx - if (_storageService.FindFilesInPackage(src, "bin/Client/Windows", "*.dll", true) - is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesCliWin}) - { - builder.Add(new AssemblyResourceInfo() - { - FilePaths = filesCliWin, - FriendlyName = "AssembliesClientWin", - InternalName = "AssembliesClientWin", - IsScript = false, - LoadPriority = 1, - Optional = false, - OwnerPackage = src, - SupportedPlatforms = Platform.Windows, - SupportedTargets = Target.Client - }); - } - - var sharedCsBuilder = ImmutableArray.CreateBuilder(); - if (_storageService.FindFilesInPackage(src, "CSharp/Shared", "*.cs", true) - is { IsSuccess: true, Value: { IsDefaultOrEmpty: false } files }) - { - sharedCsBuilder.AddRange(files); - } - - var filesCssShared = sharedCsBuilder.MoveToImmutable(); - var sharedFound = !filesCssShared.IsDefaultOrEmpty; - - // source files legacy: server - if (_storageService.FindFilesInPackage(src, "CSharp/Server", "*.cs", true) - is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesCssServer}) - { - builder.Add(new AssemblyResourceInfo() - { - FilePaths = sharedFound ? filesCssServer.Concat(filesCssShared).ToImmutableArray() : filesCssServer, - FriendlyName = "CssServer", - InternalName = "CssServer", - IsScript = true, - LoadPriority = 1, - Optional = false, - OwnerPackage = src, - SupportedPlatforms = Platform.Linux | Platform.OSX | Platform.Windows, - SupportedTargets = Target.Server - }); - } - - // source files legacy: client - if (_storageService.FindFilesInPackage(src, "CSharp/Client", "*.cs", true) - is { IsSuccess: true, Value: { IsDefaultOrEmpty: false} filesCssClient}) - { - builder.Add(new AssemblyResourceInfo() - { - FilePaths = sharedFound ? filesCssClient.Concat(filesCssShared).ToImmutableArray() : filesCssClient, - FriendlyName = "CssClient", - InternalName = "CssClient", - IsScript = true, - LoadPriority = 1, - Optional = false, - OwnerPackage = src, - SupportedPlatforms = Platform.Linux | Platform.OSX | Platform.Windows, - SupportedTargets = Target.Client - }); - } - - return builder.MoveToImmutable(); - } - private ImmutableArray GetLuaScriptsLegacy(ContentPackage src) - { - var builder = ImmutableArray.CreateBuilder(); - - if (_storageService.FindFilesInPackage(src, "Lua", "*.lua", true) - is { IsSuccess: true, Value: { IsDefaultOrEmpty: false } fileAll }) - { - builder.Add(new LuaScriptsResourceInfo() - { - FilePaths = fileAll.Where(path => !path.Contains("Autorun")).ToImmutableArray(), - InternalName = "LuaScriptsNormal", - Optional = false, - IsAutorun = false, - OwnerPackage = src, - SupportedPlatforms = Platform.Linux | Platform.OSX | Platform.Windows, - SupportedTargets = Target.Client | Target.Server - }); - - builder.Add(new LuaScriptsResourceInfo() - { - FilePaths = fileAll.Where(path => path.Contains("Autorun")).ToImmutableArray(), - InternalName = "LuaScriptsAutorun", - Optional = false, - IsAutorun = true, - OwnerPackage = src, - SupportedPlatforms = Platform.Linux | Platform.OSX | Platform.Windows, - SupportedTargets = Target.Client | Target.Server - }); - } - - return builder.MoveToImmutable(); - } - - public async Task>> TryParseResourcesAsync(IEnumerable sources) - { - ((IService)this).CheckDisposed(); - - var srcs = sources.ToImmutableArray(); - var results = new AsyncLocal>>(); - await srcs.ParallelForEachAsync(async pkg => - { - try - { - results.Value.Enqueue(await TryParseResourceAsync(pkg)); - } - catch (Exception e) - { - // this should never happen but this is to stop partial execution exit. - results.Value.Enqueue( - FluentResults.Result.Fail($"Failed to parse package {pkg?.Name}: {e.Message}")); - } - }); - return results.Value.ToImmutableArray(); - } - - public Result TryParseResource(ContentPackage src) => - TryParseResourceAsync(src).GetAwaiter().GetResult(); - public ImmutableArray> TryParseResources(IEnumerable sources) => - TryParseResourcesAsync(sources.ToImmutableArray()).GetAwaiter().GetResult(); - - private record ResourceAdditionalInfo( - string Name, - Platform SupportedPlatforms, - Target SupportedTargets, - ImmutableArray SupportedCultures, - bool IsOptional, - int LoadPriority); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ResourceInfoProcessors.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ResourceInfoProcessors.cs deleted file mode 100644 index d6fb94b32..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ResourceInfoProcessors.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using Barotrauma.LuaCs.Data; - -namespace Barotrauma.LuaCs.Services.Processing; - -public partial class ResourceInfoArrayPacker : - IProcessorService, IAssembliesResourcesInfo>, - IProcessorService, IConfigsResourcesInfo>, - IProcessorService, IConfigProfilesResourcesInfo>, - IProcessorService, ILuaScriptsResourcesInfo> -{ - private bool _isDisposed; - public IAssembliesResourcesInfo Process(IReadOnlyList src) - { - return new AssemblyResourcesInfo(src.ToImmutableArray()); - } - - public IConfigsResourcesInfo Process(IReadOnlyList src) - { - return new ConfigResourcesInfo(src.ToImmutableArray()); - } - - public IConfigProfilesResourcesInfo Process(IReadOnlyList src) - { - return new ConfigProfilesResourcesInfo(src.ToImmutableArray()); - } - - public ILuaScriptsResourcesInfo Process(IReadOnlyList src) - { - return new LuaScriptsResourcesInfo(src.ToImmutableArray()); - } - - public void Dispose() - { - // Stateless class - GC.SuppressFinalize(this); - IsDisposed = true; - } - - public bool IsDisposed - { - get => _isDisposed; - set => _isDisposed = value; - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs index 54730328f..d1650b962 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs @@ -15,6 +15,7 @@ using Barotrauma.LuaCs.Data; using Barotrauma.Steam; using FluentResults; using FluentResults.LuaCs; +using Microsoft.Toolkit.Diagnostics; using Error = FluentResults.Error; using Path = Barotrauma.IO.Path; @@ -98,49 +99,49 @@ public class StorageService : IStorageService } public virtual FluentResults.Result LoadLocalXml(ContentPackage package, string localFilePath) => - GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? TryLoadXml(r.Value) : r.ToResult(); public virtual FluentResults.Result LoadLocalBinary(ContentPackage package, string localFilePath) => - GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? TryLoadBinary(r.Value) : r.ToResult(); public virtual FluentResults.Result LoadLocalText(ContentPackage package, string localFilePath) => - GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? TryLoadText(r.Value) : r.ToResult(); public virtual FluentResults.Result SaveLocalXml(ContentPackage package, string localFilePath, XDocument document) => - GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? TrySaveXml(r.Value, document) : r.ToResult(); public virtual FluentResults.Result SaveLocalBinary(ContentPackage package, string localFilePath, in byte[] bytes) => - GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? TrySaveBinary(r.Value, bytes) : r.ToResult(); public virtual FluentResults.Result SaveLocalText(ContentPackage package, string localFilePath, in string text) => - GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? TrySaveText(r.Value, text) : r.ToResult(); public virtual async Task> LoadLocalXmlAsync(ContentPackage package, string localFilePath) => - GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? await TryLoadXmlAsync(r.Value) : r.ToResult(); public virtual async Task> LoadLocalBinaryAsync(ContentPackage package, string localFilePath) => - GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? await TryLoadBinaryAsync(r.Value) : r.ToResult(); public virtual async Task> LoadLocalTextAsync(ContentPackage package, string localFilePath) => - GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? await TryLoadTextAsync(r.Value) : r.ToResult(); public virtual async Task SaveLocalXmlAsync(ContentPackage package, string localFilePath, XDocument document) => - GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? await TrySaveXmlAsync(r.Value, document) : r.ToResult(); public virtual async Task SaveLocalBinaryAsync(ContentPackage package, string localFilePath, byte[] bytes) => - GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? await TrySaveBinaryAsync(r.Value, bytes) : r.ToResult(); public virtual async Task SaveLocalTextAsync(ContentPackage package, string localFilePath, string text) => - GetAbsFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? await TrySaveTextAsync(r.Value, text) : r.ToResult(); public virtual FluentResults.Result LoadPackageXml(ContentPackage package, string localFilePath) => - GetAbsFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + GetAbsoluePathFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? TryLoadXml(r.Value) : r.ToResult(); public virtual FluentResults.Result LoadPackageBinary(ContentPackage package, string localFilePath) => - GetAbsFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + GetAbsoluePathFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? TryLoadBinary(r.Value) : r.ToResult(); public virtual FluentResults.Result LoadPackageText(ContentPackage package, string localFilePath) => - GetAbsFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + GetAbsoluePathFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? TryLoadText(r.Value) : r.ToResult(); @@ -181,7 +182,7 @@ public class StorageService : IStorageService public virtual FluentResults.Result> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively) { ((IService)this).CheckDisposed(); - var r = GetAbsFromPackage(package, localSubfolder); + var r = GetAbsoluePathFromPackage(package, localSubfolder); if (r is { IsFailed: true }) return r.ToResult(); var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result>)>(); @@ -192,15 +193,15 @@ public class StorageService : IStorageService } public virtual async Task> LoadPackageXmlAsync(ContentPackage package, string localFilePath) => - GetAbsFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + GetAbsoluePathFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? await TryLoadXmlAsync(r.Value) : r.ToResult(); public virtual async Task> LoadPackageBinaryAsync(ContentPackage package, string localFilePath) => - GetAbsFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + GetAbsoluePathFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? await TryLoadBinaryAsync(r.Value) : r.ToResult(); public virtual async Task> LoadPackageTextAsync(ContentPackage package, string localFilePath) => - GetAbsFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } + GetAbsoluePathFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } ? await TryLoadTextAsync(r.Value) : r.ToResult(); public virtual async Task)>> LoadPackageXmlFilesAsync(ContentPackage package, ImmutableArray localFilePaths) @@ -529,7 +530,7 @@ public class StorageService : IStorageService .WithMetadata(MetadataType.ExceptionObject, this) .WithMetadata(MetadataType.Sources, localfp); - private FluentResults.Result GetAbsFromLocal(ContentPackage package, string localFilePath) + private FluentResults.Result GetAbsoluePathFromLocal(ContentPackage package, string localFilePath) { if (Path.IsPathRooted(localFilePath)) { @@ -539,13 +540,7 @@ public class StorageService : IStorageService .WithMetadata(MetadataType.RootObject, localFilePath)); } - if (package is null) - { - return new FluentResults.Result().WithError( - new Error($"{nameof(GetAbsFromPackage)} The package reference for {localFilePath} is null!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, localFilePath)); - } + Guard.IsNotNull(package, nameof(package)); return new FluentResults.Result().WithSuccess($"Path constructed") .WithValue(System.IO.Path.GetFullPath(System.IO.Path.Combine( @@ -556,15 +551,9 @@ public class StorageService : IStorageService localFilePath))); } - public FluentResults.Result GetAbsFromPackage(ContentPackage package, string localFilePath) + public FluentResults.Result GetAbsoluePathFromPackage(ContentPackage package, string localFilePath) { - if (package is null) - { - return new FluentResults.Result().WithError( - new Error($"{nameof(GetAbsFromPackage)} The package reference for {localFilePath} is null!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, localFilePath)); - } + Guard.IsNotNull(package, nameof(package)); if (localFilePath.IsNullOrWhiteSpace()) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IModConfigInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IModConfigInfo.cs new file mode 100644 index 000000000..78bac94aa --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IModConfigInfo.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Services.Processing; +using FluentResults; + +namespace Barotrauma.LuaCs.Services; + +public interface IModConfigService : IService +{ + /// + /// Loads or dynamically generates a for the given . + ///
Throws a if the package is null. + ///
+ /// + /// + Task> CreateConfigAsync([NotNull]ContentPackage src); + Task Config)>> CreateConfigsAsync(ImmutableArray src); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs index 860a8eb6e..58ca07d55 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs @@ -53,7 +53,7 @@ public interface IStorageService : IService ImmutableArray<(string, FluentResults.Result)> LoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePaths); ImmutableArray<(string, FluentResults.Result)> LoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePaths); FluentResults.Result> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively); - FluentResults.Result GetAbsFromPackage(ContentPackage package, string localFilePath); + FluentResults.Result GetAbsoluePathFromPackage(ContentPackage package, string localFilePath); // async // singles Task> LoadPackageXmlAsync(ContentPackage package, string localFilePath); From 595470ccfb4c61b344d8c78c5e6de57dee6b28e5 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sat, 3 Jan 2026 04:59:33 -0500 Subject: [PATCH 029/288] [Save/Sync] In-Progress rewrite of ConfigService and ModConfigService --- .../Services/Processing/ModConfigService.cs | 31 -- .../SharedSource/LuaCs/LuaCsSetup.cs | 5 +- .../Services/Processing/ConfigIOService.cs | 278 ------------------ .../Services/Processing/IConfigIOService.cs | 15 - .../Services/Processing/ModConfigService.cs | 26 +- 5 files changed, 16 insertions(+), 339 deletions(-) delete mode 100644 Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/Processing/ModConfigService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigIOService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConfigIOService.cs diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/Processing/ModConfigService.cs deleted file mode 100644 index 3c59ac10e..000000000 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/Processing/ModConfigService.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using System.Xml.Linq; -using Barotrauma.LuaCs.Data; -using FluentResults; - -namespace Barotrauma.LuaCs.Services.Processing; - -public partial class ModConfigService -{ - private partial async Task> GetModConfigInfoAsync(ContentPackage package, XElement root) - { - var asm = root.GetChildElements("Assembly").ToImmutableArray(); - var cfg = root.GetChildElements("Config").ToImmutableArray(); - var lua = root.GetChildElements("Lua").ToImmutableArray(); - - return FluentResults.Result.Ok(new ModConfigInfo() - { - Package = package, - PackageName = package.Name, - Assemblies = asm.Any() ? GetAssemblies(package, asm) : ImmutableArray.Empty, - Configs = cfg.Any() ? GetConfigs(package, cfg) : ImmutableArray.Empty, - ConfigProfiles = cfg.Any() ? GetConfigProfiles(package, cfg) : ImmutableArray.Empty, - LuaScripts = lua.Any() ? GetLuaScripts(package, lua) : ImmutableArray.Empty - }); - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 6b94165f4..4714df7cc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -54,10 +54,7 @@ namespace Barotrauma // TODO: INetworkingService // TODO: [Resource Converter/Parser Services] - // Loaders and Processors (yes the naming is reversed, oops). - _servicesProvider.RegisterServiceType, ModConfigService>(ServiceLifetime.Transient); - _servicesProvider.RegisterServiceType, ModConfigService>(ServiceLifetime.Transient); - _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); // service config data _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigIOService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigIOService.cs deleted file mode 100644 index b82098744..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigIOService.cs +++ /dev/null @@ -1,278 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Xml.Linq; -using Barotrauma.LuaCs.Data; -using FarseerPhysics.Common; -using FluentResults; -using OneOf; - -namespace Barotrauma.LuaCs.Services.Processing; - -public class ConfigIOService : IConfigIOService -{ - private readonly IStorageService _storageService; - private readonly IConfigServiceConfig _configServiceConfig; - - public ConfigIOService(IStorageService storageService, IConfigServiceConfig configServiceConfig) - { - this._storageService = storageService; - storageService.UseCaching = true; - _configServiceConfig = configServiceConfig; - } - - public void Dispose() - { - // stateless service - return; - } - - // stateless service - public bool IsDisposed => false; - public FluentResults.Result Reset() - { - _storageService.PurgeCache(); - return FluentResults.Result.Ok(); - } - - public async Task>> TryParseResourceAsync(IConfigResourceInfo src) - { - if (src?.OwnerPackage is null || src.FilePaths.IsDefaultOrEmpty) - return FluentResults.Result.Fail($"{nameof(TryParseResourceAsync)}: Config resource and/or components were null."); - - try - { - var infos = await _storageService.LoadPackageXmlFilesAsync(src.OwnerPackage, [..src.FilePaths.Select(fp => fp.FullPath)]); - if (infos.IsDefaultOrEmpty) - return FluentResults.Result.Fail($"{nameof(TryParseResourceAsync)}: No resources found."); - - var errList = new List(); - - var resList = infos.Select(info => - { - if (info.Item2.Errors.Any()) - errList.AddRange(info.Item2.Errors); - if (info.Item2.IsFailed || info.Item2.Value is not { } configXDoc) - { - errList.Add(new Error($"Unable to parse file: {info.Item1}")); - return default; - } - - return (info.Item1, configXDoc); - }) - .Where(doc => !doc.Item1.IsNullOrWhiteSpace() && doc.configXDoc != null) - .SelectMany(doc => doc.configXDoc.Root.GetChildElements("Configuration")) - .SelectMany(cfgContainer => cfgContainer.GetChildElements("Configs")) - .SelectMany(cfgContainer => cfgContainer.GetChildElements("Config")) - .Select(async cfgElement => - { - try - { - OneOf.OneOf defaultValue = cfgElement.GetChildElement("Value"); - if (defaultValue.AsT1 is null) - defaultValue = cfgElement.GetAttributeString("Value", string.Empty); - - var internalName = cfgElement.GetAttributeString("Name", string.Empty); - if (internalName.IsNullOrWhiteSpace()) - return null; - - return new ConfigInfo() - { - DataType = Type.GetType(cfgElement.GetAttributeString("Type", "string")), - OwnerPackage = src.OwnerPackage, - DefaultValue = defaultValue, - Value = await LoadConfigDataFromLocal(src.OwnerPackage, internalName) is { IsSuccess: true } res - ? res.Value : defaultValue, - EditableStates = cfgElement.GetAttributeBool("ReadOnly", false) - ? RunState.Unloaded // read-only - : RunState.Running, // editable at runtime - InternalName = internalName, - NetSync = Enum.Parse( - cfgElement.GetAttributeString("NetSync", nameof(NetSync.None))), -#if CLIENT - DisplayName = cfgElement.GetAttributeString("DisplayName", null), - Description = cfgElement.GetAttributeString("Description", null), - DisplayCategory = cfgElement.GetAttributeString("Category", null), - ShowInMenus = cfgElement.GetAttributeBool("ShowInMenus", true), - Tooltip = cfgElement.GetAttributeString("Tooltip", null), - ImageIconPath = cfgElement.GetAttributeString("Image", null) -#endif - }; - } - catch (Exception e) - { - errList.Add(new Error($"Failed to parse config var for package {src.OwnerPackage}")); - errList.Add(new ExceptionalError(e)); - return null; - } - }) - .Where(task => task is not null) - .ToImmutableArray(); - - var result = (await Task.WhenAll(resList)).ToImmutableArray(); - - var ret = FluentResults.Result.Ok((IReadOnlyList)result); - if (errList.Any()) - ret.Errors.AddRange(errList); - return ret; - } - catch(Exception e) - { - return FluentResults.Result.Fail($"Failed to parse config resource for package {src.OwnerPackage}"); - } - } - - public async Task>>> TryParseResourcesAsync(IEnumerable sources) - { - var results = new ConcurrentQueue>>(); - - var src = sources.ToImmutableArray(); - if (!src.Any()) - return ImmutableArray>>.Empty; - - await src.ParallelForEachAsync(async cfg => - { - var res = await TryParseResourceAsync(cfg); - results.Enqueue(res); - }, 2); // we only need 2 parallels to buffer against disk loading. - - return results.ToImmutableArray(); - } - - public async Task>> TryParseResourceAsync(IConfigProfileResourceInfo src) - { - if (src?.OwnerPackage is null || src.FilePaths.IsDefaultOrEmpty) - return FluentResults.Result.Fail($"{nameof(TryParseResourceAsync)}: Profile resource and/or components were null."); - - try - { - var infos = await _storageService.LoadPackageXmlFilesAsync(src.OwnerPackage, src.FilePaths); - if (infos.IsDefaultOrEmpty) - return FluentResults.Result.Fail($"{nameof(TryParseResourceAsync)}: No resources found."); - - var errList = new List(); - - var resList = infos.Select(info => - { - if (info.Item2.Errors.Any()) - errList.AddRange(info.Item2.Errors); - if (info.Item2.IsFailed || info.Item2.Value is not { } configXDoc) - { - errList.Add(new Error($"Unable to parse file: {info.Item1}")); - return null; - } - - return configXDoc; - }) - .Where(doc => doc is not null) - .SelectMany(doc => doc.Root.GetChildElements("Configuration")) - .SelectMany(cfgContainer => cfgContainer.GetChildElements("Profiles")) - .SelectMany(cfgContainer => cfgContainer.GetChildElements("Profile")) - .Select(cfgElement => - { - try - { - return new ConfigProfileInfo() - { - OwnerPackage = src.OwnerPackage, - InternalName = cfgElement.GetAttributeString("Name", null), - ProfileValues = cfgElement.GetChildElements("ConfigValue") - .Select Value)>(element => - { - if (element.GetAttributeString("Name", null) is not { } name) - return default; - if (element.GetAttributeString("Value", null) is { } value) - return (name, value); - if (element.GetChildElement("Value") is { } xValue) - return (name, xValue); - return default; - }) - .Where(val => val.ConfigName is not null && val.Value.Match( - s => !s.IsNullOrWhiteSpace(), - element => element is not null)) - .ToList() - }; - } - catch (Exception e) - { - errList.Add(new Error($"Failed to parse profile var for package {src.OwnerPackage}")); - errList.Add(new ExceptionalError(e)); - return null; - } - }) - .Where(cfgInfo => cfgInfo != null && !cfgInfo.InternalName.IsNullOrWhiteSpace()) - .ToImmutableArray(); - - var ret = FluentResults.Result.Ok((IReadOnlyList)resList); - if (errList.Any()) - ret.Errors.AddRange(errList); - return ret; - } - catch(Exception e) - { - return FluentResults.Result.Fail($"Failed to parse profile resource for package {src.OwnerPackage}"); - } - } - - public async Task>>> TryParseResourcesAsync(IEnumerable sources) - { - var results = new ConcurrentQueue>>(); - - var src = sources.ToImmutableArray(); - if (!src.Any()) - return ImmutableArray>>.Empty; - - await src.ParallelForEachAsync(async cfg => - { - var res = await TryParseResourceAsync(cfg); - results.Enqueue(res); - }, 2); // we only need 2 parallels to buffer against disk loading. - - return results.ToImmutableArray(); - } - - private static readonly Regex RemoveInvalidChars = new Regex($"[{Regex.Escape(new string(System.IO.Path.GetInvalidFileNameChars()))}]", - RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant); - - private string SanitizedFileName(string fileName, string replacement = "_") - { - return RemoveInvalidChars.Replace(fileName, replacement); - } - - public async Task SaveConfigDataLocal(ContentPackage package, string configName, XElement serializedValue) - { - if (package is null || package.Name.IsNullOrWhiteSpace() || configName.IsNullOrWhiteSpace() || serializedValue is null) - return FluentResults.Result.Fail($"{nameof(SaveConfigDataLocal)}: Argument(s) were null"); - - var res = await LoadPackageConfigDocInternal(package); - - throw new NotImplementedException(); //TODO: Complete once the locally saved data structure is finalized. - } - - public async Task>> LoadConfigDataFromLocal(ContentPackage package, string configName) - { - if (package is null || package.Name.IsNullOrWhiteSpace() || configName.IsNullOrWhiteSpace()) - return FluentResults.Result.Fail($"{nameof(LoadConfigDataFromLocal)}: Argument(s) were null"); - - var filePath = _configServiceConfig.LocalConfigPathPartial.Replace( - _configServiceConfig.FileNamePattern, - $"{SanitizedFileName(package.Name)}.xml"); - - var res = await _storageService.LoadLocalXmlAsync(package, filePath); - - throw new NotImplementedException(); //TODO: Complete once the locally saved data structure is finalized. - } - - private async Task> LoadPackageConfigDocInternal(ContentPackage package) - { - var filePath = _configServiceConfig.LocalConfigPathPartial.Replace( - _configServiceConfig.FileNamePattern, - $"{SanitizedFileName(package.Name)}.xml"); - - throw new NotImplementedException(); //TODO: Complete once the locally saved data structure is finalized. - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConfigIOService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConfigIOService.cs deleted file mode 100644 index 72d1dff10..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IConfigIOService.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Xml.Linq; -using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services.Processing; - -namespace Barotrauma.LuaCs.Services.Processing; - -public interface IConfigIOService : IReusableService, - IParserServiceAsync>, - IParserServiceAsync> -{ - Task SaveConfigDataLocal(ContentPackage package, string configName, XElement serializedValue); - Task>> LoadConfigDataFromLocal(ContentPackage package, string configName); -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs index 60dcbd4e1..0c3bce1f1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs @@ -9,6 +9,8 @@ using System.Threading.Tasks; using System.Xml.Linq; using Barotrauma.LuaCs.Data; using FluentResults; +using Microsoft.Toolkit.Diagnostics; +using MoonSharp.VsCodeDebugger.SDK; namespace Barotrauma.LuaCs.Services.Processing; @@ -33,7 +35,7 @@ public sealed class ModConfigService : IModConfigService _configProfileParserService = configProfileParserService; } - #region Disposal + #region Dispose public void Dispose() { @@ -44,15 +46,14 @@ public sealed class ModConfigService : IModConfigService public bool IsDisposed { get => ModUtils.Threading.GetBool(ref _isDisposed); - protected set => ModUtils.Threading.SetBool(ref _isDisposed, value); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); } #endregion public async Task> CreateConfigAsync(ContentPackage src) { - if (src is null) - ArgumentNullException.ThrowIfNull($"{nameof(CreateConfigAsync)}: Source is null."); + Guard.IsNotNull(src, nameof(src)); if (await TryGetModConfigXmlAsync(src) is { IsSuccess: true, Value: { } config }) { @@ -64,20 +65,23 @@ public sealed class ModConfigService : IModConfigService public async Task Config)>> CreateConfigsAsync(ImmutableArray src) { - var builder = ImmutableArray.CreateBuilder<(ContentPackage Source, Result Config)>(); + var builder = new ConcurrentQueue<(ContentPackage Source, Result Config)>(); - foreach (var package in src) + await src.ParallelForEachAsync(async package => { - builder.Add((package, await CreateConfigAsync(package))); - } - - return builder.ToImmutable(); + var res = await CreateConfigAsync(package); + builder.Enqueue((package, res)); + }); + + return builder.OrderBy(pkg => src.IndexOf(pkg.Source)).ToImmutableArray(); } //--- Helpers private async Task> TryGetModConfigXmlAsync(ContentPackage src) { - + return await _storageService.LoadPackageXmlAsync(src, "ModConfig.xml") is { IsSuccess: true, Value: { Root: {} config} } + ? FluentResults.Result.Ok(config) + : FluentResults.Result.Fail("ModConfig.xml not found"); } private async Task> CreateFromConfigXmlAsync(XElement src) From fe982b15b03d6b41f53fe7b2cb9daf83e957eaaa Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 3 Jan 2026 11:30:13 -0300 Subject: [PATCH 030/288] Fix LuaScriptManagementService compile error --- .../LuaCs/Services/LuaScriptManagementService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs index 8dda54b32..2acfcd9d2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs @@ -78,13 +78,13 @@ public class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataS var result = FluentResults.Result.Ok(); - foreach (var resource in _resourcesInfo) + foreach (ILuaScriptResourceInfo resource in _resourcesInfo) { - foreach (var filePath in resource.FilePaths) + foreach (ContentPath filePath in resource.FilePaths) { try { - _script?.Call(_script.LoadFile(filePath)); + _script?.Call(_script.LoadFile(filePath.Value)); } catch(Exception e) { From 1dad2babb7c9df6765fd713ba89999a0b09a9cda Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 4 Jan 2026 12:20:12 -0300 Subject: [PATCH 031/288] Move Lua compatibility files around and start adding them to LuaScriptManagementService --- .../LuaCs/{ => Lua/LuaClasses}/LuaCsHook.cs | 0 .../{ => Lua/LuaClasses}/LuaCsHookCompat.cs | 0 .../LuaCs/{ => Lua/LuaClasses}/LuaCsLogger.cs | 0 .../{ => Lua/LuaClasses}/LuaCsNetworking.cs | 0 .../LuaClasses}/LuaCsPerformanceCounter.cs | 0 .../LuaCs/{ => Lua/LuaClasses}/LuaCsSteam.cs | 0 .../LuaCs/{ => Lua/LuaClasses}/LuaCsTimer.cs | 6 +- .../{ => Lua/LuaClasses}/LuaCsUtility.cs | 0 .../{Services => Lua/LuaClasses}/LuaGame.cs | 0 .../SharedSource/LuaCs/Lua/LuaConverters.cs | 53 ++++++++++-------- .../Services/Compatibility/ILuaCsTimer.cs | 14 +++++ .../Services/LuaScriptManagementService.cs | 55 ++++++++++++++++++- 12 files changed, 101 insertions(+), 27 deletions(-) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{ => Lua/LuaClasses}/LuaCsHook.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{ => Lua/LuaClasses}/LuaCsHookCompat.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{ => Lua/LuaClasses}/LuaCsLogger.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{ => Lua/LuaClasses}/LuaCsNetworking.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{ => Lua/LuaClasses}/LuaCsPerformanceCounter.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{ => Lua/LuaClasses}/LuaCsSteam.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{ => Lua/LuaClasses}/LuaCsTimer.cs (96%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{ => Lua/LuaClasses}/LuaCsUtility.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => Lua/LuaClasses}/LuaGame.cs (100%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsTimer.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsHook.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsHook.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsHook.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsHook.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsHookCompat.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsHookCompat.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsHookCompat.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsHookCompat.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsLogger.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsLogger.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsLogger.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsLogger.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsNetworking.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsNetworking.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsNetworking.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsNetworking.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsPerformanceCounter.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsPerformanceCounter.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsPerformanceCounter.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsPerformanceCounter.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSteam.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsSteam.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSteam.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsSteam.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsTimer.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsTimer.cs similarity index 96% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsTimer.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsTimer.cs index a88f7c5b5..9e863d30e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsTimer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsTimer.cs @@ -1,10 +1,12 @@ -using System; +using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs.Services.Compatibility; +using System; using System.Collections.Generic; using System.Diagnostics; namespace Barotrauma { - public class LuaCsTimer + public class LuaCsTimer : ILuaCsTimer { public static double Time => Timing.TotalTime; public static double GetTime() => Time; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsUtility.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsUtility.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsUtility.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsUtility.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaGame.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaGame.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaGame.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaGame.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaConverters.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaConverters.cs index c7a0ba351..b7d49ace8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaConverters.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaConverters.cs @@ -1,17 +1,26 @@ -/* -using System; +using System; using MoonSharp.Interpreter; using Microsoft.Xna.Framework; using FarseerPhysics.Dynamics; using LuaCsCompatPatchFunc = Barotrauma.LuaCsPatch; using Barotrauma.Networking; using System.Collections.Immutable; +using Barotrauma.LuaCs.Services; namespace Barotrauma { - partial class LuaCsSetup + public class LuaConverters { - private void RegisterLuaConverters() + private readonly Script _script; + + public LuaConverters(Script script) + { + _script = script; + } + + private DynValue Call(object function, params object[] arguments) => _script.Call(function, arguments); + + public void RegisterLuaConverters() { RegisterAction(); RegisterAction(); @@ -25,41 +34,40 @@ namespace Barotrauma Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(LuaCsAction), v => (LuaCsAction)(args => { - if (v.Function.OwnerScript == Lua) + if (v.Function.OwnerScript == _script) { - CallLuaFunction(v.Function, args); + Call(v.Function, args); } })); Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(LuaCsFunc), v => (LuaCsFunc)(args => { - if (v.Function.OwnerScript == Lua) + if (v.Function.OwnerScript == _script) { - return CallLuaFunction(v.Function, args); + return Call(v.Function, args); } return default; })); Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(LuaCsCompatPatchFunc), v => (LuaCsCompatPatchFunc)((self, args) => { - if (v.Function.OwnerScript == Lua) + if (v.Function.OwnerScript == _script) { - return CallLuaFunction(v.Function, self, args); + return Call(v.Function, self, args); } return default; })); Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(LuaCsPatchFunc), v => (LuaCsPatchFunc)((self, args) => { - if (v.Function.OwnerScript == Lua) + if (v.Function.OwnerScript == _script) { - return CallLuaFunction(v.Function, self, args); + return Call(v.Function, self, args); } return default; })); - DynValue Call(object function, params object[] arguments) => CallLuaFunction(function, arguments); void RegisterHandler(Func converter) => Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(T), v => converter(v.Function)); RegisterHandler(f => (Character.OnDeathHandler)((a1, a2) => Call(f, a1, a2))); @@ -229,7 +237,7 @@ namespace Barotrauma RegisterImmutableArray(); } - private void RegisterImmutableArray() + private static void RegisterImmutableArray() { Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Table, typeof(ImmutableArray), v => { @@ -237,7 +245,7 @@ namespace Barotrauma }); } - private void RegisterEither() + private static void RegisterEither() { DynValue convertEitherIntoDynValue(Either either) { @@ -275,7 +283,7 @@ namespace Barotrauma }); } - private void RegisterOption(DataType dataType) + private static void RegisterOption(DataType dataType) { Script.GlobalOptions.CustomConverters.SetClrToScriptCustomConversion(typeof(Option), (Script v, object obj) => { @@ -306,13 +314,13 @@ namespace Barotrauma Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Action), v => { var function = v.Function; - return (Action)(p => CallLuaFunction(function, p)); + return (Action)(p => Call(function, p)); }); Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.ClrFunction, typeof(Action), v => { var function = v.Function; - return (Action)(p => CallLuaFunction(function, p)); + return (Action)(p => Call(function, p)); }); } @@ -321,13 +329,13 @@ namespace Barotrauma Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Action), v => { var function = v.Function; - return (Action)((a1, a2) => CallLuaFunction(function, a1, a2)); + return (Action)((a1, a2) => Call(function, a1, a2)); }); Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.ClrFunction, typeof(Action), v => { var function = v.Function; - return (Action)((a1, a2) => CallLuaFunction(function, a1, a2)); + return (Action)((a1, a2) => Call(function, a1, a2)); }); } @@ -336,13 +344,13 @@ namespace Barotrauma Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Action), v => { var function = v.Function; - return (Action)(() => CallLuaFunction(function)); + return (Action)(() => Call(function)); }); Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.ClrFunction, typeof(Action), v => { var function = v.Function; - return (Action)(() => CallLuaFunction(function)); + return (Action)(() => Call(function)); }); } @@ -392,4 +400,3 @@ namespace Barotrauma } } } -*/ diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsTimer.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsTimer.cs new file mode 100644 index 000000000..f0a3f6b46 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsTimer.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Barotrauma.LuaCs.Services.Compatibility; + +internal partial interface ILuaCsTimer : ILuaCsShim +{ + public static double Time => Timing.TotalTime; + public static double GetTime() => Time; + public static double AccumulatorMax { get; set; } + + public void Clear(); + public void Wait(LuaCsAction action, int millisecondDelay); + public void NextFrame(LuaCsAction action); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs index 2acfcd9d2..3dcbe9f94 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs @@ -1,10 +1,13 @@ #nullable enable using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Services.Compatibility; using Barotrauma.LuaCs.Services.Safe; +using Barotrauma.Networking; using FluentResults; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; +using MonoMod.RuntimeDetour; using MoonSharp.Interpreter; using MoonSharp.Interpreter.Interop; using MoonSharp.Interpreter.Loaders; @@ -20,25 +23,46 @@ using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; +using static Barotrauma.GameSettings; namespace Barotrauma.LuaCs.Services; -public class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService +class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService { private Script? _script; private bool _isRunning; [MemberNotNullWhen(true, nameof(_script))] public bool IsRunning => _isRunning; private List _resourcesInfo = new List(); + private readonly ILuaScriptLoader _luaScriptLoader; private readonly ILuaScriptServicesConfig _luaScriptServicesConfig; private readonly ILoggerService _loggerService; + private readonly LuaGame _luaGame; + private readonly ILuaCsHook _luaCsHook; + private readonly ILuaCsNetworking _luaCsNetworking; + private readonly ILuaCsUtility _luaCsUtility; + private readonly ILuaCsTimer _luaCsTimer; - public LuaScriptManagementService(ILoggerService loggerService, ILuaScriptLoader loader, ILuaScriptServicesConfig luaScriptServicesConfig) + public LuaScriptManagementService( + ILoggerService loggerService, + ILuaScriptLoader loader, + ILuaScriptServicesConfig luaScriptServicesConfig, + LuaGame luaGame, + ILuaCsHook luaCsHook, + ILuaCsNetworking luaCsNetworking, + ILuaCsUtility luaCsUtility, + ILuaCsTimer luaCsTimer) { _luaScriptLoader = loader; _luaScriptServicesConfig = luaScriptServicesConfig; _loggerService = loggerService; + + _luaGame = luaGame; + _luaCsHook = luaCsHook; + _luaCsNetworking = luaCsNetworking; + _luaCsUtility = luaCsUtility; + _luaCsTimer = luaCsTimer; } public bool IsDisposed { get; private set; } @@ -63,6 +87,33 @@ public class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataS _script.Options.CheckThreadAccess = false; Script.GlobalOptions.ShouldPCallCatchException = (Exception ex) => { return true; }; + + RegisterType(typeof(LuaGame)); + RegisterType(typeof(ILuaCsHook)); + RegisterType(typeof(ILuaCsNetworking)); + RegisterType(typeof(ILuaCsUtility)); + RegisterType(typeof(ILuaCsTimer)); + RegisterType(typeof(LuaCsFile)); + + new LuaConverters(_script).RegisterLuaConverters(); + + _script.Globals["printerror"] = (DynValue o) => { LuaCsLogger.LogError(o.ToString()); }; + + _script.Globals["dostring"] = (Func)_script.DoString; + _script.Globals["load"] = (Func)_script.LoadString; + + _script.Globals["Game"] = _luaGame; + _script.Globals["Hook"] = _luaCsHook; + _script.Globals["Timer"] = _luaCsTimer; + _script.Globals["File"] = UserData.CreateStatic(); + _script.Globals["Networking"] = _luaCsNetworking; + //_script.Globals["Steam"] = Steam; + + _script.Globals["ExecutionNumber"] = 0; + _script.Globals["CSActive"] = false; + + _script.Globals["SERVER"] = LuaCsSetup.IsServer; + _script.Globals["CLIENT"] = LuaCsSetup.IsClient; } public FluentResults.Result ExecuteLoadedScripts() From 75da3a398db724778fd11f5a95de1765730d8a2d Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 4 Jan 2026 12:20:27 -0300 Subject: [PATCH 032/288] This should be full path --- .../SharedSource/LuaCs/Services/LuaScriptManagementService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs index 3dcbe9f94..5d8d020e7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs @@ -135,7 +135,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService { try { - _script?.Call(_script.LoadFile(filePath.Value)); + _script?.Call(_script.LoadFile(filePath.FullPath)); } catch(Exception e) { From d6968f4ea9e46da909652a2df2a66a16d7f718c8 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Mon, 5 Jan 2026 08:05:40 -0500 Subject: [PATCH 033/288] [Save/Sync] Work on ModConfig loading. --- .../Data/DataInterfaceImplementations.cs | 5 +- .../LuaCs/Data/IBaseInfoDefinitions.cs | 12 ++++ .../SharedSource/LuaCs/Data/IModConfigInfo.cs | 8 ++- .../LuaCs/Data/IResourceInfoDeclarations.cs | 2 +- .../Services/Processing/ModConfigService.cs | 66 +++++++++++++++---- 5 files changed, 78 insertions(+), 15 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs index 21b718d03..26fb2464a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs @@ -14,10 +14,9 @@ namespace Barotrauma.LuaCs.Data; #region ModConfigurationInfo -public partial record ModConfigInfo : IModConfigInfo +public record ModConfigInfo : IModConfigInfo { public ContentPackage Package { get; init; } - public string PackageName { get; init; } public ImmutableArray Assemblies { get; init; } public ImmutableArray LuaScripts { get; init; } public ImmutableArray Configs { get; init; } @@ -42,6 +41,8 @@ public record BaseResourceInfo : IBaseResourceInfo public bool Optional { get; init; } public string InternalName { get; init; } public ContentPackage OwnerPackage { get; init; } + public ImmutableArray RequiredPackages { get; init; } + public ImmutableArray IncompatiblePackages { get; init; } } public record AssemblyResourceInfo : BaseResourceInfo, IAssemblyResourceInfo diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs index bf3525c0d..66dcdb626 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs @@ -6,6 +6,18 @@ using System.Globalization; namespace Barotrauma.LuaCs.Data; +public interface IDependencyInfo +{ + /// + /// List of dependency packages required by this resource. + /// + ImmutableArray RequiredPackages { get; } + /// + /// List of packages incompatible with this resource. + /// + ImmutableArray IncompatiblePackages { get; } +} + public interface IPlatformInfo { /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs index cdecd9037..77acdc7df 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using System.Xml.Linq; namespace Barotrauma.LuaCs.Data; @@ -8,5 +9,10 @@ public partial interface IModConfigInfo : IAssembliesResourcesInfo, { // package info ContentPackage Package { get; } - string PackageName { get; } } + +public record ResourceParserInfo( + ContentPackage Owner, + XElement Element, + ImmutableArray Required, + ImmutableArray Incompatible); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs index 1bc74e54a..4dd4910f7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs @@ -5,7 +5,7 @@ using System.Globalization; namespace Barotrauma.LuaCs.Data; -public interface IBaseResourceInfo : IResourceInfo, IDataInfo {} +public interface IBaseResourceInfo : IResourceInfo, IDataInfo, IDependencyInfo {} public interface IConfigResourceInfo : IBaseResourceInfo {} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs index 0c3bce1f1..2274692ac 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs @@ -17,16 +17,16 @@ namespace Barotrauma.LuaCs.Services.Processing; public sealed class ModConfigService : IModConfigService { private IStorageService _storageService; - private IParserServiceAsync _assemblyParserService; - private IParserServiceAsync _luaScriptParserService; - private IParserServiceAsync _configParserService; - private IParserServiceAsync _configProfileParserService; + private IParserServiceAsync _assemblyParserService; + private IParserServiceAsync _luaScriptParserService; + private IParserServiceAsync _configParserService; + private IParserServiceAsync _configProfileParserService; public ModConfigService(IStorageService storageService, - IParserServiceAsync assemblyParserService, - IParserServiceAsync luaScriptParserService, - IParserServiceAsync configParserService, - IParserServiceAsync configProfileParserService) + IParserServiceAsync assemblyParserService, + IParserServiceAsync luaScriptParserService, + IParserServiceAsync configParserService, + IParserServiceAsync configProfileParserService) { _storageService = storageService; _assemblyParserService = assemblyParserService; @@ -57,7 +57,7 @@ public sealed class ModConfigService : IModConfigService if (await TryGetModConfigXmlAsync(src) is { IsSuccess: true, Value: { } config }) { - return await CreateFromConfigXmlAsync(config); + return await CreateFromConfigXmlAsync(src, config); } return await CreateFromLegacyAsync(src); @@ -84,11 +84,55 @@ public sealed class ModConfigService : IModConfigService : FluentResults.Result.Fail("ModConfig.xml not found"); } - private async Task> CreateFromConfigXmlAsync(XElement src) + private async Task> CreateFromConfigXmlAsync(ContentPackage owner, XElement src) { - throw new NotImplementedException(); + /*var cfg = src.GetChildElements("Config"); + var modConfig = new ModConfigInfo() + { + Package = owner, + Assemblies = src.GetChildElements("Assembly") is {} asm ? GetAssembliesFromXml(owner, asm) + : ImmutableArray.Empty, + Configs = cfg is {} ? GetConfigsFromXml(owner, cfg) : ImmutableArray.Empty, + ConfigProfiles = cfg is {} ? GetConfigProfilesFromXml(owner, cfg) : ImmutableArray.Empty, + LuaScripts = src.GetChildElements("Lua") is {} lua ? GetLuaScriptsFromXml(owner, lua) + : ImmutableArray.Empty + };*/ + + async Task>> GetLuaScriptsFromXml(ContentPackage contentPackage, + XElement cfgElement) + { + var luaElems = cfgElement.GetChildElements("Lua").ToImmutableArray(); + if (cfgElement.GetChildElements("FileGroup").ToImmutableArray() is { IsDefaultOrEmpty: false } fileGroup + && fileGroup.SelectMany(fg => fg.GetChildElements())) + { + + } + + + throw new NotImplementedException(); + } + + async Task>> GetConfigProfilesFromXml(ContentPackage contentPackage, + XElement cfgElement) + { + throw new NotImplementedException(); + } + + async Task>> GetConfigsFromXml(ContentPackage contentPackage, + XElement cfgElement) + { + throw new NotImplementedException(); + } + + async Task>> GetAssembliesFromXml(ContentPackage contentPackage, + XElement cfgElement) + { + throw new NotImplementedException(); + } } + + private async Task> CreateFromLegacyAsync(ContentPackage src) { throw new NotImplementedException(); From 42acb32c699f73db28992cff9486a6283772c3bf Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 6 Jan 2026 07:46:07 -0500 Subject: [PATCH 034/288] Marked Async-compatibility issue with Log() in LoggerService.cs --- .../LuaCs/Services/LoggerService.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs index ed1e3c42e..3e9b9eadc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Barotrauma.Networking; using Microsoft.Xna.Framework; using MoonSharp.Interpreter; @@ -80,6 +81,8 @@ public partial class LoggerService : ILoggerService public void Log(string message, Color? color = null, ServerLog.MessageType messageType = ServerLog.MessageType.ServerMessage) { + // TODO: Make this thread Async compatible. + if (HideUserNames && !Environment.UserName.IsNullOrEmpty()) { message = message.Replace(Environment.UserName, "USERNAME"); @@ -142,15 +145,18 @@ public partial class LoggerService : ILoggerService return; } - foreach (var error in result.Errors) + if (result.Errors.Any()) { - LogError(error.Message); - - if (error.Reasons != null) + foreach (var error in result.Errors) { - foreach (var reason in error.Reasons) + LogError(error.Message); + + if (error.Reasons != null) { - LogError($" - {reason.Message}"); + foreach (var reason in error.Reasons) + { + LogError($" - {reason.Message}"); + } } } } From 3e81e2716007e94bceeedbcbd1628c3e02d6f4a2 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 8 Jan 2026 11:35:34 -0500 Subject: [PATCH 035/288] [Save/Sync] In-Progress ModConfigXml loading rewrite. + Fixed async operations lock for Dispose() pattern in working files. + Rewrote StorageService.cs: --- Now uses ContentPath instead of raw strings where possible. --- Now throws exceptions for developer errors and critical program states. + Rewrote ModConfigService.cs: --- All functions are now completely async. + Removed ConfigProfilesResources completely as they exist in common Config xml files. + Somewhat simplified package data and processes. --- .../Data/DataInterfaceImplementations.cs | 7 +- .../SharedSource/LuaCs/Data/IModConfigInfo.cs | 8 +- .../LuaCs/Data/IResourceInfoDeclarations.cs | 7 - .../LuaCs/Data/ServicesConfigData.cs | 2 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 13 - .../LuaCs/Services/ConfigService.cs | 6 +- .../Services/PackageManagementService.cs | 16 - .../Processing/ConfigFileParserService.cs | 225 ++++++++ .../Services/Processing/ModConfigService.cs | 168 ++++-- .../LuaCs/Services/StorageService.cs | 502 +++++++++--------- .../Services/_Interfaces/IConfigService.cs | 2 +- ...IModConfigInfo.cs => IModConfigService.cs} | 0 .../_Interfaces/IPackageManagementService.cs | 5 +- .../LuaCs/Services/_Interfaces/IService.cs | 9 +- .../Services/_Interfaces/IStorageService.cs | 28 +- 15 files changed, 651 insertions(+), 347 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigFileParserService.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/{IModConfigInfo.cs => IModConfigService.cs} (100%) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs index 26fb2464a..3c2c84007 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs @@ -20,7 +20,6 @@ public record ModConfigInfo : IModConfigInfo public ImmutableArray Assemblies { get; init; } public ImmutableArray LuaScripts { get; init; } public ImmutableArray Configs { get; init; } - public ImmutableArray ConfigProfiles { get; init; } } #endregion @@ -30,7 +29,6 @@ public record ModConfigInfo : IModConfigInfo public record AssemblyResourcesInfo(ImmutableArray Assemblies) : IAssembliesResourcesInfo; public record LuaScriptsResourcesInfo(ImmutableArray LuaScripts) : ILuaScriptsResourcesInfo; public record ConfigResourcesInfo(ImmutableArray Configs) : IConfigsResourcesInfo; -public record ConfigProfilesResourcesInfo(ImmutableArray ConfigProfiles) : IConfigProfilesResourcesInfo; public record BaseResourceInfo : IBaseResourceInfo { @@ -51,10 +49,11 @@ public record AssemblyResourceInfo : BaseResourceInfo, IAssemblyResourceInfo public bool IsScript { get; init; } } +/// +/// Note: Config settings and settings-profiles are stored in the same files. +/// public record ConfigResourceInfo : BaseResourceInfo, IConfigResourceInfo {} -public record ConfigProfileResourceInfo : BaseResourceInfo, IConfigProfileResourceInfo {} - public record LuaScriptsResourceInfo : BaseResourceInfo, ILuaScriptResourceInfo { public bool IsAutorun { get; init; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs index 77acdc7df..c8740e51d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs @@ -1,18 +1,18 @@ using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Xml.Linq; namespace Barotrauma.LuaCs.Data; public partial interface IModConfigInfo : IAssembliesResourcesInfo, - ILuaScriptsResourcesInfo, IConfigsResourcesInfo, - IConfigProfilesResourcesInfo + ILuaScriptsResourcesInfo, IConfigsResourcesInfo { // package info ContentPackage Package { get; } } public record ResourceParserInfo( - ContentPackage Owner, - XElement Element, + [NotNull] ContentPackage Owner, + [NotNull] XElement Element, ImmutableArray Required, ImmutableArray Incompatible); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs index 4dd4910f7..366578fdb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs @@ -9,8 +9,6 @@ public interface IBaseResourceInfo : IResourceInfo, IDataInfo, IDependencyInfo { public interface IConfigResourceInfo : IBaseResourceInfo {} -public interface IConfigProfileResourceInfo :IBaseResourceInfo {} - /// /// Represents loadable Lua files. /// @@ -53,9 +51,4 @@ public interface IConfigsResourcesInfo ImmutableArray Configs { get; } } -public interface IConfigProfilesResourcesInfo -{ - ImmutableArray ConfigProfiles { get; } -} - #endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs index b5ee25eb9..cce3a273b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs @@ -112,7 +112,7 @@ public record StorageServiceConfig : IStorageServiceConfig, IStorageServiceConfi public string LocalDataSavePath => Path.Combine(ExecutionLocation, "/Data/Mods/"); - public string LocalDataPathRegex => ""; + public string LocalDataPathRegex => "%ModDir%"; public string RunLocation => ExecutionLocation; public bool GlobalSafeIOEnabled => false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 4714df7cc..95d7a92af 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -544,7 +544,6 @@ namespace Barotrauma async Task LoadStaticAssetsAsync(IReadOnlyList packages) { var cfgRes = ImmutableArray.Empty; - var cfpRes = ImmutableArray.Empty; var luaRes = ImmutableArray.Empty; var tasksBuilder = ImmutableArray.CreateBuilder(); @@ -561,15 +560,6 @@ namespace Barotrauma res.ToResult()); })(), new Func(async () => - { - var res = await PackageManagementService.GetConfigProfilesInfosAsync(packages); - if (res.IsSuccess) - cfpRes = res.Value.ConfigProfiles; - if (res.Errors.Any()) - ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), - res.ToResult()); - })(), - new Func(async () => { var res = await PackageManagementService.GetLuaScriptsInfosAsync(packages); if (res.IsSuccess) @@ -588,9 +578,6 @@ namespace Barotrauma var res = await ConfigService.LoadConfigsAsync(cfgRes); if (res.Errors.Any()) Logger.LogResults(res); - res = await ConfigService.LoadConfigsProfilesAsync(cfpRes); - if (res.Errors.Any()) - Logger.LogResults(res); })(), new Func(async () => { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs index 47648cab3..df6e6c39f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs @@ -24,7 +24,7 @@ namespace Barotrauma.LuaCs.Services; public partial class ConfigService : IConfigService { //--- Internals - public ConfigService(IParserServiceAsync> configProfileResourceParser, + public ConfigService(IParserServiceAsync> configProfileResourceParser, IParserServiceAsync> configResourceParser, IEventService eventService, System.Lazy storageService) { _configProfileResourceParser = configProfileResourceParser; @@ -48,7 +48,7 @@ public partial class ConfigService : IConfigService // extern services private readonly IParserServiceAsync> _configResourceParser; - private readonly IParserServiceAsync> _configProfileResourceParser; + private readonly IParserServiceAsync> _configProfileResourceParser; private readonly IEventService _eventService; private readonly System.Lazy _storageService; @@ -357,7 +357,7 @@ public partial class ConfigService : IConfigService return ret; } - public async Task LoadConfigsProfilesAsync(ImmutableArray configProfileResources) + public async Task LoadConfigsProfilesAsync(ImmutableArray configProfileResources) { using var lck = await _disposeOpsLock.AcquireReaderLock(); _base.CheckDisposed(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index 064813b9e..a14ed5784 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -27,7 +27,6 @@ public partial class PackageManagementService : IPackageManagementService } public ImmutableArray Configs { get; } - public ImmutableArray ConfigProfiles { get; } public ImmutableArray LuaScripts { get; } public ImmutableArray Assemblies { get; } public async Task LoadPackageInfosAsync(ContentPackage package) @@ -75,11 +74,6 @@ public partial class PackageManagementService : IPackageManagementService throw new System.NotImplementedException(); } - public Result GetConfigProfilesInfos(ContentPackage package, bool onlySupportedResources = true) - { - throw new System.NotImplementedException(); - } - public Result GetLuaScriptsInfos(ContentPackage package, bool onlySupportedResources = true) { throw new System.NotImplementedException(); @@ -95,11 +89,6 @@ public partial class PackageManagementService : IPackageManagementService throw new System.NotImplementedException(); } - public Result GetConfigProfilesInfos(IReadOnlyList packages, bool onlySupportedResources = true) - { - throw new System.NotImplementedException(); - } - public Result GetLuaScriptsInfos(IReadOnlyList packages, bool onlySupportedResources = true) { throw new System.NotImplementedException(); @@ -115,11 +104,6 @@ public partial class PackageManagementService : IPackageManagementService throw new System.NotImplementedException(); } - public async Task> GetConfigProfilesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true) - { - throw new System.NotImplementedException(); - } - public async Task> GetLuaScriptsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true) { throw new System.NotImplementedException(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigFileParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigFileParserService.cs new file mode 100644 index 000000000..292ac3eea --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigFileParserService.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using System.Xml.Linq; +using Barotrauma.LuaCs.Data; +using FluentResults; +using Microsoft.Toolkit.Diagnostics; + +namespace Barotrauma.LuaCs.Services.Processing; + +public sealed class ConfigFileParserService : + IParserServiceAsync, + IParserServiceAsync, + IParserServiceAsync +{ + private IStorageService _storageService; + private readonly AsyncReaderWriterLock _operationsLock = new(); + + public ConfigFileParserService(IStorageService storageService) + { + _storageService = storageService; + } + + #region Dispose + + public void Dispose() + { + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) + return; + try + { + _storageService.Dispose(); + this._storageService = null; + } + catch + { + // ignored + } + } + + private int _isDisposed = 0; + public bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } + + #endregion + + // --- Assemblies + async Task> IParserServiceAsync.TryParseResourceAsync(ResourceParserInfo src) + { + using var lck = await _operationsLock.AcquireWriterLock(); + IService.CheckDisposed(this); + + if (CheckThrowNullRefs(src, "Assembly") is { IsFailed: true } fail) + return fail; + + var runtimeEnv = GetRuntimeEnvironment(src.Element); + var fileResults = await GetCheckedFiles(src.Element, src.Owner, ".dll"); + + if (fileResults.IsFailed) + return FluentResults.Result.Fail(fileResults.Errors); + + return new AssemblyResourceInfo() + { + SupportedPlatforms = runtimeEnv.Platform, + SupportedTargets = runtimeEnv.Target, + LoadPriority = src.Element.GetAttributeInt("LoadPriority", 0), + FilePaths = fileResults.Value, + Optional = src.Element.GetAttributeBool("Optional", false), + InternalName = src.Element.GetAttributeString("Name", string.Empty), + OwnerPackage = src.Owner, + RequiredPackages = src.Required, + IncompatiblePackages = src.Incompatible, + // Type Specific + FriendlyName = src.Element.GetAttributeString("FriendlyName", string.Empty), + IsScript = src.Element.GetAttributeBool("IsScript", false), + }; + } + + async Task>> IParserServiceAsync.TryParseResourcesAsync(IEnumerable sources) + { + return await this.TryParseGenericResourcesAsync(sources); + } + + // --- Config + + async Task> IParserServiceAsync.TryParseResourceAsync(ResourceParserInfo src) + { + + using var lck = await _operationsLock.AcquireWriterLock(); + IService.CheckDisposed(this); + + if (CheckThrowNullRefs(src, "Config") is { IsFailed: true } fail) + return fail; + + var runtimeEnv = GetRuntimeEnvironment(src.Element); + var fileResults = await GetCheckedFiles(src.Element, src.Owner, ".xml"); + + if (fileResults.IsFailed) + return FluentResults.Result.Fail(fileResults.Errors); + + return new ConfigResourceInfo() + { + SupportedPlatforms = runtimeEnv.Platform, + SupportedTargets = runtimeEnv.Target, + LoadPriority = src.Element.GetAttributeInt("LoadPriority", 0), + FilePaths = fileResults.Value, + Optional = src.Element.GetAttributeBool("Optional", false), + InternalName = src.Element.GetAttributeString("Name", string.Empty), + OwnerPackage = src.Owner, + RequiredPackages = src.Required, + IncompatiblePackages = src.Incompatible + }; + } + + async Task>> IParserServiceAsync.TryParseResourcesAsync(IEnumerable sources) + { + return await this.TryParseGenericResourcesAsync(sources); + } + + // --- Lua Scripts + async Task> IParserServiceAsync.TryParseResourceAsync(ResourceParserInfo src) + { + using var lck = await _operationsLock.AcquireWriterLock(); + IService.CheckDisposed(this); + + if (CheckThrowNullRefs(src, "Lua") is { IsFailed: true } fail) + return fail; + + var runtimeEnv = GetRuntimeEnvironment(src.Element); + var fileResults = await GetCheckedFiles(src.Element, src.Owner, ".lua"); + + if (fileResults.IsFailed) + return FluentResults.Result.Fail(fileResults.Errors); + + return new LuaScriptsResourceInfo() + { + SupportedPlatforms = runtimeEnv.Platform, + SupportedTargets = runtimeEnv.Target, + LoadPriority = src.Element.GetAttributeInt("LoadPriority", 0), + FilePaths = fileResults.Value, + Optional = src.Element.GetAttributeBool("Optional", false), + InternalName = src.Element.GetAttributeString("Name", string.Empty), + OwnerPackage = src.Owner, + RequiredPackages = src.Required, + IncompatiblePackages = src.Incompatible, + // Type Specific + IsAutorun = src.Element.GetAttributeBool("RunFile", false) + }; + } + + private FluentResults.Result CheckThrowNullRefs(ResourceParserInfo src, string elementName) + { + Guard.IsNotNull(src, nameof(src)); + Guard.IsNotNull(src.Owner, nameof(src.Owner)); + Guard.IsNotNull(src.Element, nameof(src.Element)); + + if (src.Element.Name != elementName) + { + return FluentResults.Result.Fail($"Element name '{elementName}' is incorrect"); + } + + return FluentResults.Result.Ok(); + } + + async Task>> IParserServiceAsync.TryParseResourcesAsync(IEnumerable sources) + { + return await this.TryParseGenericResourcesAsync(sources); + } + + // --- Helpers + private async Task>> GetCheckedFiles(XElement srcElement, ContentPackage srcOwner, string fileExtension) + { + using var lck = await _operationsLock.AcquireWriterLock(); + IService.CheckDisposed(this); + + var builder = ImmutableArray.CreateBuilder(); + var filePath = srcElement.GetAttributeString("File", string.Empty); + var folderPath = srcElement.GetAttributeString("Folder", string.Empty); + + if (!filePath.IsNullOrWhiteSpace()) + { + var cp = ContentPath.FromRaw(srcOwner, filePath); + if (_storageService.FileExists(cp.FullPath) is { IsSuccess: true, Value: true }) + { + builder.Add(cp); + } + } + + if (!folderPath.IsNullOrWhiteSpace()) + { + var cp = ContentPath.FromRaw(srcOwner, folderPath); + if (_storageService.DirectoryExists(cp.FullPath) is { IsSuccess: true, Value: true }) + { + var files = _storageService.FindFilesInPackage(cp.ContentPackage, cp.Value, fileExtension, true); + } + } + + throw new NotImplementedException(); + } + private (Platform Platform, Target Target) GetRuntimeEnvironment(XElement element) + { + return ( + Platform: element.GetAttributeEnum("Platform", Platform.Windows | Platform.Linux | Platform.OSX), + Target: element.GetAttributeEnum("Target", Target.Client | Target.Server)); + } + + private async Task>> TryParseGenericResourcesAsync(IEnumerable sources) + { + // ReSharper disable once PossibleMultipleEnumeration + Guard.IsNotNull(sources, nameof(IParserServiceAsync.TryParseResourcesAsync)); + var builder = ImmutableArray.CreateBuilder>(); + foreach (var info in sources) + { + builder.Add(await Unsafe.As>(this).TryParseResourceAsync(info)); + } + return builder.ToImmutable(); + } + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs index 2274692ac..d9d783a93 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -17,29 +18,51 @@ namespace Barotrauma.LuaCs.Services.Processing; public sealed class ModConfigService : IModConfigService { private IStorageService _storageService; + private ILoggerService _logger; private IParserServiceAsync _assemblyParserService; private IParserServiceAsync _luaScriptParserService; private IParserServiceAsync _configParserService; - private IParserServiceAsync _configProfileParserService; + private readonly AsyncReaderWriterLock _operationsLock = new(); public ModConfigService(IStorageService storageService, IParserServiceAsync assemblyParserService, IParserServiceAsync luaScriptParserService, IParserServiceAsync configParserService, - IParserServiceAsync configProfileParserService) + ILoggerService logger) { _storageService = storageService; _assemblyParserService = assemblyParserService; _luaScriptParserService = luaScriptParserService; _configParserService = configParserService; - _configProfileParserService = configProfileParserService; + _logger = logger; } #region Dispose public void Dispose() { - throw new NotImplementedException(); + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) + return; + + try + { + _storageService.Dispose(); + _logger.Dispose(); + _assemblyParserService.Dispose(); + _luaScriptParserService.Dispose(); + _configParserService.Dispose(); + + _storageService = null; + _logger = null; + _assemblyParserService = null; + _luaScriptParserService = null; + _configParserService = null; + } + catch + { + // ignored + } } private int _isDisposed = 0; @@ -54,6 +77,8 @@ public sealed class ModConfigService : IModConfigService public async Task> CreateConfigAsync(ContentPackage src) { Guard.IsNotNull(src, nameof(src)); + using var lck = await _operationsLock.AcquireReaderLock(); + IService.CheckDisposed(this); if (await TryGetModConfigXmlAsync(src) is { IsSuccess: true, Value: { } config }) { @@ -65,6 +90,11 @@ public sealed class ModConfigService : IModConfigService public async Task Config)>> CreateConfigsAsync(ImmutableArray src) { + if (src.IsDefaultOrEmpty) + ThrowHelper.ThrowArgumentNullException($"{nameof(CreateConfigsAsync)}: The supplied array is default or empty!"); + using var lck = await _operationsLock.AcquireReaderLock(); + IService.CheckDisposed(this); + var builder = new ConcurrentQueue<(ContentPackage Source, Result Config)>(); await src.ParallelForEachAsync(async package => @@ -79,55 +109,125 @@ public sealed class ModConfigService : IModConfigService //--- Helpers private async Task> TryGetModConfigXmlAsync(ContentPackage src) { - return await _storageService.LoadPackageXmlAsync(src, "ModConfig.xml") is { IsSuccess: true, Value: { Root: {} config} } + return await _storageService.LoadPackageXmlAsync(ContentPath.FromRaw(src, "%ModDir%/ModConfig.xml")) is { IsSuccess: true, Value: { Root: {} config} } ? FluentResults.Result.Ok(config) : FluentResults.Result.Fail("ModConfig.xml not found"); } private async Task> CreateFromConfigXmlAsync(ContentPackage owner, XElement src) { - /*var cfg = src.GetChildElements("Config"); - var modConfig = new ModConfigInfo() + ImmutableArray assemblyResources = default; + ImmutableArray configResources = default; + ImmutableArray luaResources = default; + + var res = await Task.WhenAll(new[] + { + new Task(async () => assemblyResources = await GetAssembliesFromXml(owner, src)), + new Task(async () => configResources = await GetConfigsFromXml(owner, src)), + new Task(async () => luaResources = await GetLuaScriptsFromXml(owner, src)), + }); + + bool isFaulted = false; + foreach (var task in res) + { + if (task.IsFaulted) + { + _logger.LogError($"{nameof(CreateFromConfigXmlAsync)}: {task.Exception?.ToString()}"); + isFaulted = true; + } + } + + if (isFaulted) + { + _logger.LogError($"{nameof(CreateFromConfigXmlAsync)}: Failed to process content package: {owner.Name}"); + return FluentResults.Result.Fail($"{nameof(CreateFromConfigXmlAsync)}: Failed to process content package: {owner.Name}"); + } + + return FluentResults.Result.Ok(new ModConfigInfo() { Package = owner, - Assemblies = src.GetChildElements("Assembly") is {} asm ? GetAssembliesFromXml(owner, asm) - : ImmutableArray.Empty, - Configs = cfg is {} ? GetConfigsFromXml(owner, cfg) : ImmutableArray.Empty, - ConfigProfiles = cfg is {} ? GetConfigProfilesFromXml(owner, cfg) : ImmutableArray.Empty, - LuaScripts = src.GetChildElements("Lua") is {} lua ? GetLuaScriptsFromXml(owner, lua) - : ImmutableArray.Empty - };*/ + Assemblies = assemblyResources, + Configs = configResources, + LuaScripts = luaResources + }); - async Task>> GetLuaScriptsFromXml(ContentPackage contentPackage, + async Task> GetLuaScriptsFromXml(ContentPackage contentPackage, XElement cfgElement) { - var luaElems = cfgElement.GetChildElements("Lua").ToImmutableArray(); - if (cfgElement.GetChildElements("FileGroup").ToImmutableArray() is { IsDefaultOrEmpty: false } fileGroup - && fileGroup.SelectMany(fg => fg.GetChildElements())) + return await GetResourceFromXml(contentPackage, cfgElement, "Lua", "FileGroup", _luaScriptParserService); + } + + async Task> GetConfigsFromXml(ContentPackage contentPackage, + XElement cfgElement) + { + return await GetResourceFromXml(contentPackage, cfgElement, "Config", "FileGroup", _configParserService); + + } + + async Task> GetAssembliesFromXml(ContentPackage contentPackage, + XElement cfgElement) + { + return await GetResourceFromXml(contentPackage, cfgElement, "Assembly", "FileGroup", _assemblyParserService); + } + + async Task> GetResourceFromXml(ContentPackage contentPackage, XElement cfgElement, string elemName, string fileGroupName, IParserServiceAsync resourceService) + { + var elems = GetResourceElementsWithName(owner, cfgElement, elemName, fileGroupName); + if (elems.IsDefaultOrEmpty) + return ImmutableArray.Empty; + + var results = await resourceService.TryParseResourcesAsync(elems); + Guard.IsNotEmpty((IReadOnlyCollection>)results, nameof(results)); + + var resources = ImmutableArray.CreateBuilder(); + foreach (var result in results) { - + if (result.Errors.Count > 0) + { + _logger.LogResults(result.ToResult()); + continue; + } + resources.Add(result.Value); } + return resources.MoveToImmutable(); + } + + ImmutableArray GetResourceElementsWithName(ContentPackage package, XElement root, string elemName, string groupName) + { + var elems = ImmutableArray.CreateBuilder(); + elems.AddRange(root.GetChildElements(elemName) + .Select(e => new ResourceParserInfo(package, e, ImmutableArray.Empty, ImmutableArray.Empty)) + .ToImmutableArray()); - throw new NotImplementedException(); - } + if (root.GetChildElements(groupName).ToImmutableArray() is { IsDefaultOrEmpty: false } fileGroups) + { + foreach (var fileGroup in fileGroups) + { + if (fileGroup.GetChildElements(elemName).ToImmutableArray() is { IsDefaultOrEmpty: false } subLuaElems) + { + var cond = GetDependencyIdentifiers(fileGroup, true); + var negCond = GetDependencyIdentifiers(fileGroup, false); - async Task>> GetConfigProfilesFromXml(ContentPackage contentPackage, - XElement cfgElement) - { - throw new NotImplementedException(); - } + foreach (var element in subLuaElems) + { + elems.Add(new ResourceParserInfo(package, element, cond, negCond)); + } + } + } + } - async Task>> GetConfigsFromXml(ContentPackage contentPackage, - XElement cfgElement) - { - throw new NotImplementedException(); + return elems.MoveToImmutable(); } - - async Task>> GetAssembliesFromXml(ContentPackage contentPackage, - XElement cfgElement) + + ImmutableArray GetDependencyIdentifiers(XElement fg, bool depsLoadedSetting) { - throw new NotImplementedException(); + return fg.GetChildElements("Conditional") + .Where(cElem => bool.TryParse(cElem.GetAttribute("IsLoaded").Value, out bool isLoaded) && isLoaded == depsLoadedSetting) + .SelectMany(cElem2 => cElem2.GetAttributeString("Dependencies", String.Empty) + .Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries) + .Select(ident => new Identifier(ident))) + .ToImmutableArray(); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs index d1650b962..724b2c4dc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs @@ -2,52 +2,52 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; -using System.Collections.ObjectModel; using System.IO; -using System.Linq; -using System.Reflection; -using System.Security; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using Barotrauma.LuaCs.Data; -using Barotrauma.Steam; using FluentResults; using FluentResults.LuaCs; using Microsoft.Toolkit.Diagnostics; using Error = FluentResults.Error; -using Path = Barotrauma.IO.Path; +using Path = System.IO.Path; namespace Barotrauma.LuaCs.Services; public class StorageService : IStorageService { - public StorageService(IStorageServiceConfig configData) { - _configData = configData; + ConfigData = configData; } private readonly ConcurrentDictionary> _fsCache = new(); - protected readonly IStorageServiceConfig _configData; - + protected readonly IStorageServiceConfig ConfigData; + protected readonly AsyncReaderWriterLock OperationsLock = new(); + public bool IsDisposed => ModUtils.Threading.GetBool(ref _isDisposed); private int _isDisposed = 0; public void Dispose() { - ModUtils.Threading.SetBool(ref _isDisposed, true); + if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) + return; + using var lck = OperationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + _fsCache.Clear(); } public void PurgeCache() { - ((IService)this).CheckDisposed(); + using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); _fsCache.Clear(); } public void PurgeFileFromCache(string absolutePath) { - ((IService)this).CheckDisposed(); + using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); if (absolutePath.IsNullOrWhiteSpace()) return; @@ -67,7 +67,8 @@ public class StorageService : IStorageService public void PurgeFilesFromCache(params string[] absolutePaths) { - ((IService)this).CheckDisposed(); + using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); if (absolutePaths.Length < 1) return; @@ -91,6 +92,201 @@ public class StorageService : IStorageService } } + // --- Local Game Content + protected Result GetAbsolutePathForLocal(ContentPackage package, string localFilePath) + { + if (Path.IsPathRooted(localFilePath)) + ThrowHelper.ThrowArgumentException($"{nameof(GetAbsolutePathForLocal)}: The path {localFilePath} is an absolute path."); + + try + { + var path = System.IO.Path.GetFullPath(Path.Combine( + ConfigData.LocalPackageDataPath.Replace(ConfigData.LocalDataPathRegex, package.ToIdentifier().Value) + .CleanUpPathCrossPlatform(), + localFilePath)); + if (!path.StartsWith(ConfigData.LocalDataSavePath)) + ThrowHelper.ThrowUnauthorizedAccessException($"{nameof(GetAbsolutePathForLocal)}: The local path of '{path}' is not a local path!"); + return path; + } + catch (Exception e) + { + if (e is ArgumentNullException or ArgumentException or UnauthorizedAccessException) + throw; // these are dev errors and should be propagated. + return FluentResults.Result.Fail(new ExceptionalError(e)); + } + } + + private Result LoadLocalData(ContentPackage package, string localFilePath, Func> dataLoader) + { + Guard.IsNotNull(package, nameof(package)); + Guard.IsNotNullOrWhiteSpace(localFilePath, nameof(localFilePath)); + using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + var res = GetAbsolutePathForLocal(package, localFilePath); + return res is { IsFailed: true } ? res.ToResult() : dataLoader(res.Value); + } + + public Result LoadLocalXml(ContentPackage package, string localFilePath) => LoadLocalData(package, localFilePath, TryLoadXml); + public Result LoadLocalBinary(ContentPackage package, string localFilePath) => LoadLocalData(package, localFilePath, TryLoadBinary); + public Result LoadLocalText(ContentPackage package, string localFilePath) => LoadLocalData(package, localFilePath, TryLoadText); + + + private FluentResults.Result SaveLocalData(ContentPackage package, string localFilePath, in T data, Func dataSaver) + { + Guard.IsNotNull(package, nameof(package)); + Guard.IsNotNullOrWhiteSpace(localFilePath, nameof(localFilePath)); + using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + var res = GetAbsolutePathForLocal(package, localFilePath); + return res is { IsFailed: true } ? res.ToResult() : dataSaver(res.Value, data); + } + + public FluentResults.Result SaveLocalXml(ContentPackage package, string localFilePath, XDocument document) + => SaveLocalData(package, localFilePath, document, (path, data) => TrySaveXml(path, in data)); + public FluentResults.Result SaveLocalBinary(ContentPackage package, string localFilePath, in byte[] bytes) + => SaveLocalData(package, localFilePath, bytes, (path, data) => TrySaveBinary(path, in data)); + public FluentResults.Result SaveLocalText(ContentPackage package, string localFilePath, in string text) + => SaveLocalData(package, localFilePath, text, (path, data) => TrySaveText(path, in data)); + + private async Task> LoadLocalDataAsync(ContentPackage package, string localFilePath, + Func>> dataLoader) + { + Guard.IsNotNull(package, nameof(package)); + Guard.IsNotNullOrWhiteSpace(localFilePath, nameof(localFilePath)); + using var lck = await OperationsLock.AcquireReaderLock(); + IService.CheckDisposed(this); + var res = GetAbsolutePathForLocal(package, localFilePath); + return res is { IsFailed: true } ? res.ToResult() : await dataLoader(res.Value); + } + + public async Task> LoadLocalXmlAsync(ContentPackage package, string localFilePath) + => await LoadLocalDataAsync(package, localFilePath, async path => await TryLoadXmlAsync(path)); + public async Task> LoadLocalBinaryAsync(ContentPackage package, string localFilePath) + => await LoadLocalDataAsync(package, localFilePath, async path => await TryLoadBinaryAsync(path)); + public async Task> LoadLocalTextAsync(ContentPackage package, string localFilePath) + => await LoadLocalDataAsync(package, localFilePath, async path => await TryLoadTextAsync(path)); + + private async Task SaveLocalDataAsync(ContentPackage package, string localFilePath, + T data, Func> dataSaver) + { + Guard.IsNotNull(package, nameof(package)); + Guard.IsNotNullOrWhiteSpace(localFilePath, nameof(localFilePath)); + IService.CheckDisposed(this); + using var lck = await OperationsLock.AcquireReaderLock(); + var res = GetAbsolutePathForLocal(package, localFilePath); + return res is { IsFailed: true } ? res.ToResult() : await dataSaver(res.Value, data); + } + + public async Task SaveLocalXmlAsync(ContentPackage package, string localFilePath, XDocument document) + => await SaveLocalDataAsync(package, localFilePath, document, async (path, doc) => await TrySaveXmlAsync(path, doc)); + public async Task SaveLocalBinaryAsync(ContentPackage package, string localFilePath, byte[] bytes) + => await SaveLocalDataAsync(package, localFilePath, bytes, async (path, bin) => await TrySaveBinaryAsync(path, bin)); + public async Task SaveLocalTextAsync(ContentPackage package, string localFilePath, string text) + => await SaveLocalDataAsync(package, localFilePath, text, async (path, txt) => await TrySaveTextAsync(path, txt)); + + + // --- Package Content + private Result LoadPackageData(ContentPath filePath, Func> dataLoader) + { + Guard.IsNotNull(filePath, nameof(filePath)); + Guard.IsNotNullOrWhiteSpace(filePath.FullPath, nameof(filePath.FullPath)); + using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + if (!filePath.FullPath.StartsWith(ConfigData.WorkshopModsDirectory) && !filePath.FullPath.StartsWith(ConfigData.LocalModsDirectory)) + ThrowHelper.ThrowUnauthorizedAccessException($"{nameof(LoadPackageData)}: The filepath of `{filePath.FullPath}' is not in a package directory!"); + return dataLoader(filePath.FullPath); + } + + public Result LoadPackageXml(ContentPath filePath) + => LoadPackageData(filePath, path => TryLoadXml(filePath.FullPath)); + public Result LoadPackageBinary(ContentPath filePath) + => LoadPackageData(filePath, path => TryLoadBinary(filePath.FullPath)); + public Result LoadPackageText(ContentPath filePath) + => LoadPackageData(filePath, path => TryLoadText(filePath.FullPath)); + + private ImmutableArray<(ContentPath, Result)> LoadPackageDataFiles(ImmutableArray filePaths, Func> dataLoader) + { + if (filePaths.IsDefaultOrEmpty) + ThrowHelper.ThrowArgumentNullException($"{nameof(LoadPackageData)}: File paths is empty!"); + using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + var builder = ImmutableArray.CreateBuilder<(ContentPath, Result)>(); + foreach (var path in filePaths) + { + builder.Add((path, LoadPackageData(path, dataLoader))); + } + return builder.MoveToImmutable(); + } + + public ImmutableArray<(ContentPath, Result)> LoadPackageXmlFiles(ImmutableArray filePaths) + => LoadPackageDataFiles(filePaths, TryLoadXml); + public ImmutableArray<(ContentPath, Result)> LoadPackageBinaryFiles(ImmutableArray filePaths) + => LoadPackageDataFiles(filePaths, TryLoadBinary); + public ImmutableArray<(ContentPath, Result)> LoadPackageTextFiles(ImmutableArray filePaths) + => LoadPackageDataFiles(filePaths, TryLoadText); + + public Result> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively) + { + Guard.IsNotNull(package, nameof(package)); + try + { + var fullPath = localSubfolder.IsNullOrWhiteSpace() + ? Path.GetFullPath(package.Path) + : Path.GetFullPath(package.Path, localSubfolder); + return System.IO.Directory.GetFiles(fullPath, regexFilter, + searchRecursively ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly) + .ToImmutableArray(); + } + catch (Exception e) + { + if (e is ArgumentNullException or ArgumentException) + throw; + return FluentResults.Result.Fail(new ExceptionalError(e) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, package)); + } + } + + + private async Task> LoadPackageDataAsync(ContentPath filePath, Func>> dataLoader) + { + Guard.IsNotNull(filePath, nameof(filePath)); + Guard.IsNotNullOrWhiteSpace(filePath.FullPath, nameof(filePath.FullPath)); + using var lck = await OperationsLock.AcquireReaderLock(); + IService.CheckDisposed(this); + if (!filePath.FullPath.StartsWith(ConfigData.WorkshopModsDirectory) && !filePath.FullPath.StartsWith(ConfigData.LocalModsDirectory)) + ThrowHelper.ThrowUnauthorizedAccessException($"{nameof(LoadPackageData)}: The filepath of `{filePath.FullPath}' is not in a package directory!"); + return await dataLoader(filePath.FullPath); + } + + public async Task> LoadPackageXmlAsync(ContentPath filePath) + => await LoadPackageDataAsync(filePath, async path => await TryLoadXmlAsync(path)); + public async Task> LoadPackageBinaryAsync(ContentPath filePath) + => await LoadPackageDataAsync(filePath, async path => await TryLoadBinaryAsync(path)); + public async Task> LoadPackageTextAsync(ContentPath filePath) + => await LoadPackageDataAsync(filePath, async path => await TryLoadTextAsync(path)); + + private async Task)>> LoadPackageDataFilesAsync( + ImmutableArray filePaths, Func>> dataLoader) + { + if (filePaths.IsDefaultOrEmpty) + ThrowHelper.ThrowArgumentNullException($"{nameof(LoadPackageData)}: File paths is empty!"); + using var lck = await OperationsLock.AcquireReaderLock(); + var builder = ImmutableArray.CreateBuilder<(ContentPath, Result)>(); + foreach (var path in filePaths) + { + builder.Add((path, await LoadPackageDataAsync(path, dataLoader))); + } + return builder.MoveToImmutable(); + } + + public async Task)>> LoadPackageXmlFilesAsync(ImmutableArray filePaths) + => await LoadPackageDataFilesAsync(filePaths, async path => await TryLoadXmlAsync(path)); + public async Task)>> LoadPackageBinaryFilesAsync(ImmutableArray filePaths) + => await LoadPackageDataFilesAsync(filePaths, async path => await TryLoadBinaryAsync(path)); + public async Task)>> LoadPackageTextFilesAsync(ImmutableArray filePaths) + => await LoadPackageDataFilesAsync(filePaths, async path => await TryLoadTextAsync(path)); + + private int _useCaching; public bool UseCaching { @@ -98,156 +294,14 @@ public class StorageService : IStorageService set => ModUtils.Threading.SetBool(ref _useCaching, value); } - public virtual FluentResults.Result LoadLocalXml(ContentPackage package, string localFilePath) => - GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } - ? TryLoadXml(r.Value) : r.ToResult(); - public virtual FluentResults.Result LoadLocalBinary(ContentPackage package, string localFilePath) => - GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } - ? TryLoadBinary(r.Value) : r.ToResult(); - public virtual FluentResults.Result LoadLocalText(ContentPackage package, string localFilePath) => - GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } - ? TryLoadText(r.Value) : r.ToResult(); - public virtual FluentResults.Result SaveLocalXml(ContentPackage package, string localFilePath, XDocument document) => - GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } - ? TrySaveXml(r.Value, document) : r.ToResult(); - public virtual FluentResults.Result SaveLocalBinary(ContentPackage package, string localFilePath, in byte[] bytes) => - GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } - ? TrySaveBinary(r.Value, bytes) : r.ToResult(); - public virtual FluentResults.Result SaveLocalText(ContentPackage package, string localFilePath, in string text) => - GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } - ? TrySaveText(r.Value, text) : r.ToResult(); - public virtual async Task> LoadLocalXmlAsync(ContentPackage package, string localFilePath) => - GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } - ? await TryLoadXmlAsync(r.Value) : r.ToResult(); - public virtual async Task> LoadLocalBinaryAsync(ContentPackage package, string localFilePath) => - GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } - ? await TryLoadBinaryAsync(r.Value) : r.ToResult(); - public virtual async Task> LoadLocalTextAsync(ContentPackage package, string localFilePath) => - GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } - ? await TryLoadTextAsync(r.Value) : r.ToResult(); - public virtual async Task SaveLocalXmlAsync(ContentPackage package, string localFilePath, XDocument document) => - GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } - ? await TrySaveXmlAsync(r.Value, document) : r.ToResult(); - public virtual async Task SaveLocalBinaryAsync(ContentPackage package, string localFilePath, byte[] bytes) => - GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } - ? await TrySaveBinaryAsync(r.Value, bytes) : r.ToResult(); - public virtual async Task SaveLocalTextAsync(ContentPackage package, string localFilePath, string text) => - GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } - ? await TrySaveTextAsync(r.Value, text) : r.ToResult(); - public virtual FluentResults.Result LoadPackageXml(ContentPackage package, string localFilePath) => - GetAbsoluePathFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } - ? TryLoadXml(r.Value) : r.ToResult(); - public virtual FluentResults.Result LoadPackageBinary(ContentPackage package, string localFilePath) => - GetAbsoluePathFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } - ? TryLoadBinary(r.Value) : r.ToResult(); - public virtual FluentResults.Result LoadPackageText(ContentPackage package, string localFilePath) => - GetAbsoluePathFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } - ? TryLoadText(r.Value) : r.ToResult(); + // Method group redirect + private FluentResults.Result TryLoadXml(string filePath) => TryLoadXml(filePath, null); - - - public virtual ImmutableArray<(string, FluentResults.Result)> LoadPackageXmlFiles(ContentPackage package, ImmutableArray localFilePaths) + public virtual FluentResults.Result TryLoadXml(string filePath, Encoding encoding) { - ((IService)this).CheckDisposed(); - if (localFilePaths.IsDefaultOrEmpty) - return ImmutableArray<(string, FluentResults.Result)>.Empty; - var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result)>(localFilePaths.Length); - foreach (var path in localFilePaths) - builder.Add((path, LoadPackageXml(package, path))); - return builder.MoveToImmutable(); - } - - public virtual ImmutableArray<(string, FluentResults.Result)> LoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePaths) - { - ((IService)this).CheckDisposed(); - if (localFilePaths.IsDefaultOrEmpty) - return ImmutableArray<(string, FluentResults.Result)>.Empty; - var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result)>(localFilePaths.Length); - foreach (var path in localFilePaths) - builder.Add((path, LoadPackageBinary(package, path))); - return builder.MoveToImmutable(); - } - - public virtual ImmutableArray<(string, FluentResults.Result)> LoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePaths) - { - ((IService)this).CheckDisposed(); - if (localFilePaths.IsDefaultOrEmpty) - return ImmutableArray<(string, FluentResults.Result)>.Empty; - var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result)>(localFilePaths.Length); - foreach (var path in localFilePaths) - builder.Add((path, LoadPackageText(package, path))); - return builder.MoveToImmutable(); - } - - public virtual FluentResults.Result> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively) - { - ((IService)this).CheckDisposed(); - var r = GetAbsoluePathFromPackage(package, localSubfolder); - if (r is { IsFailed: true }) - return r.ToResult(); - var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result>)>(); - var sOption = searchRecursively ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; - string[] arr = Directory.GetFiles(localSubfolder, regexFilter.IsNullOrWhiteSpace() ? "*.*" : regexFilter, sOption); - return new FluentResults.Result>().WithSuccess($"Files found.") - .WithValue(arr.ToImmutableArray()); - } - - public virtual async Task> LoadPackageXmlAsync(ContentPackage package, string localFilePath) => - GetAbsoluePathFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } - ? await TryLoadXmlAsync(r.Value) : r.ToResult(); - - public virtual async Task> LoadPackageBinaryAsync(ContentPackage package, string localFilePath) => - GetAbsoluePathFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } - ? await TryLoadBinaryAsync(r.Value) : r.ToResult(); - - public virtual async Task> LoadPackageTextAsync(ContentPackage package, string localFilePath) => - GetAbsoluePathFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null } - ? await TryLoadTextAsync(r.Value) : r.ToResult(); - - public virtual async Task)>> LoadPackageXmlFilesAsync(ContentPackage package, ImmutableArray localFilePaths) - { - ((IService)this).CheckDisposed(); - if (localFilePaths.IsDefaultOrEmpty) - return ImmutableArray<(string, FluentResults.Result)>.Empty; - var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result)>(localFilePaths.Length); + using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); - await localFilePaths.ParallelForEachAsync(async path => - { - builder.Add((path, await LoadPackageXmlAsync(package, path))); - }, maxDegreeOfParallelism: 2); - return builder.MoveToImmutable(); - } - - public virtual async Task)>> LoadPackageBinaryFilesAsync(ContentPackage package, ImmutableArray localFilePaths) - { - ((IService)this).CheckDisposed(); - if (localFilePaths.IsDefaultOrEmpty) - return ImmutableArray<(string, FluentResults.Result)>.Empty; - var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result)>(localFilePaths.Length); - await localFilePaths.ParallelForEachAsync(async path => - { - builder.Add((path, await LoadPackageBinaryAsync(package, path))); - }, maxDegreeOfParallelism: 2); - return builder.MoveToImmutable(); - } - - public virtual async Task)>> LoadPackageTextFilesAsync(ContentPackage package, ImmutableArray localFilePaths) - { - ((IService)this).CheckDisposed(); - if (localFilePaths.IsDefaultOrEmpty) - return ImmutableArray<(string, FluentResults.Result)>.Empty; - var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result)>(localFilePaths.Length); - await localFilePaths.ParallelForEachAsync(async path => - { - builder.Add((path, await LoadPackageTextAsync(package, path))); - }, maxDegreeOfParallelism: 2); - return builder.MoveToImmutable(); - } - - - public virtual FluentResults.Result TryLoadXml(string filePath, Encoding encoding = null) - { - ((IService)this).CheckDisposed(); var r = TryLoadText(filePath, encoding); if (r is { IsSuccess: true, Value: not null }) return XDocument.Parse(r.Value); @@ -258,9 +312,13 @@ public class StorageService : IStorageService } } - public virtual FluentResults.Result TryLoadText(string filePath, Encoding encoding = null) + // Method group redirect + private FluentResults.Result TryLoadText(string filePath) => TryLoadText(filePath, null); + public virtual FluentResults.Result TryLoadText(string filePath, Encoding encoding) { - ((IService)this).CheckDisposed(); + using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + if (UseCaching && _fsCache.TryGetValue(filePath, out var result) && result.TryPickT1(out var cachedVal, out _)) { @@ -280,7 +338,9 @@ public class StorageService : IStorageService public virtual FluentResults.Result TryLoadBinary(string filePath) { - ((IService)this).CheckDisposed(); + using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + if (UseCaching && _fsCache.TryGetValue(filePath, out var result) && result.TryPickT0(out var cachedVal, out _)) { @@ -301,14 +361,10 @@ public class StorageService : IStorageService public virtual FluentResults.Result TrySaveXml(string filePath, in XDocument document, Encoding encoding = null) => TrySaveText(filePath, document.ToString(), encoding); public virtual FluentResults.Result TrySaveText(string filePath, in string text, Encoding encoding = null) { - ((IService)this).CheckDisposed(); - if (text.IsNullOrWhiteSpace()) - { - return FluentResults.Result.Fail($"Contents are empty for {filePath}") - .WithError(new Error($"Contents are empty for {filePath}") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.Sources, filePath)); - } + Guard.IsNotNullOrWhiteSpace(text, nameof(text)); + using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + string t = text; //copy return IOExceptionsOperationRunner(nameof(TrySaveText), filePath, () => { @@ -321,16 +377,15 @@ public class StorageService : IStorageService }); } + public virtual FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes) { - ((IService)this).CheckDisposed(); - if (bytes is null || bytes.Length == 0) - { - return FluentResults.Result.Fail($"Byte array is null or empty for {filePath}") - .WithError(new Error($"Byte array is null or empty for {filePath}") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.Sources, filePath)); - } + Guard.IsNotNull(bytes, nameof(bytes)); + Guard.HasSizeGreaterThanOrEqualTo(bytes, 1, nameof(bytes)); + using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + byte[] b = new byte[bytes.Length]; System.Buffer.BlockCopy(bytes, 0, b, 0, bytes.Length); return IOExceptionsOperationRunner(nameof(TrySaveBinary), filePath, () => @@ -346,7 +401,7 @@ public class StorageService : IStorageService public virtual FluentResults.Result FileExists(string filePath) { - ((IService)this).CheckDisposed(); + IService.CheckDisposed(this); return IOExceptionsOperationRunner(nameof(FileExists), filePath, () => { var fp = filePath.CleanUpPath(); @@ -357,7 +412,7 @@ public class StorageService : IStorageService public virtual FluentResults.Result DirectoryExists(string directoryPath) { - ((IService)this).CheckDisposed(); + IService.CheckDisposed(this); try { var di = new DirectoryInfo(directoryPath); @@ -371,7 +426,7 @@ public class StorageService : IStorageService public virtual async Task> TryLoadXmlAsync(string filePath, Encoding encoding = null) { - ((IService)this).CheckDisposed(); + IService.CheckDisposed(this); if (UseCaching && _fsCache.TryGetValue(filePath, out var cachedVal) && cachedVal.TryPickT2(out var cachedDoc, out _)) return FluentResults.Result.Ok(cachedDoc); @@ -391,7 +446,7 @@ public class StorageService : IStorageService public virtual async Task> TryLoadTextAsync(string filePath, Encoding encoding = null) { - ((IService)this).CheckDisposed(); + IService.CheckDisposed(this); if (UseCaching && _fsCache.TryGetValue(filePath, out var cachedVal) && cachedVal.TryPickT1(out var cachedTxt, out _)) return FluentResults.Result.Ok(cachedTxt); @@ -409,7 +464,7 @@ public class StorageService : IStorageService public virtual async Task> TryLoadBinaryAsync(string filePath) { - ((IService)this).CheckDisposed(); + IService.CheckDisposed(this); if (UseCaching && _fsCache.TryGetValue(filePath, out var cachedVal) && cachedVal.TryPickT0(out var cachedBin, out _)) { @@ -427,14 +482,9 @@ public class StorageService : IStorageService public virtual async Task TrySaveXmlAsync(string filePath, XDocument document, Encoding encoding = null) => await TrySaveTextAsync(filePath, document.ToString(), encoding); public virtual async Task TrySaveTextAsync(string filePath, string text, Encoding encoding = null) { - ((IService)this).CheckDisposed(); - if (text.IsNullOrWhiteSpace()) - { - return FluentResults.Result.Fail($"Contents are empty for {filePath}") - .WithError(new Error($"Contents are empty for {filePath}") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.Sources, filePath)); - } + Guard.IsNotNullOrWhiteSpace(text, nameof(text)); + using var lck = await OperationsLock.AcquireReaderLock(); + IService.CheckDisposed(this); string t = text.ToString(); //copy return await IOExceptionsOperationRunnerAsync(nameof(TrySaveText), filePath, async () => @@ -450,14 +500,12 @@ public class StorageService : IStorageService public virtual async Task TrySaveBinaryAsync(string filePath, byte[] bytes) { - ((IService)this).CheckDisposed(); - if (bytes is null || bytes.Length == 0) - { - return FluentResults.Result.Fail($"Byte array is null or empty for {filePath}") - .WithError(new Error($"Byte array is null or empty for {filePath}") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.Sources, filePath)); - } + Guard.IsNotNull(bytes, nameof(bytes)); + Guard.HasSizeGreaterThanOrEqualTo(bytes, 1, nameof(bytes)); + using var lck = await OperationsLock.AcquireReaderLock(); + IService.CheckDisposed(this); + + byte[] b = new byte[bytes.Length]; System.Buffer.BlockCopy(bytes, 0, b, 0, bytes.Length); return await IOExceptionsOperationRunnerAsync(nameof(TrySaveBinary), filePath, async () => @@ -479,6 +527,8 @@ public class StorageService : IStorageService } catch (Exception e) { + if (e is ArgumentException or ArgumentNullException) + throw; return ReturnException(e, filepath).WithError(GetGeneralError(funcName, filepath)); } } @@ -491,6 +541,8 @@ public class StorageService : IStorageService } catch (Exception e) { + if (e is ArgumentException or ArgumentNullException) + throw; return ReturnException(e, filepath).WithError(GetGeneralError(funcName, filepath)); } } @@ -503,6 +555,8 @@ public class StorageService : IStorageService } catch (Exception e) { + if (e is ArgumentException or ArgumentNullException) + throw; return ReturnException(e, filepath).WithError(GetGeneralError(funcName, filepath)); } } @@ -515,6 +569,8 @@ public class StorageService : IStorageService } catch (Exception e) { + if (e is ArgumentException or ArgumentNullException) + throw; return ReturnException(e, filepath).WithError(GetGeneralError(funcName, filepath)); } } @@ -530,50 +586,6 @@ public class StorageService : IStorageService .WithMetadata(MetadataType.ExceptionObject, this) .WithMetadata(MetadataType.Sources, localfp); - private FluentResults.Result GetAbsoluePathFromLocal(ContentPackage package, string localFilePath) - { - if (Path.IsPathRooted(localFilePath)) - { - return new FluentResults.Result().WithError( - new Error($"The path '{localFilePath}' is a rooted path. Must be relative!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, localFilePath)); - } - - Guard.IsNotNull(package, nameof(package)); - - return new FluentResults.Result().WithSuccess($"Path constructed") - .WithValue(System.IO.Path.GetFullPath(System.IO.Path.Combine( - _configData.RunLocation, - _configData.LocalPackageDataPath.Replace( - _configData.LocalDataPathRegex, - package.TryExtractSteamWorkshopId(out var id) ? id.Value.ToString() : package.Name), - localFilePath))); - } - - public FluentResults.Result GetAbsoluePathFromPackage(ContentPackage package, string localFilePath) - { - Guard.IsNotNull(package, nameof(package)); - - if (localFilePath.IsNullOrWhiteSpace()) - { - return new FluentResults.Result().WithValue(Path.GetFullPath(package.Path.CleanUpPath())); - } - - var path = localFilePath.CleanUpPath(); - - if (Path.IsPathRooted(path)) - { - return new FluentResults.Result().WithError( - new Error($"The path '{localFilePath}' is a rooted path. Must be relative!") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, localFilePath)); - } - - return new FluentResults.Result().WithSuccess($"Path constructed") - .WithValue(Path.Combine(Path.GetFullPath(package.Path.CleanUpPath()), path)); - } - private FluentResults.Result ReturnException(TException exception, ContentPackage package) where TException : Exception { return new FluentResults.Result().WithError(new ExceptionalError(exception) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs index b26cc724b..f9f08d689 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs @@ -27,7 +27,7 @@ public partial interface IConfigService : IReusableService, ILuaConfigService // Config Files/Resources Task LoadConfigsAsync(ImmutableArray configResources); - Task LoadConfigsProfilesAsync(ImmutableArray configProfileResources); + Task LoadConfigsProfilesAsync(ImmutableArray configProfileResources); // Immediate Mode FluentResults.Result AddConfig(IConfigInfo configInfo) where TConfig : IConfigBase; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IModConfigInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IModConfigService.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IModConfigInfo.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IModConfigService.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs index 0f185aa8b..ae566c125 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs @@ -9,7 +9,7 @@ using Barotrauma.LuaCs.Data; namespace Barotrauma.LuaCs.Services; -public interface IPackageManagementService : IReusableService, IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo, IAssembliesResourcesInfo +public interface IPackageManagementService : IReusableService, IConfigsResourcesInfo, ILuaScriptsResourcesInfo, IAssembliesResourcesInfo { /// /// Loads and parses the provided for supported by the current runtime environment. @@ -46,16 +46,13 @@ public interface IPackageManagementService : IReusableService, IConfigsResources // single FluentResults.Result GetAssembliesInfos(ContentPackage package, bool onlySupportedResources = true); FluentResults.Result GetConfigsInfos(ContentPackage package, bool onlySupportedResources = true); - FluentResults.Result GetConfigProfilesInfos(ContentPackage package, bool onlySupportedResources = true); FluentResults.Result GetLuaScriptsInfos(ContentPackage package, bool onlySupportedResources = true); // collection FluentResults.Result GetAssembliesInfos(IReadOnlyList packages, bool onlySupportedResources = true); FluentResults.Result GetConfigsInfos(IReadOnlyList packages, bool onlySupportedResources = true); - FluentResults.Result GetConfigProfilesInfos(IReadOnlyList packages, bool onlySupportedResources = true); FluentResults.Result GetLuaScriptsInfos(IReadOnlyList packages, bool onlySupportedResources = true); Task> GetAssembliesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); Task> GetConfigsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); - Task> GetConfigProfilesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); Task> GetLuaScriptsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IService.cs index 3e8457e19..a5502fc94 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IService.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.Toolkit.Diagnostics; namespace Barotrauma.LuaCs.Services; @@ -25,6 +26,12 @@ public interface IService : IDisposable public void CheckDisposed() { if (IsDisposed) - throw new ObjectDisposedException($"Tried to call method on disposed object '{this.GetType().Name}'!"); + ThrowHelper.ThrowObjectDisposedException($"Tried to call method on disposed object '{this.GetType().Name}'!"); + } + + static void CheckDisposed(IService service) + { + if (service.IsDisposed) + ThrowHelper.ThrowObjectDisposedException($"Tried to call method on disposed object '{service.GetType().Name}'!"); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs index 58ca07d55..19f112840 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs @@ -3,6 +3,7 @@ using System.Collections.Immutable; using System.Text; using System.Threading.Tasks; using System.Xml.Linq; +using FluentResults; namespace Barotrauma.LuaCs.Services; @@ -28,7 +29,7 @@ public interface IStorageService : IService /// void PurgeFilesFromCache(params string[] absolutePaths); - // -- local game folder storage + // -- local game folder storage FluentResults.Result LoadLocalXml(ContentPackage package, string localFilePath); FluentResults.Result LoadLocalBinary(ContentPackage package, string localFilePath); FluentResults.Result LoadLocalText(ContentPackage package, string localFilePath); @@ -45,24 +46,23 @@ public interface IStorageService : IService // -- package directory // singles - FluentResults.Result LoadPackageXml(ContentPackage package, string localFilePath); - FluentResults.Result LoadPackageBinary(ContentPackage package, string localFilePath); - FluentResults.Result LoadPackageText(ContentPackage package, string localFilePath); + Result LoadPackageXml(ContentPath filePath); + Result LoadPackageBinary(ContentPath filePath); + Result LoadPackageText(ContentPath filePath); // collections - ImmutableArray<(string, FluentResults.Result)> LoadPackageXmlFiles(ContentPackage package, ImmutableArray localFilePaths); - ImmutableArray<(string, FluentResults.Result)> LoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePaths); - ImmutableArray<(string, FluentResults.Result)> LoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePaths); + ImmutableArray<(ContentPath, Result)> LoadPackageXmlFiles(ImmutableArray filePaths); + ImmutableArray<(ContentPath, Result)> LoadPackageBinaryFiles(ImmutableArray filePaths); + ImmutableArray<(ContentPath, Result)> LoadPackageTextFiles(ImmutableArray filePaths); FluentResults.Result> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively); - FluentResults.Result GetAbsoluePathFromPackage(ContentPackage package, string localFilePath); // async // singles - Task> LoadPackageXmlAsync(ContentPackage package, string localFilePath); - Task> LoadPackageBinaryAsync(ContentPackage package, string localFilePath); - Task> LoadPackageTextAsync(ContentPackage package, string localFilePath); + Task> LoadPackageXmlAsync(ContentPath filePath); + Task> LoadPackageBinaryAsync(ContentPath filePath); + Task> LoadPackageTextAsync(ContentPath filePath); // collections - Task)>> LoadPackageXmlFilesAsync(ContentPackage package, ImmutableArray localFilePaths); - Task)>> LoadPackageBinaryFilesAsync(ContentPackage package, ImmutableArray localFilePaths); - Task)>> LoadPackageTextFilesAsync(ContentPackage package, ImmutableArray localFilePaths); + Task)>> LoadPackageXmlFilesAsync(ImmutableArray filePaths); + Task)>> LoadPackageBinaryFilesAsync(ImmutableArray filePaths); + Task)>> LoadPackageTextFilesAsync(ImmutableArray filePaths); // -- absolute paths FluentResults.Result TryLoadXml(string filePath, Encoding encoding = null); From 0438d3c4ba646e256c83d0a2e4c7de8181b1d8d0 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Mon, 12 Jan 2026 13:03:58 -0500 Subject: [PATCH 036/288] [Save/Sync] - Work on PMS. --- .../Services/PackageManagementService.cs | 10 -- .../SharedSource/LuaCs/Data/IModConfigInfo.cs | 2 +- .../Services/PackageManagementService.cs | 142 +++++++++++------- .../_Interfaces/IPackageManagementService.cs | 53 +------ 4 files changed, 94 insertions(+), 113 deletions(-) delete mode 100644 Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageManagementService.cs diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageManagementService.cs deleted file mode 100644 index ef6ee88a6..000000000 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/PackageManagementService.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; -using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services.Processing; - -namespace Barotrauma.LuaCs.Services; - -public partial class PackageManagementService -{ - -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs index c8740e51d..70cb2d6d7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs @@ -4,7 +4,7 @@ using System.Xml.Linq; namespace Barotrauma.LuaCs.Data; -public partial interface IModConfigInfo : IAssembliesResourcesInfo, +public interface IModConfigInfo : IAssembliesResourcesInfo, ILuaScriptsResourcesInfo, IConfigsResourcesInfo { // package info diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index a14ed5784..802b2dc07 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.Collections.Immutable; using System.Threading.Tasks; using Barotrauma.LuaCs.Data; @@ -6,105 +7,132 @@ using FluentResults; namespace Barotrauma.LuaCs.Services; -public partial class PackageManagementService : IPackageManagementService +public sealed class PackageManagementService : IPackageManagementService { + // svc + private ILoggerService _logger; + private IModConfigService _modConfigService; + private ILuaScriptManagementService _luaScriptManagementService; + private IPluginManagementService _pluginManagementService; + // state + private readonly ConcurrentDictionary _loadedPackages = new(); + private readonly ConcurrentDictionary _runningPackages = new(); + // control + private readonly AsyncReaderWriterLock _operationsLock = new(); + + public PackageManagementService(ILoggerService logger, IModConfigService modConfigService, ILuaScriptManagementService luaScriptManagementService, IPluginManagementService pluginManagementService) + { + _logger = logger; + _modConfigService = modConfigService; + _luaScriptManagementService = luaScriptManagementService; + _pluginManagementService = pluginManagementService; + } + public void Dispose() { - throw new System.NotImplementedException(); + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) + return; + + _logger.LogMessage($"{nameof(PackageManagementService)} is disposing"); + _luaScriptManagementService.Dispose(); + _pluginManagementService.Dispose(); + _modConfigService.Dispose(); + _logger.Dispose(); + + _logger = null; + _luaScriptManagementService = null; + _pluginManagementService = null; + _modConfigService = null; + _loadedPackages.Clear(); + _runningPackages.Clear(); } private int _isDisposed = 0; public bool IsDisposed { get => ModUtils.Threading.GetBool(ref _isDisposed); - private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + set => ModUtils.Threading.SetBool(ref _isDisposed, value); } - public FluentResults.Result Reset() { - throw new System.NotImplementedException(); + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (IsDisposed) + return FluentResults.Result.Fail($"{nameof(PackageManagementService)}failed to reset. Has already been disposed."); + + var operationResult = new FluentResults.Result(); + CombineResultErrors(operationResult, UnsafeStopRunningPackagesInternal()); + CombineResultErrors(operationResult, UnsafeUnloadAllPackagesInternal()); + return operationResult; + + void CombineResultErrors(FluentResults.Result result, + ImmutableArray<(ContentPackage Package, FluentResults.Result OperationResult)> packRes) + { + if (packRes.IsDefaultOrEmpty) + return; + + foreach (var r in packRes) + { + if (r.OperationResult.IsSuccess) + continue; + _logger.LogResults(r.OperationResult); + result.WithErrors(r.OperationResult.Errors); + } + } } - public ImmutableArray Configs { get; } - public ImmutableArray LuaScripts { get; } - public ImmutableArray Assemblies { get; } - public async Task LoadPackageInfosAsync(ContentPackage package) + public FluentResults.Result LoadPackageInfo(ContentPackage package) { throw new System.NotImplementedException(); } - public async Task> LoadPackagesInfosAsync(IReadOnlyList packages) + public ImmutableArray<(ContentPackage Package, FluentResults.Result LoadSuccessResult)> LoadPackagesInfo(IReadOnlyCollection packages) { throw new System.NotImplementedException(); } - public IReadOnlyList GetAllLoadedPackages() + private FluentResults.Result UnsafeLoadPackageInfoInternal(ContentPackage package) { throw new System.NotImplementedException(); } - public bool IsPackageLoaded(ContentPackage package) + public ImmutableArray<(ContentPackage Package, FluentResults.Result ExecutionResult)> ExecuteLoadedPackages() { throw new System.NotImplementedException(); } - public ImmutableArray FilterUnloadableResources(IReadOnlyList resources, bool enabledPackagesOnly = false) where T : IResourceInfo + private ImmutableArray<(ContentPackage Package, FluentResults.Result StopExectionResult)> UnsafeStopRunningPackagesInternal() + { + throw new System.NotImplementedException(); + } + + public ImmutableArray<(ContentPackage Package, FluentResults.Result StopExecutionResult)> StopRunningPackages() { throw new System.NotImplementedException(); } - public void DisposePackageInfos(ContentPackage package) + private FluentResults.Result UnsafeUnloadPackageInternal(ContentPackage package) { throw new System.NotImplementedException(); } - public void DisposePackagesInfos(IReadOnlyList packages) + private ImmutableArray<(ContentPackage Package, FluentResults.Result UnloadSuccessResult)> UnsafeUnloadAllPackagesInternal() + { + throw new System.NotImplementedException(); + } + + public FluentResults.Result UnloadPackage(ContentPackage package) + { + throw new System.NotImplementedException(); + } + + public ImmutableArray<(ContentPackage Package, FluentResults.Result UnloadSuccessResult)> UnloadPackages(IReadOnlyCollection packages) { throw new System.NotImplementedException(); } - public Result GetAssembliesInfos(ContentPackage package, bool onlySupportedResources = true) - { - throw new System.NotImplementedException(); - } - - public Result GetConfigsInfos(ContentPackage package, bool onlySupportedResources = true) - { - throw new System.NotImplementedException(); - } - - public Result GetLuaScriptsInfos(ContentPackage package, bool onlySupportedResources = true) - { - throw new System.NotImplementedException(); - } - - public Result GetAssembliesInfos(IReadOnlyList packages, bool onlySupportedResources = true) - { - throw new System.NotImplementedException(); - } - - public Result GetConfigsInfos(IReadOnlyList packages, bool onlySupportedResources = true) - { - throw new System.NotImplementedException(); - } - - public Result GetLuaScriptsInfos(IReadOnlyList packages, bool onlySupportedResources = true) - { - throw new System.NotImplementedException(); - } - - public async Task> GetAssembliesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true) - { - throw new System.NotImplementedException(); - } - - public async Task> GetConfigsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true) - { - throw new System.NotImplementedException(); - } - - public async Task> GetLuaScriptsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true) + public ImmutableArray<(ContentPackage Package, FluentResults.Result UnloadSuccessResult)> UnloadAllPackages() { throw new System.NotImplementedException(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs index ae566c125..3f0614154 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs @@ -9,50 +9,13 @@ using Barotrauma.LuaCs.Data; namespace Barotrauma.LuaCs.Services; -public interface IPackageManagementService : IReusableService, IConfigsResourcesInfo, ILuaScriptsResourcesInfo, IAssembliesResourcesInfo +public interface IPackageManagementService : IReusableService { - /// - /// Loads and parses the provided for supported by the current runtime environment. - /// Will overwrite any existing package data. - /// - /// Package to load. - /// - Task LoadPackageInfosAsync(ContentPackage package); - /// - /// Loads and parses the provided collection for supported by the current runtime environment. - /// Will overwrite any existing package data. - /// - /// List of packages to load. - /// - Task> LoadPackagesInfosAsync(IReadOnlyList packages); - IReadOnlyList GetAllLoadedPackages(); - bool IsPackageLoaded(ContentPackage package); - - /// - /// Filters out resources not suitable for the current environment using the following criteria:
- /// - Platform (Operating System)
- /// - Target (Client|Server)
- /// - Null/Invalid
- /// - Dependency Package Registered in PMS
- ///
- /// - /// - /// - ImmutableArray FilterUnloadableResources(IReadOnlyList resources, bool enabledPackagesOnly = false) - where T : IResourceInfo; - void DisposePackageInfos(ContentPackage package); - void DisposePackagesInfos(IReadOnlyList packages); - - // single - FluentResults.Result GetAssembliesInfos(ContentPackage package, bool onlySupportedResources = true); - FluentResults.Result GetConfigsInfos(ContentPackage package, bool onlySupportedResources = true); - FluentResults.Result GetLuaScriptsInfos(ContentPackage package, bool onlySupportedResources = true); - // collection - FluentResults.Result GetAssembliesInfos(IReadOnlyList packages, bool onlySupportedResources = true); - FluentResults.Result GetConfigsInfos(IReadOnlyList packages, bool onlySupportedResources = true); - FluentResults.Result GetLuaScriptsInfos(IReadOnlyList packages, bool onlySupportedResources = true); - - Task> GetAssembliesInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); - Task> GetConfigsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); - Task> GetLuaScriptsInfosAsync(IReadOnlyList packages, bool onlySupportedResources = true); + public FluentResults.Result LoadPackageInfo(ContentPackage package); + public ImmutableArray<(ContentPackage Package, FluentResults.Result LoadSuccessResult)> LoadPackagesInfo(IReadOnlyCollection packages); + public ImmutableArray<(ContentPackage Package, FluentResults.Result ExecutionResult)> ExecuteLoadedPackages(); + public ImmutableArray<(ContentPackage Package, FluentResults.Result StopExecutionResult)> StopRunningPackages(); + public FluentResults.Result UnloadPackage(ContentPackage package); + public ImmutableArray<(ContentPackage Package, FluentResults.Result UnloadSuccessResult)> UnloadPackages(IReadOnlyCollection packages); + public ImmutableArray<(ContentPackage Package, FluentResults.Result UnloadSuccessResult)> UnloadAllPackages(); } From 0bfceacaf3361f25fffa87fcb4bc1aa6845de56e Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 13 Jan 2026 14:14:32 -0500 Subject: [PATCH 037/288] - Completed most of PackageManagementService.cs - Some areas of code need to be rewritten for the simplified loading and execution process. --- .../ClientSource/LuaCs/LuaCsSetup.cs | 7 +- .../LuaCs/Data/ServicesConfigData.cs | 18 + .../SharedSource/LuaCs/LuaCsSetup.cs | 55 +- .../LuaCs/Services/ConfigService.cs | 573 ++---------------- .../Services/LuaScriptManagementService.cs | 4 +- .../Services/PackageManagementService.cs | 252 ++++++-- .../LuaCs/Services/PluginManagementService.cs | 2 +- .../Services/_Interfaces/IConfigService.cs | 1 + .../ILuaScriptManagementService.cs | 5 +- .../_Interfaces/IPackageManagementService.cs | 14 +- .../_Interfaces/IPluginManagementService.cs | 5 +- 11 files changed, 310 insertions(+), 626 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index 894641c17..08f5522c3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -25,8 +25,9 @@ namespace Barotrauma public void CheckCsEnabled() { - - var csharpMods = PackageManagementService.Assemblies + throw new NotImplementedException($"Replace PMS.Assemblies with checks on ContentPackageManager.EnabledPackages"); + + /*var csharpMods = PackageManagementService.Assemblies .GroupBy(ass => ass.OwnerPackage) .Select(grp => grp.Key) .Where(ContentPackageManager.EnabledPackages.All.Contains) @@ -66,7 +67,7 @@ namespace Barotrauma { this.IsCsEnabled.TrySetValue(false); return true; - }; + };*/ } /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs index cce3a273b..f24283ac5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs @@ -10,6 +10,7 @@ using System.Security.AccessControl; using Barotrauma.LuaCs.Services; using Barotrauma.Networking; using FluentResults; +using OneOf.Types; namespace Barotrauma.LuaCs.Data; @@ -224,3 +225,20 @@ public record LuaScriptServicesConfig : ILuaScriptServicesConfig public bool IsDisposed => false; } + +// --- Package Management Service +public interface IPackageManagementServiceConfig : IService +{ + bool IsCsEnabled { get; } +} + +public class PackageManagementServiceConfig : IPackageManagementServiceConfig +{ + public void Dispose() + { + // ignored + } + + public bool IsDisposed => false; + public bool IsCsEnabled => true; +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 95d7a92af..0ea4484cf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -340,7 +340,6 @@ namespace Barotrauma foreach (var package in toRemove) _toUnload.Enqueue(package); - ProcessPackagesListDifferences(); } @@ -358,16 +357,14 @@ namespace Barotrauma while (_toUnload.TryDequeue(out var cp)) { - LuaScriptManagementService.DisposePackageResources(cp); - ConfigService.DisposePackageData(cp); - PackageManagementService.DisposePackageInfos(cp); + } var ls = new List(); while (_toLoad.TryDequeue(out var cp)) { - if (PackageManagementService.LoadPackageInfosAsync(cp).GetAwaiter().GetResult() is + if (PackageManagementService.LoadPackageInfo(cp) is { IsFailed: true } failure) { Logger.LogError($"Failed to load package infos for {cp.Name}"); @@ -456,8 +453,7 @@ namespace Barotrauma return; // load core - var result1 = PackageManagementService.LoadPackageInfosAsync(ContentPackageManager.VanillaCorePackage) - .GetAwaiter().GetResult(); + var result1 = PackageManagementService.LoadPackageInfo(ContentPackageManager.VanillaCorePackage); if (result1.IsFailed) { Logger.LogError($"Unable to load LuaCs CorePackage resources! Running in degraded mode."); @@ -477,16 +473,9 @@ namespace Barotrauma void LoadContentPackagesInfos(IReadOnlyList packages) { - var result2 = PackageManagementService.LoadPackagesInfosAsync(packages) - .GetAwaiter().GetResult(); - - foreach (var entry in result2) - { - if (entry.Item2.IsSuccess) - Logger.LogMessage($"Successfully parsed package: {entry.Item1.Name}"); - else if (entry.Item2.IsFailed) - Logger.LogResults(entry.Item2); - } + var result2 = PackageManagementService.LoadPackagesInfo([..packages]); + if (result2.IsFailed) + Logger.LogResults(result2); } void LoadStaticAssets() @@ -500,7 +489,7 @@ namespace Barotrauma return; while (_toUnload.TryDequeue(out var cp)) - PackageManagementService.DisposePackageInfos(cp); + PackageManagementService.UnloadPackage(cp); LoadStaticAssetsAsync(PackageManagementService.GetAllLoadedPackages()).GetAwaiter().GetResult(); LoadLuaCsConfig(); @@ -543,11 +532,13 @@ namespace Barotrauma async Task LoadStaticAssetsAsync(IReadOnlyList packages) { - var cfgRes = ImmutableArray.Empty; - var luaRes = ImmutableArray.Empty; + throw new NotImplementedException(); + /*var cfgRes = ImmutableArray.Empty; + var luaRes = ImmutableArray.Empty; + var tasksBuilder = ImmutableArray.CreateBuilder(); - + //---- get resource infos tasksBuilder.AddRange( new Func(async () => @@ -568,10 +559,10 @@ namespace Barotrauma ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), res.ToResult()); })()); - + await Task.WhenAll(tasksBuilder.MoveToImmutable()); tasksBuilder.Clear(); - + //---- load resources tasksBuilder.AddRange(new Func(async () => { @@ -586,12 +577,12 @@ namespace Barotrauma Logger.LogResults(res); })()); - await Task.WhenAll(tasksBuilder.MoveToImmutable()); + await Task.WhenAll(tasksBuilder.MoveToImmutable());*/ } void RunScripts() { - if (!IsStaticAssetsLoaded) + /*if (!IsStaticAssetsLoaded) { throw new InvalidOperationException($"{nameof(RunScripts)} cannot load assets in the '{CurrentRunState}' state."); } @@ -661,23 +652,23 @@ namespace Barotrauma LuaScriptManagementService.ExecuteLoadedScripts(); if (CurrentRunState < RunState.Running) - _runState = RunState.Running; + _runState = RunState.Running;*/ } void UnloadContentPackageInfos() { - if (IsStaticAssetsLoaded) + /*if (IsStaticAssetsLoaded) { throw new InvalidOperationException($"{nameof(UnloadStaticAssets)}: Cannot unload static assets when the current run state is {CurrentRunState}."); } PackageManagementService.Reset(); - _toUnload.Clear(); + _toUnload.Clear();*/ } void UnloadStaticAssets() { - if (IsCodeRunning) + /*if (IsCodeRunning) { throw new InvalidOperationException($"{nameof(UnloadStaticAssets)}: Cannot unload static assets when the current run state is {CurrentRunState}."); } @@ -689,12 +680,12 @@ namespace Barotrauma if (CurrentRunState >= RunState.Configuration) { _runState = RunState.Parsed; - } + }*/ } void StopScripts() { - EventService.ClearAllSubscribers(); + /*EventService.ClearAllSubscribers(); LuaScriptManagementService.UnloadActiveScripts(); PluginManagementService.UnloadManagedAssemblies(); SubscribeToLuaCsEvents(); @@ -702,7 +693,7 @@ namespace Barotrauma if (IsCodeRunning) { _runState = RunState.Configuration; - } + }*/ } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs index df6e6c39f..f3f8d5f65 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs @@ -23,668 +23,179 @@ namespace Barotrauma.LuaCs.Services; public partial class ConfigService : IConfigService { - //--- Internals - public ConfigService(IParserServiceAsync> configProfileResourceParser, - IParserServiceAsync> configResourceParser, IEventService eventService, System.Lazy storageService) - { - _configProfileResourceParser = configProfileResourceParser; - _configResourceParser = configResourceParser; - _eventService = eventService; - _storageService = storageService; - this._base = this; - } - - // data, states - private readonly IService _base; - private int _isDisposed = 0; - private readonly ConcurrentDictionary>> _configTypeInitializers = new(); - private readonly ConcurrentDictionary<(ContentPackage Package, string ConfigName), IConfigBase> _configs = new(); - private readonly ConcurrentDictionary> _packageConfigReverseLookup = new(); - private readonly ConcurrentDictionary<(ContentPackage Package, string ProfileName), ImmutableArray<(string ConfigName, OneOf.OneOf Value)>> _configProfiles = new(); - private readonly ConcurrentDictionary> _packageProfilesReverseLookup = new(); - private readonly ConcurrentDictionary _packageNameMap= new(); - - private readonly AsyncReaderWriterLock _disposeOpsLock = new(); - - // extern services - private readonly IParserServiceAsync> _configResourceParser; - private readonly IParserServiceAsync> _configProfileResourceParser; - private readonly IEventService _eventService; - private readonly System.Lazy _storageService; - - //--- GC - public bool IsDisposed => ModUtils.Threading.GetBool(ref _isDisposed); - public void Dispose() { - // stop all ops - using var lck = _disposeOpsLock.AcquireWriterLock().GetAwaiter().GetResult(); - // set flag - ModUtils.Threading.SetBool(ref _isDisposed, true); - - _configTypeInitializers.Clear(); - if (!_configs.IsEmpty) - { - foreach (var config in _configs) - { - if (config.Value is IDisposable disposable) - disposable.Dispose(); - config.Value.OnValueChanged -= this.SaveConfigEvent; - } - _configs.Clear(); - } - - _configProfiles.Clear(); - _packageConfigReverseLookup.Clear(); - _packageNameMap.Clear(); - _packageProfilesReverseLookup.Clear(); - - GC.SuppressFinalize(this); + throw new NotImplementedException(); } - + + public bool IsDisposed { get; } public FluentResults.Result Reset() { - using var lck = _disposeOpsLock.AcquireWriterLock().GetAwaiter().GetResult(); - _base.CheckDisposed(); - - _configTypeInitializers.Clear(); - _configs.Clear(); - _configProfiles.Clear(); - _packageConfigReverseLookup.Clear(); - _packageNameMap.Clear(); - _packageProfilesReverseLookup.Clear(); - - return FluentResults.Result.Ok(); + throw new NotImplementedException(); } - //--- API contracts - // Notes: - // -- Lua Interface uses strong types due to lua limitations. May be required to move API to an adapter class - // to allow testing abstraction. - // -- Lua interface should not propagate errors. - #region LuaInterface - - private bool TryGetConfigValue(string packageName, string configName, out T value) where T : IEquatable - { - value = default; - using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); - if (ModUtils.Threading.GetBool(ref _isDisposed)) - return false; - if (!_packageNameMap.TryGetValue(packageName, out var package)) - return false; - if (!_configs.TryGetValue((package, configName), out var config)) - return false; - if (config is not IConfigEntry entry) - return false; - value = entry.Value; - return true; - } - public bool TryGetConfigBool(string packageName, string configName, out bool value) { - return TryGetConfigValue(packageName, configName, out value); + throw new NotImplementedException(); } public bool TryGetConfigInt(string packageName, string configName, out int value) { - return TryGetConfigValue(packageName, configName, out value); + throw new NotImplementedException(); } public bool TryGetConfigFloat(string packageName, string configName, out float value) { - return TryGetConfigValue(packageName, configName, out value); + throw new NotImplementedException(); } public bool TryGetConfigNumber(string packageName, string configName, out double value) { - return TryGetConfigValue(packageName, configName, out value); + throw new NotImplementedException(); } public bool TryGetConfigString(string packageName, string configName, out string value) { - return TryGetConfigValue(packageName, configName, out value); + throw new NotImplementedException(); } public bool TryGetConfigVector2(string packageName, string configName, out Vector2 value) { - return TryGetConfigValue(packageName, configName, out value); + throw new NotImplementedException(); } public bool TryGetConfigVector3(string packageName, string configName, out Vector3 value) { - return TryGetConfigValue(packageName, configName, out value); + throw new NotImplementedException(); } public bool TryGetConfigColor(string packageName, string configName, out Color value) { - return TryGetConfigValue(packageName, configName, out value); + throw new NotImplementedException(); } public bool TryGetConfigList(string packageName, string configName, out IReadOnlyList value) { - value = null; - using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); - if (ModUtils.Threading.GetBool(ref _isDisposed)) - return false; - if (!_packageNameMap.TryGetValue(packageName, out var package)) - return false; - if (!_configs.TryGetValue((package, configName), out var config)) - return false; - if (config is not IConfigList entry) - return false; - value = entry.Options; - return value is not null && value.Count > 0; - } - - private void SetConfigValue(string packageName, string configName, T value) where T : IEquatable - { - using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); - if (ModUtils.Threading.GetBool(ref _isDisposed)) - return; - if (!_packageNameMap.TryGetValue(packageName, out var package)) - return; - if (!_configs.TryGetValue((package, configName), out var config)) - return; - if (config is not IConfigEntry entry) - return; - entry.TrySetValue(value); + throw new NotImplementedException(); } public void SetConfigBool(string packageName, string configName, bool value) { - SetConfigValue(packageName, configName, value); + throw new NotImplementedException(); } public void SetConfigInt(string packageName, string configName, int value) { - SetConfigValue(packageName, configName, value); + throw new NotImplementedException(); } public void SetConfigFloat(string packageName, string configName, float value) { - SetConfigValue(packageName, configName, value); + throw new NotImplementedException(); } public void SetConfigNumber(string packageName, string configName, double value) { - SetConfigValue(packageName, configName, value); + throw new NotImplementedException(); } public void SetConfigString(string packageName, string configName, string value) { - SetConfigValue(packageName, configName, value); + throw new NotImplementedException(); } public void SetConfigVector2(string packageName, string configName, Vector2 value) { - SetConfigValue(packageName, configName, value); + throw new NotImplementedException(); } public void SetConfigVector3(string packageName, string configName, Vector3 value) { - SetConfigValue(packageName, configName, value); + throw new NotImplementedException(); } public void SetConfigColor(string packageName, string configName, Color value) { - SetConfigValue(packageName, configName, value); + throw new NotImplementedException(); } public void SetConfigList(string packageName, string configName, string value) { - using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); - if (ModUtils.Threading.GetBool(ref _isDisposed)) - return; - if (!_packageNameMap.TryGetValue(packageName, out var package)) - return; - if (!_configs.TryGetValue((package, configName), out var config)) - return; - if (config is not IConfigList entry) - return; - entry.TrySetValue(value); + throw new NotImplementedException(); } public bool TryApplyProfileSettings(string packageName, string profileName) { - - if (ModUtils.Threading.GetBool(ref _isDisposed)) - return false; - if (packageName.IsNullOrWhiteSpace() || profileName.IsNullOrWhiteSpace()) - return false; - ContentPackage package = null; - using (var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult()) - { - if (!_packageNameMap.TryGetValue(packageName, out package) || package == null) - return false; - } - // exit semaphore before invocation. Note: Race condition, may require copy implementation. - return this.ApplyProfileSettings(package, profileName).IsSuccess; + throw new NotImplementedException(); } - #endregion - - public void RegisterTypeInitializer(Func> initializer, bool replaceIfExists = false) - where TData : IEquatable where TConfig : IConfigBase + public void RegisterTypeInitializer(Func> initializer, bool replaceIfExists = false) where TData : IEquatable where TConfig : IConfigBase { - using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); - _base.CheckDisposed(); - - Type dataType = typeof(TData); - if (_configTypeInitializers.ContainsKey(dataType) && !replaceIfExists) - return; - _configTypeInitializers[dataType] = (info => - { - var res = initializer(info); - if (res.IsFailed) - return FluentResults.Result.Fail($"Failed to initialize config type {dataType.Name}").WithErrors(res.Errors); - return res.Value; - }); + throw new NotImplementedException(); } - private void AddConfigInstance((ContentPackage Package, string ConfigName) key, IConfigBase instance) - { - _configs[key] = instance; - if (!_packageNameMap.ContainsKey(key.Package.Name)) - _packageNameMap[key.Package.Name] = key.Package; - if (!_packageConfigReverseLookup.TryGetValue(key.Package, out var list)) - { - list = new ConcurrentBag<(ContentPackage Package, string ConfigName)>(); - _packageConfigReverseLookup[key.Package] = list; - } - list.Add(key); - // save hook - instance.OnValueChanged += this.SaveConfigEvent; - _eventService.PublishEvent(sub => sub.OnConfigCreated(instance)); - } - - private void AddProfileInstance((ContentPackage Package, string ProfileName) key, IConfigProfileInfo profile) - { - _configProfiles[key] = profile.ProfileValues.ToImmutableArray(); - if (!_packageNameMap.ContainsKey(key.Package.Name)) - _packageNameMap[key.Package.Name] = key.Package; - if (!_packageProfilesReverseLookup.TryGetValue(key.Package, out var list)) - { - list = new ConcurrentBag<(ContentPackage Package, string ProfileName)>(); - _packageProfilesReverseLookup[key.Package] = list; - } - list.Add(key); - } - public async Task LoadConfigsAsync(ImmutableArray configResources) { - using var lck = await _disposeOpsLock.AcquireReaderLock(); - _base.CheckDisposed(); - - if (configResources.IsDefaultOrEmpty) - return FluentResults.Result.Fail($"{nameof(LoadConfigsAsync)}: Array is empty."); - - var results = await _configResourceParser.TryParseResourcesAsync(configResources); - var ret = new FluentResults.Result(); - - foreach (var result in results) - { - if (result.Errors.Any()) - ret.Errors.AddRange(result.Errors); - if (result.IsFailed || result.Value is not { Count: > 0 } res) - continue; - - foreach (var configInfo in res) - { - if (_configs.ContainsKey((configInfo.OwnerPackage, configInfo.InternalName))) - { - ret.Errors.Add(new Error($"{nameof(LoadConfigsAsync)}: Config already exists for the compound key {configInfo.OwnerPackage.Name} | {configInfo.InternalName}")); - continue; - } - - if (!_configTypeInitializers.TryGetValue(configInfo.DataType, out var initializer)) - { - ret.Errors.Add(new Error($"{nameof(LoadConfigsAsync)} No type initializer for {configInfo.DataType}")); - continue; - } - - - - var cfg = initializer(configInfo); - if (cfg.Errors.Any()) - ret.Errors.AddRange(cfg.Errors); - if (cfg.IsFailed || cfg.Value is not {} val) - continue; - - AddConfigInstance((configInfo.OwnerPackage, configInfo.InternalName), val); - } - } - - return ret; + throw new NotImplementedException(); } public async Task LoadConfigsProfilesAsync(ImmutableArray configProfileResources) { - using var lck = await _disposeOpsLock.AcquireReaderLock(); - _base.CheckDisposed(); - - if (configProfileResources.IsDefaultOrEmpty) - return FluentResults.Result.Fail($"{nameof(LoadConfigsProfilesAsync)}: Array is empty."); - - var results = await _configProfileResourceParser.TryParseResourcesAsync(configProfileResources); - var ret = new FluentResults.Result(); - - foreach (var result in results) - { - if (result.Errors.Any()) - ret.Errors.AddRange(result.Errors); - if (result.IsFailed || result.Value is not { Count: > 0 } res) - continue; - - foreach (var profileInfo in res) - { - if (_configProfiles.ContainsKey((profileInfo.OwnerPackage, profileInfo.InternalName))) - { - ret.Errors.Add(new Error($"{nameof(LoadConfigsProfilesAsync)}: Config already exists for the compound key {profileInfo.OwnerPackage.Name} | {profileInfo.InternalName}")); - continue; - } - - AddProfileInstance((profileInfo.OwnerPackage, profileInfo.InternalName), profileInfo); - } - } - - return ret; + throw new NotImplementedException(); } - public FluentResults.Result AddConfig(IConfigInfo configInfo) where TConfig : IConfigBase + public Result AddConfig(IConfigInfo configInfo) where TConfig : IConfigBase { - using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); - _base.CheckDisposed(); - - if (configInfo is null) - return FluentResults.Result.Fail($"{nameof(AddConfig)}: Config is null."); - - if (!_configTypeInitializers.TryGetValue(configInfo.DataType, out var initializer)) - return FluentResults.Result.Fail($"{nameof(AddConfig)}: No type initializer for {configInfo.DataType}"); - - var errList = new List(); - - try - { - var cfg = initializer(configInfo); - if (cfg.Errors.Any()) - errList.AddRange(cfg.Errors); - if (cfg.IsFailed || cfg.Value is null) - return FluentResults.Result.Fail($"Failed to initialize {configInfo.DataType}").WithErrors(errList); - AddConfigInstance((configInfo.OwnerPackage, configInfo.InternalName), cfg.Value); - return (TConfig)cfg.Value; - } - catch(Exception ex) - { - return FluentResults.Result.Fail($"Failed to initialize {configInfo.DataType}").WithError(new ExceptionalError(ex)); - } + throw new NotImplementedException(); } public FluentResults.Result ApplyProfileSettings(ContentPackage package, string profileName) { - using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); - _base.CheckDisposed(); - - if (package == null || string.IsNullOrEmpty(profileName)) - return FluentResults.Result.Fail($"{nameof(ApplyProfileSettings)}: ContentPackage and/or name were null or empty."); - - if (!_configProfiles.TryGetValue((package, profileName), out var list)) - return FluentResults.Result.Fail($"No profiles found for package {package.Name} with name {profileName}"); - - if (list.IsDefaultOrEmpty) - return FluentResults.Result.Fail($"{nameof(ApplyProfileSettings)}: No stored values for profile {profileName}."); - - var errList = new List(); - - foreach (var profileVal in list) - { - if (!_configs.TryGetValue((package, profileVal.ConfigName), out var val)) - continue; - - if (!val.TrySetValue(profileVal.Value)) - errList.Add(new Error($"Failed to apply value from profile named {profileName} to {val.InternalName}")); - // continue - } - - return FluentResults.Result.Ok().WithErrors(errList); + throw new NotImplementedException(); } public FluentResults.Result DisposePackageData(ContentPackage package) { - // stop regular ops during deletion ops - using var lck = _disposeOpsLock.AcquireWriterLock().GetAwaiter().GetResult(); - _base.CheckDisposed(); - - if (package is null) - return FluentResults.Result.Fail($"{nameof(DisposePackageData)}: Package was null."); + throw new NotImplementedException(); + } - var errList = new List(); - - if (_packageConfigReverseLookup.Remove(package, out var cfgKeys)) - { - if (cfgKeys.Any()) - { - foreach (var key in cfgKeys) - { - try - { - _configs.Remove(key, out var cfg); - cfg?.Dispose(); - } - catch (Exception e) - { - errList.Add(new ExceptionalError(e)); - } - } - } - } - - if (_packageProfilesReverseLookup.Remove(package, out var profileKeys)) - { - if (profileKeys.Any()) - { - foreach (var key in profileKeys) - { - _configProfiles.Remove(key, out _); - } - } - } - - _packageNameMap.Remove(package.Name, out _); - - return FluentResults.Result.Ok().WithErrors(errList); + public FluentResults.Result DisposeAllPackageData() + { + throw new NotImplementedException(); } public Result> GetConfigsForPackage(ContentPackage package) { - using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); - _base.CheckDisposed(); - - if (!_packageConfigReverseLookup.TryGetValue(package, out var keys) || keys.IsEmpty) - return FluentResults.Result.Fail($"No configs found for package {package.Name}"); - - return _configs.Where(kvp => keys.Contains(kvp.Key)).ToFrozenDictionary(kvp => kvp.Key, kvp => kvp.Value); + throw new NotImplementedException(); } public Result Value)>>> GetProfilesForPackage(ContentPackage package) { - using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); - _base.CheckDisposed(); - - if (!_packageProfilesReverseLookup.TryGetValue(package, out var keys) || keys.IsEmpty) - return FluentResults.Result.Fail($"No profiles found for package {package.Name}"); - - return _configProfiles.Where(kvp => keys.Contains(kvp.Key)).ToFrozenDictionary(kvp => kvp.Key, kvp => kvp.Value); + throw new NotImplementedException(); } public IReadOnlyDictionary<(ContentPackage Package, string Name), IConfigBase> GetAllConfigs() { - using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); - _base.CheckDisposed(); - - return _configs.ToFrozenDictionary(kvp => kvp.Key, kvp => kvp.Value); + throw new NotImplementedException(); } public bool TryGetConfig(ContentPackage package, string name, out T config) where T : IConfigBase { - using var lck = _disposeOpsLock.AcquireReaderLock().GetAwaiter().GetResult(); - _base.CheckDisposed(); - - config = default; - if (!_configs.TryGetValue((package, name), out var value)) - return false; - try - { - config = (T)value; - return true; - } - catch - { - return false; - } + throw new NotImplementedException(); } public async Task SaveAllConfigs() { - using var lck = await _disposeOpsLock.AcquireReaderLock(); - _base.CheckDisposed(); - if (_configs.IsEmpty) - return FluentResults.Result.Ok(); - var toSave = _configs.Where(kvp => kvp.Value is not null).Select(kvp => kvp.Value) - .ToImmutableArray(); - var errList = ImmutableArray.CreateBuilder(); - foreach (var config in toSave) - { - var res = await SaveConfigInternal(config); - if (res.Errors.Any()) - errList.AddRange(res.Errors); - } - return FluentResults.Result.Ok().WithErrors(errList.MoveToImmutable()); + throw new NotImplementedException(); } public async Task SaveConfigsForPackage(ContentPackage package) { - if (package is null) - return FluentResults.Result.Fail($"{nameof(SaveConfigsForPackage)}: Package was null."); - using var lck = await _disposeOpsLock.AcquireReaderLock(); - _base.CheckDisposed(); - if (!_packageConfigReverseLookup.TryGetValue(package, out var keys) || keys.IsEmpty) - return FluentResults.Result.Fail($"No configs found for package {package.Name}"); - ConcurrentQueue toSave = new(); - foreach (var key in keys) - { - if (_configs.TryGetValue(key, out var config)) - toSave.Enqueue(config); - } - if (toSave.IsEmpty) - return FluentResults.Result.Fail($"No configs found for package {package.Name}"); - var errList = ImmutableArray.CreateBuilder(); - while (toSave.TryDequeue(out var config)) - { - var res = await SaveConfigInternal(config); - if (res.Errors.Any()) - errList.AddRange(res.Errors); - } - return FluentResults.Result.Ok().WithErrors(errList.MoveToImmutable()); + throw new NotImplementedException(); } public async Task SaveConfig((ContentPackage Package, string ConfigName) config) { - if (config.Package is null || config.ConfigName.IsNullOrWhiteSpace()) - return FluentResults.Result.Fail($"{nameof(SaveConfig)}: Config properties were null or empty."); - using var lck = await _disposeOpsLock.AcquireReaderLock(); - _base.CheckDisposed(); - if (!_configs.TryGetValue(config, out var instance)) - return FluentResults.Result.Fail($"{nameof(SaveConfig)}: No config found for package {config.Package.Name} and name {config.ConfigName}"); - return await SaveConfigInternal(instance); + throw new NotImplementedException(); } - - private void SaveConfigEvent(IConfigBase instance) - { - using var lck = _disposeOpsLock.AcquireWriterLock().GetAwaiter().GetResult(); - _base.CheckDisposed(); - SaveConfigInternal(instance).GetAwaiter().GetResult(); - } - - private async Task SaveConfigInternal(IConfigBase instance) - { - var localStorePath = Path.Combine("Config", SanitizedFileName($"{instance.OwnerPackage.Name}.xml)")); - // Locking and checks must be handled by the caller. - var val = instance.GetSerializableValue(); - var docRes = await _storageService.Value.LoadLocalXmlAsync(instance.OwnerPackage, localStorePath); - XDocument doc; - XElement cfgElement; - XElement valueElement; - - // structure is - /* - * - * <[instance.InternalName]> - * - * <--Contents Here-> - * - * - * - */ - - if (docRes.IsFailed || docRes.Value is null) - { - doc = new XDocument( - new XElement("Config", new XAttribute("ContentPackage", instance.OwnerPackage.Name), - cfgElement = new XElement(instance.InternalName, valueElement = new XElement("Value")))); - } - else - { - doc = docRes.Value; - var e1 = doc.GetChildElement("Config"); - if (e1 is null) - { - e1 = new XElement("Config"); - doc.Add(e1); - } - - cfgElement = e1.GetChildElement(instance.InternalName); - if (cfgElement is null) - { - cfgElement = new XElement(instance.InternalName); - e1.Add(cfgElement); - } - - valueElement = cfgElement.GetChildElement("Value"); - if (valueElement is null) - { - valueElement = new XElement("Value"); - cfgElement.Add(valueElement); - } - } - - valueElement.Remove(); // remove from cfg - - // get potential updated element - var updatedElement = val.Match(str => - { - valueElement.RemoveAll(); - valueElement.Value = str; - return valueElement; - }, element => - { - valueElement.RemoveAll(); - valueElement.Add(element); - return valueElement; - }); - - // (re) add updated element. - cfgElement.Add(updatedElement); - - return await _storageService.Value.SaveLocalXmlAsync(instance.OwnerPackage, localStorePath, doc); - } - - private static readonly Regex RemoveInvalidChars = new Regex($"[{Regex.Escape(new string(System.IO.Path.GetInvalidFileNameChars()))}]", - RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant); - - private string SanitizedFileName(string fileName, string replacement = "_") - { - return RemoveInvalidChars.Replace(fileName, replacement); - } - - } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs index 5d8d020e7..17b2c13c1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs @@ -116,8 +116,10 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _script.Globals["CLIENT"] = LuaCsSetup.IsClient; } - public FluentResults.Result ExecuteLoadedScripts() + public FluentResults.Result ExecuteLoadedScripts(ImmutableArray executionOrder) { + throw new NotImplementedException($"Need to implement {nameof(executionOrder)} logic."); + if (_isRunning) { return FluentResults.Result.Fail("Tried to execute Lua scripts without unloading first."); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index 802b2dc07..f8f92afaf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -1,9 +1,12 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Threading.Tasks; using Barotrauma.LuaCs.Data; using FluentResults; +using Microsoft.Toolkit.Diagnostics; namespace Barotrauma.LuaCs.Services; @@ -12,20 +15,37 @@ public sealed class PackageManagementService : IPackageManagementService // svc private ILoggerService _logger; private IModConfigService _modConfigService; + private IConfigService _configService; private ILuaScriptManagementService _luaScriptManagementService; private IPluginManagementService _pluginManagementService; + private IPackageManagementServiceConfig _runConfig; // state private readonly ConcurrentDictionary _loadedPackages = new(); private readonly ConcurrentDictionary _runningPackages = new(); // control + /// + /// Service Disposal Lock. + /// private readonly AsyncReaderWriterLock _operationsLock = new(); + /// + /// Execution of packages lock. + ///
Read: Package loading/unloading (Multi-operation mode). + ///
Write: Package execution (exclusive mode). + ///
+ private readonly AsyncReaderWriterLock _executionLock = new(); - public PackageManagementService(ILoggerService logger, IModConfigService modConfigService, ILuaScriptManagementService luaScriptManagementService, IPluginManagementService pluginManagementService) + public PackageManagementService(ILoggerService logger, + IModConfigService modConfigService, + ILuaScriptManagementService luaScriptManagementService, + IPluginManagementService pluginManagementService, + IConfigService configService, IPackageManagementServiceConfig runConfig) { _logger = logger; _modConfigService = modConfigService; _luaScriptManagementService = luaScriptManagementService; _pluginManagementService = pluginManagementService; + _configService = configService; + _runConfig = runConfig; } public void Dispose() @@ -34,7 +54,7 @@ public sealed class PackageManagementService : IPackageManagementService if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) return; - _logger.LogMessage($"{nameof(PackageManagementService)} is disposing"); + _logger.LogMessage($"{nameof(PackageManagementService)} is disposing."); _luaScriptManagementService.Dispose(); _pluginManagementService.Dispose(); _modConfigService.Dispose(); @@ -61,79 +81,215 @@ public sealed class PackageManagementService : IPackageManagementService if (IsDisposed) return FluentResults.Result.Fail($"{nameof(PackageManagementService)}failed to reset. Has already been disposed."); - var operationResult = new FluentResults.Result(); - CombineResultErrors(operationResult, UnsafeStopRunningPackagesInternal()); - CombineResultErrors(operationResult, UnsafeUnloadAllPackagesInternal()); - return operationResult; - - void CombineResultErrors(FluentResults.Result result, - ImmutableArray<(ContentPackage Package, FluentResults.Result OperationResult)> packRes) + try { - if (packRes.IsDefaultOrEmpty) - return; - - foreach (var r in packRes) - { - if (r.OperationResult.IsSuccess) - continue; - _logger.LogResults(r.OperationResult); - result.WithErrors(r.OperationResult.Errors); - } + var operationResult = new FluentResults.Result(); + operationResult.WithReasons(_configService.Reset().Reasons); + operationResult.WithReasons(_luaScriptManagementService.Reset().Reasons); + operationResult.WithReasons(_pluginManagementService.Reset().Reasons); + _runningPackages.Clear(); + _loadedPackages.Clear(); + return operationResult; + } + catch (Exception e) + { + return FluentResults.Result.Fail(new ExceptionalError(e)); } } public FluentResults.Result LoadPackageInfo(ContentPackage package) { - throw new System.NotImplementedException(); + Guard.IsNotNull(package, nameof(package)); + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + using var executeLock = _executionLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + + IService.CheckDisposed(this); + if (_loadedPackages.TryGetValue(package, out var result)) + { + _logger.LogWarning($"{nameof(LoadPackageInfo)}: Tried to load already-loaded package {package.Name}."); + return FluentResults.Result.Ok(); + } + + var pkgCfgInfo = _modConfigService.CreateConfigAsync(package).ConfigureAwait(false).GetAwaiter().GetResult(); + if (pkgCfgInfo.IsFailed) + { + _logger.LogResults(pkgCfgInfo.ToResult()); + return pkgCfgInfo.ToResult(); + } + return UnsafeAddPackageInternal(package, pkgCfgInfo.Value); } - public ImmutableArray<(ContentPackage Package, FluentResults.Result LoadSuccessResult)> LoadPackagesInfo(IReadOnlyCollection packages) + public FluentResults.Result LoadPackagesInfo(ImmutableArray packages) { - throw new System.NotImplementedException(); + if (packages.IsDefaultOrEmpty) + ThrowHelper.ThrowArgumentException($"{nameof(LoadPackagesInfo)}: packages list is empty."); + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + using var executeLock = _executionLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + + IService.CheckDisposed(this); + var result = new FluentResults.Result(); + var pkgConfigs = _modConfigService.CreateConfigsAsync([..packages]).ConfigureAwait(false).GetAwaiter().GetResult(); + foreach (var pkgConfig in pkgConfigs) + { + result.WithReasons(pkgConfig.Config.Reasons); + if (pkgConfig.Config.IsSuccess) + result.WithReasons(UnsafeAddPackageInternal(pkgConfig.Source, pkgConfig.Config.Value).Reasons); + } + + return result; } - private FluentResults.Result UnsafeLoadPackageInfoInternal(ContentPackage package) + private FluentResults.Result UnsafeAddPackageInternal(ContentPackage package, IModConfigInfo config) { - throw new System.NotImplementedException(); + if (_loadedPackages.TryGetValue(package, out var result)) + { + _logger.LogWarning($"Tried to load already-loaded package {package.Name}."); + return FluentResults.Result.Ok(); + } + + _loadedPackages[package] = config; + var res = new FluentResults.Result(); + res.WithReasons(_luaScriptManagementService.LoadScriptResourcesAsync(config.LuaScripts).ConfigureAwait(false).GetAwaiter().GetResult().Reasons); + return res; } - public ImmutableArray<(ContentPackage Package, FluentResults.Result ExecutionResult)> ExecuteLoadedPackages() + public FluentResults.Result ExecuteLoadedPackages(ImmutableArray executionOrder) { - throw new System.NotImplementedException(); - } + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + using var executeLock = _executionLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); - private ImmutableArray<(ContentPackage Package, FluentResults.Result StopExectionResult)> UnsafeStopRunningPackagesInternal() - { - throw new System.NotImplementedException(); + if (executionOrder.IsDefaultOrEmpty) + return FluentResults.Result.Fail($"{nameof(ExecuteLoadedPackages)}: No packages in the execution order list."); + + if (!_runningPackages.IsEmpty) + { + return FluentResults.Result.Fail( + $"{nameof(ExecuteLoadedPackages)}: There are already packages running! List: { + _runningPackages.Aggregate(string.Empty, (acc, kvp) => "-" + kvp + "\n" + kvp.Key.Name)}"); + } + + if (_loadedPackages.IsEmpty) + return FluentResults.Result.Fail($"{nameof(ExecuteLoadedPackages)}: No packages loaded. Nothing to run!)"); + + var result = new FluentResults.Result(); + + // get loading order. Note: packages not in the execution order list will load first. + var loadingOrderedPackages = _loadedPackages.OrderBy(pkg => executionOrder.IndexOf(pkg.Key)) + .ToImmutableArray(); + + //mod settings + var settings = loadingOrderedPackages + .SelectMany(pkg => pkg.Value.Configs.OrderBy(scr => scr.LoadPriority)) + .ToImmutableArray(); + if (!settings.IsDefaultOrEmpty) + { + result.WithReasons(_configService.LoadConfigsAsync(settings).ConfigureAwait(false).GetAwaiter() + .GetResult().Reasons); + result.WithReasons(_configService.LoadConfigsProfilesAsync(settings).ConfigureAwait(false) + .GetAwaiter().GetResult().Reasons); + } + + //lua scripts + var luaScripts = loadingOrderedPackages + .SelectMany(pkg => pkg.Value.LuaScripts.OrderBy(scr => scr.LoadPriority)) + .ToImmutableArray(); + if (!luaScripts.IsDefaultOrEmpty) + result.WithReasons(_luaScriptManagementService.ExecuteLoadedScripts(luaScripts).Reasons); + + if (_runConfig.IsCsEnabled) + { + var plugins = + loadingOrderedPackages.SelectMany(pkg => pkg.Value.Assemblies.OrderBy(scr => scr.LoadPriority)) + .ToImmutableArray(); + if (!plugins.IsDefaultOrEmpty) + result.WithReasons(_pluginManagementService.LoadAssemblyResources(plugins).Reasons); + } + + return result; } - public ImmutableArray<(ContentPackage Package, FluentResults.Result StopExecutionResult)> StopRunningPackages() + public FluentResults.Result StopRunningPackages() { - throw new System.NotImplementedException(); - } - - private FluentResults.Result UnsafeUnloadPackageInternal(ContentPackage package) - { - throw new System.NotImplementedException(); - } - - private ImmutableArray<(ContentPackage Package, FluentResults.Result UnloadSuccessResult)> UnsafeUnloadAllPackagesInternal() - { - throw new System.NotImplementedException(); + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + using var executeLock = _executionLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + if (_loadedPackages.IsEmpty || _runningPackages.IsEmpty) + { + _logger.LogWarning($"{nameof(StopRunningPackages)}: No packages are currently executing."); + return FluentResults.Result.Ok(); + } + + var res = new FluentResults.Result(); + res.WithReasons(_luaScriptManagementService.UnloadActiveScripts().Reasons); + res.WithReasons(_pluginManagementService.UnloadManagedAssemblies().Reasons); + _runningPackages.Clear(); + return res; } public FluentResults.Result UnloadPackage(ContentPackage package) { - throw new System.NotImplementedException(); + Guard.IsNotNull(package, nameof(package)); + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + using var executeLock = _executionLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + if (!_loadedPackages.ContainsKey(package)) + return FluentResults.Result.Fail($"{nameof(UnloadPackage)}: The package is not loaded."); + if (!_runningPackages.IsEmpty) + return FluentResults.Result.Fail($"{nameof(UnloadPackage)}: Packages are currently executing."); + var result = new FluentResults.Result(); + result.WithReasons(_luaScriptManagementService.DisposePackageResources(package).Reasons); + result.WithReasons(_configService.DisposePackageData(package).Reasons); + _loadedPackages.TryRemove(package, out _); + return result; } - public ImmutableArray<(ContentPackage Package, FluentResults.Result UnloadSuccessResult)> UnloadPackages(IReadOnlyCollection packages) + public FluentResults.Result UnloadPackages(ImmutableArray packages) { - throw new System.NotImplementedException(); + if (packages.IsDefaultOrEmpty) + return FluentResults.Result.Fail($"{nameof(UnloadPackages)}: Package list is empty."); + + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + using var executeLock = _executionLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + var result = new FluentResults.Result(); + foreach (var package in packages) + result.WithReasons(UnloadPackage(package).Reasons); + return result; } - public ImmutableArray<(ContentPackage Package, FluentResults.Result UnloadSuccessResult)> UnloadAllPackages() + public FluentResults.Result UnloadAllPackages() { - throw new System.NotImplementedException(); + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + using var executeLock = _executionLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + if (_loadedPackages.IsEmpty) + return FluentResults.Result.Ok(); + if (!_runningPackages.IsEmpty) + return FluentResults.Result.Fail($"{nameof(UnloadAllPackages)}: Packages are currently executing."); + var result = new FluentResults.Result(); + result.WithReasons(_luaScriptManagementService.DisposeAllPackageResources().Reasons); + result.WithReasons(_configService.DisposeAllPackageData().Reasons); + _loadedPackages.Clear(); + return result; + } + + public ImmutableArray GetAllLoadedPackages() + { + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + return [.._loadedPackages.Keys]; + } + + public bool IsPackageRunning(ContentPackage package) + { + Guard.IsNotNull(package, nameof(package)); + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + return _runningPackages.ContainsKey(package); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 2138961e7..667447b3f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -128,7 +128,7 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage throw new NotImplementedException(); } - public IReadOnlyList> ActivateTypeInstances(ImmutableArray types, bool serviceInjection = true, + public ImmutableArray> ActivateTypeInstances(ImmutableArray types, bool serviceInjection = true, bool hostInstanceReference = false) where T : IDisposable { throw new NotImplementedException(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs index f9f08d689..38498830b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs @@ -35,6 +35,7 @@ public partial interface IConfigService : IReusableService, ILuaConfigService // Utility FluentResults.Result ApplyProfileSettings(ContentPackage package, string profileName); FluentResults.Result DisposePackageData(ContentPackage package); + FluentResults.Result DisposeAllPackageData(); FluentResults.Result> GetConfigsForPackage(ContentPackage package); FluentResults.Result Value)>>> GetProfilesForPackage(ContentPackage package); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs index 74be9298f..4ca8ea401 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs @@ -27,11 +27,12 @@ public interface ILuaScriptManagementService : IReusableService Task LoadScriptResourcesAsync(ImmutableArray resourcesInfo); /// - /// + /// Executes already loaded into memory scripts data, in the supplied order. /// + /// /// // [Required] - FluentResults.Result ExecuteLoadedScripts(); + FluentResults.Result ExecuteLoadedScripts(ImmutableArray executionOrder); /// /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs index 3f0614154..3979ce69d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs @@ -12,10 +12,12 @@ namespace Barotrauma.LuaCs.Services; public interface IPackageManagementService : IReusableService { public FluentResults.Result LoadPackageInfo(ContentPackage package); - public ImmutableArray<(ContentPackage Package, FluentResults.Result LoadSuccessResult)> LoadPackagesInfo(IReadOnlyCollection packages); - public ImmutableArray<(ContentPackage Package, FluentResults.Result ExecutionResult)> ExecuteLoadedPackages(); - public ImmutableArray<(ContentPackage Package, FluentResults.Result StopExecutionResult)> StopRunningPackages(); - public FluentResults.Result UnloadPackage(ContentPackage package); - public ImmutableArray<(ContentPackage Package, FluentResults.Result UnloadSuccessResult)> UnloadPackages(IReadOnlyCollection packages); - public ImmutableArray<(ContentPackage Package, FluentResults.Result UnloadSuccessResult)> UnloadAllPackages(); + public FluentResults.Result LoadPackagesInfo(ImmutableArray packages); + public FluentResults.Result ExecuteLoadedPackages(ImmutableArray executionOrder); + public FluentResults.Result StopRunningPackages(); + public FluentResults.Result UnloadPackage(ContentPackage package); + public FluentResults.Result UnloadPackages(ImmutableArray packages); + public FluentResults.Result UnloadAllPackages(); + public ImmutableArray GetAllLoadedPackages(); + public bool IsPackageRunning(ContentPackage package); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs index dbb87bdf9..57648ccd1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs @@ -47,12 +47,13 @@ public interface IPluginManagementService : IReusableService /// /// /// - IReadOnlyList> ActivateTypeInstances(ImmutableArray types, bool serviceInjection = true, + ImmutableArray> ActivateTypeInstances(ImmutableArray types, bool serviceInjection = true, bool hostInstanceReference = false) where T : IDisposable; + /// /// Unloads all managed , , and s. /// - /// Success of the operation.
Note: does not guarantee .NET runtime assembly unloading success.
+ /// Success of the operation.
Note: does not guarantee .NET runtime assembly unloading success.
FluentResults.Result UnloadManagedAssemblies(); } From 055a5089019f9f080880107f36ae37ce96bc3f2e Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 14 Jan 2026 08:50:09 -0500 Subject: [PATCH 038/288] - Deleted unused service IPackageListRetrievalService.cs - Added caching function to LuaScriptLoader.cs - Added sample async code to LuaScriptManagementService.cs - Removed most of the State functions in LuaCsSetup.cs (requires rewrite). - Fixed CsEnabled check. - Moved IsRunningWorkshop check to client-only project. --- .../ClientSource/LuaCs/LuaCsSetup.cs | 13 +- .../ServerSource/LuaCs/LuaCsSetup.cs | 10 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 244 +----------------- .../Services/LuaScriptManagementService.cs | 28 +- .../Services/PackageListRetrievalService.cs | 30 --- .../Services/PackageManagementService.cs | 34 ++- .../LuaCs/Services/Safe/ILuaScriptLoader.cs | 7 +- .../LuaCs/Services/Safe/LuaScriptLoader.cs | 12 + .../IPackageListRetrievalService.cs | 9 - .../_Interfaces/IPackageManagementService.cs | 2 + 10 files changed, 94 insertions(+), 295 deletions(-) delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageListRetrievalService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageListRetrievalService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index 08f5522c3..a60b0c8c5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -25,16 +25,7 @@ namespace Barotrauma public void CheckCsEnabled() { - throw new NotImplementedException($"Replace PMS.Assemblies with checks on ContentPackageManager.EnabledPackages"); - - /*var csharpMods = PackageManagementService.Assemblies - .GroupBy(ass => ass.OwnerPackage) - .Select(grp => grp.Key) - .Where(ContentPackageManager.EnabledPackages.All.Contains) - .ToImmutableArray(); - - if (!csharpMods.Any()) - return; + var csharpMods = PackageManagementService.GetLoadedAssemblyPackages(); StringBuilder sb = new StringBuilder(); @@ -67,7 +58,7 @@ namespace Barotrauma { this.IsCsEnabled.TrySetValue(false); return true; - };*/ + }; } /// diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs index 9eaca6c7d..ae63d3cc8 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs @@ -1,4 +1,6 @@ -using Barotrauma.Networking; +using System; +using System.IO; +using Barotrauma.Networking; namespace Barotrauma; @@ -19,4 +21,10 @@ partial class LuaCsSetup private partial bool ShouldRunCs() => IsCsEnabled.Value || (GetPackage(new SteamWorkshopId(CsForBarotraumaSteamId.Value), false, false) is { } && GameMain.Server.ServerPeer is LidgrenServerPeer); + + // ReSharper disable once InconsistentNaming + private static readonly Lazy isRunningInsideWorkshop = new Lazy(() => + Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly()!.Location) != + Directory.GetCurrentDirectory()); + public static bool IsRunningInsideWorkshop => isRunningInsideWorkshop.Value; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 0ea4484cf..cf3d11c58 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -16,6 +16,7 @@ using Barotrauma.LuaCs.Services.Safe; using Barotrauma.Networking; using FluentResults; using ImpromptuInterface; +using Microsoft.Toolkit.Diagnostics; namespace Barotrauma { @@ -39,7 +40,6 @@ namespace Barotrauma // == sub processes void RegisterServices() { - _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); @@ -194,18 +194,7 @@ namespace Barotrauma public ILuaCsHook Hook => this.EventService; #endregion - - public static bool IsRunningInsideWorkshop - { - get - { -#if SERVER - return Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) != Directory.GetCurrentDirectory(); -#else - return false; // unnecessary but just keeps things clear that this is NOT for client stuff -#endif - } - } + private partial bool ShouldRunCs(); @@ -329,6 +318,8 @@ namespace Barotrauma private void UpdateLoadedPackagesList() { + throw new NotImplementedException($"Rewrite the loading state system."); + var newPackSet = ContentPackageManager.AllPackages .Union(ContentPackageManager.EnabledPackages.All) .ToHashSet(); @@ -340,10 +331,10 @@ namespace Barotrauma foreach (var package in toRemove) _toUnload.Enqueue(package); - ProcessPackagesListDifferences(); + //ProcessPackagesListDifferences(); } - void ProcessPackagesListDifferences() + /*void ProcessPackagesListDifferences() { if (IsCodeRunning) return; @@ -379,13 +370,13 @@ namespace Barotrauma { LoadStaticAssetsAsync(ls).GetAwaiter().GetResult(); } - } + }*/ void SetRunState(RunState runState) { if (CurrentRunState == runState) return; - if (runState > CurrentRunState) + /*if (runState > CurrentRunState) { if (CurrentRunState <= RunState.Parsed) { @@ -444,58 +435,7 @@ namespace Barotrauma } // we should be unloaded completely now | RunState.Unloaded - } - } - - void LoadCurrentContentPackageInfos() - { - if (CurrentRunState >= RunState.Parsed) - return; - - // load core - var result1 = PackageManagementService.LoadPackageInfo(ContentPackageManager.VanillaCorePackage); - if (result1.IsFailed) - { - Logger.LogError($"Unable to load LuaCs CorePackage resources! Running in degraded mode."); - Logger.LogResults(result1); - } - - // load regular - var list = ContentPackageManager.RegularPackages - .Union(ContentPackageManager.EnabledPackages.All) - .ToImmutableList(); - - LoadContentPackagesInfos(list); - - if (CurrentRunState < RunState.Parsed) - _runState = RunState.Parsed; - } - - void LoadContentPackagesInfos(IReadOnlyList packages) - { - var result2 = PackageManagementService.LoadPackagesInfo([..packages]); - if (result2.IsFailed) - Logger.LogResults(result2); - } - - void LoadStaticAssets() - { - if (CurrentRunState < RunState.Parsed) - { - throw new InvalidOperationException($"{nameof(LoadStaticAssets)} cannot load assets in the '{CurrentRunState}' state."); - } - - if (CurrentRunState >= RunState.Configuration) - return; - - while (_toUnload.TryDequeue(out var cp)) - PackageManagementService.UnloadPackage(cp); - - LoadStaticAssetsAsync(PackageManagementService.GetAllLoadedPackages()).GetAwaiter().GetResult(); - LoadLuaCsConfig(); - - if (CurrentRunState < RunState.Configuration) - _runState = RunState.Configuration; + }*/ } void LoadLuaCsConfig() @@ -530,172 +470,6 @@ namespace Barotrauma ReloadPackagesOnLobbyStart = null; } - async Task LoadStaticAssetsAsync(IReadOnlyList packages) - { - throw new NotImplementedException(); - - /*var cfgRes = ImmutableArray.Empty; - var luaRes = ImmutableArray.Empty; - - var tasksBuilder = ImmutableArray.CreateBuilder(); - - //---- get resource infos - tasksBuilder.AddRange( - new Func(async () => - { - var res = await PackageManagementService.GetConfigsInfosAsync(packages); - if (res.IsSuccess) - cfgRes = res.Value.Configs; - if (res.Errors.Any()) - ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), - res.ToResult()); - })(), - new Func(async () => - { - var res = await PackageManagementService.GetLuaScriptsInfosAsync(packages); - if (res.IsSuccess) - luaRes = res.Value.LuaScripts; - if (res.Errors.Any()) - ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state), - res.ToResult()); - })()); - - await Task.WhenAll(tasksBuilder.MoveToImmutable()); - tasksBuilder.Clear(); - - //---- load resources - tasksBuilder.AddRange(new Func(async () => - { - var res = await ConfigService.LoadConfigsAsync(cfgRes); - if (res.Errors.Any()) - Logger.LogResults(res); - })(), - new Func(async () => - { - var res = await LuaScriptManagementService.LoadScriptResourcesAsync(luaRes); - if (res.Errors.Any()) - Logger.LogResults(res); - })()); - - await Task.WhenAll(tasksBuilder.MoveToImmutable());*/ - } - - void RunScripts() - { - /*if (!IsStaticAssetsLoaded) - { - throw new InvalidOperationException($"{nameof(RunScripts)} cannot load assets in the '{CurrentRunState}' state."); - } - - if (CurrentRunState >= RunState.Running) - return; - - if (ShouldRunCs()) - { - var asmRes = - PackageManagementService.GetAssembliesInfos(PackageManagementService - .GetAllLoadedPackages() - .Where(ContentPackageManager.EnabledPackages.All.Contains) - .ToList()); - if (asmRes.IsFailed) - { - Logger.LogError($"{nameof(RunScripts)}: Errors will retrieving assembly resources, cannot load scripts!"); - Logger.LogResults(asmRes.ToResult()); - return; - } - var res = PluginManagementService.LoadAssemblyResources(asmRes.Value.Assemblies); - if (res.IsFailed) - { - Logger.LogError($"{nameof(RunScripts)}: Failed to initialize scripts!"); - Logger.LogResults(res.ToResult()); - } - else - { - if (res.Errors.Any()) - Logger.LogResults(res.ToResult()); - if (PluginManagementService.GetImplementingTypes() is {IsSuccess: true} types) - { - var typeInst = PluginManagementService.ActivateTypeInstances(types.Value, true, true); - foreach (var loadRes in typeInst) - { - if (loadRes is { IsSuccess: true, Value: { Item2: { } pluginInstance } }) - { - EventService.Subscribe(pluginInstance); - EventService.Subscribe(pluginInstance); - EventService.Subscribe(pluginInstance); - } - else - { - Logger.LogResults(loadRes.ToResult()); - } - } - - EventService.PublishEvent(sub => sub.PreInitPatching()); - EventService.PublishEvent(sub => sub.Initialize()); - EventService.PublishEvent(sub => sub.OnLoadCompleted()); - } - } - } - - //lua - var luaRes = PackageManagementService.LuaScripts - .Select(ls => ls.OwnerPackage) - .Where(p => p is not null) - .Where(ContentPackageManager.EnabledPackages.All.Contains) - .ToImmutableArray(); - if (luaRes.IsDefaultOrEmpty) - { - Logger.LogError($"{nameof(RunScripts)}: Failed to get enabled lua script resources!"); - return; - } - - LuaScriptManagementService.ExecuteLoadedScripts(); - - if (CurrentRunState < RunState.Running) - _runState = RunState.Running;*/ - } - - void UnloadContentPackageInfos() - { - /*if (IsStaticAssetsLoaded) - { - throw new InvalidOperationException($"{nameof(UnloadStaticAssets)}: Cannot unload static assets when the current run state is {CurrentRunState}."); - } - - PackageManagementService.Reset(); - _toUnload.Clear();*/ - } - - void UnloadStaticAssets() - { - /*if (IsCodeRunning) - { - throw new InvalidOperationException($"{nameof(UnloadStaticAssets)}: Cannot unload static assets when the current run state is {CurrentRunState}."); - } - - PluginManagementService.Reset(); - LuaScriptManagementService.Reset(); - ConfigService.Reset(); - - if (CurrentRunState >= RunState.Configuration) - { - _runState = RunState.Parsed; - }*/ - } - - void StopScripts() - { - /*EventService.ClearAllSubscribers(); - LuaScriptManagementService.UnloadActiveScripts(); - PluginManagementService.UnloadManagedAssemblies(); - SubscribeToLuaCsEvents(); - - if (IsCodeRunning) - { - _runState = RunState.Configuration; - }*/ - } - } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs index 17b2c13c1..ac523f836 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs @@ -23,6 +23,7 @@ using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.Toolkit.Diagnostics; using static Barotrauma.GameSettings; namespace Barotrauma.LuaCs.Services; @@ -34,6 +35,8 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService [MemberNotNullWhen(true, nameof(_script))] public bool IsRunning => _isRunning; private List _resourcesInfo = new List(); + + private readonly AsyncReaderWriterLock _operationsLock = new (); private readonly ILuaScriptLoader _luaScriptLoader; private readonly ILuaScriptServicesConfig _luaScriptServicesConfig; @@ -67,13 +70,30 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService public bool IsDisposed { get; private set; } - public Task LoadScriptResourcesAsync(ImmutableArray resourcesInfo) + public async Task LoadScriptResourcesAsync(ImmutableArray resourcesInfo) { + // Do any exception checks you can before acquiring a lock to avoid needlessly holding up resources. + if (resourcesInfo.IsDefaultOrEmpty) + ThrowHelper.ThrowArgumentNullException($"{nameof(LoadScriptResourcesAsync)}: The parameter is empty!"); + + // Acquire a lock: + // Reader = Allow parallel operations (try to avoid nesting acquiring the lock when possible) + // Writer = Exclusive use (ie. executing scripts or Dispose()) + using var lck = await _operationsLock.AcquireWriterLock(); // IDisposable using with generate a try-finally and release for you. + IService.CheckDisposed(this); // Check disposed after you have the lock + + // If you use a ConcurrentDictionary instead of a List, it will handle threading issues for you. _resourcesInfo.AddRange(resourcesInfo.OrderBy(static r => r.LoadPriority)); - // TODO disk caching - - return Task.FromResult(FluentResults.Result.Ok()); + // Use the StorageService's caching function by just loading the file with caching turned on. + // Right now the LuaScriptLoader has this on by default. + var cacheRes = await _luaScriptLoader.CacheResourcesAsync(resourcesInfo); + + // Aggregate and return results to the caller to deal with. Optionally, log here if you want. + // Automatically converted to a Task when 'async' is in the method declaration. + if (cacheRes.IsFailed) + return cacheRes.ToResult(); + return new FluentResults.Result().WithReasons(cacheRes.Value.SelectMany(cr => cr.Item2.Reasons)); } private void SetupEnvironment() diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageListRetrievalService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageListRetrievalService.cs deleted file mode 100644 index 37e1c2bfc..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageListRetrievalService.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Generic; - -namespace Barotrauma.LuaCs.Services; - -public sealed class PackageListRetrievalService : IPackageListRetrievalService -{ - public void Dispose() - { - // stateless service - return; - } - - public void CheckDisposed() - { - // stateless service - return; - } - - public bool IsDisposed => false; - - public IEnumerable GetEnabledContentPackages() - { - return ContentPackageManager.EnabledPackages.All; - } - - public IEnumerable GetAllContentPackages() - { - return ContentPackageManager.AllPackages; - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index f8f92afaf..0a518281a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -141,16 +141,31 @@ public sealed class PackageManagementService : IPackageManagementService private FluentResults.Result UnsafeAddPackageInternal(ContentPackage package, IModConfigInfo config) { - if (_loadedPackages.TryGetValue(package, out var result)) + if (_loadedPackages.TryGetValue(package, out _)) { _logger.LogWarning($"Tried to load already-loaded package {package.Name}."); return FluentResults.Result.Ok(); } _loadedPackages[package] = config; - var res = new FluentResults.Result(); - res.WithReasons(_luaScriptManagementService.LoadScriptResourcesAsync(config.LuaScripts).ConfigureAwait(false).GetAwaiter().GetResult().Reasons); - return res; + try + { + var res = new FluentResults.Result(); + var r = Task.WhenAll( + new Task>(async Task () => new FluentResults.Result() + .WithReasons((await _configService.LoadConfigsAsync(config.Configs)).Reasons) + .WithReasons((await _configService.LoadConfigsProfilesAsync(config.Configs)).Reasons)), + new Task>(async () => await _luaScriptManagementService.LoadScriptResourcesAsync(config.LuaScripts)) + ).ConfigureAwait(false).GetAwaiter().GetResult(); + + foreach (var task in r) + res.WithReasons(task.ConfigureAwait(false).GetAwaiter().GetResult().Reasons); + return res; + } + catch (Exception e) + { + return FluentResults.Result.Fail(new ExceptionalError(e)); + } } public FluentResults.Result ExecuteLoadedPackages(ImmutableArray executionOrder) @@ -292,4 +307,15 @@ public sealed class PackageManagementService : IPackageManagementService IService.CheckDisposed(this); return _runningPackages.ContainsKey(package); } + + public ImmutableArray GetLoadedAssemblyPackages() + { + 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) + .Select(cfg => cfg.Package)]; + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs index 4f6475d06..f86dcd965 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs @@ -1,8 +1,13 @@ -using MoonSharp.Interpreter.Loaders; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Barotrauma.LuaCs.Data; +using FluentResults; +using MoonSharp.Interpreter.Loaders; namespace Barotrauma.LuaCs.Services.Safe; public interface ILuaScriptLoader : IService, IScriptLoader { void ClearCaches(); + Task)>>> CacheResourcesAsync(ImmutableArray resourceInfos); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs index ab1e19553..a2e23af47 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Text; using System.IO; using MoonSharp.Interpreter; using MoonSharp.Interpreter.Loaders; using System.Linq; +using System.Threading.Tasks; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Services.Safe; +using FluentResults; namespace Barotrauma.LuaCs.Services.Safe { @@ -59,6 +62,15 @@ namespace Barotrauma.LuaCs.Services.Safe _storageService?.PurgeCache(); } + public async Task)>>> CacheResourcesAsync(ImmutableArray resourceInfos) + { + // TODO: Needs an async lock? + IService.CheckDisposed(this); + if (!_storageService.UseCaching) + return FluentResults.Result.Fail($"Caching is not enabled."); + return await this._storageService.LoadPackageTextFilesAsync([..resourceInfos.SelectMany(ri => ri.FilePaths)]); + } + public override bool ScriptFileExists(string file) { ((IService)this).CheckDisposed(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageListRetrievalService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageListRetrievalService.cs deleted file mode 100644 index c534047a5..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageListRetrievalService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace Barotrauma.LuaCs.Services; - -public interface IPackageListRetrievalService : IService -{ - IEnumerable GetEnabledContentPackages(); - IEnumerable GetAllContentPackages(); -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs index 3979ce69d..820613c41 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Threading.Tasks; using Barotrauma.Extensions; using Barotrauma.LuaCs.Data; +using FluentResults; namespace Barotrauma.LuaCs.Services; @@ -20,4 +21,5 @@ public interface IPackageManagementService : IReusableService public FluentResults.Result UnloadAllPackages(); public ImmutableArray GetAllLoadedPackages(); public bool IsPackageRunning(ContentPackage package); + public ImmutableArray GetLoadedAssemblyPackages(); } From 3ddaceb5ac8791523407843cfecb460d288d3a8d Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 15 Jan 2026 08:13:23 -0500 Subject: [PATCH 039/288] - SafeStorageService glow up. - ILuaScriptLoader now inherits the ISafeStorageValidation interface. - LuaScriptLoader now uses the SafeStorageService. --- .../LuaCs/Services/Safe/ILuaScriptLoader.cs | 2 +- .../Services/Safe/ISafeStorageService.cs | 25 ++- .../LuaCs/Services/Safe/LuaScriptLoader.cs | 124 +++++++------ .../LuaCs/Services/Safe/SafeStorageService.cs | 165 ++++++------------ .../LuaCs/Services/StorageService.cs | 124 ++++++++++++- 5 files changed, 261 insertions(+), 179 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs index f86dcd965..5dde8dfbf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs @@ -6,7 +6,7 @@ using MoonSharp.Interpreter.Loaders; namespace Barotrauma.LuaCs.Services.Safe; -public interface ILuaScriptLoader : IService, IScriptLoader +public interface ILuaScriptLoader : IService, IScriptLoader, ISafeStorageValidation { void ClearCaches(); Task)>>> CacheResourcesAsync(ImmutableArray resourceInfos); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ISafeStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ISafeStorageService.cs index 93247a023..773bbbc28 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ISafeStorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ISafeStorageService.cs @@ -2,7 +2,9 @@ namespace Barotrauma.LuaCs.Services.Safe; -public interface ISafeStorageService : IStorageService +public interface ISafeStorageService : IStorageService, ISafeStorageValidation { } + +public interface ISafeStorageValidation { /// /// Checks the given file path to see if it can be read. This includes any permissions, whitelists and OS checks. @@ -16,26 +18,33 @@ public interface ISafeStorageService : IStorageService /// /// Adds the given path to the specified whitelists. /// - /// Either the fully-qualified or local reference path to the given file. - /// + /// The path to the file, exactly as it will be passed to the Try(Load|Save) methods in . + /// Whether to add it to the read whitelist only, or Read+Write whitelists. void AddFileToWhitelist(string path, bool readOnly = true); /// - /// Removes the given path from all whitelists (Read|Write). + /// Adds the given collection of file paths to whitelists (Read|+Write) + /// + /// The paths to the files, formatted exactly as it will be passed to the Try(Load|Save) methods in . + /// Whether to add it to the read whitelist only, or Read+Write whitelists. + void AddFilesToWhitelist(ImmutableArray paths, bool readOnly = true); + + /// + /// Removes the given path from all whitelists (Read|+Write). /// /// void RemoveFileFromAllWhitelists(string path); /// - /// Sets the whitelist filtering for read-only file permissions for the instance. + /// Sets the whitelist filtering for read-only file permissions for the instance. Overwrites previous list. /// - /// List of absolute file paths allowed. + /// List of file paths allowed, as will be passed to the Try(Load|Save) methods. FluentResults.Result SetReadOnlyWhitelist(ImmutableArray filePaths); /// - /// Sets the whitelist filtering for read & write file permissions for the instance. + /// Sets the whitelist filtering for read & write file permissions for the instance. Overwrites previous lists. /// - /// List of absolute file paths allowed. + /// List of file paths allowed, as will be passed to the Try(Load|Save) methods. FluentResults.Result SetReadWriteWhitelist(ImmutableArray filePaths); /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs index a2e23af47..c78f8c221 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs @@ -15,99 +15,77 @@ namespace Barotrauma.LuaCs.Services.Safe { public class LuaScriptLoader : ScriptLoaderBase, ILuaScriptLoader { - public LuaScriptLoader(IStorageService storageService, Lazy loggerService, ILuaScriptServicesConfig luaScriptServicesConfig) + public LuaScriptLoader(ISafeStorageService storageService, Lazy loggerService) { this._storageService = storageService; this._loggerService = loggerService; - this._luaScriptServicesConfig = luaScriptServicesConfig; - _storageService.UseCaching = _luaScriptServicesConfig.UseCaching; - if (_luaScriptServicesConfig.SafeLuaIOEnabled) - { - //_storageService.EnableWhitelistOnly(); - } } - private readonly IStorageService _storageService; + private readonly ISafeStorageService _storageService; private readonly Lazy _loggerService; - private readonly ILuaScriptServicesConfig _luaScriptServicesConfig; public override object LoadFile(string file, Table globalContext) { - ((IService)this).CheckDisposed(); - - if (!CanReadFromPath(file)) + IService.CheckDisposed(this); + if (file.IsNullOrWhiteSpace()) { - LogErrors($"File access to \"{file}\" is not allowed."); + return null; + } + + var res = _storageService.TryLoadText(file); + + if (res.IsFailed || res is not { Value: { } script}) + { + UnsafeLogErrors($"Failed to load file '{file}'.", res.ToResult()); return null; } - if (_storageService.TryLoadText(file) is not { IsSuccess: true, Value: not null } script) + if (script.IsNullOrWhiteSpace()) { - LogErrors($"Failed to load file \"{file}\"."); + UnsafeLogErrors($"The file '{file}' is empty. ", res.ToResult()); return null; } - if (script.Value.IsNullOrWhiteSpace()) - { - LogErrors($"The file \"{file}\" was empty."); - return null; - } - - return script.Value; + return script; } public void ClearCaches() { - ((IService)this).CheckDisposed(); + IService.CheckDisposed(this); _storageService?.PurgeCache(); } public async Task)>>> CacheResourcesAsync(ImmutableArray resourceInfos) { - // TODO: Needs an async lock? IService.CheckDisposed(this); if (!_storageService.UseCaching) + { return FluentResults.Result.Fail($"Caching is not enabled."); + } + return await this._storageService.LoadPackageTextFilesAsync([..resourceInfos.SelectMany(ri => ri.FilePaths)]); } public override bool ScriptFileExists(string file) { - ((IService)this).CheckDisposed(); - - if (!CanReadFromPath(file)) - { - LogErrors($"File access to \"{file}\" is not allowed."); - return false; - } - + IService.CheckDisposed(this); var result = _storageService.FileExists(file); - if (result is { IsFailed: true }) { - LogErrors($"Unable to find and load file \"{file}\"."); + UnsafeLogErrors($"Unable to find and load file \"{file}\".", result.ToResult()); return false; } - return result.IsSuccess; + return true; } - private bool CanReadFromPath(string file) - { - throw new NotImplementedException(); - } - - private bool CanWriteToPath(string file) - { - throw new NotImplementedException(); - } - - private void LogErrors(string message, FluentResults.Result result = null) + private void UnsafeLogErrors(string message, FluentResults.Result result = null) { _loggerService.Value.LogError($"{nameof(LuaScriptLoader)}: {message}"); - - if (result is null || result.Errors.Count <= 0) + if (result is null || result.Errors.Count <= 0) + { return; + } foreach (var error in result.Errors) { @@ -117,14 +95,58 @@ namespace Barotrauma.LuaCs.Services.Safe public void Dispose() { - if (IsDisposed) + if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) + { return; - IsDisposed = true; + } _storageService?.Dispose(); _loggerService?.Value.Dispose(); } - public bool IsDisposed { get; private set; } + private int _isDisposed = 0; + public bool IsDisposed => ModUtils.Threading.GetBool(ref _isDisposed); + + public bool IsFileAccessible(string path, bool readOnly, bool checkWhitelistOnly = true) + { + IService.CheckDisposed(this); + return _storageService.IsFileAccessible(path, readOnly, checkWhitelistOnly); + } + + public void AddFileToWhitelist(string path, bool readOnly = true) + { + IService.CheckDisposed(this); + _storageService.AddFileToWhitelist(path, readOnly); + } + + public void AddFilesToWhitelist(ImmutableArray paths, bool readOnly = true) + { + IService.CheckDisposed(this); + _storageService.AddFilesToWhitelist(paths, readOnly); + } + + public void RemoveFileFromAllWhitelists(string path) + { + IService.CheckDisposed(this); + _storageService.RemoveFileFromAllWhitelists(path); + } + + public FluentResults.Result SetReadOnlyWhitelist(ImmutableArray filePaths) + { + IService.CheckDisposed(this); + return _storageService.SetReadOnlyWhitelist(filePaths); + } + + public FluentResults.Result SetReadWriteWhitelist(ImmutableArray filePaths) + { + IService.CheckDisposed(this); + return _storageService.SetReadWriteWhitelist(filePaths); + } + + public void ClearAllWhitelists() + { + IService.CheckDisposed(this); + _storageService.ClearAllWhitelists(); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs index 77e9a2aee..d6063366f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs @@ -11,33 +11,47 @@ using Barotrauma.LuaCs.Data; using FarseerPhysics.Common; using FluentResults; using FluentResults.LuaCs; +using Microsoft.Toolkit.Diagnostics; using Path = System.IO.Path; namespace Barotrauma.LuaCs.Services.Safe; public class SafeStorageService : StorageService, ISafeStorageService { - private ConcurrentDictionary _fileListRead = new (), _fileListWrite = new(); + private ConcurrentDictionary + _fileListRead = new (), + _fileListWrite = new(); + private readonly AsyncReaderWriterLock _higherOperationsLock = new(); public SafeStorageService(IStorageServiceConfig configData) : base(configData) { + IsReadOperationAllowedEval = async Task (fp) => IsFileAccessible(fp, true, true); + IsWriteOperationAllowedEval = async Task (fp) => IsFileAccessible(fp, false, true); } private string GetFullPath(string path) => System.IO.Path.GetFullPath(path).CleanUpPathCrossPlatform(); public bool IsFileAccessible(string path, bool readOnly, bool checkWhitelistOnly = true) { - ((IService)this).CheckDisposed(); + Guard.IsNotNullOrWhiteSpace(path, nameof(path)); + using var lck = _higherOperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); try { path = GetFullPath(path); if (!_fileListRead.ContainsKey(path)) + { return false; + } if (!readOnly && !_fileListWrite.ContainsKey(path)) + { return false; + } if (checkWhitelistOnly) + { return true; + } using var fs = System.IO.File.Open( path, FileMode.Open, readOnly ? FileAccess.Read : FileAccess.ReadWrite, FileShare.ReadWrite); return readOnly ? fs.CanRead : fs.CanWrite; @@ -50,13 +64,18 @@ public class SafeStorageService : StorageService, ISafeStorageService public void AddFileToWhitelist(string path, bool readOnly = true) { - ((IService)this).CheckDisposed(); + Guard.IsNotNullOrWhiteSpace(path, nameof(path)); + using var lck = _higherOperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + try { path = GetFullPath(path); _fileListRead.AddOrUpdate(path, s => 0, (s, b) => 0); if (!readOnly) + { _fileListWrite.AddOrUpdate(path, s => 0, (s, b) => 0); + } } catch { @@ -64,9 +83,23 @@ public class SafeStorageService : StorageService, ISafeStorageService } } + public void AddFilesToWhitelist(ImmutableArray paths, bool readOnly = true) + { + if (paths.IsDefaultOrEmpty) + ThrowHelper.ThrowArgumentNullException(nameof(paths)); + foreach (var path in paths) + { + AddFileToWhitelist(path, readOnly); + } + } + + public void RemoveFileFromAllWhitelists(string path) { - ((IService)this).CheckDisposed(); + Guard.IsNotNullOrWhiteSpace(path, nameof(path)); + using var lck = _higherOperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + try { path = GetFullPath(path); @@ -81,13 +114,18 @@ public class SafeStorageService : StorageService, ISafeStorageService public FluentResults.Result SetReadOnlyWhitelist(ImmutableArray filePaths) { - ((IService)this).CheckDisposed(); + using var lck = _higherOperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); if (filePaths.IsDefaultOrEmpty) + { return FluentResults.Result.Fail($"{nameof(SetReadOnlyWhitelist)}: FilePaths cannot be empty."); + } + _fileListRead.Clear(); var res = new FluentResults.Result(); foreach (var path in filePaths) { + Guard.IsNotNullOrWhiteSpace(path, nameof(path)); try { var p = Path.GetFullPath(path.CleanUpPathCrossPlatform()); @@ -121,14 +159,19 @@ public class SafeStorageService : StorageService, ISafeStorageService public FluentResults.Result SetReadWriteWhitelist(ImmutableArray filePaths) { - ((IService)this).CheckDisposed(); if (filePaths.IsDefaultOrEmpty) + { return FluentResults.Result.Fail($"{nameof(SetReadOnlyWhitelist)}: FilePaths cannot be empty."); + } + using var lck = _higherOperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + _fileListRead.Clear(); _fileListWrite.Clear(); var res = new FluentResults.Result(); foreach (var path in filePaths) { + Guard.IsNotNullOrWhiteSpace(path, nameof(path)); try { var p = Path.GetFullPath(path.CleanUpPathCrossPlatform()); @@ -167,115 +210,9 @@ public class SafeStorageService : StorageService, ISafeStorageService public void ClearAllWhitelists() { - ((IService)this).CheckDisposed(); + using var lck = _higherOperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); _fileListRead.Clear(); _fileListWrite.Clear(); } - - #region Base_Overrides - - private bool ReadCheck(string path) - { - return IsFileAccessible(path, true, true); - } - - private bool WriteCheck(string path) - { - return IsFileAccessible(path, false, true); - } - - public override Result FileExists(string filePath) - { - if (!ReadCheck(filePath)) - return FluentResults.Result.Fail("Cannot access file."); - return base.FileExists(filePath); - } - - public override Result TryLoadBinary(string filePath) - { - if (!ReadCheck(filePath)) - return FluentResults.Result.Fail("Cannot access file."); - return base.TryLoadBinary(filePath); - } - - public override async Task> TryLoadBinaryAsync(string filePath) - { - if (!ReadCheck(filePath)) - return FluentResults.Result.Fail("Cannot access file."); - return await base.TryLoadBinaryAsync(filePath); - } - - public override Result TryLoadText(string filePath, Encoding encoding = null) - { - if (!ReadCheck(filePath)) - return FluentResults.Result.Fail("Cannot access file."); - return base.TryLoadText(filePath, encoding); - } - - public override async Task> TryLoadTextAsync(string filePath, Encoding encoding = null) - { - if (!ReadCheck(filePath)) - return FluentResults.Result.Fail("Cannot access file."); - return await base.TryLoadTextAsync(filePath, encoding); - } - - public override Result TryLoadXml(string filePath, Encoding encoding = null) - { - if (!ReadCheck(filePath)) - return FluentResults.Result.Fail("Cannot access file."); - return base.TryLoadXml(filePath, encoding); - } - - public override async Task> TryLoadXmlAsync(string filePath, Encoding encoding = null) - { - if (!ReadCheck(filePath)) - return FluentResults.Result.Fail("Cannot access file."); - return await base.TryLoadXmlAsync(filePath, encoding); - } - - public override FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes) - { - if (!WriteCheck(filePath)) - return FluentResults.Result.Fail("Cannot write to file."); - return base.TrySaveBinary(filePath, in bytes); - } - - public override async Task TrySaveBinaryAsync(string filePath, byte[] bytes) - { - if (!WriteCheck(filePath)) - return FluentResults.Result.Fail("Cannot write to file."); - return await base.TrySaveBinaryAsync(filePath, bytes); - } - - public override FluentResults.Result TrySaveText(string filePath, in string text, Encoding encoding = null) - { - if (!WriteCheck(filePath)) - return FluentResults.Result.Fail("Cannot write to file."); - return base.TrySaveText(filePath, in text, encoding); - } - - public override async Task TrySaveTextAsync(string filePath, string text, Encoding encoding = null) - { - if (!WriteCheck(filePath)) - return FluentResults.Result.Fail("Cannot write to file."); - return await base.TrySaveTextAsync(filePath, text, encoding); - } - - public override FluentResults.Result TrySaveXml(string filePath, in XDocument document, Encoding encoding = null) - { - if (!WriteCheck(filePath)) - return FluentResults.Result.Fail("Cannot write to file."); - return base.TrySaveXml(filePath, in document, encoding); - } - - public override async Task TrySaveXmlAsync(string filePath, XDocument document, Encoding encoding = null) - { - if (!WriteCheck(filePath)) - return FluentResults.Result.Fail("Cannot write to file."); - return await base.TrySaveXmlAsync(filePath, document, encoding); - } - - #endregion - - } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs index 724b2c4dc..220a6570f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs @@ -21,19 +21,54 @@ public class StorageService : IStorageService public StorageService(IStorageServiceConfig configData) { ConfigData = configData; + IsReadOperationAllowedEval = async Task (str) => true; + IsWriteOperationAllowedEval = async Task (str) => true; + } + + public StorageService(IStorageServiceConfig configData, + Func> isReadOperationAllowedEval, + Func> isWriteOperationAllowedEval) + { + Guard.IsNotNull(isReadOperationAllowedEval, nameof(isReadOperationAllowedEval)); + Guard.IsNotNull(isWriteOperationAllowedEval, nameof(isWriteOperationAllowedEval)); + ConfigData = configData; + IsReadOperationAllowedEval = isReadOperationAllowedEval; + IsWriteOperationAllowedEval = isWriteOperationAllowedEval; } private readonly ConcurrentDictionary> _fsCache = new(); protected readonly IStorageServiceConfig ConfigData; protected readonly AsyncReaderWriterLock OperationsLock = new(); + + private Func> _isReadOperationAllowedEval; + protected Func> IsReadOperationAllowedEval + { + get => _isReadOperationAllowedEval; + set + { + if (value is not null) + _isReadOperationAllowedEval = value; + } + } + + private Func> _isWriteOperationAllowedEval; + protected Func> IsWriteOperationAllowedEval + { + get => _isWriteOperationAllowedEval; + set + { + if (value is not null) + _isWriteOperationAllowedEval = value; + } + } public bool IsDisposed => ModUtils.Threading.GetBool(ref _isDisposed); private int _isDisposed = 0; - public void Dispose() + public virtual void Dispose() { + using var lck = OperationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) return; - using var lck = OperationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); _fsCache.Clear(); } @@ -254,7 +289,10 @@ public class StorageService : IStorageService using var lck = await OperationsLock.AcquireReaderLock(); IService.CheckDisposed(this); if (!filePath.FullPath.StartsWith(ConfigData.WorkshopModsDirectory) && !filePath.FullPath.StartsWith(ConfigData.LocalModsDirectory)) - ThrowHelper.ThrowUnauthorizedAccessException($"{nameof(LoadPackageData)}: The filepath of `{filePath.FullPath}' is not in a package directory!"); + { + ThrowHelper.ThrowUnauthorizedAccessException( + $"{nameof(LoadPackageData)}: The filepath of `{filePath.FullPath}' is not in a package directory!"); + } return await dataLoader(filePath.FullPath); } @@ -269,7 +307,9 @@ public class StorageService : IStorageService ImmutableArray filePaths, Func>> dataLoader) { if (filePaths.IsDefaultOrEmpty) + { ThrowHelper.ThrowArgumentNullException($"{nameof(LoadPackageData)}: File paths is empty!"); + } using var lck = await OperationsLock.AcquireReaderLock(); var builder = ImmutableArray.CreateBuilder<(ContentPath, Result)>(); foreach (var path in filePaths) @@ -299,6 +339,7 @@ public class StorageService : IStorageService public virtual FluentResults.Result TryLoadXml(string filePath, Encoding encoding) { + Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath)); using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); @@ -316,9 +357,15 @@ public class StorageService : IStorageService private FluentResults.Result TryLoadText(string filePath) => TryLoadText(filePath, null); public virtual FluentResults.Result TryLoadText(string filePath, Encoding encoding) { + Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath)); using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); + if (IsReadOperationAllowedEval?.Invoke(filePath).ConfigureAwait(false).GetAwaiter().GetResult() is not true) + { + return FluentResults.Result.Fail($"{nameof(TryLoadText)}: File '{filePath}' is not allowed."); + } + if (UseCaching && _fsCache.TryGetValue(filePath, out var result) && result.TryPickT1(out var cachedVal, out _)) { @@ -338,9 +385,15 @@ public class StorageService : IStorageService public virtual FluentResults.Result TryLoadBinary(string filePath) { + Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath)); using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); + if (IsReadOperationAllowedEval?.Invoke(filePath).ConfigureAwait(false).GetAwaiter().GetResult() is not true) + { + return FluentResults.Result.Fail($"{nameof(TryLoadBinary)}: File '{filePath}' is not allowed."); + } + if (UseCaching && _fsCache.TryGetValue(filePath, out var result) && result.TryPickT0(out var cachedVal, out _)) { @@ -353,7 +406,9 @@ public class StorageService : IStorageService fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); var fileData = System.IO.File.ReadAllBytes(fp); if (UseCaching) + { _fsCache[filePath] = fileData; + } return new FluentResults.Result().WithSuccess($"Loaded file successfully").WithValue(fileData); }); } @@ -365,6 +420,11 @@ public class StorageService : IStorageService using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); + if (IsWriteOperationAllowedEval?.Invoke(filePath).ConfigureAwait(false).GetAwaiter().GetResult() is not true) + { + return FluentResults.Result.Fail($"{nameof(TrySaveText)}: File '{filePath}' is not allowed."); + } + string t = text; //copy return IOExceptionsOperationRunner(nameof(TrySaveText), filePath, () => { @@ -380,11 +440,16 @@ public class StorageService : IStorageService public virtual FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes) { + Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath)); Guard.IsNotNull(bytes, nameof(bytes)); Guard.HasSizeGreaterThanOrEqualTo(bytes, 1, nameof(bytes)); using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); + if (IsWriteOperationAllowedEval?.Invoke(filePath).ConfigureAwait(false).GetAwaiter().GetResult() is not true) + { + return FluentResults.Result.Fail($"{nameof(TrySaveBinary)}: File '{filePath}' is not allowed."); + } byte[] b = new byte[bytes.Length]; System.Buffer.BlockCopy(bytes, 0, b, 0, bytes.Length); @@ -401,7 +466,14 @@ public class StorageService : IStorageService public virtual FluentResults.Result FileExists(string filePath) { + Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath)); IService.CheckDisposed(this); + // lock not needed + if (IsReadOperationAllowedEval?.Invoke(filePath).ConfigureAwait(false).GetAwaiter().GetResult() is not true) + { + return FluentResults.Result.Fail($"{nameof(FileExists)}: File '{filePath}' is not allowed."); + } + return IOExceptionsOperationRunner(nameof(FileExists), filePath, () => { var fp = filePath.CleanUpPath(); @@ -412,7 +484,14 @@ public class StorageService : IStorageService public virtual FluentResults.Result DirectoryExists(string directoryPath) { + Guard.IsNotNullOrWhiteSpace(directoryPath, nameof(directoryPath)); IService.CheckDisposed(this); + // lock not needed + if (IsReadOperationAllowedEval?.Invoke(directoryPath).ConfigureAwait(false).GetAwaiter().GetResult() is not true) + { + return FluentResults.Result.Fail($"{nameof(DirectoryExists)}: File '{directoryPath}' is not allowed."); + } + try { var di = new DirectoryInfo(directoryPath); @@ -426,10 +505,20 @@ public class StorageService : IStorageService public virtual async Task> TryLoadXmlAsync(string filePath, Encoding encoding = null) { + Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath)); + using var lck = await OperationsLock.AcquireReaderLock(); IService.CheckDisposed(this); + if (await IsReadOperationAllowedEval.Invoke(filePath) is not true) + { + return FluentResults.Result.Fail($"{nameof(TryLoadXmlAsync)}: File '{filePath}' is not allowed."); + } + if (UseCaching && _fsCache.TryGetValue(filePath, out var cachedVal) && cachedVal.TryPickT2(out var cachedDoc, out _)) + { return FluentResults.Result.Ok(cachedDoc); + } + try { await using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); @@ -446,10 +535,19 @@ public class StorageService : IStorageService public virtual async Task> TryLoadTextAsync(string filePath, Encoding encoding = null) { + Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath)); + using var lck = await OperationsLock.AcquireReaderLock(); IService.CheckDisposed(this); + if (await IsReadOperationAllowedEval.Invoke(filePath) is not true) + { + return FluentResults.Result.Fail($"{nameof(TryLoadTextAsync)}: File '{filePath}' is not allowed."); + } + if (UseCaching && _fsCache.TryGetValue(filePath, out var cachedVal) && cachedVal.TryPickT1(out var cachedTxt, out _)) + { return FluentResults.Result.Ok(cachedTxt); + } return await IOExceptionsOperationRunnerAsync(nameof(TryLoadTextAsync), filePath, async () => { @@ -464,7 +562,14 @@ public class StorageService : IStorageService public virtual async Task> TryLoadBinaryAsync(string filePath) { + Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath)); + using var lck = await OperationsLock.AcquireReaderLock(); IService.CheckDisposed(this); + if (await IsReadOperationAllowedEval.Invoke(filePath) is not true) + { + return FluentResults.Result.Fail($"{nameof(TryLoadBinaryAsync)}: File '{filePath}' is not allowed."); + } + if (UseCaching && _fsCache.TryGetValue(filePath, out var cachedVal) && cachedVal.TryPickT0(out var cachedBin, out _)) { @@ -479,13 +584,18 @@ public class StorageService : IStorageService }); } + // method group overload public virtual async Task TrySaveXmlAsync(string filePath, XDocument document, Encoding encoding = null) => await TrySaveTextAsync(filePath, document.ToString(), encoding); public virtual async Task TrySaveTextAsync(string filePath, string text, Encoding encoding = null) { Guard.IsNotNullOrWhiteSpace(text, nameof(text)); using var lck = await OperationsLock.AcquireReaderLock(); IService.CheckDisposed(this); - + if (await IsWriteOperationAllowedEval.Invoke(filePath) is not true) + { + return FluentResults.Result.Fail($"{nameof(TrySaveTextAsync)}: File '{filePath}' is not allowed."); + } + string t = text.ToString(); //copy return await IOExceptionsOperationRunnerAsync(nameof(TrySaveText), filePath, async () => { @@ -500,11 +610,15 @@ public class StorageService : IStorageService public virtual async Task TrySaveBinaryAsync(string filePath, byte[] bytes) { + Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath)); Guard.IsNotNull(bytes, nameof(bytes)); Guard.HasSizeGreaterThanOrEqualTo(bytes, 1, nameof(bytes)); using var lck = await OperationsLock.AcquireReaderLock(); IService.CheckDisposed(this); - + if (await IsWriteOperationAllowedEval.Invoke(filePath) is not true) + { + return FluentResults.Result.Fail($"{nameof(TrySaveBinaryAsync)}: File '{filePath}' is not allowed."); + } byte[] b = new byte[bytes.Length]; System.Buffer.BlockCopy(bytes, 0, b, 0, bytes.Length); From 6362a9c34f905d90a23ca258b45ff9bf4a0fc5f4 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 16 Jan 2026 15:06:25 -0500 Subject: [PATCH 040/288] - Work on LuaCs system state machine. --- .../ClientSource/LuaCs/LuaCsSetup.cs | 2 +- .../ServerSource/LuaCs/LuaCsSetup.cs | 10 - .../LuaCs/Lua/LuaClasses/LuaUserData.cs | 9 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 364 ++++++------------ .../SharedSource/LuaCs/StateMachine.cs | 107 +++++ 5 files changed, 240 insertions(+), 252 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/StateMachine.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index a60b0c8c5..d34a3d308 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -73,7 +73,7 @@ namespace Barotrauma case MainMenuScreen: case ModDownloadScreen: case ServerListScreen: - SetRunState(RunState.Configuration); + SetRunState(RunState.LoadedNoExec); break; // running lobby or editor states case CampaignEndScreen: diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs index ae63d3cc8..3e39a4b72 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs @@ -17,14 +17,4 @@ partial class LuaCsSetup SetRunState(RunState.Unloaded); SetRunState(RunState.Running); } - - private partial bool ShouldRunCs() => IsCsEnabled.Value || - (GetPackage(new SteamWorkshopId(CsForBarotraumaSteamId.Value), false, false) is { } - && GameMain.Server.ServerPeer is LidgrenServerPeer); - - // ReSharper disable once InconsistentNaming - private static readonly Lazy isRunningInsideWorkshop = new Lazy(() => - Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly()!.Location) != - Directory.GetCurrentDirectory()); - public static bool IsRunningInsideWorkshop => isRunningInsideWorkshop.Value; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs index dd5ae0469..61d60b65e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs @@ -1,4 +1,4 @@ -/* + using System; using System.Collections.Generic; using System.Linq; @@ -8,9 +8,10 @@ using MoonSharp.Interpreter.Interop; namespace Barotrauma { - partial class LuaUserData + internal class LuaUserData { - public static Type GetType(string typeName) => LuaCsSetup.GetType(typeName); + [Obsolete("Use IPluginManagementService::GetTypesByName()")] + public static Type GetType(string typeName) => throw new NotImplementedException(); //LuaCsSetup.GetType(typeName); public static IUserDataDescriptor RegisterType(string typeName) { @@ -362,4 +363,4 @@ namespace Barotrauma } } } -*/ + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index cf3d11c58..4a7059455 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -4,8 +4,10 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; +using System.Net.Mime; using System.Threading; using System.Threading.Tasks; +using Barotrauma.LuaCs; using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Events; @@ -14,6 +16,7 @@ using Barotrauma.LuaCs.Services.Compatibility; using Barotrauma.LuaCs.Services.Processing; using Barotrauma.LuaCs.Services.Safe; using Barotrauma.Networking; +using Barotrauma.Steam; using FluentResults; using ImpromptuInterface; using Microsoft.Toolkit.Diagnostics; @@ -24,56 +27,101 @@ namespace Barotrauma internal delegate void LuaCsErrorHandler(Exception ex, LuaCsMessageOrigin origin); internal delegate void LuaCsExceptionHandler(Exception ex, LuaCsMessageOrigin origin); - partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventAllPackageListChanged, IEventEnabledPackageListChanged, IEventReloadAllPackages + partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventAllPackageListChanged, IEventEnabledPackageListChanged, + IEventReloadAllPackages { public LuaCsSetup() { // == startup _servicesProvider = new ServicesProvider(); - RegisterServices(); - ValidateLuaCsContent(); + RegisterServices(_servicesProvider); + if (!ValidateLuaCsContent()) + { + Logger.LogError($"{nameof(LuaCsSetup)}: ModConfigXml missing. Unable to continue."); + throw new ApplicationException($"{nameof(LuaCsSetup)}: Lua ModConfig.xml is missing. Unable to continue."); + } SubscribeToLuaCsEvents(); + _runStateMachine = SetupStateMachine(); + //LoadLuaCsConfig(); return; // == end - // == sub processes - void RegisterServices() + StateMachine SetupStateMachine() { - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - - // TODO: IConfigService - // TODO: INetworkingService - // TODO: [Resource Converter/Parser Services] - - _servicesProvider.RegisterServiceType(ServiceLifetime.Transient); - - // service config data - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - _servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - - // gen IL - _servicesProvider.Compile(); - } + return new StateMachine(false, RunState.Unloaded, RunStateUnloaded_OnEnter, null) + .AddState(RunState.LoadedNoExec, RunStateLoadedNoExec_OnEnter, RunStateLoadedNoExec_OnExit) + .AddState(RunState.Running, RunStateRunning_OnEnter, RunStateRunning_OnExit); - // Validates LuaCs assets in /Content are valid and ready to use. - void ValidateLuaCsContent() - { - // check if /Content/Lua/ModConfig.xml exists - // if not, try to copy it from the Workshop Mod (ie. installation mode) - // if that fails, throw an error and exit. + // ReSharper disable InconsistentNaming + void RunStateUnloaded_OnEnter(State currentState) + { + + } + + void RunStateLoadedNoExec_OnEnter(State currentState) + { + + } + + void RunStateLoadedNoExec_OnExit(State currentState) + { + + } + + void RunStateRunning_OnEnter(State currentState) + { + + } + + void RunStateRunning_OnExit(State currentState) + { + + } + // ReSharper restore InconsistentNaming } } + bool ValidateLuaCsContent() + { +#if DEBUG + // TODO: we just wanna boot for now + return true; +#endif + // check if /Content/Lua/ModConfig.xml exists + // if not, try to copy it from the Local Mods folder + // if not, try to copy it from the Workshop Mods folder + // if that fails, throw an error and exit. + throw new NotImplementedException(); + } + + void RegisterServices(IServicesProvider servicesProvider) + { + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + + // TODO: IConfigService + // TODO: INetworkingService + // TODO: [Resource Converter/Parser Services] + + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + + // service config data + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + + // gen IL + servicesProvider.Compile(); + } + void SubscribeToLuaCsEvents() { EventService.Subscribe(this); // game state hook in @@ -86,104 +134,97 @@ namespace Barotrauma #if SERVER public const bool IsServer = true; - public const bool IsClient = false; #else public const bool IsServer = false; - public const bool IsClient = true; #endif + public const bool IsClient = !IsServer; #endregion #region Services_ConfigVars - + /* * === Singleton Services */ private readonly IServicesProvider _servicesProvider; - public PerformanceCounterService PerformanceCounter => - _servicesProvider.GetService(); + public PerformanceCounterService PerformanceCounter => _servicesProvider.GetService(); public ILoggerService Logger => _servicesProvider.GetService(); public IConfigService ConfigService => _servicesProvider.GetService(); - public IPackageManagementService PackageManagementService => - _servicesProvider.GetService(); - public IPluginManagementService PluginManagementService => - _servicesProvider.GetService(); - public ILuaScriptManagementService LuaScriptManagementService => - _servicesProvider.GetService(); + public IPackageManagementService PackageManagementService => _servicesProvider.GetService(); + public IPluginManagementService PluginManagementService => _servicesProvider.GetService(); + public ILuaScriptManagementService LuaScriptManagementService => _servicesProvider.GetService(); public INetworkingService NetworkingService => _servicesProvider.GetService(); public IEventService EventService => _servicesProvider.GetService(); public LuaGame Game => _servicesProvider.GetService(); + + #region LuaCsInternal + + /* - * === Config Vars + * === Config Vars */ /// /// Whether C# plugin code is enabled. /// - public IConfigEntry IsCsEnabled { get; private set; } + internal IConfigEntry IsCsEnabled { get; private set; } /// /// Whether mods marked as 'forced' or 'always load' should only be loaded if they're in the enabled mods list. /// - public IConfigEntry TreatForcedModsAsNormal { get; private set; } + internal IConfigEntry TreatForcedModsAsNormal { get; private set; } /// /// Whether the lua script runner from Workshop package should be used over the in-built version. /// - public IConfigEntry PreferToUseWorkshopLuaSetup { get; private set; } + internal IConfigEntry PreferToUseWorkshopLuaSetup { get; private set; } /// /// Whether the popup error GUI should be hidden/suppressed. /// - public IConfigEntry DisableErrorGUIOverlay { get; private set; } + internal IConfigEntry DisableErrorGUIOverlay { get; private set; } /// /// Whether usernames are anonymized or show in logs. /// - public IConfigEntry HideUserNamesInLogs { get; private set; } + internal IConfigEntry HideUserNamesInLogs { get; private set; } /// /// The SteamId of the Workshop LuaCs CPackage in use, if available. /// - public IConfigEntry LuaForBarotraumaSteamId { get; private set; } - - /// - /// The SteamId of the Workshop LuaCs CsForBarotrauma add-on, if available. - /// - public IConfigEntry CsForBarotraumaSteamId { get; private set; } - - /// - /// Whether to (re)load all package assets when a lobby starts/code session begins. - /// Intended for development use, or when packages are expected to change outside of External Updates (ie. Steam Workshop). - /// - public IConfigEntry ReloadPackagesOnLobbyStart { get; private set; } + internal IConfigEntry LuaForBarotraumaSteamId { get; private set; } /// /// TODO: @evilfactory@users.noreply.github.com /// - public IConfigEntry RestrictMessageSize { get; private set; } + internal IConfigEntry RestrictMessageSize { get; private set; } /// /// The local save path for all local data storage for mods. /// - public IConfigEntry LocalDataSavePath { get; private set; } + internal IConfigEntry LocalDataSavePath { get; private set; } + + #endregion /** * == Ops Vars */ private RunState _runState; + /// /// The current run state of all services managed by LuaCs. /// - public RunState CurrentRunState => _runState; + public RunState CurrentRunState + { + get => _runState; + private set => _runState = value; + } - private bool CPacksParsed => CurrentRunState >= RunState.Parsed; - private bool IsStaticAssetsLoaded => CurrentRunState >= RunState.Configuration; - private bool IsCodeRunning => CurrentRunState >= RunState.Running; + private readonly StateMachine _runStateMachine; private readonly ConcurrentQueue _toLoad = new(); private readonly ConcurrentQueue _toUnload = new(); @@ -194,61 +235,6 @@ namespace Barotrauma public ILuaCsHook Hook => this.EventService; #endregion - - - private partial bool ShouldRunCs(); - - - // TODO: Rework - [Obsolete("Use IPluginManagementService::GetTypesByName()")] - public static Type GetType(string typeName, bool throwOnError = false, bool ignoreCase = false) - { - throw new NotImplementedException(); - //return AssemblyManager.GetTypesByName(typeName).FirstOrDefault((Type)null); - } - - public static ContentPackage GetPackage(ContentPackageId id, bool fallbackToAll = true, bool useBackup = false) - { - foreach (ContentPackage package in ContentPackageManager.EnabledPackages.All) - { - if (package.UgcId.ValueEquals(id)) - { - return package; - } - } - - if (fallbackToAll) - { - foreach (ContentPackage package in ContentPackageManager.LocalPackages) - { - if (package.UgcId.ValueEquals(id)) - { - return package; - } - } - - foreach (ContentPackage package in ContentPackageManager.AllPackages) - { - if (package.UgcId.ValueEquals(id)) - { - return package; - } - } - } - - if (useBackup && ContentPackageManager.EnabledPackages.BackupPackages.Regular != null) - { - foreach (ContentPackage package in ContentPackageManager.EnabledPackages.BackupPackages.Regular.Value) - { - if (package.UgcId.ValueEquals(id)) - { - return package; - } - } - } - - return null; - } public void Dispose() { @@ -333,109 +319,12 @@ namespace Barotrauma //ProcessPackagesListDifferences(); } - - /*void ProcessPackagesListDifferences() - { - if (IsCodeRunning) - return; - - // no reason to do anything if we're fully unloaded. - if (!CPacksParsed) - { - _toLoad.Clear(); - _toUnload.Clear(); - } - - while (_toUnload.TryDequeue(out var cp)) - { - - } - - var ls = new List(); - - while (_toLoad.TryDequeue(out var cp)) - { - if (PackageManagementService.LoadPackageInfo(cp) is - { IsFailed: true } failure) - { - Logger.LogError($"Failed to load package infos for {cp.Name}"); - Logger.LogResults(failure); - continue; - } - - ls.Add(cp); - } - - if (ls.Any()) - { - LoadStaticAssetsAsync(ls).GetAwaiter().GetResult(); - } - }*/ - void SetRunState(RunState runState) + void SetRunState(RunState newRunState) { - if (CurrentRunState == runState) + if (CurrentRunState == newRunState) return; - /*if (runState > CurrentRunState) - { - if (CurrentRunState <= RunState.Parsed) - { - LoadCurrentContentPackageInfos(); - } - - if (runState <= CurrentRunState) - { - return; - } - - if (CurrentRunState <= RunState.Configuration) - { - LoadStaticAssets(); - } - - if (runState <= CurrentRunState) - { - return; - } - - if (CurrentRunState <= RunState.Running) - { - RunScripts(); - } - } - else if (runState < CurrentRunState) - { - if (CurrentRunState >= RunState.Running) - { - StopScripts(); - ProcessPackagesListDifferences(); - _runState = RunState.Configuration; - } - - if (runState >= CurrentRunState) - { - return; - } - - if (CurrentRunState == RunState.Configuration) - { - UnloadStaticAssets(); - _runState = RunState.Parsed; - } - - if (runState >= CurrentRunState) - { - return; - } - - if (CurrentRunState == RunState.Parsed) - { - UnloadContentPackageInfos(); - _runState = RunState.Unloaded; - } - - // we should be unloaded completely now | RunState.Unloaded - }*/ + } void LoadLuaCsConfig() @@ -450,12 +339,8 @@ namespace Barotrauma : throw new NullReferenceException($"{nameof(HideUserNamesInLogs)} cannot be loaded."); LuaForBarotraumaSteamId = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "LuaForBarotraumaSteamId", out var val5) ? val5 : throw new NullReferenceException($"{nameof(LuaForBarotraumaSteamId)} cannot be loaded."); - CsForBarotraumaSteamId = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "CsForBarotraumaSteamId", out var val6) ? val6 - : throw new NullReferenceException($"{nameof(CsForBarotraumaSteamId)} cannot be loaded."); RestrictMessageSize = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "RestrictMessageSize", out var val7) ? val7 : throw new NullReferenceException($"{nameof(RestrictMessageSize)} cannot be loaded."); - ReloadPackagesOnLobbyStart = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "ReloadPackagesOnLobbyStart", out var val8) ? val8 - : throw new NullReferenceException($"{nameof(ReloadPackagesOnLobbyStart)} cannot be loaded."); } void DisposeLuaCsConfig() @@ -465,12 +350,8 @@ namespace Barotrauma DisableErrorGUIOverlay = null; HideUserNamesInLogs = null; LuaForBarotraumaSteamId = null; - CsForBarotraumaSteamId = null; RestrictMessageSize = null; - ReloadPackagesOnLobbyStart = null; } - - } /// @@ -479,9 +360,18 @@ namespace Barotrauma /// public enum RunState : byte { - Unloaded = 0, // No asset data loaded. - Parsed, // CPacks' ResourceInfos are parsed. - Configuration, // localization and configuration assets loaded. - Running // all assets loaded, code running. + /// + /// No assets are loaded, code execution suspended. + /// + Unloaded = 0, + /// + /// Loaded mod configs, settings and assets. No code execution. + /// + LoadedNoExec = 1, + /// + /// All assets loaded, code execution is active. + /// + Running = 2 } } + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/StateMachine.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/StateMachine.cs new file mode 100644 index 000000000..79dc5779f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/StateMachine.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using Microsoft.Toolkit.Diagnostics; + +namespace Barotrauma.LuaCs; + +public class StateMachine where T : Enum +{ + private readonly ConcurrentDictionary> _states; + private State _currentState; + public T CurrentState => _currentState.StateId; + private bool _errorOnSameStateSelected; + private readonly AsyncReaderWriterLock _operationsLock = new(); + + public StateMachine(bool errorOnSameState, T defaultState, Action> onEnter, Action> onExit) + { + _errorOnSameStateSelected = errorOnSameState; + _states = new ConcurrentDictionary>(); + var defState = new State(defaultState, onEnter, onExit); + _currentState = defState; + _states[defaultState] = defState; + } + + public StateMachine AddState(T stateId, Action> onEnter, Action> onExit) + { + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (_states.TryGetValue(stateId, out _)) + { + ThrowHelper.ThrowArgumentException($"State with id {stateId} already exists."); + } + + _states[stateId] = new State(stateId, onEnter, onExit); + return this; + } + + public StateMachine RemoveState(T stateId) + { + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (EqualityComparer.Default.Equals(stateId, CurrentState)) + { + ThrowHelper.ThrowInvalidOperationException($"State with id {CurrentState} is active. Cannot remove."); + } + + _states.TryRemove(stateId, out _); + return this; + } + + public StateMachine AddOrReplaceState(T oldStateId, T newStateId, Action> onEnter, Action> onExit) + { + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (EqualityComparer.Default.Equals(oldStateId, CurrentState)) + { + ThrowHelper.ThrowInvalidOperationException($"State with id {CurrentState} is active. Cannot replace."); + } + + _states[oldStateId] = new State(newStateId, onEnter, onExit); + return this; + } + + public StateMachine GotoState(T stateId) + { + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (EqualityComparer.Default.Equals(stateId, CurrentState)) + { + if (_errorOnSameStateSelected) + { + ThrowHelper.ThrowInvalidOperationException($"State with id {stateId} is already selected."); + } + + return this; + } + + if (!_states.TryGetValue(stateId, out var newState)) + { + ThrowHelper.ThrowArgumentNullException($"Target state with id {stateId} does not exist."); + } + + _currentState.OnExit(); + _currentState = newState; + _currentState.OnEnter(); + return this; + } +} + +public class State where T : Enum +{ + public T StateId; + private Action> _onEnter, _onExit; + public State(T stateId, Action> onExitState, Action> onEnterState) + { + StateId = stateId; + _onEnter = onEnterState; + _onExit = onExitState; + } + + public virtual void OnEnter() + { + _onEnter?.Invoke(this); + } + + public virtual void OnExit() + { + _onExit?.Invoke(this); + } +} + From 6a21255a38f9cf78156c291065437f725c32e845 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 18 Jan 2026 19:36:03 -0500 Subject: [PATCH 041/288] - IT BUILDS!!2:BARO BOOGALOO --- .../ClientSource/LuaCs/LuaCsSetup.cs | 2 - .../SharedSource/LuaCs/IEvents.cs | 20 -- .../LuaCs/Lua/LuaClasses/LuaCsTimer.cs | 7 + .../LuaCs/Lua/LuaClasses/LuaGame.cs | 4 + .../LuaCs/Lua/LuaClasses/LuaUserData.cs | 5 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 323 +++++++++--------- .../Services/Compatibility/ILuaCsShim.cs | 2 +- .../LuaCs/Services/ConfigService.cs | 21 ++ .../Services/LuaScriptManagementService.cs | 25 +- .../Services/PackageManagementService.cs | 39 ++- .../_Interfaces/IPackageManagementService.cs | 5 +- 11 files changed, 258 insertions(+), 195 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index d34a3d308..a1c793843 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -21,8 +21,6 @@ namespace Barotrauma } } - private partial bool ShouldRunCs() => IsCsEnabled.Value; - public void CheckCsEnabled() { var csharpMods = PackageManagementService.GetLoadedAssemblyPackages(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index ffefc003c..b3125f8b4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -61,11 +61,6 @@ internal interface IEventReloadAllPackages : IEvent void OnReloadAllPackages(); } -internal interface IEventConfigVarInstanced : IEvent -{ - void OnConfigCreated(IConfigBase config); -} - #endregion #region GameEvents @@ -215,11 +210,6 @@ public interface IEventPluginPreInitialize : IEvent public interface IEventAssemblyLoaded : IEvent { void OnAssemblyLoaded(Assembly assembly); - static IEventAssemblyLoaded IEvent.GetLuaRunner(IDictionary luaFunc) => new - { - IsLuaRunner = Return.Arguments(() => true), - OnAssemblyLoaded = ReturnVoid.Arguments((ass) => luaFunc[nameof(OnAssemblyLoaded)](ass)) - }.ActLike(); } /// @@ -228,11 +218,6 @@ public interface IEventAssemblyLoaded : IEvent public interface IEventAssemblyContextCreated : IEvent { void OnAssemblyCreated(IAssemblyLoaderService loaderService); - static IEventAssemblyContextCreated IEvent.GetLuaRunner(IDictionary luaFunc) => new - { - IsLuaRunner = Return.Arguments(() => true), - OnAssemblyContextCreated = ReturnVoid.Arguments((loader) => luaFunc[nameof(OnAssemblyCreated)](loader)) - }.ActLike(); } /// @@ -241,11 +226,6 @@ public interface IEventAssemblyContextCreated : IEvent { void OnAssemblyUnloading(WeakReference loaderService); - static IEventAssemblyContextUnloading IEvent.GetLuaRunner(IDictionary luaFunc) => new - { - IsLuaRunner = Return.Arguments(() => true), - OnAssemblyUnloading = ReturnVoid.Arguments>((loader) => luaFunc[nameof(OnAssemblyUnloading)](loader)) - }.ActLike(); } #endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsTimer.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsTimer.cs index 9e863d30e..e276ed3d2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsTimer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsTimer.cs @@ -120,5 +120,12 @@ namespace Barotrauma TimedAction timedAction = new TimedAction(action, 0); AddTimer(timedAction); } + + public void Dispose() + { + // ignored + } + + public bool IsDisposed => false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaGame.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaGame.cs index a97beb235..6557b2fab 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaGame.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaGame.cs @@ -272,6 +272,10 @@ namespace Barotrauma.LuaCs.Services public LuaGame() { +#if DEBUG + return; // startup testing +#endif + throw new NotImplementedException(); /*LuaUserData.MakeFieldAccessible(UserData.RegisterType(typeof(GameSettings)), "currentConfig"); Settings = UserData.CreateStatic(typeof(GameSettings));*/ diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs index 61d60b65e..cd0ecdda5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs @@ -269,9 +269,10 @@ namespace Barotrauma descriptor.RemoveMember(methodName); descriptor.AddMember(methodName, new ObjectCallbackMemberDescriptor(methodName, (object arg1, ScriptExecutionContext arg2, CallbackArguments arg3) => { - if (GameMain.LuaCs != null) + /*if (GameMain.LuaCs != null) return GameMain.LuaCs.CallLuaFunction(function, arg3.GetArray()); - return null; + return null;*/ + throw new NotImplementedException(); })); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 4a7059455..a6ec81487 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -27,59 +27,22 @@ namespace Barotrauma internal delegate void LuaCsErrorHandler(Exception ex, LuaCsMessageOrigin origin); internal delegate void LuaCsExceptionHandler(Exception ex, LuaCsMessageOrigin origin); - partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventAllPackageListChanged, IEventEnabledPackageListChanged, + partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventEnabledPackageListChanged, IEventReloadAllPackages { public LuaCsSetup() { // == startup - _servicesProvider = new ServicesProvider(); - RegisterServices(_servicesProvider); + _servicesProvider = SetupServicesProvider(); if (!ValidateLuaCsContent()) { Logger.LogError($"{nameof(LuaCsSetup)}: ModConfigXml missing. Unable to continue."); throw new ApplicationException($"{nameof(LuaCsSetup)}: Lua ModConfig.xml is missing. Unable to continue."); } - SubscribeToLuaCsEvents(); _runStateMachine = SetupStateMachine(); - //LoadLuaCsConfig(); - - return; - // == end - - StateMachine SetupStateMachine() - { - return new StateMachine(false, RunState.Unloaded, RunStateUnloaded_OnEnter, null) - .AddState(RunState.LoadedNoExec, RunStateLoadedNoExec_OnEnter, RunStateLoadedNoExec_OnExit) - .AddState(RunState.Running, RunStateRunning_OnEnter, RunStateRunning_OnExit); - - // ReSharper disable InconsistentNaming - void RunStateUnloaded_OnEnter(State currentState) - { - - } - - void RunStateLoadedNoExec_OnEnter(State currentState) - { - - } - - void RunStateLoadedNoExec_OnExit(State currentState) - { - - } - - void RunStateRunning_OnEnter(State currentState) - { - - } - - void RunStateRunning_OnExit(State currentState) - { - - } - // ReSharper restore InconsistentNaming - } + SubscribeToLuaCsEvents(); + SetRunState(RunState.LoadedNoExec); + LoadLuaCsConfig(); } bool ValidateLuaCsContent() @@ -88,44 +51,16 @@ namespace Barotrauma // TODO: we just wanna boot for now return true; #endif - // check if /Content/Lua/ModConfig.xml exists - // if not, try to copy it from the Local Mods folder - // if not, try to copy it from the Workshop Mods folder + // check if /Content/ModConfig.xml exists + // if not, try to copy missing files from the Local Mods folder + // if not, try to copy missing files from the Workshop Mods folder // if that fails, throw an error and exit. throw new NotImplementedException(); } - void RegisterServices(IServicesProvider servicesProvider) - { - servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - servicesProvider.RegisterServiceType(ServiceLifetime.Transient); - servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - servicesProvider.RegisterServiceType(ServiceLifetime.Transient); - servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - - // TODO: IConfigService - // TODO: INetworkingService - // TODO: [Resource Converter/Parser Services] - - servicesProvider.RegisterServiceType(ServiceLifetime.Transient); - - // service config data - servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - - // gen IL - servicesProvider.Compile(); - } - void SubscribeToLuaCsEvents() { EventService.Subscribe(this); // game state hook in - EventService.Subscribe(this); EventService.Subscribe(this); EventService.Subscribe(this); } @@ -141,14 +76,14 @@ namespace Barotrauma #endregion - #region Services_ConfigVars + #region Services_CVars /* * === Singleton Services */ private readonly IServicesProvider _servicesProvider; - + public PerformanceCounterService PerformanceCounter => _servicesProvider.GetService(); public ILoggerService Logger => _servicesProvider.GetService(); public IConfigService ConfigService => _servicesProvider.GetService(); @@ -158,14 +93,8 @@ namespace Barotrauma public INetworkingService NetworkingService => _servicesProvider.GetService(); public IEventService EventService => _servicesProvider.GetService(); public LuaGame Game => _servicesProvider.GetService(); - - #region LuaCsInternal - - - /* - * === Config Vars - */ + internal IStorageService StorageService => _servicesProvider.GetService(); /// /// Whether C# plugin code is enabled. @@ -207,13 +136,58 @@ namespace Barotrauma /// internal IConfigEntry LocalDataSavePath { get; private set; } + void LoadLuaCsConfig() + { + IsCsEnabled = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "IsCsEnabled", out var val1) ? val1 + : throw new NullReferenceException($"{nameof(IsCsEnabled)} cannot be loaded."); + TreatForcedModsAsNormal = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "TreatForcedModsAsNormal", out var val2) ? val2 + : throw new NullReferenceException($"{nameof(TreatForcedModsAsNormal)} cannot be loaded."); + DisableErrorGUIOverlay = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "DisableErrorGUIOverlay", out var val3) ? val3 + : throw new NullReferenceException($"{nameof(DisableErrorGUIOverlay)} cannot be loaded."); + HideUserNamesInLogs = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "HideUserNamesInLogs", out var val4) ? val4 + : throw new NullReferenceException($"{nameof(HideUserNamesInLogs)} cannot be loaded."); + LuaForBarotraumaSteamId = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "LuaForBarotraumaSteamId", out var val5) ? val5 + : throw new NullReferenceException($"{nameof(LuaForBarotraumaSteamId)} cannot be loaded."); + RestrictMessageSize = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "RestrictMessageSize", out var val7) ? val7 + : throw new NullReferenceException($"{nameof(RestrictMessageSize)} cannot be loaded."); + } + + private IServicesProvider SetupServicesProvider() + { + var servicesProvider = new ServicesProvider(); + + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + // TODO: INetworkingService + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType, ConfigFileParserService>(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType, ConfigFileParserService>(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType, ConfigFileParserService>(ServiceLifetime.Transient); + // service config data + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + // gen IL + servicesProvider.Compile(); + return servicesProvider; + } + #endregion - /** - * == Ops Vars - */ + #region StateMachine + private RunState _runState; - /// /// The current run state of all services managed by LuaCs. /// @@ -222,14 +196,116 @@ namespace Barotrauma get => _runState; private set => _runState = value; } - - + private readonly StateMachine _runStateMachine; - private readonly ConcurrentQueue _toLoad = new(); - private readonly ConcurrentQueue _toUnload = new(); + + public void OnEnabledPackageListChanged(CorePackage package, IEnumerable regularPackages) + { + ProcessEnabledPackageChanges(new []{ package }.Concat(regularPackages).ToImmutableArray()); + } + + public void OnReloadAllPackages() + { + if (CurrentRunState <= RunState.Unloaded) + { + return; + } + + var state = CurrentRunState; + SetRunState(RunState.Unloaded); + SetRunState(state); + } + + private void ProcessEnabledPackageChanges(ImmutableArray packages) + { + if (CurrentRunState < RunState.LoadedNoExec) + { + return; + } + + var state = CurrentRunState; + if (CurrentRunState > RunState.LoadedNoExec) + { + SetRunState(RunState.LoadedNoExec); + } + + PackageManagementService.SyncLoadedPackagesList(packages); + SetRunState(state); // restore + } + + private void SetRunState(RunState targetRunState) + { + if (CurrentRunState == targetRunState) + { + return; + } + _runStateMachine.GotoState(targetRunState); + } + + private StateMachine SetupStateMachine() + { + return new StateMachine(false, RunState.Unloaded, RunStateUnloaded_OnEnter, null) + .AddState(RunState.LoadedNoExec, RunStateLoadedNoExec_OnEnter, null) + .AddState(RunState.Running, RunStateRunning_OnEnter, RunStateRunning_OnExit); + + // ReSharper disable InconsistentNaming + void RunStateUnloaded_OnEnter(State currentState) + { + if (PackageManagementService.IsAnyPackageRunning()) + { + Logger.LogResults(PackageManagementService.StopRunningPackages()); + } + + if (PackageManagementService.IsAnyPackageRunning()) + { + DisposeLuaCsConfig(); + Logger.LogResults(PackageManagementService.UnloadAllPackages()); + } + + CurrentRunState = RunState.Unloaded; + } + + void RunStateLoadedNoExec_OnEnter(State currentState) + { + if (PackageManagementService.IsAnyPackageRunning()) + { + Logger.LogResults(PackageManagementService.StopRunningPackages()); + } + + if (!PackageManagementService.IsAnyPackageLoaded()) + { + Logger.LogResults(PackageManagementService.LoadPackagesInfo(ContentPackageManager.EnabledPackages.All.ToImmutableArray())); + LoadLuaCsConfig(); + } + + CurrentRunState = RunState.LoadedNoExec; + } + + void RunStateRunning_OnEnter(State currentState) + { + if (!PackageManagementService.IsAnyPackageLoaded()) + { + Logger.LogResults(PackageManagementService.LoadPackagesInfo(ContentPackageManager.EnabledPackages.All.ToImmutableArray())); + LoadLuaCsConfig(); + } + + if (!PackageManagementService.IsAnyPackageRunning()) + { + Logger.LogResults(PackageManagementService.ExecuteLoadedPackages(ContentPackageManager.EnabledPackages.All.ToImmutableArray())); + } + + CurrentRunState = RunState.Running; + } + + void RunStateRunning_OnExit(State currentState) + { + Logger.LogResults(PackageManagementService.StopRunningPackages()); + } + // ReSharper restore InconsistentNaming + } #endregion - + #region LegacyRedirects public ILuaCsHook Hook => this.EventService; @@ -276,72 +352,7 @@ namespace Barotrauma /// The new game screen. public partial void OnScreenSelected(Screen screen); - public void OnAllPackageListChanged(IEnumerable corePackages, IEnumerable regularPackages) - { - UpdateLoadedPackagesList(); - } - - public void OnEnabledPackageListChanged(CorePackage corePackage, IEnumerable regularPackages) - { - UpdateLoadedPackagesList(); - } - public void OnReloadAllPackages() - { - if (CurrentRunState <= RunState.Unloaded) - return; - var state = CurrentRunState; - SetRunState(RunState.Unloaded); - SetRunState(CurrentRunState); - } - - public void ForceRunState(RunState newState) - { - if (CurrentRunState == newState) - return; - SetRunState(newState); - } - - private void UpdateLoadedPackagesList() - { - throw new NotImplementedException($"Rewrite the loading state system."); - - var newPackSet = ContentPackageManager.AllPackages - .Union(ContentPackageManager.EnabledPackages.All) - .ToHashSet(); - var currPackSet = PackageManagementService.GetAllLoadedPackages().ToHashSet(); - var toAdd = newPackSet.Except(currPackSet); - var toRemove = currPackSet.Except(newPackSet); - foreach (var package in toAdd) - _toLoad.Enqueue(package); - foreach (var package in toRemove) - _toUnload.Enqueue(package); - - //ProcessPackagesListDifferences(); - } - - void SetRunState(RunState newRunState) - { - if (CurrentRunState == newRunState) - return; - - } - - void LoadLuaCsConfig() - { - IsCsEnabled = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "IsCsEnabled", out var val1) ? val1 - : throw new NullReferenceException($"{nameof(IsCsEnabled)} cannot be loaded."); - TreatForcedModsAsNormal = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "TreatForcedModsAsNormal", out var val2) ? val2 - : throw new NullReferenceException($"{nameof(TreatForcedModsAsNormal)} cannot be loaded."); - DisableErrorGUIOverlay = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "DisableErrorGUIOverlay", out var val3) ? val3 - : throw new NullReferenceException($"{nameof(DisableErrorGUIOverlay)} cannot be loaded."); - HideUserNamesInLogs = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "HideUserNamesInLogs", out var val4) ? val4 - : throw new NullReferenceException($"{nameof(HideUserNamesInLogs)} cannot be loaded."); - LuaForBarotraumaSteamId = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "LuaForBarotraumaSteamId", out var val5) ? val5 - : throw new NullReferenceException($"{nameof(LuaForBarotraumaSteamId)} cannot be loaded."); - RestrictMessageSize = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "RestrictMessageSize", out var val7) ? val7 - : throw new NullReferenceException($"{nameof(RestrictMessageSize)} cannot be loaded."); - } void DisposeLuaCsConfig() { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsShim.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsShim.cs index 661b26360..19a9cf663 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsShim.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsShim.cs @@ -1,6 +1,6 @@ namespace Barotrauma.LuaCs.Services.Compatibility; -public interface ILuaCsShim +public interface ILuaCsShim : IService { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs index f3f8d5f65..8bcdb69fa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs @@ -34,6 +34,8 @@ public partial class ConfigService : IConfigService throw new NotImplementedException(); } + #region LuaInterface + public bool TryGetConfigBool(string packageName, string configName, out bool value) { throw new NotImplementedException(); @@ -128,7 +130,10 @@ public partial class ConfigService : IConfigService { throw new NotImplementedException(); } + + #endregion + public void RegisterTypeInitializer(Func> initializer, bool replaceIfExists = false) where TData : IEquatable where TConfig : IConfigBase { throw new NotImplementedException(); @@ -136,11 +141,17 @@ public partial class ConfigService : IConfigService public async Task LoadConfigsAsync(ImmutableArray configResources) { +#if DEBUG + return FluentResults.Result.Ok(); // just for startup testing +#endif throw new NotImplementedException(); } public async Task LoadConfigsProfilesAsync(ImmutableArray configProfileResources) { +#if DEBUG + return FluentResults.Result.Ok(); // just for startup testing +#endif throw new NotImplementedException(); } @@ -156,11 +167,17 @@ public partial class ConfigService : IConfigService public FluentResults.Result DisposePackageData(ContentPackage package) { +#if DEBUG + return FluentResults.Result.Ok(); // just for startup testing +#endif throw new NotImplementedException(); } public FluentResults.Result DisposeAllPackageData() { +#if DEBUG + return FluentResults.Result.Ok(); // just for startup testing +#endif throw new NotImplementedException(); } @@ -181,6 +198,10 @@ public partial class ConfigService : IConfigService public bool TryGetConfig(ContentPackage package, string name, out T config) where T : IConfigBase { +#if DEBUG + config = default(T); + return true; // just for startup testing +#endif throw new NotImplementedException(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs index ac523f836..f69e2faff 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs @@ -43,19 +43,20 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService private readonly ILoggerService _loggerService; private readonly LuaGame _luaGame; private readonly ILuaCsHook _luaCsHook; - private readonly ILuaCsNetworking _luaCsNetworking; - private readonly ILuaCsUtility _luaCsUtility; - private readonly ILuaCsTimer _luaCsTimer; + //private readonly ILuaCsNetworking _luaCsNetworking; + //private readonly ILuaCsUtility _luaCsUtility; + //private readonly ILuaCsTimer _luaCsTimer; public LuaScriptManagementService( ILoggerService loggerService, ILuaScriptLoader loader, ILuaScriptServicesConfig luaScriptServicesConfig, LuaGame luaGame, - ILuaCsHook luaCsHook, - ILuaCsNetworking luaCsNetworking, - ILuaCsUtility luaCsUtility, - ILuaCsTimer luaCsTimer) + ILuaCsHook luaCsHook + //ILuaCsNetworking luaCsNetworking, + //ILuaCsUtility luaCsUtility, + //ILuaCsTimer luaCsTimer + ) { _luaScriptLoader = loader; _luaScriptServicesConfig = luaScriptServicesConfig; @@ -63,9 +64,9 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _luaGame = luaGame; _luaCsHook = luaCsHook; - _luaCsNetworking = luaCsNetworking; - _luaCsUtility = luaCsUtility; - _luaCsTimer = luaCsTimer; + //_luaCsNetworking = luaCsNetworking; + //_luaCsUtility = luaCsUtility; + //_luaCsTimer = luaCsTimer; } public bool IsDisposed { get; private set; } @@ -124,9 +125,9 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _script.Globals["Game"] = _luaGame; _script.Globals["Hook"] = _luaCsHook; - _script.Globals["Timer"] = _luaCsTimer; + //_script.Globals["Timer"] = _luaCsTimer; _script.Globals["File"] = UserData.CreateStatic(); - _script.Globals["Networking"] = _luaCsNetworking; + //_script.Globals["Networking"] = _luaCsNetworking; //_script.Globals["Steam"] = Steam; _script.Globals["ExecutionNumber"] = 0; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index 0a518281a..3626f6e1c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -223,7 +223,30 @@ public sealed class PackageManagementService : IPackageManagementService return result; } - + + public FluentResults.Result SyncLoadedPackagesList(ImmutableArray packages) + { + if (packages.IsDefaultOrEmpty) + ThrowHelper.ThrowArgumentNullException(nameof(packages)); + if (!_runningPackages.IsEmpty) + ThrowHelper.ThrowInvalidOperationException($"{nameof(SyncLoadedPackagesList)}: There are packages running!"); + + var toRemove = _loadedPackages.Keys.Except(packages).ToImmutableArray(); + var toAdd = packages.Except(_loadedPackages.Keys) + .OrderBy(pack => packages.IndexOf(pack)).ToImmutableArray(); + + var result = new FluentResults.Result(); + + result.WithReasons(UnloadPackages(toRemove).Reasons); + + if (result.IsFailed) + { + return result; + } + + return result.WithReasons(LoadPackagesInfo(toAdd).Reasons); + } + public FluentResults.Result StopRunningPackages() { using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); @@ -308,6 +331,20 @@ public sealed class PackageManagementService : IPackageManagementService return _runningPackages.ContainsKey(package); } + public bool IsAnyPackageLoaded() + { + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + return !_loadedPackages.IsEmpty; + } + + public bool IsAnyPackageRunning() + { + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + return !_runningPackages.IsEmpty; + } + public ImmutableArray GetLoadedAssemblyPackages() { using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs index 820613c41..2c6f2bc39 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs @@ -15,11 +15,14 @@ public interface IPackageManagementService : IReusableService public FluentResults.Result LoadPackageInfo(ContentPackage package); public FluentResults.Result LoadPackagesInfo(ImmutableArray packages); public FluentResults.Result ExecuteLoadedPackages(ImmutableArray executionOrder); + public FluentResults.Result SyncLoadedPackagesList(ImmutableArray packages); public FluentResults.Result StopRunningPackages(); public FluentResults.Result UnloadPackage(ContentPackage package); public FluentResults.Result UnloadPackages(ImmutableArray packages); public FluentResults.Result UnloadAllPackages(); public ImmutableArray GetAllLoadedPackages(); - public bool IsPackageRunning(ContentPackage package); public ImmutableArray GetLoadedAssemblyPackages(); + public bool IsPackageRunning(ContentPackage package); + public bool IsAnyPackageLoaded(); + public bool IsAnyPackageRunning(); } From 6e7b7c804c57b9f1d207ec2abc41e21a14f4ac3a Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 19 Jan 2026 18:00:39 -0500 Subject: [PATCH 042/288] - Added other package locations to if statement check. --- .../SharedSource/LuaCs/Services/StorageService.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs index 220a6570f..5554fc734 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs @@ -3,11 +3,13 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; +using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using Barotrauma.LuaCs.Data; +using Barotrauma.Networking; using FluentResults; using FluentResults.LuaCs; using Microsoft.Toolkit.Diagnostics; @@ -227,8 +229,17 @@ public class StorageService : IStorageService Guard.IsNotNullOrWhiteSpace(filePath.FullPath, nameof(filePath.FullPath)); using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); - if (!filePath.FullPath.StartsWith(ConfigData.WorkshopModsDirectory) && !filePath.FullPath.StartsWith(ConfigData.LocalModsDirectory)) + if (!filePath.FullPath.StartsWith(ConfigData.WorkshopModsDirectory) + && !filePath.FullPath.StartsWith(ConfigData.LocalModsDirectory) + && !filePath.FullPath.StartsWith(ConfigData.TempDownloadsDirectory) + && !filePath.FullPath.StartsWith(ContentPackageManager.VanillaCorePackage!.Dir) +#if CLIENT + && !filePath.FullPath.StartsWith(ModReceiver.DownloadFolder) +#endif + ) + { ThrowHelper.ThrowUnauthorizedAccessException($"{nameof(LoadPackageData)}: The filepath of `{filePath.FullPath}' is not in a package directory!"); + } return dataLoader(filePath.FullPath); } From dda26df250eedd7400b395005ac1ae936e9c6dd1 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 19 Jan 2026 18:03:58 -0500 Subject: [PATCH 043/288] For sure this time....right guys? --- .../SharedSource/LuaCs/Services/StorageService.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs index 5554fc734..ec28224b5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs @@ -231,10 +231,9 @@ public class StorageService : IStorageService IService.CheckDisposed(this); if (!filePath.FullPath.StartsWith(ConfigData.WorkshopModsDirectory) && !filePath.FullPath.StartsWith(ConfigData.LocalModsDirectory) - && !filePath.FullPath.StartsWith(ConfigData.TempDownloadsDirectory) && !filePath.FullPath.StartsWith(ContentPackageManager.VanillaCorePackage!.Dir) #if CLIENT - && !filePath.FullPath.StartsWith(ModReceiver.DownloadFolder) + && !filePath.FullPath.StartsWith(ConfigData.TempDownloadsDirectory) #endif ) { From f1e1b9238d0cd9be47a3e4d722c13855bcf07e47 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Mon, 19 Jan 2026 21:20:04 -0300 Subject: [PATCH 044/288] The deadlock situation is craaaaazy Fix --- .../Services/Processing/ModConfigService.cs | 8 +- .../LuaCs/Services/Safe/SafeStorageService.cs | 4 +- .../LuaCs/Services/ServicesProvider.cs | 5 +- .../LuaCs/Services/StorageService.cs | 85 +++++++++---------- 4 files changed, 51 insertions(+), 51 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs index d9d783a93..c6d41fb36 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs @@ -235,6 +235,12 @@ public sealed class ModConfigService : IModConfigService private async Task> CreateFromLegacyAsync(ContentPackage src) { - throw new NotImplementedException(); + return new ModConfigInfo() + { + Package = src, + Assemblies = ImmutableArray.Empty, + Configs = ImmutableArray.Empty, + LuaScripts = ImmutableArray.Empty + }; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs index d6063366f..aa812801b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs @@ -25,8 +25,8 @@ public class SafeStorageService : StorageService, ISafeStorageService public SafeStorageService(IStorageServiceConfig configData) : base(configData) { - IsReadOperationAllowedEval = async Task (fp) => IsFileAccessible(fp, true, true); - IsWriteOperationAllowedEval = async Task (fp) => IsFileAccessible(fp, false, true); + IsReadOperationAllowedEval = (fp) => IsFileAccessible(fp, true, true); + IsWriteOperationAllowedEval = (fp) => IsFileAccessible(fp, false, true); } private string GetFullPath(string path) => System.IO.Path.GetFullPath(path).CleanUpPathCrossPlatform(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs index 17213a0cf..65f79bb17 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs @@ -17,7 +17,10 @@ public class ServicesProvider : IServicesProvider public ServicesProvider() { - _serviceContainerInst = new ServiceContainer(); + _serviceContainerInst = new ServiceContainer(new ContainerOptions() + { + EnablePropertyInjection = false + }); } public void RegisterServiceType(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs index ec28224b5..5af427abf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs @@ -23,27 +23,16 @@ public class StorageService : IStorageService public StorageService(IStorageServiceConfig configData) { ConfigData = configData; - IsReadOperationAllowedEval = async Task (str) => true; - IsWriteOperationAllowedEval = async Task (str) => true; - } - - public StorageService(IStorageServiceConfig configData, - Func> isReadOperationAllowedEval, - Func> isWriteOperationAllowedEval) - { - Guard.IsNotNull(isReadOperationAllowedEval, nameof(isReadOperationAllowedEval)); - Guard.IsNotNull(isWriteOperationAllowedEval, nameof(isWriteOperationAllowedEval)); - ConfigData = configData; - IsReadOperationAllowedEval = isReadOperationAllowedEval; - IsWriteOperationAllowedEval = isWriteOperationAllowedEval; + IsReadOperationAllowedEval = bool (str) => true; + IsWriteOperationAllowedEval = bool (str) => true; } private readonly ConcurrentDictionary> _fsCache = new(); protected readonly IStorageServiceConfig ConfigData; protected readonly AsyncReaderWriterLock OperationsLock = new(); - private Func> _isReadOperationAllowedEval; - protected Func> IsReadOperationAllowedEval + private Func _isReadOperationAllowedEval; + protected Func IsReadOperationAllowedEval { get => _isReadOperationAllowedEval; set @@ -53,8 +42,8 @@ public class StorageService : IStorageService } } - private Func> _isWriteOperationAllowedEval; - protected Func> IsWriteOperationAllowedEval + private Func _isWriteOperationAllowedEval; + protected Func IsWriteOperationAllowedEval { get => _isWriteOperationAllowedEval; set @@ -221,25 +210,28 @@ public class StorageService : IStorageService public async Task SaveLocalTextAsync(ContentPackage package, string localFilePath, string text) => await SaveLocalDataAsync(package, localFilePath, text, async (path, txt) => await TrySaveTextAsync(path, txt)); + private bool IsPackagePathValid(ContentPath contentPath) + { + return contentPath.FullPath.StartsWith(ConfigData.WorkshopModsDirectory) + || contentPath.FullPath.StartsWith(ConfigData.LocalModsDirectory) +#if CLIENT + || contentPath.FullPath.StartsWith(ConfigData.TempDownloadsDirectory) +#endif + || contentPath.FullPath.StartsWith(Path.GetFullPath(ContentPackageManager.VanillaCorePackage!.Dir).CleanUpPathCrossPlatform()); + } // --- Package Content - private Result LoadPackageData(ContentPath filePath, Func> dataLoader) + private Result LoadPackageData(ContentPath contentPath, Func> dataLoader) { - Guard.IsNotNull(filePath, nameof(filePath)); - Guard.IsNotNullOrWhiteSpace(filePath.FullPath, nameof(filePath.FullPath)); + Guard.IsNotNull(contentPath, nameof(contentPath)); + Guard.IsNotNullOrWhiteSpace(contentPath.FullPath, nameof(contentPath.FullPath)); using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); - if (!filePath.FullPath.StartsWith(ConfigData.WorkshopModsDirectory) - && !filePath.FullPath.StartsWith(ConfigData.LocalModsDirectory) - && !filePath.FullPath.StartsWith(ContentPackageManager.VanillaCorePackage!.Dir) -#if CLIENT - && !filePath.FullPath.StartsWith(ConfigData.TempDownloadsDirectory) -#endif - ) + if (!IsPackagePathValid(contentPath)) { - ThrowHelper.ThrowUnauthorizedAccessException($"{nameof(LoadPackageData)}: The filepath of `{filePath.FullPath}' is not in a package directory!"); + ThrowHelper.ThrowUnauthorizedAccessException($"{nameof(LoadPackageData)}: The filepath of `{contentPath.FullPath}' is not in a package directory!"); } - return dataLoader(filePath.FullPath); + return dataLoader(contentPath.FullPath); } public Result LoadPackageXml(ContentPath filePath) @@ -292,18 +284,17 @@ public class StorageService : IStorageService } - private async Task> LoadPackageDataAsync(ContentPath filePath, Func>> dataLoader) + private async Task> LoadPackageDataAsync(ContentPath contentPath, Func>> dataLoader) { - Guard.IsNotNull(filePath, nameof(filePath)); - Guard.IsNotNullOrWhiteSpace(filePath.FullPath, nameof(filePath.FullPath)); + Guard.IsNotNull(contentPath, nameof(contentPath)); + Guard.IsNotNullOrWhiteSpace(contentPath.FullPath, nameof(contentPath.FullPath)); using var lck = await OperationsLock.AcquireReaderLock(); IService.CheckDisposed(this); - if (!filePath.FullPath.StartsWith(ConfigData.WorkshopModsDirectory) && !filePath.FullPath.StartsWith(ConfigData.LocalModsDirectory)) + if (!IsPackagePathValid(contentPath)) { - ThrowHelper.ThrowUnauthorizedAccessException( - $"{nameof(LoadPackageData)}: The filepath of `{filePath.FullPath}' is not in a package directory!"); + ThrowHelper.ThrowUnauthorizedAccessException($"{nameof(LoadPackageDataAsync)}: The filepath of `{contentPath.FullPath}' is not in a package directory!"); } - return await dataLoader(filePath.FullPath); + return await dataLoader(contentPath.FullPath); } public async Task> LoadPackageXmlAsync(ContentPath filePath) @@ -371,7 +362,7 @@ public class StorageService : IStorageService using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); - if (IsReadOperationAllowedEval?.Invoke(filePath).ConfigureAwait(false).GetAwaiter().GetResult() is not true) + if (IsReadOperationAllowedEval?.Invoke(filePath) is not true) { return FluentResults.Result.Fail($"{nameof(TryLoadText)}: File '{filePath}' is not allowed."); } @@ -399,7 +390,7 @@ public class StorageService : IStorageService using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); - if (IsReadOperationAllowedEval?.Invoke(filePath).ConfigureAwait(false).GetAwaiter().GetResult() is not true) + if (IsReadOperationAllowedEval?.Invoke(filePath) is not true) { return FluentResults.Result.Fail($"{nameof(TryLoadBinary)}: File '{filePath}' is not allowed."); } @@ -430,7 +421,7 @@ public class StorageService : IStorageService using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); - if (IsWriteOperationAllowedEval?.Invoke(filePath).ConfigureAwait(false).GetAwaiter().GetResult() is not true) + if (IsWriteOperationAllowedEval?.Invoke(filePath) is not true) { return FluentResults.Result.Fail($"{nameof(TrySaveText)}: File '{filePath}' is not allowed."); } @@ -456,7 +447,7 @@ public class StorageService : IStorageService using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); - if (IsWriteOperationAllowedEval?.Invoke(filePath).ConfigureAwait(false).GetAwaiter().GetResult() is not true) + if (IsWriteOperationAllowedEval?.Invoke(filePath) is not true) { return FluentResults.Result.Fail($"{nameof(TrySaveBinary)}: File '{filePath}' is not allowed."); } @@ -479,7 +470,7 @@ public class StorageService : IStorageService Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath)); IService.CheckDisposed(this); // lock not needed - if (IsReadOperationAllowedEval?.Invoke(filePath).ConfigureAwait(false).GetAwaiter().GetResult() is not true) + if (IsReadOperationAllowedEval?.Invoke(filePath) is not true) { return FluentResults.Result.Fail($"{nameof(FileExists)}: File '{filePath}' is not allowed."); } @@ -497,7 +488,7 @@ public class StorageService : IStorageService Guard.IsNotNullOrWhiteSpace(directoryPath, nameof(directoryPath)); IService.CheckDisposed(this); // lock not needed - if (IsReadOperationAllowedEval?.Invoke(directoryPath).ConfigureAwait(false).GetAwaiter().GetResult() is not true) + if (IsReadOperationAllowedEval?.Invoke(directoryPath) is not true) { return FluentResults.Result.Fail($"{nameof(DirectoryExists)}: File '{directoryPath}' is not allowed."); } @@ -518,7 +509,7 @@ public class StorageService : IStorageService Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath)); using var lck = await OperationsLock.AcquireReaderLock(); IService.CheckDisposed(this); - if (await IsReadOperationAllowedEval.Invoke(filePath) is not true) + if (IsReadOperationAllowedEval.Invoke(filePath) is not true) { return FluentResults.Result.Fail($"{nameof(TryLoadXmlAsync)}: File '{filePath}' is not allowed."); } @@ -548,7 +539,7 @@ public class StorageService : IStorageService Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath)); using var lck = await OperationsLock.AcquireReaderLock(); IService.CheckDisposed(this); - if (await IsReadOperationAllowedEval.Invoke(filePath) is not true) + if (IsReadOperationAllowedEval.Invoke(filePath) is not true) { return FluentResults.Result.Fail($"{nameof(TryLoadTextAsync)}: File '{filePath}' is not allowed."); } @@ -575,7 +566,7 @@ public class StorageService : IStorageService Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath)); using var lck = await OperationsLock.AcquireReaderLock(); IService.CheckDisposed(this); - if (await IsReadOperationAllowedEval.Invoke(filePath) is not true) + if (IsReadOperationAllowedEval.Invoke(filePath) is not true) { return FluentResults.Result.Fail($"{nameof(TryLoadBinaryAsync)}: File '{filePath}' is not allowed."); } @@ -601,7 +592,7 @@ public class StorageService : IStorageService Guard.IsNotNullOrWhiteSpace(text, nameof(text)); using var lck = await OperationsLock.AcquireReaderLock(); IService.CheckDisposed(this); - if (await IsWriteOperationAllowedEval.Invoke(filePath) is not true) + if (IsWriteOperationAllowedEval.Invoke(filePath) is not true) { return FluentResults.Result.Fail($"{nameof(TrySaveTextAsync)}: File '{filePath}' is not allowed."); } @@ -625,7 +616,7 @@ public class StorageService : IStorageService Guard.HasSizeGreaterThanOrEqualTo(bytes, 1, nameof(bytes)); using var lck = await OperationsLock.AcquireReaderLock(); IService.CheckDisposed(this); - if (await IsWriteOperationAllowedEval.Invoke(filePath) is not true) + if (IsWriteOperationAllowedEval.Invoke(filePath) is not true) { return FluentResults.Result.Fail($"{nameof(TrySaveBinaryAsync)}: File '{filePath}' is not allowed."); } From cc07db941fd5007f6bf27d8141a22389baa352f3 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 20 Jan 2026 07:51:44 -0500 Subject: [PATCH 045/288] - Fixed the "deadlock" (tasks not started). --- .../SharedSource/LuaCs/LuaCsSetup.cs | 4 ++-- .../LuaCs/Services/PackageManagementService.cs | 15 ++++++++++----- .../LuaCs/Services/Processing/ModConfigService.cs | 1 + 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index a6ec81487..4a31ec71c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -36,8 +36,8 @@ namespace Barotrauma _servicesProvider = SetupServicesProvider(); if (!ValidateLuaCsContent()) { - Logger.LogError($"{nameof(LuaCsSetup)}: ModConfigXml missing. Unable to continue."); - throw new ApplicationException($"{nameof(LuaCsSetup)}: Lua ModConfig.xml is missing. Unable to continue."); + Logger.LogError($"{nameof(LuaCsSetup)}: ModConfig.xml missing. Unable to continue."); + throw new ApplicationException($"{nameof(LuaCsSetup)}: Lua's ModConfig.xml is missing. Unable to continue."); } _runStateMachine = SetupStateMachine(); SubscribeToLuaCsEvents(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index 3626f6e1c..22a3626b6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -151,12 +151,17 @@ public sealed class PackageManagementService : IPackageManagementService try { var res = new FluentResults.Result(); - var r = Task.WhenAll( - new Task>(async Task () => new FluentResults.Result() + var configsTask = new Task>(async Task () => + new FluentResults.Result() .WithReasons((await _configService.LoadConfigsAsync(config.Configs)).Reasons) - .WithReasons((await _configService.LoadConfigsProfilesAsync(config.Configs)).Reasons)), - new Task>(async () => await _luaScriptManagementService.LoadScriptResourcesAsync(config.LuaScripts)) - ).ConfigureAwait(false).GetAwaiter().GetResult(); + .WithReasons((await _configService.LoadConfigsProfilesAsync(config.Configs)).Reasons)); + var luaScriptsTask = new Task>(async () => + await _luaScriptManagementService.LoadScriptResourcesAsync(config.LuaScripts)); + + configsTask.Start(); + luaScriptsTask.Start(); + + var r = Task.WhenAll(configsTask, luaScriptsTask).ConfigureAwait(false).GetAwaiter().GetResult(); foreach (var task in r) res.WithReasons(task.ConfigureAwait(false).GetAwaiter().GetResult().Reasons); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs index c6d41fb36..6025fce08 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs @@ -235,6 +235,7 @@ public sealed class ModConfigService : IModConfigService private async Task> CreateFromLegacyAsync(ContentPackage src) { + // TODO: Implement legacy mod analysis return new ModConfigInfo() { Package = src, From 7e0e671539e6787c9e19ee1cb8d5a909cd2e100c Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 22 Jan 2026 14:58:03 -0500 Subject: [PATCH 046/288] - ConfigService.cs alpha testing. --- .../{IConfigControl.cs => ISettingControl.cs} | 4 +- .../ClientSource/LuaCs/Data/IConfigInfo.cs | 6 +- .../ClientSource/LuaCs/LuaCsSetup.cs | 24 +- .../LuaCs/Services/ConfigService.cs | 2 +- .../LuaCs/Services/IConfigService.cs | 2 +- .../LuaCs/Configuration/ConfigEntry.cs | 103 ----- .../LuaCs/Configuration/ConfigList.cs | 111 ----- .../{IConfigBase.cs => ISettingBase.cs} | 7 +- .../{IConfigEntry.cs => ISettingEntry.cs} | 4 +- .../{IConfigEnum.cs => ISettingEnum.cs} | 2 +- .../{IConfigList.cs => ISettingList.cs} | 4 +- ...figRangeEntry.cs => ISettingRangeEntry.cs} | 2 +- .../LuaCs/Configuration/SettingEntry.cs | 85 ++++ .../LuaCs/Configuration/SettingList.cs | 94 ++++ .../Data/DataInterfaceImplementations.cs | 9 +- .../SharedSource/LuaCs/Data/IConfigInfo.cs | 18 +- .../LuaCs/Data/IConfigProfileInfo.cs | 2 +- .../SharedSource/LuaCs/IEvents.cs | 8 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 36 +- .../LuaCs/Services/ConfigInitializers.cs | 106 ++--- .../LuaCs/Services/ConfigService.cs | 433 +++++++++++------- .../LuaCs/Services/EventService.cs | 3 + .../Processing/IHelperServiceDefinitions.cs | 5 + ...rvice.cs => ModConfigFileParserService.cs} | 4 +- .../Processing/SettingsFileParserService.cs | 212 +++++++++ .../LuaCs/Services/Safe/ILuaConfigService.cs | 24 +- .../Services/_Interfaces/IConfigService.cs | 31 +- 27 files changed, 795 insertions(+), 546 deletions(-) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/{IConfigControl.cs => ISettingControl.cs} (68%) delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ConfigEntry.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ConfigList.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/{IConfigBase.cs => ISettingBase.cs} (61%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/{IConfigEntry.cs => ISettingEntry.cs} (55%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/{IConfigEnum.cs => ISettingEnum.cs} (61%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/{IConfigList.cs => ISettingList.cs} (52%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/{IConfigRangeEntry.cs => ISettingRangeEntry.cs} (66%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingEntry.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingList.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/{ConfigFileParserService.cs => ModConfigFileParserService.cs} (98%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/SettingsFileParserService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IConfigControl.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/ISettingControl.cs similarity index 68% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IConfigControl.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/ISettingControl.cs index 4e3afbd93..9907af9cc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IConfigControl.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/ISettingControl.cs @@ -2,9 +2,9 @@ namespace Barotrauma.LuaCs.Configuration; -public interface IConfigControl : IConfigBase +public interface ISettingControl : ISettingBase { - event Action OnDown; + event Action OnDown; KeyOrMouse Value { get; } bool IsAssignable(KeyOrMouse value); bool TrySetValue(KeyOrMouse value); diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs index 801bc5ac4..1e26659a1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs @@ -7,11 +7,11 @@ public partial interface IConfigInfo : IConfigDisplayInfo { } public interface IConfigDisplayInfo { /// - /// User-friendly name or Localization Token. + /// Localization Token for display name. /// string DisplayName { get; } /// - /// User-friendly description or Localization Token. + /// Localization Token for description. /// string Description { get; } /// @@ -29,5 +29,5 @@ public interface IConfigDisplayInfo /// /// Icon for display in menus, if available. /// - string ImageIconPath { get; } + ContentPath ImageIconPath { get; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index a1c793843..4a30e21f4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -21,8 +21,20 @@ namespace Barotrauma } } - public void CheckCsEnabled() + /// + /// + /// + /// Returns whether the IsCsEnabled has been changed to true/enabled. Returns false if already enabled. + public bool CheckCsEnabled() { + // fast exit if enabled or unavailable. + if (this.IsCsEnabled?.Value ?? true ) + { + return false; + } + + bool isCsValueChanged = false; + var csharpMods = PackageManagementService.GetLoadedAssemblyPackages(); StringBuilder sb = new StringBuilder(); @@ -38,7 +50,7 @@ namespace Barotrauma if (GameMain.Client == null || GameMain.Client.IsServerOwner) { new GUIMessageBox("", $"You have CSharp mods enabled but don't have the CSharp Scripting enabled, those mods might not work, go to the Main Menu, click on LuaCs Settings and check Enable CSharp Scripting.\n\n{sb}"); - return; + return false; } GUIMessageBox msg = new GUIMessageBox( @@ -49,6 +61,7 @@ namespace Barotrauma msg.Buttons[0].OnClicked = (GUIButton button, object obj) => { this.IsCsEnabled.TrySetValue(true); + isCsValueChanged = true; return true; }; @@ -57,6 +70,8 @@ namespace Barotrauma this.IsCsEnabled.TrySetValue(false); return true; }; + + return isCsValueChanged; } /// @@ -85,7 +100,10 @@ namespace Barotrauma case SpriteEditorScreen: case SubEditorScreen: case TestScreen: // notes: TestScreen is a Linux edge case editor screen and is deprecated. - CheckCsEnabled(); + if (CheckCsEnabled() && this.CurrentRunState >= RunState.Running) + { + SetRunState(RunState.LoadedNoExec); + } SetRunState(RunState.Running); break; default: diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs index a628969b2..ab00224e5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs @@ -19,7 +19,7 @@ public partial class ConfigService throw new NotImplementedException(); } - public Result AddConfigControl(IConfigInfo configInfo) + public Result AddConfigControl(IConfigInfo configInfo) { throw new NotImplementedException(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs index cbe7bf94d..7218ef2cc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs @@ -13,5 +13,5 @@ public partial interface IConfigService ImmutableArray GetDisplayableConfigs(); ImmutableArray GetDisplayableConfigsForPackage(ContentPackage package); - FluentResults.Result AddConfigControl(IConfigInfo configInfo); + FluentResults.Result AddConfigControl(IConfigInfo configInfo); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ConfigEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ConfigEntry.cs deleted file mode 100644 index 4fd22a1cb..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ConfigEntry.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Xml.Linq; -using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services; -using Barotrauma.Networking; -using OneOf; - -namespace Barotrauma.LuaCs.Configuration; - -public class ConfigEntry : IConfigEntry where T : IEquatable -{ - - private readonly Action, INetReadMessage> _readMessageHandler; - private readonly Action, INetWriteMessage> _writeMessageHandler; - - public ConfigEntry(IConfigInfo configInfo, Action, INetReadMessage> readMessageHandler, - Action, INetWriteMessage> writeMessageHandler) - { - _readMessageHandler = readMessageHandler; - _writeMessageHandler = writeMessageHandler; - } - - public string InternalName { get; init; } - public ContentPackage OwnerPackage { get; init; } - - public bool Equals(IConfigBase other) - { - if (ReferenceEquals(this, other)) - return true; - throw new NotImplementedException(); - } - - public void Dispose() - { - throw new NotImplementedException(); - } - - public Type GetValueType() - { - throw new NotImplementedException(); - } - - public string GetValue() - { - throw new NotImplementedException(); - } - - public bool TrySetValue(OneOf value) - { - throw new NotImplementedException(); - } - - public bool IsAssignable(OneOf value) - { - throw new NotImplementedException(); - } - - private event Action> _onValueChanged; - public event Action> OnValueChanged - { - add => _onValueChanged += value; - remove => _onValueChanged -= value; - } - - event Action IConfigBase.OnValueChanged - { - add => _onValueChanged += value; - remove => _onValueChanged -= value; - } - - public OneOf GetSerializableValue() - { - throw new NotImplementedException(); - } - - public Guid InstanceId => throw new NotImplementedException(); - - public NetSync SyncType => throw new NotImplementedException(); - - public ClientPermissions WritePermissions => throw new NotImplementedException(); - - public void ReadNetMessage(INetReadMessage message) - { - throw new NotImplementedException(); - } - - public void WriteNetMessage(INetWriteMessage message) - { - throw new NotImplementedException(); - } - - public T Value => throw new NotImplementedException(); - - public bool TrySetValue(T value) - { - throw new NotImplementedException(); - } - - public bool IsAssignable(T value) - { - throw new NotImplementedException(); - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ConfigList.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ConfigList.cs deleted file mode 100644 index d67d09e7d..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ConfigList.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Xml.Linq; -using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services; -using Barotrauma.Networking; -using OneOf; - -namespace Barotrauma.LuaCs.Configuration; - -public class ConfigList : IConfigList where T : IEquatable -{ - private readonly Action, INetReadMessage> _readMessageHandler; - private readonly Action, INetWriteMessage> _writeMessageHandler; - - public ConfigList(IConfigInfo configInfo, Action, INetReadMessage> readMessageHandler, - Action, INetWriteMessage> writeMessageHandler) - { - _readMessageHandler = readMessageHandler; - _writeMessageHandler = writeMessageHandler; - } - - public string InternalName => throw new NotImplementedException(); - - public ContentPackage OwnerPackage => throw new NotImplementedException(); - - public bool Equals(IConfigBase other) - { - throw new NotImplementedException(); - } - - public void Dispose() - { - throw new NotImplementedException(); - } - - public Type GetValueType() - { - throw new NotImplementedException(); - } - - public string GetValue() - { - throw new NotImplementedException(); - } - - public bool TrySetValue(OneOf value) - { - throw new NotImplementedException(); - } - - public bool IsAssignable(OneOf value) - { - throw new NotImplementedException(); - } - - private event Action> _onValueChanged; - - public event Action> OnValueChanged - { - add => _onValueChanged += value; - remove => _onValueChanged -= value; - } - - event Action> IConfigEntry.OnValueChanged - { - add => _onValueChanged += value; - remove => _onValueChanged -= value; - } - - event Action IConfigBase.OnValueChanged - { - add => _onValueChanged += value; - remove => _onValueChanged -= value; - } - - public T Value => throw new NotImplementedException(); - - public bool TrySetValue(T value) - { - throw new NotImplementedException(); - } - - public bool IsAssignable(T value) - { - throw new NotImplementedException(); - } - - public OneOf GetSerializableValue() - { - throw new NotImplementedException(); - } - - public Guid InstanceId => throw new NotImplementedException(); - - public NetSync SyncType => throw new NotImplementedException(); - - public ClientPermissions WritePermissions => throw new NotImplementedException(); - - public void ReadNetMessage(INetReadMessage message) - { - throw new NotImplementedException(); - } - - public void WriteNetMessage(INetWriteMessage message) - { - throw new NotImplementedException(); - } - - public IReadOnlyList Options => throw new NotImplementedException(); -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigBase.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingBase.cs similarity index 61% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigBase.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingBase.cs index 8e65f68b5..6adc49f31 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigBase.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingBase.cs @@ -6,12 +6,13 @@ using Barotrauma.Networking; namespace Barotrauma.LuaCs.Configuration; -public partial interface IConfigBase : IDataInfo, IEquatable, IDisposable +public partial interface ISettingBase : IDataInfo, IEquatable, IDisposable { Type GetValueType(); - string GetValue(); + string GetStringValue(); bool TrySetValue(OneOf.OneOf value); bool IsAssignable(OneOf.OneOf value); - event Action OnValueChanged; + event Func, bool> IsNewValueValid; + event Action OnValueChanged; OneOf.OneOf GetSerializableValue(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingEntry.cs similarity index 55% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingEntry.cs index 9b173233a..a6e545543 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingEntry.cs @@ -3,10 +3,10 @@ using Barotrauma.LuaCs.Services; namespace Barotrauma.LuaCs.Configuration; -public interface IConfigEntry : IConfigBase, INetworkSyncEntity where T : IEquatable +public interface ISettingEntry : ISettingBase, INetworkSyncEntity where T : IEquatable { T Value { get; } bool TrySetValue(T value); bool IsAssignable(T value); - new event Action> OnValueChanged; + new event Action> OnValueChanged; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEnum.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingEnum.cs similarity index 61% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEnum.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingEnum.cs index 742f80a46..83e9604f0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigEnum.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingEnum.cs @@ -3,7 +3,7 @@ using Barotrauma.LuaCs.Services; namespace Barotrauma.LuaCs.Configuration; -public interface IConfigEnum : IConfigBase, INetworkSyncEntity +public interface ISettingEnum : ISettingBase, INetworkSyncEntity { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigList.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingList.cs similarity index 52% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigList.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingList.cs index d78b5779d..ee74412cb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigList.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingList.cs @@ -4,8 +4,8 @@ using Barotrauma.LuaCs.Services; namespace Barotrauma.LuaCs.Configuration; -public interface IConfigList : IConfigEntry, INetworkSyncEntity where T : IEquatable +public interface ISettingList : ISettingEntry, INetworkSyncEntity where T : IEquatable { IReadOnlyList Options { get; } - new event Action> OnValueChanged; + new event Action> OnValueChanged; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigRangeEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingRangeEntry.cs similarity index 66% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigRangeEntry.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingRangeEntry.cs index 571bb1d3f..57eb47fab 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/IConfigRangeEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingRangeEntry.cs @@ -2,7 +2,7 @@ using System; namespace Barotrauma.LuaCs.Configuration; -public interface IConfigRangeEntry : IConfigEntry where T : IConvertible, IEquatable +public interface ISettingRangeEntry : ISettingEntry where T : IConvertible, IEquatable { T MinValue { get; } T MaxValue { get; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingEntry.cs new file mode 100644 index 000000000..37d7d5e9f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingEntry.cs @@ -0,0 +1,85 @@ +using System; +using System.Xml.Linq; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Services; +using Barotrauma.Networking; +using OneOf; + +namespace Barotrauma.LuaCs.Configuration; + +public class SettingEntry : ISettingEntry where T : IEquatable +{ + public string InternalName { get; } + public ContentPackage OwnerPackage { get; } + public bool Equals(ISettingBase other) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + public Type GetValueType() + { + throw new NotImplementedException(); + } + + public string GetStringValue() + { + throw new NotImplementedException(); + } + + public bool TrySetValue(OneOf value) + { + throw new NotImplementedException(); + } + + public bool IsAssignable(OneOf value) + { + throw new NotImplementedException(); + } + + public event Func, bool> IsNewValueValid; + public T Value { get; } + public bool TrySetValue(T value) + { + throw new NotImplementedException(); + } + + public bool IsAssignable(T value) + { + throw new NotImplementedException(); + } + + event Action> ISettingEntry.OnValueChanged + { + add => throw new NotImplementedException(); + remove => throw new NotImplementedException(); + } + + event Action ISettingBase.OnValueChanged + { + add => throw new NotImplementedException(); + remove => throw new NotImplementedException(); + } + + public OneOf GetSerializableValue() + { + throw new NotImplementedException(); + } + + public Guid InstanceId { get; } + public NetSync SyncType { get; } + public ClientPermissions WritePermissions { get; } + public void ReadNetMessage(INetReadMessage message) + { + throw new NotImplementedException(); + } + + public void WriteNetMessage(INetWriteMessage message) + { + throw new NotImplementedException(); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingList.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingList.cs new file mode 100644 index 000000000..973a14abe --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingList.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Xml.Linq; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Services; +using Barotrauma.Networking; +using OneOf; + +namespace Barotrauma.LuaCs.Configuration; + +public class SettingList : ISettingList where T : IEquatable +{ + public string InternalName { get; } + public ContentPackage OwnerPackage { get; } + public bool Equals(ISettingBase other) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + public Type GetValueType() + { + throw new NotImplementedException(); + } + + public string GetStringValue() + { + throw new NotImplementedException(); + } + + public bool TrySetValue(OneOf value) + { + throw new NotImplementedException(); + } + + public bool IsAssignable(OneOf value) + { + throw new NotImplementedException(); + } + + public event Func, bool> IsNewValueValid; + public T Value { get; } + public bool TrySetValue(T value) + { + throw new NotImplementedException(); + } + + public bool IsAssignable(T value) + { + throw new NotImplementedException(); + } + + event Action> ISettingList.OnValueChanged + { + add => throw new NotImplementedException(); + remove => throw new NotImplementedException(); + } + + public IReadOnlyList Options { get; } + + event Action> ISettingEntry.OnValueChanged + { + add => throw new NotImplementedException(); + remove => throw new NotImplementedException(); + } + + event Action ISettingBase.OnValueChanged + { + add => throw new NotImplementedException(); + remove => throw new NotImplementedException(); + } + + public OneOf GetSerializableValue() + { + throw new NotImplementedException(); + } + + public Guid InstanceId { get; } + public NetSync SyncType { get; } + public ClientPermissions WritePermissions { get; } + public void ReadNetMessage(INetReadMessage message) + { + throw new NotImplementedException(); + } + + public void WriteNetMessage(INetWriteMessage message) + { + throw new NotImplementedException(); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs index 3c2c84007..798e8734e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs @@ -67,9 +67,8 @@ public record ConfigInfo : IConfigInfo { public string InternalName { get; init; } public ContentPackage OwnerPackage { get; init; } - public Type DataType { get; init; } - public OneOf DefaultValue { get; init; } - public OneOf Value { get; init; } + public string DataType { get; init; } + public XElement Element { get; init; } public RunState EditableStates { get; init; } public NetSync NetSync { get; init; } @@ -79,7 +78,7 @@ public record ConfigInfo : IConfigInfo public string DisplayCategory { get; init; } public bool ShowInMenus { get; init; } public string Tooltip { get; init; } - public string ImageIconPath { get; init; } + public ContentPath ImageIconPath { get; init; } #endif } @@ -87,7 +86,7 @@ public record ConfigProfileInfo : IConfigProfileInfo { public string InternalName { get; init; } public ContentPackage OwnerPackage { get; init; } - public IReadOnlyList<(string ConfigName, OneOf Value)> ProfileValues { get; init; } + public IReadOnlyList<(string ConfigName, XElement Element)> ProfileValues { get; init; } } #endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs index 4d8a5e5ea..164d37791 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs @@ -13,26 +13,18 @@ public partial interface IConfigInfo : IDataInfo /// /// Specifies the type initializer that will be used to instantiate the config var. /// - Type DataType { get; } + string DataType { get; } /// - /// The default value. + /// The 'Setting' XML element. /// - OneOf.OneOf DefaultValue { get; } - /// - /// The value the last time this config was saved. If not found, returns the default value. - ///
[If(Type='')]
- /// The value is from the 'Value' Attribute. Typically used for types with single/simple values, such as primitives. - ///
[If(Type='')]
- /// The value is from the first 'Value' child element. Typically used with complex config types, such as range and list. - ///
- OneOf.OneOf Value { get; } + XElement Element { get; } /// /// In what (s) is this config editable. Will be editable in the selected state, and lower value states. ///

/// [Important]
Setting this to value lower than 'Configuration` will render this config read-only. ///

Expected Behaviour: - ///
[|]: Read-Only. - ///
[]: Can only be changed at the Main Menu (not in a lobby). + ///
[|]: Read-Only. + ///
[]: Can only be changed at the Main Menu (not in a lobby). ///
[]: Can be changed at the Main Menu and while a lobby is active. ///
RunState EditableStates { get; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs index d60fb2772..5d5f9065d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs @@ -5,5 +5,5 @@ namespace Barotrauma.LuaCs.Data; public interface IConfigProfileInfo : IDataInfo { - IReadOnlyList<(string ConfigName, OneOf.OneOf Value)> ProfileValues { get; } + IReadOnlyList<(string ConfigName, XElement Element)> ProfileValues { get; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index b3125f8b4..d3c6ac779 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -30,7 +30,7 @@ public interface IEvent : IEvent where T : IEvent } } -#region RuntimeEvents +#region RuntimeServiceEvents /// /// Called when the current (game state) changes. Upstream Type 'Screen' is internal. @@ -61,6 +61,12 @@ internal interface IEventReloadAllPackages : IEvent void OnReloadAllPackages(); } +internal interface IEventSettingInstanceLifetime : IEvent +{ + void OnSettingInstanceCreated(T configInstance) where T : ISettingBase; + void OnSettingInstanceDisposed(T configInstance) where T : ISettingBase; +} + #endregion #region GameEvents diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 4a31ec71c..c0811c934 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -99,56 +99,56 @@ namespace Barotrauma /// /// Whether C# plugin code is enabled. /// - internal IConfigEntry IsCsEnabled { get; private set; } + internal ISettingEntry IsCsEnabled { get; private set; } /// /// Whether mods marked as 'forced' or 'always load' should only be loaded if they're in the enabled mods list. /// - internal IConfigEntry TreatForcedModsAsNormal { get; private set; } + internal ISettingEntry TreatForcedModsAsNormal { get; private set; } /// /// Whether the lua script runner from Workshop package should be used over the in-built version. /// - internal IConfigEntry PreferToUseWorkshopLuaSetup { get; private set; } + internal ISettingEntry PreferToUseWorkshopLuaSetup { get; private set; } /// /// Whether the popup error GUI should be hidden/suppressed. /// - internal IConfigEntry DisableErrorGUIOverlay { get; private set; } + internal ISettingEntry DisableErrorGUIOverlay { get; private set; } /// /// Whether usernames are anonymized or show in logs. /// - internal IConfigEntry HideUserNamesInLogs { get; private set; } + internal ISettingEntry HideUserNamesInLogs { get; private set; } /// /// The SteamId of the Workshop LuaCs CPackage in use, if available. /// - internal IConfigEntry LuaForBarotraumaSteamId { get; private set; } + internal ISettingEntry LuaForBarotraumaSteamId { get; private set; } /// /// TODO: @evilfactory@users.noreply.github.com /// - internal IConfigEntry RestrictMessageSize { get; private set; } + internal ISettingEntry RestrictMessageSize { get; private set; } /// /// The local save path for all local data storage for mods. /// - internal IConfigEntry LocalDataSavePath { get; private set; } + internal ISettingEntry LocalDataSavePath { get; private set; } void LoadLuaCsConfig() { - IsCsEnabled = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "IsCsEnabled", out var val1) ? val1 + IsCsEnabled = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "IsCsEnabled", out var val1) ? val1 : throw new NullReferenceException($"{nameof(IsCsEnabled)} cannot be loaded."); - TreatForcedModsAsNormal = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "TreatForcedModsAsNormal", out var val2) ? val2 + TreatForcedModsAsNormal = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "TreatForcedModsAsNormal", out var val2) ? val2 : throw new NullReferenceException($"{nameof(TreatForcedModsAsNormal)} cannot be loaded."); - DisableErrorGUIOverlay = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "DisableErrorGUIOverlay", out var val3) ? val3 + DisableErrorGUIOverlay = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "DisableErrorGUIOverlay", out var val3) ? val3 : throw new NullReferenceException($"{nameof(DisableErrorGUIOverlay)} cannot be loaded."); - HideUserNamesInLogs = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "HideUserNamesInLogs", out var val4) ? val4 + HideUserNamesInLogs = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "HideUserNamesInLogs", out var val4) ? val4 : throw new NullReferenceException($"{nameof(HideUserNamesInLogs)} cannot be loaded."); - LuaForBarotraumaSteamId = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "LuaForBarotraumaSteamId", out var val5) ? val5 + LuaForBarotraumaSteamId = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "LuaForBarotraumaSteamId", out var val5) ? val5 : throw new NullReferenceException($"{nameof(LuaForBarotraumaSteamId)} cannot be loaded."); - RestrictMessageSize = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "RestrictMessageSize", out var val7) ? val7 + RestrictMessageSize = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "RestrictMessageSize", out var val7) ? val7 : throw new NullReferenceException($"{nameof(RestrictMessageSize)} cannot be loaded."); } @@ -170,9 +170,11 @@ namespace Barotrauma // TODO: INetworkingService servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); - servicesProvider.RegisterServiceType, ConfigFileParserService>(ServiceLifetime.Transient); - servicesProvider.RegisterServiceType, ConfigFileParserService>(ServiceLifetime.Transient); - servicesProvider.RegisterServiceType, ConfigFileParserService>(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType, ModConfigFileParserService>(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType, ModConfigFileParserService>(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType, ModConfigFileParserService>(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType, SettingsFileParserService>(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType, SettingsFileParserService>(ServiceLifetime.Transient); // service config data servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigInitializers.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigInitializers.cs index d3bad9e9d..388923d64 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigInitializers.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigInitializers.cs @@ -26,37 +26,19 @@ public class ConfigInitializers : IService // stateless service public bool IsDisposed => false; - private Result> CreateConfigEntry(IConfigInfo configInfo, - Action, INetReadMessage> readHandler, - Action, INetWriteMessage> writeHandler) + private Result> CreateConfigEntry(IConfigInfo configInfo, + Action, INetReadMessage> readHandler, + Action, INetWriteMessage> writeHandler) where T : IEquatable { - try - { - var ice = new ConfigEntry(configInfo, readHandler, writeHandler); - return FluentResults.Result.Ok>(ice); - } - catch (Exception e) - { - return FluentResults.Result.Fail($"Error while initializing config var: {configInfo?.OwnerPackage} - {configInfo?.InternalName}") - .WithError(new ExceptionalError(e)); - } + throw new NotImplementedException(); } - private Result> CreateConfigList(IConfigInfo configInfo, - Action, INetReadMessage> readHandler, Action, INetWriteMessage> writeHandler) + private Result> CreateConfigList(IConfigInfo configInfo, + Action, INetReadMessage> readHandler, Action, INetWriteMessage> writeHandler) where T : IEquatable { - try - { - var icl = new ConfigList(configInfo, readHandler, writeHandler); - return FluentResults.Result.Ok>(icl); - } - catch (Exception e) - { - return FluentResults.Result.Fail($"Error while initializing config var: {configInfo?.OwnerPackage} - {configInfo?.InternalName}") - .WithError(new ExceptionalError(e)); - } + throw new NotImplementedException(); } public void RegisterTypeInitializers(IConfigService configService) @@ -64,30 +46,30 @@ public class ConfigInitializers : IService if (configService == null) throw new ArgumentNullException($"{nameof(RegisterTypeInitializers)}: {nameof(IConfigService)} is null."); - configService.RegisterTypeInitializer>(this.CreateConfigBool); - configService.RegisterTypeInitializer>(this.CreateConfigSbyte); - configService.RegisterTypeInitializer>(this.CreateConfigByte); - configService.RegisterTypeInitializer>(this.CreateConfigShort); - configService.RegisterTypeInitializer>(this.CreateConfigUShort); - configService.RegisterTypeInitializer>(this.CreateConfigInt32); - configService.RegisterTypeInitializer>(this.CreateConfigUInt32); - configService.RegisterTypeInitializer>(this.CreateConfigInt64); - configService.RegisterTypeInitializer>(this.CreateConfigUInt64); - configService.RegisterTypeInitializer>(this.CreateConfigFloat32); - configService.RegisterTypeInitializer>(this.CreateConfigFloat64); - configService.RegisterTypeInitializer>(this.CreateConfigFloat128); - configService.RegisterTypeInitializer>(this.CreateConfigChar); - configService.RegisterTypeInitializer>(this.CreateConfigString); - configService.RegisterTypeInitializer>(this.CreateConfigColor); - configService.RegisterTypeInitializer>(this.CreateConfigVector2); - configService.RegisterTypeInitializer>(this.CreateConfigVector3); - configService.RegisterTypeInitializer>(this.CreateConfigVector4); + /*configService.RegisterTypeInitializer>(this.CreateConfigBool); + configService.RegisterTypeInitializer>(this.CreateConfigSbyte); + configService.RegisterTypeInitializer>(this.CreateConfigByte); + configService.RegisterTypeInitializer>(this.CreateConfigShort); + configService.RegisterTypeInitializer>(this.CreateConfigUShort); + configService.RegisterTypeInitializer>(this.CreateConfigInt32); + configService.RegisterTypeInitializer>(this.CreateConfigUInt32); + configService.RegisterTypeInitializer>(this.CreateConfigInt64); + configService.RegisterTypeInitializer>(this.CreateConfigUInt64); + configService.RegisterTypeInitializer>(this.CreateConfigFloat32); + configService.RegisterTypeInitializer>(this.CreateConfigFloat64); + configService.RegisterTypeInitializer>(this.CreateConfigFloat128); + configService.RegisterTypeInitializer>(this.CreateConfigChar); + configService.RegisterTypeInitializer>(this.CreateConfigString); + configService.RegisterTypeInitializer>(this.CreateConfigColor); + configService.RegisterTypeInitializer>(this.CreateConfigVector2); + configService.RegisterTypeInitializer>(this.CreateConfigVector3); + configService.RegisterTypeInitializer>(this.CreateConfigVector4);*/ } #region InitializerWrappers_NetworkInjected - private void AssignValueConditional(T val, IConfigEntry inst) where T : IEquatable + private void AssignValueConditional(T val, ISettingEntry inst) where T : IEquatable { #if SERVER if (inst.SyncType is NetSync.None or NetSync.ServerAuthority) @@ -100,7 +82,7 @@ public class ConfigInitializers : IService #endif } - private Result> CreateConfigBool(IConfigInfo configInfo) + private Result> CreateConfigBool(IConfigInfo configInfo) { return CreateConfigEntry(configInfo, (inst, readMsg) => { @@ -112,7 +94,7 @@ public class ConfigInitializers : IService }); } - private Result> CreateConfigSbyte(IConfigInfo configInfo) + private Result> CreateConfigSbyte(IConfigInfo configInfo) { return CreateConfigEntry(configInfo, (inst, readMsg) => { @@ -124,7 +106,7 @@ public class ConfigInitializers : IService }); } - private Result> CreateConfigByte(IConfigInfo configInfo) + private Result> CreateConfigByte(IConfigInfo configInfo) { return CreateConfigEntry(configInfo, (inst, readMsg) => { @@ -136,7 +118,7 @@ public class ConfigInitializers : IService }); } - private Result> CreateConfigShort(IConfigInfo configInfo) + private Result> CreateConfigShort(IConfigInfo configInfo) { return CreateConfigEntry(configInfo, (inst, readMsg) => { @@ -148,7 +130,7 @@ public class ConfigInitializers : IService }); } - private Result> CreateConfigUShort(IConfigInfo configInfo) + private Result> CreateConfigUShort(IConfigInfo configInfo) { return CreateConfigEntry(configInfo, (inst, readMsg) => { @@ -160,7 +142,7 @@ public class ConfigInitializers : IService }); } - private Result> CreateConfigInt32(IConfigInfo configInfo) + private Result> CreateConfigInt32(IConfigInfo configInfo) { return CreateConfigEntry(configInfo, (inst, readMsg) => { @@ -172,7 +154,7 @@ public class ConfigInitializers : IService }); } - private Result> CreateConfigUInt32(IConfigInfo configInfo) + private Result> CreateConfigUInt32(IConfigInfo configInfo) { return CreateConfigEntry(configInfo, (inst, readMsg) => { @@ -184,7 +166,7 @@ public class ConfigInitializers : IService }); } - private Result> CreateConfigInt64(IConfigInfo configInfo) + private Result> CreateConfigInt64(IConfigInfo configInfo) { return CreateConfigEntry(configInfo, (inst, readMsg) => { @@ -196,7 +178,7 @@ public class ConfigInitializers : IService }); } - private Result> CreateConfigUInt64(IConfigInfo configInfo) + private Result> CreateConfigUInt64(IConfigInfo configInfo) { return CreateConfigEntry(configInfo, (inst, readMsg) => { @@ -208,7 +190,7 @@ public class ConfigInitializers : IService }); } - private Result> CreateConfigFloat32(IConfigInfo configInfo) + private Result> CreateConfigFloat32(IConfigInfo configInfo) { return CreateConfigEntry(configInfo, (inst, readMsg) => { @@ -220,7 +202,7 @@ public class ConfigInitializers : IService }); } - private Result> CreateConfigFloat64(IConfigInfo configInfo) + private Result> CreateConfigFloat64(IConfigInfo configInfo) { return CreateConfigEntry(configInfo, (inst, readMsg) => { @@ -232,7 +214,7 @@ public class ConfigInitializers : IService }); } - private Result> CreateConfigFloat128(IConfigInfo configInfo) + private Result> CreateConfigFloat128(IConfigInfo configInfo) { return CreateConfigEntry(configInfo, (inst, readMsg) => { @@ -253,7 +235,7 @@ public class ConfigInitializers : IService }); } - private Result> CreateConfigChar(IConfigInfo configInfo) + private Result> CreateConfigChar(IConfigInfo configInfo) { return CreateConfigEntry(configInfo, (inst, readMsg) => { @@ -265,7 +247,7 @@ public class ConfigInitializers : IService }); } - private Result> CreateConfigString(IConfigInfo configInfo) + private Result> CreateConfigString(IConfigInfo configInfo) { return CreateConfigEntry(configInfo, (inst, readMsg) => { @@ -277,7 +259,7 @@ public class ConfigInitializers : IService }); } - private Result> CreateConfigColor(IConfigInfo configInfo) + private Result> CreateConfigColor(IConfigInfo configInfo) { return CreateConfigEntry(configInfo, (inst, readMsg) => { @@ -289,7 +271,7 @@ public class ConfigInitializers : IService }); } - private Result> CreateConfigVector2(IConfigInfo configInfo) + private Result> CreateConfigVector2(IConfigInfo configInfo) { return CreateConfigEntry(configInfo, (inst, readMsg) => { @@ -302,7 +284,7 @@ public class ConfigInitializers : IService }); } - private Result> CreateConfigVector3(IConfigInfo configInfo) + private Result> CreateConfigVector3(IConfigInfo configInfo) { return CreateConfigEntry(configInfo, (inst, readMsg) => { @@ -316,7 +298,7 @@ public class ConfigInitializers : IService }); } - private Result> CreateConfigVector4(IConfigInfo configInfo) + private Result> CreateConfigVector4(IConfigInfo configInfo) { return CreateConfigEntry(configInfo, (inst, readMsg) => { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs index 8bcdb69fa..88c1f1e32 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs @@ -6,217 +6,318 @@ using System.Collections.Immutable; using System.IO; using System.Linq; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Events; using Barotrauma.LuaCs.Services.Processing; -using Barotrauma.Networking; -using Dynamitey.DynamicObjects; using FluentResults; -using Microsoft.Xna.Framework; -using OneOf; -using Path = Barotrauma.IO.Path; +using Microsoft.Toolkit.Diagnostics; namespace Barotrauma.LuaCs.Services; -public partial class ConfigService : IConfigService +public sealed partial class ConfigService : IConfigService { + #region Disposal_Locks_Reset + + private readonly AsyncReaderWriterLock _operationLock = new (); + private readonly AsyncReaderWriterLock _settingsByPackageLock = new (); + private int _isDisposed = 0; + public bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } + public void Dispose() { - throw new NotImplementedException(); - } + using var lck = _operationLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + using var settingsLck = _settingsByPackageLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) + { + return; + } + + _logger.LogDebug($"{nameof(ConfigService)}: Disposing."); + + _configInfoParserService.Dispose(); + _configProfileInfoParserService.Dispose(); - public bool IsDisposed { get; } + if (!_settingsInstances.IsEmpty) + { + foreach (var instance in _settingsInstances) + { + try + { + if (instance.Value is null) + { + continue; + } + + _eventService.PublishEvent(sub => + // ReSharper disable once AccessToDisposedClosure + sub.OnSettingInstanceDisposed(instance.Value)); + instance.Value.Dispose(); + } + catch + { + // ignored + continue; + } + } + } + + _settingsInstances.Clear(); + _instanceFactory.Clear(); + _settingsInstancesByPackage.Clear(); + + _storageService = null; + _logger = null; + _eventService = null; + _configInfoParserService = null; + _configProfileInfoParserService = null; + } + public FluentResults.Result Reset() { - throw new NotImplementedException(); - } + using var lck = _operationLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + var result = new FluentResults.Result(); + + if (!_settingsInstances.IsEmpty) + { + foreach (var instance in _settingsInstances) + { + try + { + if (instance.Value is null) + { + continue; + } - #region LuaInterface + _eventService.PublishEvent(sub => + // ReSharper disable once AccessToDisposedClosure + sub.OnSettingInstanceDisposed(instance.Value)); + instance.Value.Dispose(); + } + catch (Exception e) + { + result.WithError(new ExceptionalError(e)); + } + } + } + + _settingsInstances.Clear(); + _instanceFactory.Clear(); + _settingsInstancesByPackage.Clear(); - public bool TryGetConfigBool(string packageName, string configName, out bool value) - { - throw new NotImplementedException(); + return result; } - public bool TryGetConfigInt(string packageName, string configName, out int value) - { - throw new NotImplementedException(); - } - - public bool TryGetConfigFloat(string packageName, string configName, out float value) - { - throw new NotImplementedException(); - } - - public bool TryGetConfigNumber(string packageName, string configName, out double value) - { - throw new NotImplementedException(); - } - - public bool TryGetConfigString(string packageName, string configName, out string value) - { - throw new NotImplementedException(); - } - - public bool TryGetConfigVector2(string packageName, string configName, out Vector2 value) - { - throw new NotImplementedException(); - } - - public bool TryGetConfigVector3(string packageName, string configName, out Vector3 value) - { - throw new NotImplementedException(); - } - - public bool TryGetConfigColor(string packageName, string configName, out Color value) - { - throw new NotImplementedException(); - } - - public bool TryGetConfigList(string packageName, string configName, out IReadOnlyList value) - { - throw new NotImplementedException(); - } - - public void SetConfigBool(string packageName, string configName, bool value) - { - throw new NotImplementedException(); - } - - public void SetConfigInt(string packageName, string configName, int value) - { - throw new NotImplementedException(); - } - - public void SetConfigFloat(string packageName, string configName, float value) - { - throw new NotImplementedException(); - } - - public void SetConfigNumber(string packageName, string configName, double value) - { - throw new NotImplementedException(); - } - - public void SetConfigString(string packageName, string configName, string value) - { - throw new NotImplementedException(); - } - - public void SetConfigVector2(string packageName, string configName, Vector2 value) - { - throw new NotImplementedException(); - } - - public void SetConfigVector3(string packageName, string configName, Vector3 value) - { - throw new NotImplementedException(); - } - - public void SetConfigColor(string packageName, string configName, Color value) - { - throw new NotImplementedException(); - } - - public void SetConfigList(string packageName, string configName, string value) - { - throw new NotImplementedException(); - } - - public bool TryApplyProfileSettings(string packageName, string profileName) - { - throw new NotImplementedException(); - } - - #endregion + - public void RegisterTypeInitializer(Func> initializer, bool replaceIfExists = false) where TData : IEquatable where TConfig : IConfigBase + private readonly ConcurrentDictionary<(ContentPackage OwnerPackage, string InternalName), ISettingBase> + _settingsInstances = new(); + private readonly ConcurrentDictionary> + _instanceFactory = new(); + private readonly ConcurrentDictionary> + _settingsInstancesByPackage = new(); + + private IStorageService _storageService; + private ILoggerService _logger; + private IEventService _eventService; + private IParserServiceOneToManyAsync _configInfoParserService; + private IParserServiceOneToManyAsync _configProfileInfoParserService; + + public ConfigService(ILoggerService logger, + IStorageService storageService, + IParserServiceOneToManyAsync configInfoParserService, + IParserServiceOneToManyAsync configProfileInfoParserService, IEventService eventService) { - throw new NotImplementedException(); + _logger = logger; + _storageService = storageService; + _configInfoParserService = configInfoParserService; + _configProfileInfoParserService = configProfileInfoParserService; + _eventService = eventService; + } + + + public void RegisterSettingTypeInitializer(string typeIdentifier, Func settingFactory) where T : class, ISettingBase + { + Guard.IsNotNullOrWhiteSpace(typeIdentifier, nameof(typeIdentifier)); + Guard.IsNotNull(settingFactory, nameof(settingFactory)); + using var lck = _operationLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + if (_instanceFactory.ContainsKey(typeIdentifier)) + { + ThrowHelper.ThrowArgumentException($"{nameof(RegisterSettingTypeInitializer)}: The type identifier {typeIdentifier} is already registered."); + } + + _instanceFactory[typeIdentifier] = settingFactory; } public async Task LoadConfigsAsync(ImmutableArray configResources) { -#if DEBUG - return FluentResults.Result.Ok(); // just for startup testing -#endif - throw new NotImplementedException(); + using var lck = _operationLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + if (configResources.IsDefaultOrEmpty) + { + return FluentResults.Result.Ok(); + } + + var taskBuilder = ImmutableArray.CreateBuilder>>(); + var toProcessErrors = new ConcurrentStack(); + + var taskCtx = TaskScheduler.FromCurrentSynchronizationContext(); + + foreach (var resource in configResources) + { + taskBuilder.Add(await Task.Factory.StartNew>>(async Task> () => + { + var r = await _configInfoParserService.TryParseResourcesAsync(resource); + if (r.IsFailed) + { + toProcessErrors.PushRange(r.Errors.ToArray()); + return ImmutableArray.Empty; + } + return r.Value; + })); + } + + var taskResults = await Task.WhenAll(taskBuilder.MoveToImmutable()); + + if (toProcessErrors.Count > 0) + { + return FluentResults.Result.Fail($"{nameof(LoadConfigsAsync)}: Errors while loading configuration info: ").WithErrors(toProcessErrors.ToArray()); + } + + var toProcessDocs = taskResults + .Where(tr => !tr.IsDefaultOrEmpty) + .SelectMany(tr => tr) + .ToImmutableArray(); + + var instanceQueue = new Queue<(IConfigInfo configInfo, Func factory)>(); + + foreach (var info in toProcessDocs) + { + if (!_instanceFactory.TryGetValue(info.DataType, out var factory)) + { + return FluentResults.Result.Fail($"{nameof(LoadConfigsAsync)}: Could not retrieve the instance factory for the data type of '{info.DataType}'!"); + } + if (_settingsInstances.ContainsKey((info.OwnerPackage, info.InternalName))) + { + // duplicate for some reason (ie. double loading). This should never happen. + ThrowHelper.ThrowInvalidOperationException($"{nameof(LoadConfigsAsync)}: A setting for the [ContentPackage].[InternalName] of '[{info.OwnerPackage.Name}].[{info.InternalName}]' already exists!"); + } + + instanceQueue.Enqueue((info, factory)); + } + + var toProcessInstanceQueue = new Queue<(IConfigInfo info, ISettingBase instance)>(); + + while (instanceQueue.TryDequeue(out var instanceFactoryInfo)) + { + try + { + toProcessInstanceQueue.Enqueue((instanceFactoryInfo.configInfo, instanceFactoryInfo.factory(instanceFactoryInfo.configInfo))); + } + catch (Exception e) + { + FluentResults.Result.Fail( + $"{nameof(LoadConfigsAsync)}: Error while instancing setting for '{instanceFactoryInfo.configInfo.OwnerPackage}.{instanceFactoryInfo.configInfo.InternalName}': {e.Message}!"); + } + } + + using var settingsLck = await _settingsByPackageLock.AcquireWriterLock(); // block to protect new bag instance creation + var result = new FluentResults.Result(); + + while (toProcessInstanceQueue.TryDequeue(out var newInstanceData)) + { + _settingsInstances[(newInstanceData.info.OwnerPackage, newInstanceData.info.InternalName)] = newInstanceData.instance; + if (!_settingsInstancesByPackage.TryGetValue(newInstanceData.info.OwnerPackage, out _)) + { + _settingsInstancesByPackage[newInstanceData.info.OwnerPackage] = new ConcurrentBag(); + } + _settingsInstancesByPackage[newInstanceData.info.OwnerPackage].Add(newInstanceData.instance); + result.WithReasons(_eventService.PublishEvent(sub => + sub.OnSettingInstanceCreated(newInstanceData.instance)).Reasons); + } + + return result; } public async Task LoadConfigsProfilesAsync(ImmutableArray configProfileResources) { #if DEBUG - return FluentResults.Result.Ok(); // just for startup testing + // TODO: Implement profiles. + return FluentResults.Result.Ok(); #endif throw new NotImplementedException(); } - public Result AddConfig(IConfigInfo configInfo) where TConfig : IConfigBase - { - throw new NotImplementedException(); - } - - public FluentResults.Result ApplyProfileSettings(ContentPackage package, string profileName) - { - throw new NotImplementedException(); - } - public FluentResults.Result DisposePackageData(ContentPackage package) { -#if DEBUG - return FluentResults.Result.Ok(); // just for startup testing -#endif - throw new NotImplementedException(); + Guard.IsNotNull(package, nameof(package)); + using var lck = _operationLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + ConcurrentBag toDispose; + using (var settingsLck = _settingsByPackageLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult()) + { + if (!_settingsInstancesByPackage.TryRemove(package, out toDispose) || toDispose is null) + { + return FluentResults.Result.Ok(); + } + } + + var result = new FluentResults.Result(); + + foreach (var setting in toDispose) + { + result.WithReasons(_eventService.PublishEvent(sub => sub.OnSettingInstanceDisposed(setting)).Reasons); + try + { + setting.Dispose(); + } + catch (Exception e) + { + result.WithError(new ExceptionalError(e)); + } + } + + return result; } public FluentResults.Result DisposeAllPackageData() { -#if DEBUG - return FluentResults.Result.Ok(); // just for startup testing -#endif - throw new NotImplementedException(); + return this.Reset(); } - public Result> GetConfigsForPackage(ContentPackage package) + public bool TryGetConfig(ContentPackage package, string internalName, out T instance) where T : ISettingBase { - throw new NotImplementedException(); - } + Guard.IsNotNull(package, nameof(package)); + Guard.IsNotNullOrWhiteSpace(internalName, nameof(internalName)); + using var lck = _operationLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + using var settingsLck = + _settingsByPackageLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + instance = default; + + if(!_settingsInstances.TryGetValue((package, internalName), out var inst)) + { + return false; + } - public Result Value)>>> GetProfilesForPackage(ContentPackage package) - { - throw new NotImplementedException(); - } - - public IReadOnlyDictionary<(ContentPackage Package, string Name), IConfigBase> GetAllConfigs() - { - throw new NotImplementedException(); - } - - public bool TryGetConfig(ContentPackage package, string name, out T config) where T : IConfigBase - { -#if DEBUG - config = default(T); - return true; // just for startup testing -#endif - throw new NotImplementedException(); - } - - public async Task SaveAllConfigs() - { - throw new NotImplementedException(); - } - - public async Task SaveConfigsForPackage(ContentPackage package) - { - throw new NotImplementedException(); - } - - public async Task SaveConfig((ContentPackage Package, string ConfigName) config) - { - throw new NotImplementedException(); + if (inst is not T instanceT) + { + return false; + } + + instance = instanceT; + return true; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs index c237015a7..80ce7628c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs @@ -325,6 +325,9 @@ public class EventService : IEventService, IEventAssemblyContextUnloading } catch (Exception e) { +#if DEBUG + throw; //make errors apparent +#endif errors.Enqueue(new Error($"Error while executing runner for {eventType.Name} on type {eventSub.GetType().Name}.") .WithMetadata(MetadataType.ExceptionObject, this) .WithMetadata(MetadataType.RootObject, eventSub) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IHelperServiceDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IHelperServiceDefinitions.cs index a1ddb3f94..b9d3146f9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IHelperServiceDefinitions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IHelperServiceDefinitions.cs @@ -18,3 +18,8 @@ public interface IParserServiceAsync : IService Task> TryParseResourceAsync(TSrc src); Task>> TryParseResourcesAsync(IEnumerable sources); } + +public interface IParserServiceOneToManyAsync : IService +{ + Task>> TryParseResourcesAsync(TSrc src); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigFileParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigFileParserService.cs similarity index 98% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigFileParserService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigFileParserService.cs index 292ac3eea..1e4399e94 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ConfigFileParserService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigFileParserService.cs @@ -10,7 +10,7 @@ using Microsoft.Toolkit.Diagnostics; namespace Barotrauma.LuaCs.Services.Processing; -public sealed class ConfigFileParserService : +public sealed class ModConfigFileParserService : IParserServiceAsync, IParserServiceAsync, IParserServiceAsync @@ -18,7 +18,7 @@ public sealed class ConfigFileParserService : private IStorageService _storageService; private readonly AsyncReaderWriterLock _operationsLock = new(); - public ConfigFileParserService(IStorageService storageService) + public ModConfigFileParserService(IStorageService storageService) { _storageService = storageService; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/SettingsFileParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/SettingsFileParserService.cs new file mode 100644 index 000000000..3a3f631d0 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/SettingsFileParserService.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using System.Xml.Linq; +using Barotrauma.LuaCs.Data; +using FluentResults; +using Microsoft.Toolkit.Diagnostics; +using OneOf; + +namespace Barotrauma.LuaCs.Services.Processing; + +public sealed class SettingsFileParserService : + IParserServiceOneToManyAsync, + IParserServiceOneToManyAsync +{ + #region DisposalControl + + private AsyncReaderWriterLock _operationLock = new(); + + public void Dispose() + { + using var lck = _operationLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) + { + return; + } + _storageService.Dispose(); + _storageService = null; + } + + private int _isDisposed = 0; + public bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } + + #endregion + + private IStorageService _storageService; + + public SettingsFileParserService(IStorageService storageService) + { + _storageService = storageService; + } + + async Task>> IParserServiceOneToManyAsync + .TryParseResourcesAsync(IConfigResourceInfo src) + { + Guard.IsNotNull(src, nameof(src)); + Guard.IsNotNull(src.OwnerPackage, nameof(src.OwnerPackage)); + using var lck = await _operationLock.AcquireReaderLock(); + IService.CheckDisposed(this); + + if (src.FilePaths.IsDefaultOrEmpty) + { + return ReturnFail($"The config file list is empty."); + } + + var parsedInfo = ImmutableArray.CreateBuilder(); + + foreach ((ContentPath path, Result docLoadResult) res in await _storageService.LoadPackageXmlFilesAsync(src.FilePaths)) + { + if (res.docLoadResult.IsFailed) + { + return ReturnFail($"Failed to load document for {src.OwnerPackage.Name}").WithErrors(res.docLoadResult.Errors); + } + + var settingElements = res.docLoadResult.Value.GetChildElement("Configuration") + .GetChildElements("Settings").SelectMany(e => e.GetChildElements("Setting")).ToImmutableArray(); + if (settingElements.IsDefaultOrEmpty) + { + continue; + } + + var packageIdent = res.path.ContentPackage.ToIdentifier().ToString(); + + foreach (var element in settingElements) + { + var name = element.GetAttributeString("Name", string.Empty); + if (name.IsNullOrWhiteSpace()) + { + return ReturnFail( + $"The internal name for a setting in the config file '{res.path.FullPath}' is empty!"); + } + + var newSetting = new ConfigInfo() + { + InternalName = name, + OwnerPackage = res.path.ContentPackage, + DataType = element.GetAttributeString("Type", string.Empty), + Element = element, + EditableStates = element.GetAttributeBool("AllowChangesWhileExecuting", true) ? RunState.Running : + element.GetAttributeBool("ReadOnly", false) ? RunState.LoadedNoExec : + RunState.Unloaded, + NetSync = element.GetAttributeEnum("NetSync", NetSync.None), +#if CLIENT + DisplayName = $"{packageIdent}.{name}.DisplayName", + Description = $"{packageIdent}.{name}.Description", + DisplayCategory = $"{packageIdent}.{name}.DisplayCategory", + ShowInMenus = element.GetAttributeBool("ShowInMenus", true), + Tooltip = $"{packageIdent}.{name}.Tooltip", + ImageIconPath = element.GetAttributeString("ImageIcon", string.Empty) is {} val && !val.IsNullOrWhiteSpace() ? + ContentPath.FromRaw(res.path.ContentPackage, val) : ContentPath.Empty +#endif + }; + if (!IsInfoValid(newSetting)) + { + return ReturnFail($"A setting was invalid. ContentPackage: {res.path.ContentPackage}"); + } + parsedInfo.Add(newSetting); + } + } + + return FluentResults.Result.Ok(parsedInfo.MoveToImmutable()); + + // Helpers + + FluentResults.Result ReturnFail(string msg) + { + return FluentResults.Result.Fail($"{nameof(IParserServiceOneToManyAsync.TryParseResourcesAsync)}: {msg}"); + } + + bool IsInfoValid(ConfigInfo info) + { + return info.OwnerPackage != null + && !info.InternalName.IsNullOrWhiteSpace() + && !info.DataType.IsNullOrWhiteSpace() + && !info.DataType.IsNullOrWhiteSpace() + && info.Element != null +#if CLIENT + && !info.DisplayName.IsNullOrWhiteSpace() + && !info.Description.IsNullOrWhiteSpace() + && !info.DisplayCategory.IsNullOrWhiteSpace() + && !info.Tooltip.IsNullOrWhiteSpace() +#endif + ; + } + } + + async Task>> + IParserServiceOneToManyAsync + .TryParseResourcesAsync(IConfigResourceInfo src) + { + Guard.IsNotNull(src, nameof(src)); + Guard.IsNotNull(src.OwnerPackage, nameof(src.OwnerPackage)); + using var lck = await _operationLock.AcquireReaderLock(); + IService.CheckDisposed(this); + + if (src.FilePaths.IsDefaultOrEmpty) + { + return ReturnFail($"The config file list is empty."); + } + + var parsedInfo = ImmutableArray.CreateBuilder(); + + foreach ((ContentPath path, Result docLoadResult) res in await _storageService + .LoadPackageXmlFilesAsync(src.FilePaths)) + { + if (res.docLoadResult.IsFailed) + { + return ReturnFail($"Failed to load document for {src.OwnerPackage.Name}") + .WithErrors(res.docLoadResult.Errors); + } + + var profileCollection = res.docLoadResult.Value.GetChildElement("Configuration") + .GetChildElement("Profiles"); + if (profileCollection == null) + { + continue; + } + + foreach (var profile in profileCollection.GetChildElements("Profile")) + { + var profileName = profile.GetAttributeString("Name", string.Empty); + Guard.IsNotNullOrWhiteSpace(profileName, nameof(profileName)); + + var settingValues = profile.GetChildElements("SettingValue").ToImmutableArray(); + if (settingValues.IsDefaultOrEmpty) + { + ThrowHelper.ThrowArgumentNullException(nameof(settingValues)); + } + + var profileValuesBuilder = ImmutableArray.CreateBuilder<(string ConfigName, XElement Value)>(); + + foreach (var settingValue in settingValues) + { + var cfgName = settingValue.GetAttributeString("Name", string.Empty); + Guard.IsNotNullOrWhiteSpace(cfgName, nameof(cfgName)); + profileValuesBuilder.Add((cfgName, settingValue)); + } + + parsedInfo.Add(new ConfigProfileInfo() + { + InternalName = profileName, + OwnerPackage = res.path.ContentPackage, + ProfileValues = profileValuesBuilder.MoveToImmutable() + }); + } + } + + return parsedInfo.MoveToImmutable(); + + FluentResults.Result ReturnFail(string msg) + { + return FluentResults.Result.Fail($"{nameof(IParserServiceOneToManyAsync.TryParseResourcesAsync)}: {msg}"); + } + } + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs index 182f4fc4e..f349e0f6a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs @@ -6,27 +6,5 @@ namespace Barotrauma.LuaCs.Services.Safe; public interface ILuaConfigService : ILuaService { - // get values - bool TryGetConfigBool(string packageName, string configName, out bool value); - bool TryGetConfigInt(string packageName, string configName, out int value); - bool TryGetConfigFloat(string packageName, string configName, out float value); - bool TryGetConfigNumber(string packageName, string configName, out double value); - bool TryGetConfigString(string packageName, string configName, out string value); - bool TryGetConfigVector2(string packageName, string configName, out Vector2 value); - bool TryGetConfigVector3(string packageName, string configName, out Vector3 value); - bool TryGetConfigColor(string packageName, string configName, out Color value); - bool TryGetConfigList(string packageName, string configName, out IReadOnlyList value); - // set values - void SetConfigBool(string packageName, string configName, bool value); - void SetConfigInt(string packageName, string configName, int value); - void SetConfigFloat(string packageName, string configName, float value); - void SetConfigNumber(string packageName, string configName, double value); - void SetConfigString(string packageName, string configName, string value); - void SetConfigVector2(string packageName, string configName, Vector2 value); - void SetConfigVector3(string packageName, string configName, Vector3 value); - void SetConfigColor(string packageName, string configName, Color value); - void SetConfigList(string packageName, string configName, string value); - // profiles - bool TryApplyProfileSettings(string packageName, string profileName); - + } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs index 38498830b..cddaa17bb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs @@ -15,33 +15,18 @@ namespace Barotrauma.LuaCs.Services; public partial interface IConfigService : IReusableService, ILuaConfigService { + void RegisterSettingTypeInitializer(string typeIdentifier, Func settingFactory) + where T : class, ISettingBase; /// - /// Registers a type initializer from instancing config types by indicated type from config. + /// /// - /// - /// - /// The as parsed from the configuration info. - /// The resulting configuration instance. - void RegisterTypeInitializer(Func> initializer, bool replaceIfExists = false) - where TData : IEquatable where TConfig : IConfigBase; - - // Config Files/Resources + /// + /// + /// + /// Task LoadConfigsAsync(ImmutableArray configResources); Task LoadConfigsProfilesAsync(ImmutableArray configProfileResources); - - // Immediate Mode - FluentResults.Result AddConfig(IConfigInfo configInfo) where TConfig : IConfigBase; - - // Utility - FluentResults.Result ApplyProfileSettings(ContentPackage package, string profileName); FluentResults.Result DisposePackageData(ContentPackage package); FluentResults.Result DisposeAllPackageData(); - FluentResults.Result> GetConfigsForPackage(ContentPackage package); - FluentResults.Result Value)>>> - GetProfilesForPackage(ContentPackage package); - IReadOnlyDictionary<(ContentPackage Package, string Name), IConfigBase> GetAllConfigs(); - bool TryGetConfig(ContentPackage package, string name, out T config) where T : IConfigBase; - Task SaveAllConfigs(); - Task SaveConfigsForPackage(ContentPackage package); - Task SaveConfig((ContentPackage Package, string ConfigName) config); + bool TryGetConfig(ContentPackage package, string internalName, out T instance) where T : ISettingBase; } From 09faa8403a061733984cd20c459633a8239ccea4 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 22 Jan 2026 17:04:09 -0500 Subject: [PATCH 047/288] Fixed StatemMachine State delegate assignment being backwards. --- .../BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs | 6 +----- .../BarotraumaShared/SharedSource/LuaCs/StateMachine.cs | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index c0811c934..343494749 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -41,8 +41,6 @@ namespace Barotrauma } _runStateMachine = SetupStateMachine(); SubscribeToLuaCsEvents(); - SetRunState(RunState.LoadedNoExec); - LoadLuaCsConfig(); } bool ValidateLuaCsContent() @@ -258,7 +256,7 @@ namespace Barotrauma Logger.LogResults(PackageManagementService.StopRunningPackages()); } - if (PackageManagementService.IsAnyPackageRunning()) + if (PackageManagementService.IsAnyPackageLoaded()) { DisposeLuaCsConfig(); Logger.LogResults(PackageManagementService.UnloadAllPackages()); @@ -353,9 +351,7 @@ namespace Barotrauma /// /// The new game screen. public partial void OnScreenSelected(Screen screen); - - void DisposeLuaCsConfig() { IsCsEnabled = null; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/StateMachine.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/StateMachine.cs index 79dc5779f..99f8ac1fe 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/StateMachine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/StateMachine.cs @@ -87,7 +87,7 @@ public class State where T : Enum { public T StateId; private Action> _onEnter, _onExit; - public State(T stateId, Action> onExitState, Action> onEnterState) + public State(T stateId, Action> onEnterState, Action> onExitState) { StateId = stateId; _onEnter = onEnterState; From 15e0a2bd107624641606e349adf97cca5ba828cb Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 22 Jan 2026 17:29:53 -0500 Subject: [PATCH 048/288] Disabled LuaCsConfig until ModConfig.xml and ISettingEntry are completed. --- Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 343494749..699543597 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -275,7 +275,8 @@ namespace Barotrauma if (!PackageManagementService.IsAnyPackageLoaded()) { Logger.LogResults(PackageManagementService.LoadPackagesInfo(ContentPackageManager.EnabledPackages.All.ToImmutableArray())); - LoadLuaCsConfig(); + // TODO: Implement full xml content necessary for this to work. + //LoadLuaCsConfig(); } CurrentRunState = RunState.LoadedNoExec; From 6e66a3114a864a883eb4122e2ea5d0d76f8d3313 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 23 Jan 2026 13:48:01 -0500 Subject: [PATCH 049/288] - Made Package loading conditional on resources being available. - Made States registration use named parameters. - Changed IPluginManagementService interface to better suit expected return results. --- .../ClientSource/LuaCs/LuaCsSettingsMenu.cs | 20 ++-- .../ClientSource/LuaCs/LuaCsSetup.cs | 8 +- .../Characters/CharacterNetworking.cs | 2 +- .../LuaCs/Lua/LuaClasses/LuaCsUtility.cs | 2 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 105 ++++++++++++------ .../Services/PackageManagementService.cs | 51 +++++---- .../LuaCs/Services/PluginManagementService.cs | 20 +++- .../_Interfaces/IPluginManagementService.cs | 2 +- .../SharedSource/LuaCs/StateMachine.cs | 2 +- 9 files changed, 136 insertions(+), 76 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs index f1e5985bb..b6408d2a4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs @@ -19,55 +19,55 @@ namespace Barotrauma new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Enable CSharp Scripting") { - Selected = GameMain.LuaCs.IsCsEnabled?.Value ?? false, + Selected = GameMain.LuaCs.IsCsEnabled, ToolTip = "This enables CSharp Scripting for mods to use, WARNING: CSharp is NOT sandboxed, be careful with what mods you download.", OnSelected = (GUITickBox tick) => { - GameMain.LuaCs.IsCsEnabled?.TrySetValue(tick.Selected); + GameMain.LuaCs.IsCsEnabled = tick.Selected; return true; } }; new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Treat Forced Mods As Normal") { - Selected = GameMain.LuaCs.TreatForcedModsAsNormal?.Value ?? false, + Selected = GameMain.LuaCs.TreatForcedModsAsNormal, ToolTip = "This makes mods that were setup to run even when disabled to only run when enabled.", OnSelected = (GUITickBox tick) => { - GameMain.LuaCs.TreatForcedModsAsNormal?.TrySetValue(tick.Selected); + GameMain.LuaCs.TreatForcedModsAsNormal = tick.Selected; return true; } }; new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Prefer To Use Workshop Lua Setup") { - Selected = GameMain.LuaCs.PreferToUseWorkshopLuaSetup?.Value ?? false, + Selected = GameMain.LuaCs.PreferToUseWorkshopLuaSetup, ToolTip = "This makes Lua look first for the Lua/LuaSetup.lua located in the Workshop package instead of the one located locally.", OnSelected = (GUITickBox tick) => { - GameMain.LuaCs.PreferToUseWorkshopLuaSetup?.TrySetValue(tick.Selected); + GameMain.LuaCs.PreferToUseWorkshopLuaSetup = tick.Selected; return true; } }; new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Disable Error GUI Overlay") { - Selected = GameMain.LuaCs.DisableErrorGUIOverlay?.Value ?? false, + Selected = GameMain.LuaCs.DisableErrorGUIOverlay, ToolTip = "", OnSelected = (GUITickBox tick) => { - GameMain.LuaCs.DisableErrorGUIOverlay?.TrySetValue(tick.Selected); + GameMain.LuaCs.DisableErrorGUIOverlay = tick.Selected; return true; } }; new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Hide usernames In Error Logs") { - Selected = GameMain.LuaCs.HideUserNamesInLogs?.Value ?? false, + Selected = GameMain.LuaCs.HideUserNamesInLogs, ToolTip = "Hides the operating system username when displaying error logs (eg your username on windows).", OnSelected = (GUITickBox tick) => { - GameMain.LuaCs.HideUserNamesInLogs?.TrySetValue(tick.Selected); + GameMain.LuaCs.HideUserNamesInLogs = tick.Selected; return true; } }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index 4a30e21f4..35dafe4e2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -15,7 +15,7 @@ namespace Barotrauma { public void AddToGUIUpdateList() { - if (!DisableErrorGUIOverlay.Value) + if (!DisableErrorGUIOverlay) { LuaCsLogger.AddToGUIUpdateList(); } @@ -28,7 +28,7 @@ namespace Barotrauma public bool CheckCsEnabled() { // fast exit if enabled or unavailable. - if (this.IsCsEnabled?.Value ?? true ) + if (this.IsCsEnabled) { return false; } @@ -60,14 +60,14 @@ namespace Barotrauma msg.Buttons[0].OnClicked = (GUIButton button, object obj) => { - this.IsCsEnabled.TrySetValue(true); + this.IsCsEnabled = true; isCsValueChanged = true; return true; }; msg.Buttons[1].OnClicked = (GUIButton button, object obj) => { - this.IsCsEnabled.TrySetValue(false); + this.IsCsEnabled = false; return true; }; diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs index ea7816fba..560dd223e 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs @@ -815,7 +815,7 @@ namespace Barotrauma var tempBuffer = new ReadWriteMessage(); WriteStatus(tempBuffer, forceAfflictionData: true); - if (msgLengthBeforeStatus + tempBuffer.LengthBytes >= 255 && restrictMessageSize && (GameMain.LuaCs.RestrictMessageSize?.Value ?? false)) + if (msgLengthBeforeStatus + tempBuffer.LengthBytes >= 255 && restrictMessageSize && (GameMain.LuaCs.RestrictMessageSize)) { msg.WriteBoolean(false); if (msgLengthBeforeStatus < 255) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsUtility.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsUtility.cs index f801a23ef..fd797cfc7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsUtility.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsUtility.cs @@ -60,7 +60,7 @@ namespace Barotrauma foreach (var package in ContentPackageManager.AllPackages) { - if (package.UgcId.ValueEquals(new SteamWorkshopId(GameMain.LuaCs.LuaForBarotraumaSteamId?.Value ?? 0ul)) + if (package.UgcId.ValueEquals(new SteamWorkshopId(GameMain.LuaCs.LuaForBarotraumaSteamId)) && pathStartsWith(getFullPath(package.Path))) { return false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 699543597..f19a3a5c2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -5,6 +5,7 @@ using System.Collections.Immutable; using System.IO; using System.Linq; using System.Net.Mime; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Barotrauma.LuaCs; @@ -93,60 +94,102 @@ namespace Barotrauma public LuaGame Game => _servicesProvider.GetService(); internal IStorageService StorageService => _servicesProvider.GetService(); - + /// /// Whether C# plugin code is enabled. /// - internal ISettingEntry IsCsEnabled { get; private set; } - + public bool IsCsEnabled + { + get => _isCsEnabled?.Value ?? false; + internal set => _isCsEnabled?.TrySetValue(value); + } + + private ISettingEntry _isCsEnabled; + /// /// Whether mods marked as 'forced' or 'always load' should only be loaded if they're in the enabled mods list. /// - internal ISettingEntry TreatForcedModsAsNormal { get; private set; } - + public bool TreatForcedModsAsNormal + { + get => _treatForcedModsAsNormal?.Value ?? true; + internal set => _treatForcedModsAsNormal?.TrySetValue(value); + } + + private ISettingEntry _treatForcedModsAsNormal; + /// /// Whether the lua script runner from Workshop package should be used over the in-built version. /// - internal ISettingEntry PreferToUseWorkshopLuaSetup { get; private set; } - + public bool PreferToUseWorkshopLuaSetup + { + get => _preferToUseWorkshopLuaSetup?.Value ?? false; + internal set => _preferToUseWorkshopLuaSetup?.TrySetValue(value); + } + private ISettingEntry _preferToUseWorkshopLuaSetup; + /// /// Whether the popup error GUI should be hidden/suppressed. /// - internal ISettingEntry DisableErrorGUIOverlay { get; private set; } - + public bool DisableErrorGUIOverlay + { + get => _disableErrorGUIOverlay?.Value ?? false; + internal set => _disableErrorGUIOverlay?.TrySetValue(value); + } + private ISettingEntry _disableErrorGUIOverlay; + /// /// Whether usernames are anonymized or show in logs. /// - internal ISettingEntry HideUserNamesInLogs { get; private set; } - + public bool HideUserNamesInLogs + { + get => _hideUserNamesInLogs?.Value ?? false; + internal set => _hideUserNamesInLogs?.TrySetValue(value); + } + private ISettingEntry _hideUserNamesInLogs; + /// /// The SteamId of the Workshop LuaCs CPackage in use, if available. /// - internal ISettingEntry LuaForBarotraumaSteamId { get; private set; } - + public ulong LuaForBarotraumaSteamId + { + get => _luaForBarotraumaSteamId?.Value ?? 0; + internal set => _luaForBarotraumaSteamId?.TrySetValue(value); + } + private ISettingEntry _luaForBarotraumaSteamId; + /// /// TODO: @evilfactory@users.noreply.github.com /// - internal ISettingEntry RestrictMessageSize { get; private set; } - + public bool RestrictMessageSize + { + get => _restrictMessageSize?.Value ?? false; + internal set => _restrictMessageSize?.TrySetValue(value); + } + private ISettingEntry _restrictMessageSize; + /// /// The local save path for all local data storage for mods. /// - internal ISettingEntry LocalDataSavePath { get; private set; } + public string LocalDataSavePath + { + get => _localDataSavePath?.Value ?? Path.Combine(Directory.GetCurrentDirectory(), "/Data/Mods"); + internal set => _localDataSavePath?.TrySetValue(value); + } + private ISettingEntry _localDataSavePath; void LoadLuaCsConfig() { - IsCsEnabled = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "IsCsEnabled", out var val1) ? val1 + _isCsEnabled = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "IsCsEnabled", out var val1) ? val1 : throw new NullReferenceException($"{nameof(IsCsEnabled)} cannot be loaded."); - TreatForcedModsAsNormal = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "TreatForcedModsAsNormal", out var val2) ? val2 + _treatForcedModsAsNormal = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "TreatForcedModsAsNormal", out var val2) ? val2 : throw new NullReferenceException($"{nameof(TreatForcedModsAsNormal)} cannot be loaded."); - DisableErrorGUIOverlay = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "DisableErrorGUIOverlay", out var val3) ? val3 + _disableErrorGUIOverlay = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "DisableErrorGUIOverlay", out var val3) ? val3 : throw new NullReferenceException($"{nameof(DisableErrorGUIOverlay)} cannot be loaded."); - HideUserNamesInLogs = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "HideUserNamesInLogs", out var val4) ? val4 + _hideUserNamesInLogs = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "HideUserNamesInLogs", out var val4) ? val4 : throw new NullReferenceException($"{nameof(HideUserNamesInLogs)} cannot be loaded."); - LuaForBarotraumaSteamId = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "LuaForBarotraumaSteamId", out var val5) ? val5 + _luaForBarotraumaSteamId = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "LuaForBarotraumaSteamId", out var val5) ? val5 : throw new NullReferenceException($"{nameof(LuaForBarotraumaSteamId)} cannot be loaded."); - RestrictMessageSize = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "RestrictMessageSize", out var val7) ? val7 + _restrictMessageSize = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "RestrictMessageSize", out var val7) ? val7 : throw new NullReferenceException($"{nameof(RestrictMessageSize)} cannot be loaded."); } @@ -244,9 +287,9 @@ namespace Barotrauma private StateMachine SetupStateMachine() { - return new StateMachine(false, RunState.Unloaded, RunStateUnloaded_OnEnter, null) - .AddState(RunState.LoadedNoExec, RunStateLoadedNoExec_OnEnter, null) - .AddState(RunState.Running, RunStateRunning_OnEnter, RunStateRunning_OnExit); + return new StateMachine(false, RunState.Unloaded, onEnter: RunStateUnloaded_OnEnter, null) + .AddState(RunState.LoadedNoExec, onEnter: RunStateLoadedNoExec_OnEnter, null) + .AddState(RunState.Running, onEnter: RunStateRunning_OnEnter, RunStateRunning_OnExit); // ReSharper disable InconsistentNaming void RunStateUnloaded_OnEnter(State currentState) @@ -355,12 +398,12 @@ namespace Barotrauma void DisposeLuaCsConfig() { - IsCsEnabled = null; - TreatForcedModsAsNormal = null; - DisableErrorGUIOverlay = null; - HideUserNamesInLogs = null; - LuaForBarotraumaSteamId = null; - RestrictMessageSize = null; + _isCsEnabled = null; + _treatForcedModsAsNormal = null; + _disableErrorGUIOverlay = null; + _hideUserNamesInLogs = null; + _luaForBarotraumaSteamId = null; + _restrictMessageSize = null; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index 22a3626b6..a16ec5e50 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -151,17 +151,28 @@ public sealed class PackageManagementService : IPackageManagementService try { var res = new FluentResults.Result(); - var configsTask = new Task>(async Task () => - new FluentResults.Result() - .WithReasons((await _configService.LoadConfigsAsync(config.Configs)).Reasons) - .WithReasons((await _configService.LoadConfigsProfilesAsync(config.Configs)).Reasons)); - var luaScriptsTask = new Task>(async () => - await _luaScriptManagementService.LoadScriptResourcesAsync(config.LuaScripts)); + var tasks = ImmutableArray.CreateBuilder>>(); + + if (!config.Configs.IsDefaultOrEmpty) + { + tasks.Add(Task.Factory.StartNew(async Task () => + new FluentResults.Result() + .WithReasons((await _configService.LoadConfigsAsync(config.Configs)).Reasons) + .WithReasons((await _configService.LoadConfigsProfilesAsync(config.Configs)).Reasons))); + } + + if (!config.LuaScripts.IsDefaultOrEmpty) + { + tasks.Add(Task.Factory.StartNew(async () => + await _luaScriptManagementService.LoadScriptResourcesAsync(config.LuaScripts))); + } + + if (tasks.Count == 0) + { + return FluentResults.Result.Ok(); + } - configsTask.Start(); - luaScriptsTask.Start(); - - var r = Task.WhenAll(configsTask, luaScriptsTask).ConfigureAwait(false).GetAwaiter().GetResult(); + var r = Task.WhenAll(tasks.ToArray()).ConfigureAwait(false).GetAwaiter().GetResult(); foreach (var task in r) res.WithReasons(task.ConfigureAwait(false).GetAwaiter().GetResult().Reasons); @@ -180,7 +191,9 @@ public sealed class PackageManagementService : IPackageManagementService IService.CheckDisposed(this); if (executionOrder.IsDefaultOrEmpty) + { return FluentResults.Result.Fail($"{nameof(ExecuteLoadedPackages)}: No packages in the execution order list."); + } if (!_runningPackages.IsEmpty) { @@ -190,7 +203,9 @@ public sealed class PackageManagementService : IPackageManagementService } if (_loadedPackages.IsEmpty) + { return FluentResults.Result.Fail($"{nameof(ExecuteLoadedPackages)}: No packages loaded. Nothing to run!)"); + } var result = new FluentResults.Result(); @@ -198,24 +213,16 @@ public sealed class PackageManagementService : IPackageManagementService var loadingOrderedPackages = _loadedPackages.OrderBy(pkg => executionOrder.IndexOf(pkg.Key)) .ToImmutableArray(); - //mod settings - var settings = loadingOrderedPackages - .SelectMany(pkg => pkg.Value.Configs.OrderBy(scr => scr.LoadPriority)) - .ToImmutableArray(); - if (!settings.IsDefaultOrEmpty) - { - result.WithReasons(_configService.LoadConfigsAsync(settings).ConfigureAwait(false).GetAwaiter() - .GetResult().Reasons); - result.WithReasons(_configService.LoadConfigsProfilesAsync(settings).ConfigureAwait(false) - .GetAwaiter().GetResult().Reasons); - } + // NOTE: Config/Settings are instanced in LoadPackages() //lua scripts var luaScripts = loadingOrderedPackages .SelectMany(pkg => pkg.Value.LuaScripts.OrderBy(scr => scr.LoadPriority)) .ToImmutableArray(); if (!luaScripts.IsDefaultOrEmpty) + { result.WithReasons(_luaScriptManagementService.ExecuteLoadedScripts(luaScripts).Reasons); + } if (_runConfig.IsCsEnabled) { @@ -223,7 +230,9 @@ public sealed class PackageManagementService : IPackageManagementService loadingOrderedPackages.SelectMany(pkg => pkg.Value.Assemblies.OrderBy(scr => scr.LoadPriority)) .ToImmutableArray(); if (!plugins.IsDefaultOrEmpty) + { result.WithReasons(_pluginManagementService.LoadAssemblyResources(plugins).Reasons); + } } return result; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 667447b3f..9cd47e486 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -13,6 +13,7 @@ using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Events; using FluentResults; using FluentResults.LuaCs; +using ImpromptuInterface.Build; using Microsoft.CodeAnalysis; using OneOf; @@ -21,14 +22,15 @@ namespace Barotrauma.LuaCs.Services; public class PluginManagementService : IPluginManagementService, IAssemblyManagementService { private readonly Func _assemblyLoaderServiceFactory; - private readonly ConcurrentDictionary ResourceInfos, IAssemblyLoaderService Loader)> _packageAssemblyResources; - private readonly ConcurrentDictionary> _pluginInstances; + private readonly ConcurrentDictionary ResourceInfos, IAssemblyLoaderService Loader)> _packageAssemblyResources = new(); + private readonly ConcurrentDictionary> _pluginInstances = new(); private readonly Lazy _eventService; - private readonly ConditionalWeakTable _unloadingAssemblyLoaders; - private readonly ConditionalWeakTable> _assemblyTypesCache; + private readonly ConditionalWeakTable _unloadingAssemblyLoaders = new(); + private readonly ConditionalWeakTable> _assemblyTypesCache = new(); public PluginManagementService( - Func assemblyLoaderServiceFactory, + Func assemblyLoaderServiceFactory, Lazy eventService) { _assemblyLoaderServiceFactory = assemblyLoaderServiceFactory; @@ -123,14 +125,20 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage throw new NotImplementedException(); } - public Result> LoadAssemblyResources(ImmutableArray resource) + public FluentResults.Result LoadAssemblyResources(ImmutableArray resource) { +#if DEBUG + return FluentResults.Result.Fail($"{nameof(LoadAssemblyResources)}: Plugin loading not currently implemented."); +#endif throw new NotImplementedException(); } public ImmutableArray> ActivateTypeInstances(ImmutableArray types, bool serviceInjection = true, bool hostInstanceReference = false) where T : IDisposable { +#if DEBUG + return ImmutableArray>.Empty; +#endif throw new NotImplementedException(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs index 57648ccd1..81ebe8f0d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs @@ -37,7 +37,7 @@ public interface IPluginManagementService : IReusableService ///
/// /// Success/Failure and list of failed resources, if any. - FluentResults.Result> LoadAssemblyResources(ImmutableArray resource); + FluentResults.Result LoadAssemblyResources(ImmutableArray resource); /// /// Creates instances of the given type and provides Property Injection and instance reference caching. Disposes of diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/StateMachine.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/StateMachine.cs index 99f8ac1fe..809a2f9c1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/StateMachine.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/StateMachine.cs @@ -30,7 +30,7 @@ public class StateMachine where T : Enum ThrowHelper.ThrowArgumentException($"State with id {stateId} already exists."); } - _states[stateId] = new State(stateId, onEnter, onExit); + _states[stateId] = new State(stateId, onEnterState: onEnter, onExitState: onExit); return this; } From ee0988588f3aae83c60559f16534e7ac99ab6284 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 24 Jan 2026 15:41:31 -0300 Subject: [PATCH 050/288] Add ModConfig for test mod --- .../[DebugOnlyTest]TestLuaMod/Lua/{Autorun => }/init.lua | 0 .../LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml | 4 ++++ 2 files changed, 4 insertions(+) rename Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/{Autorun => }/init.lua (100%) create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/Autorun/init.lua b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua similarity index 100% rename from Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/Autorun/init.lua rename to Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml new file mode 100644 index 000000000..83469054f --- /dev/null +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From 6faf1a0fcba66d2532b26683aa1e710587a3f282 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 24 Jan 2026 15:52:29 -0300 Subject: [PATCH 051/288] Fix perfidious task moment --- .../LuaCs/Services/Processing/ModConfigService.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs index 6025fce08..f1329f262 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; +using Barotrauma.Extensions; using Barotrauma.LuaCs.Data; using FluentResults; using Microsoft.Toolkit.Diagnostics; @@ -120,12 +121,15 @@ public sealed class ModConfigService : IModConfigService ImmutableArray configResources = default; ImmutableArray luaResources = default; - var res = await Task.WhenAll(new[] + var tasks = new[] { new Task(async () => assemblyResources = await GetAssembliesFromXml(owner, src)), new Task(async () => configResources = await GetConfigsFromXml(owner, src)), new Task(async () => luaResources = await GetLuaScriptsFromXml(owner, src)), - }); + }; + + tasks.ForEach(t => t.Start()); + var res = await Task.WhenAll(tasks); bool isFaulted = false; foreach (var task in res) From 8dc58445f989a75fdeed7323d7c3002fb8584ca1 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 24 Jan 2026 19:01:59 -0300 Subject: [PATCH 052/288] MoveToImmutable -> ToImmutable --- .../LuaCs/Services/Processing/ModConfigService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs index f1329f262..cd9571aa4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs @@ -193,7 +193,7 @@ public sealed class ModConfigService : IModConfigService } resources.Add(result.Value); } - return resources.MoveToImmutable(); + return resources.ToImmutable(); } ImmutableArray GetResourceElementsWithName(ContentPackage package, XElement root, string elemName, string groupName) @@ -221,7 +221,7 @@ public sealed class ModConfigService : IModConfigService } } - return elems.MoveToImmutable(); + return elems.ToImmutable(); } ImmutableArray GetDependencyIdentifiers(XElement fg, bool depsLoadedSetting) From d0f5cb87e799f6338f9a941c1e089429b68e337d Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 25 Jan 2026 14:50:29 -0500 Subject: [PATCH 053/288] - Fixed resource data not returning into the correct context. --- .../Processing/ModConfigFileParserService.cs | 46 +++++++------ .../Services/Processing/ModConfigService.cs | 64 ++++++++----------- 2 files changed, 50 insertions(+), 60 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigFileParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigFileParserService.cs index 1e4399e94..dd2059909 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigFileParserService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigFileParserService.cs @@ -53,14 +53,14 @@ public sealed class ModConfigFileParserService : // --- Assemblies async Task> IParserServiceAsync.TryParseResourceAsync(ResourceParserInfo src) { - using var lck = await _operationsLock.AcquireWriterLock(); + using var lck = await _operationsLock.AcquireReaderLock(); IService.CheckDisposed(this); if (CheckThrowNullRefs(src, "Assembly") is { IsFailed: true } fail) return fail; var runtimeEnv = GetRuntimeEnvironment(src.Element); - var fileResults = await GetCheckedFiles(src.Element, src.Owner, ".dll"); + var fileResults = await UnsafeGetCheckedFiles(src.Element, src.Owner, ".dll"); if (fileResults.IsFailed) return FluentResults.Result.Fail(fileResults.Errors); @@ -91,15 +91,14 @@ public sealed class ModConfigFileParserService : async Task> IParserServiceAsync.TryParseResourceAsync(ResourceParserInfo src) { - - using var lck = await _operationsLock.AcquireWriterLock(); + using var lck = await _operationsLock.AcquireReaderLock(); IService.CheckDisposed(this); if (CheckThrowNullRefs(src, "Config") is { IsFailed: true } fail) return fail; var runtimeEnv = GetRuntimeEnvironment(src.Element); - var fileResults = await GetCheckedFiles(src.Element, src.Owner, ".xml"); + var fileResults = await UnsafeGetCheckedFiles(src.Element, src.Owner, ".xml"); if (fileResults.IsFailed) return FluentResults.Result.Fail(fileResults.Errors); @@ -126,14 +125,14 @@ public sealed class ModConfigFileParserService : // --- Lua Scripts async Task> IParserServiceAsync.TryParseResourceAsync(ResourceParserInfo src) { - using var lck = await _operationsLock.AcquireWriterLock(); + using var lck = await _operationsLock.AcquireReaderLock(); IService.CheckDisposed(this); if (CheckThrowNullRefs(src, "Lua") is { IsFailed: true } fail) return fail; var runtimeEnv = GetRuntimeEnvironment(src.Element); - var fileResults = await GetCheckedFiles(src.Element, src.Owner, ".lua"); + var fileResults = await UnsafeGetCheckedFiles(src.Element, src.Owner, ".lua"); if (fileResults.IsFailed) return FluentResults.Result.Fail(fileResults.Errors); @@ -174,34 +173,39 @@ public sealed class ModConfigFileParserService : } // --- Helpers - private async Task>> GetCheckedFiles(XElement srcElement, ContentPackage srcOwner, string fileExtension) + private async Task>> UnsafeGetCheckedFiles(XElement srcElement, ContentPackage srcOwner, string fileExtension) { - using var lck = await _operationsLock.AcquireWriterLock(); - IService.CheckDisposed(this); - var builder = ImmutableArray.CreateBuilder(); - var filePath = srcElement.GetAttributeString("File", string.Empty); - var folderPath = srcElement.GetAttributeString("Folder", string.Empty); + var filePath = srcElement.GetAttributeContentPath("File", srcOwner); + var folderPath = srcElement.GetAttributeContentPath("Folder", srcOwner); + var res = new FluentResults.Result>(); + if (!filePath.IsNullOrWhiteSpace()) { - var cp = ContentPath.FromRaw(srcOwner, filePath); - if (_storageService.FileExists(cp.FullPath) is { IsSuccess: true, Value: true }) + if (_storageService.FileExists(filePath.FullPath) is { IsSuccess: true, Value: true }) { - builder.Add(cp); + builder.Add(filePath); + } + else + { + res.WithError($"{srcOwner.Name}: The file '{filePath}' is missing!"); } } if (!folderPath.IsNullOrWhiteSpace()) { - var cp = ContentPath.FromRaw(srcOwner, folderPath); - if (_storageService.DirectoryExists(cp.FullPath) is { IsSuccess: true, Value: true }) + if (_storageService.DirectoryExists(folderPath.FullPath) is { IsSuccess: true, Value: true }) { - var files = _storageService.FindFilesInPackage(cp.ContentPackage, cp.Value, fileExtension, true); + var files = _storageService.FindFilesInPackage(srcOwner, folderPath.Value, fileExtension, true); + } + else + { + res.WithError($"{srcOwner.Name}: The folder '{filePath}' is missing!"); } } - - throw new NotImplementedException(); + + return res.WithValue(builder.ToImmutable()); } private (Platform Platform, Target Target) GetRuntimeEnvironment(XElement element) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs index cd9571aa4..a58c47f13 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs @@ -95,16 +95,26 @@ public sealed class ModConfigService : IModConfigService ThrowHelper.ThrowArgumentNullException($"{nameof(CreateConfigsAsync)}: The supplied array is default or empty!"); using var lck = await _operationsLock.AcquireReaderLock(); IService.CheckDisposed(this); - - var builder = new ConcurrentQueue<(ContentPackage Source, Result Config)>(); - await src.ParallelForEachAsync(async package => + var builder = ImmutableArray.CreateBuilder>>>(src.Length); + foreach (var srcItem in src) { - var res = await CreateConfigAsync(package); - builder.Enqueue((package, res)); - }); + builder.Add(Task.Factory.StartNew(async Task> () => await CreateConfigAsync(srcItem))); + } + var taskResults = await Task.WhenAll(builder.ToImmutable()); + var returnResults = ImmutableArray.CreateBuilder<(ContentPackage Source, Result Config)>(); + foreach (var taskResult in taskResults) + { + if (taskResult.IsFaulted) + { + ThrowHelper.ThrowInvalidOperationException($"{nameof(CreateConfigsAsync)}: Task failed: {taskResult.Exception?.Message}"); + } - return builder.OrderBy(pkg => src.IndexOf(pkg.Source)).ToImmutableArray(); + var r = await taskResult; + returnResults.Add((r.Value.Package, r)); + } + + return returnResults.ToImmutable(); } //--- Helpers @@ -117,42 +127,18 @@ public sealed class ModConfigService : IModConfigService private async Task> CreateFromConfigXmlAsync(ContentPackage owner, XElement src) { - ImmutableArray assemblyResources = default; - ImmutableArray configResources = default; - ImmutableArray luaResources = default; - - var tasks = new[] - { - new Task(async () => assemblyResources = await GetAssembliesFromXml(owner, src)), - new Task(async () => configResources = await GetConfigsFromXml(owner, src)), - new Task(async () => luaResources = await GetLuaScriptsFromXml(owner, src)), - }; - - tasks.ForEach(t => t.Start()); - var res = await Task.WhenAll(tasks); - - bool isFaulted = false; - foreach (var task in res) - { - if (task.IsFaulted) - { - _logger.LogError($"{nameof(CreateFromConfigXmlAsync)}: {task.Exception?.ToString()}"); - isFaulted = true; - } - } - - if (isFaulted) - { - _logger.LogError($"{nameof(CreateFromConfigXmlAsync)}: Failed to process content package: {owner.Name}"); - return FluentResults.Result.Fail($"{nameof(CreateFromConfigXmlAsync)}: Failed to process content package: {owner.Name}"); - } + var asmTask = Task.Factory.StartNew(async () => await GetAssembliesFromXml(owner, src)); + var cfgTask = Task.Factory.StartNew(async () => await GetConfigsFromXml(owner, src)); + var luaTask = Task.Factory.StartNew(async () => await GetLuaScriptsFromXml(owner, src)); + + await Task.WhenAll(asmTask, cfgTask, luaTask); return FluentResults.Result.Ok(new ModConfigInfo() { Package = owner, - Assemblies = assemblyResources, - Configs = configResources, - LuaScripts = luaResources + Assemblies = await await asmTask, + Configs = await await cfgTask, + LuaScripts = await await luaTask }); async Task> GetLuaScriptsFromXml(ContentPackage contentPackage, From 8dedc5446832097cffc7c14034b22a7abab2b62c Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:35:26 -0300 Subject: [PATCH 054/288] Add Any for Platform and Target enums --- .../SharedSource/LuaCs/Data/EPlatformsTargets.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs index aaf289f8e..56d192dd4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/EPlatformsTargets.cs @@ -6,14 +6,16 @@ namespace Barotrauma.LuaCs.Data; [Flags] public enum Platform { - Linux=0x1, - OSX=0x2, - Windows=0x4 + Linux = 0x1, + OSX = 0x2, + Windows = 0x4, + Any = Linux | OSX | Windows } [Flags] public enum Target { - Client=0x1, - Server=0x2 + Client = 0x1, + Server = 0x2, + Any = Client | Server } From 25081466be4251a22f2361311189cd6f89413b9e Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:50:23 -0300 Subject: [PATCH 055/288] Add the content package and move the Lua files to it --- .../Lua/CompatibilityLib.lua | 0 .../LuaCsForBarotrauma}/Lua/DefaultHook.lua | 0 .../Lua/DefaultLib/LibClient.lua | 0 .../Lua/DefaultLib/LibServer.lua | 0 .../Lua/DefaultLib/LibShared.lua | 0 .../Lua/DefaultLib/Utils/Math.lua | 0 .../Lua/DefaultLib/Utils/SteamApi.lua | 0 .../Lua/DefaultLib/Utils/String.lua | 0 .../Lua/DefaultLib/Utils/Util.lua | 0 .../Lua/DefaultRegister/RegisterClient.lua | 0 .../Lua/DefaultRegister/RegisterServer.lua | 0 .../Lua/DefaultRegister/RegisterShared.lua | 0 .../LuaCsForBarotrauma}/Lua/LuaSetup.lua | 0 .../LuaCsForBarotrauma/Lua/LuaUserData.lua | 97 +++++++++++++++++++ .../LuaCsForBarotrauma}/Lua/ModLoader.lua | 0 .../LuaCsForBarotrauma}/Lua/PostSetup.lua | 0 .../LuaCsForBarotrauma/ModConfig.xml | 4 + .../LocalMods/LuaCsForBarotrauma/filelist.xml | 3 + .../BarotraumaShared/Lua/.vscode/launch.json | 11 --- .../Lua/.vscode/settings.json | 40 -------- .../Lua/Content/ModConfig.xml | 0 .../Lua/Content/Schemas/IModConfig.xsd | 50 ---------- 22 files changed, 104 insertions(+), 101 deletions(-) rename Barotrauma/BarotraumaShared/{ => LocalMods/LuaCsForBarotrauma}/Lua/CompatibilityLib.lua (100%) rename Barotrauma/BarotraumaShared/{ => LocalMods/LuaCsForBarotrauma}/Lua/DefaultHook.lua (100%) rename Barotrauma/BarotraumaShared/{ => LocalMods/LuaCsForBarotrauma}/Lua/DefaultLib/LibClient.lua (100%) rename Barotrauma/BarotraumaShared/{ => LocalMods/LuaCsForBarotrauma}/Lua/DefaultLib/LibServer.lua (100%) rename Barotrauma/BarotraumaShared/{ => LocalMods/LuaCsForBarotrauma}/Lua/DefaultLib/LibShared.lua (100%) rename Barotrauma/BarotraumaShared/{ => LocalMods/LuaCsForBarotrauma}/Lua/DefaultLib/Utils/Math.lua (100%) rename Barotrauma/BarotraumaShared/{ => LocalMods/LuaCsForBarotrauma}/Lua/DefaultLib/Utils/SteamApi.lua (100%) rename Barotrauma/BarotraumaShared/{ => LocalMods/LuaCsForBarotrauma}/Lua/DefaultLib/Utils/String.lua (100%) rename Barotrauma/BarotraumaShared/{ => LocalMods/LuaCsForBarotrauma}/Lua/DefaultLib/Utils/Util.lua (100%) rename Barotrauma/BarotraumaShared/{ => LocalMods/LuaCsForBarotrauma}/Lua/DefaultRegister/RegisterClient.lua (100%) rename Barotrauma/BarotraumaShared/{ => LocalMods/LuaCsForBarotrauma}/Lua/DefaultRegister/RegisterServer.lua (100%) rename Barotrauma/BarotraumaShared/{ => LocalMods/LuaCsForBarotrauma}/Lua/DefaultRegister/RegisterShared.lua (100%) rename Barotrauma/BarotraumaShared/{ => LocalMods/LuaCsForBarotrauma}/Lua/LuaSetup.lua (100%) create mode 100644 Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaUserData.lua rename Barotrauma/BarotraumaShared/{ => LocalMods/LuaCsForBarotrauma}/Lua/ModLoader.lua (100%) rename Barotrauma/BarotraumaShared/{ => LocalMods/LuaCsForBarotrauma}/Lua/PostSetup.lua (100%) create mode 100644 Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml create mode 100644 Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/filelist.xml delete mode 100644 Barotrauma/BarotraumaShared/Lua/.vscode/launch.json delete mode 100644 Barotrauma/BarotraumaShared/Lua/.vscode/settings.json delete mode 100644 Barotrauma/BarotraumaShared/Lua/Content/ModConfig.xml delete mode 100644 Barotrauma/BarotraumaShared/Lua/Content/Schemas/IModConfig.xsd diff --git a/Barotrauma/BarotraumaShared/Lua/CompatibilityLib.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/CompatibilityLib.lua similarity index 100% rename from Barotrauma/BarotraumaShared/Lua/CompatibilityLib.lua rename to Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/CompatibilityLib.lua diff --git a/Barotrauma/BarotraumaShared/Lua/DefaultHook.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultHook.lua similarity index 100% rename from Barotrauma/BarotraumaShared/Lua/DefaultHook.lua rename to Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultHook.lua diff --git a/Barotrauma/BarotraumaShared/Lua/DefaultLib/LibClient.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/LibClient.lua similarity index 100% rename from Barotrauma/BarotraumaShared/Lua/DefaultLib/LibClient.lua rename to Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/LibClient.lua diff --git a/Barotrauma/BarotraumaShared/Lua/DefaultLib/LibServer.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/LibServer.lua similarity index 100% rename from Barotrauma/BarotraumaShared/Lua/DefaultLib/LibServer.lua rename to Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/LibServer.lua diff --git a/Barotrauma/BarotraumaShared/Lua/DefaultLib/LibShared.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/LibShared.lua similarity index 100% rename from Barotrauma/BarotraumaShared/Lua/DefaultLib/LibShared.lua rename to Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/LibShared.lua diff --git a/Barotrauma/BarotraumaShared/Lua/DefaultLib/Utils/Math.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/Utils/Math.lua similarity index 100% rename from Barotrauma/BarotraumaShared/Lua/DefaultLib/Utils/Math.lua rename to Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/Utils/Math.lua diff --git a/Barotrauma/BarotraumaShared/Lua/DefaultLib/Utils/SteamApi.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/Utils/SteamApi.lua similarity index 100% rename from Barotrauma/BarotraumaShared/Lua/DefaultLib/Utils/SteamApi.lua rename to Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/Utils/SteamApi.lua diff --git a/Barotrauma/BarotraumaShared/Lua/DefaultLib/Utils/String.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/Utils/String.lua similarity index 100% rename from Barotrauma/BarotraumaShared/Lua/DefaultLib/Utils/String.lua rename to Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/Utils/String.lua diff --git a/Barotrauma/BarotraumaShared/Lua/DefaultLib/Utils/Util.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/Utils/Util.lua similarity index 100% rename from Barotrauma/BarotraumaShared/Lua/DefaultLib/Utils/Util.lua rename to Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/Utils/Util.lua diff --git a/Barotrauma/BarotraumaShared/Lua/DefaultRegister/RegisterClient.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultRegister/RegisterClient.lua similarity index 100% rename from Barotrauma/BarotraumaShared/Lua/DefaultRegister/RegisterClient.lua rename to Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultRegister/RegisterClient.lua diff --git a/Barotrauma/BarotraumaShared/Lua/DefaultRegister/RegisterServer.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultRegister/RegisterServer.lua similarity index 100% rename from Barotrauma/BarotraumaShared/Lua/DefaultRegister/RegisterServer.lua rename to Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultRegister/RegisterServer.lua diff --git a/Barotrauma/BarotraumaShared/Lua/DefaultRegister/RegisterShared.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultRegister/RegisterShared.lua similarity index 100% rename from Barotrauma/BarotraumaShared/Lua/DefaultRegister/RegisterShared.lua rename to Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultRegister/RegisterShared.lua diff --git a/Barotrauma/BarotraumaShared/Lua/LuaSetup.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua similarity index 100% rename from Barotrauma/BarotraumaShared/Lua/LuaSetup.lua rename to Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaUserData.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaUserData.lua new file mode 100644 index 000000000..387fc7f2b --- /dev/null +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaUserData.lua @@ -0,0 +1,97 @@ +local clrLuaUserData = LuaUserData +local luaUserData = {} + +luaUserData.Descriptors = {} + +LuaSetup.LuaUserData = luaUserData + +luaUserData.IsRegistered = clrLuaUserData.IsRegistered +luaUserData.UnregisterType = clrLuaUserData.UnregisterType +luaUserData.RegisterGenericType = clrLuaUserData.RegisterGenericType +luaUserData.RegisterExtensionType = clrLuaUserData.RegisterExtensionType +luaUserData.UnregisterGenericType = clrLuaUserData.UnregisterGenericType +luaUserData.IsTargetType = clrLuaUserData.IsTargetType +luaUserData.TypeOf = clrLuaUserData.TypeOf +luaUserData.GetType = clrLuaUserData.GetType +luaUserData.CreateEnumTable = clrLuaUserData.CreateEnumTable +luaUserData.MakeFieldAccessible = clrLuaUserData.MakeFieldAccessible +luaUserData.MakeMethodAccessible = clrLuaUserData.MakeMethodAccessible +luaUserData.MakePropertyAccessible = clrLuaUserData.MakePropertyAccessible +luaUserData.AddMethod = clrLuaUserData.AddMethod +luaUserData.AddField = clrLuaUserData.AddField +luaUserData.RemoveMember = clrLuaUserData.RemoveMember +luaUserData.CreateUserDataFromDescriptor = clrLuaUserData.CreateUserDataFromDescriptor +luaUserData.CreateUserDataFromType = clrLuaUserData.CreateUserDataFromType +luaUserData.HasMember = clrLuaUserData.HasMember + +luaUserData.RegisterType = function(typeName) + local success, result = pcall(clrLuaUserData.RegisterType, typeName) + + if not success then + error(result, 2) + end + + luaUserData.Descriptors[typeName] = result + + return result +end + +luaUserData.RegisterTypeBarotrauma = function(typeName) + typeName = "Barotrauma." .. typeName + local success, result = pcall(luaUserData.RegisterType, typeName) + + if not success then + error(result, 2) + end + + return result +end + +luaUserData.AddCallMetaTable = function (userdata) + if userdata == nil then + error("Attempted to add a call metatable to a nil value.", 2) + end + + if not LuaUserData.HasMember(userdata, ".ctor") then + error("Attempted to add a call metatable to a userdata that does not have a constructor.", 2) + end + + debug.setmetatable(userdata, { + __call = function(obj, ...) + if userdata == nil then + error("userdata was nil.", 2) + end + + local success, result = pcall(userdata.__new, ...) + + + if not success then + error(result, 2) + end + + return result + end + }) +end + +luaUserData.CreateStatic = function(typeName) + if type(typeName) ~= "string" then + error("Expected a string for typeName, got " .. type(typeName) .. ".", 2) + end + + local success, result = pcall(clrLuaUserData.CreateStatic, typeName) + + if not success then + error(result, 2) + end + + if result == nil then + return + end + + if LuaUserData.HasMember(result, ".ctor") then + luaUserData.AddCallMetaTable(result) + end + + return result +end \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/Lua/ModLoader.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/ModLoader.lua similarity index 100% rename from Barotrauma/BarotraumaShared/Lua/ModLoader.lua rename to Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/ModLoader.lua diff --git a/Barotrauma/BarotraumaShared/Lua/PostSetup.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/PostSetup.lua similarity index 100% rename from Barotrauma/BarotraumaShared/Lua/PostSetup.lua rename to Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/PostSetup.lua diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml new file mode 100644 index 000000000..e74277984 --- /dev/null +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/filelist.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/filelist.xml new file mode 100644 index 000000000..4a679f48e --- /dev/null +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/filelist.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/Lua/.vscode/launch.json b/Barotrauma/BarotraumaShared/Lua/.vscode/launch.json deleted file mode 100644 index bd4a5a056..000000000 --- a/Barotrauma/BarotraumaShared/Lua/.vscode/launch.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "MoonSharp Attach", - "type": "moonsharp-debug", - "debugServer": 41912, - "request": "attach" - } - ] -} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/Lua/.vscode/settings.json b/Barotrauma/BarotraumaShared/Lua/.vscode/settings.json deleted file mode 100644 index 237e6445e..000000000 --- a/Barotrauma/BarotraumaShared/Lua/.vscode/settings.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "Lua.diagnostics.globals": [ - "Game", - "Player", - "Random", - "Hook", - "Timer", - "bit32", - "TotalTime", - "DoFile", - "WayPoint", - "SpawnType", - "Level", - "Submarine", - "Vector2", - "PositionType", - "ServerLog_MessageType", - "Character", - "TraitorMessageType", - "ChatMessageType", - "CauseOfDeathType", - "CreateVector2", - "Item", - "ChatMessage", - "AfflictionPrefab", - "Gap", - "File", - "Networking", - "printNoLog", - "Client", - "SERVER", - "setmodulepaths", - "Type", - "BindingFlags", - "UserData", - "LuaUserData", - "CLIENT", - "ContentPackageManager" - ] -} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/Lua/Content/ModConfig.xml b/Barotrauma/BarotraumaShared/Lua/Content/ModConfig.xml deleted file mode 100644 index e69de29bb..000000000 diff --git a/Barotrauma/BarotraumaShared/Lua/Content/Schemas/IModConfig.xsd b/Barotrauma/BarotraumaShared/Lua/Content/Schemas/IModConfig.xsd deleted file mode 100644 index 1908c9960..000000000 --- a/Barotrauma/BarotraumaShared/Lua/Content/Schemas/IModConfig.xsd +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From c2aa7dd9488c18ac08a520410999691eb02f43e2 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 25 Jan 2026 16:05:39 -0500 Subject: [PATCH 056/288] Added packages to running packages list on execution. --- .../SharedSource/LuaCs/Services/PackageManagementService.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index a16ec5e50..126c85a24 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -234,6 +234,11 @@ public sealed class PackageManagementService : IPackageManagementService result.WithReasons(_pluginManagementService.LoadAssemblyResources(plugins).Reasons); } } + + foreach (var package in loadingOrderedPackages) + { + _runningPackages[package.Key] = package.Value; + } return result; } From 0a9c673753069d9ea5cfdcf5388468e91f38e4ec Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 25 Jan 2026 16:48:23 -0500 Subject: [PATCH 057/288] - Removed unused settings - Added Settings.xml --- .../ClientSource/LuaCs/LuaCsSettingsMenu.cs | 22 -------------- .../Config/SettingsShared.xml | 11 +++++++ .../LuaCsForBarotrauma/ModConfig.xml | 5 ++-- .../SharedSource/LuaCs/LuaCsSetup.cs | 29 +++---------------- 4 files changed, 18 insertions(+), 49 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs index b6408d2a4..5f1e6d90e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs @@ -28,28 +28,6 @@ namespace Barotrauma } }; - new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Treat Forced Mods As Normal") - { - Selected = GameMain.LuaCs.TreatForcedModsAsNormal, - ToolTip = "This makes mods that were setup to run even when disabled to only run when enabled.", - OnSelected = (GUITickBox tick) => - { - GameMain.LuaCs.TreatForcedModsAsNormal = tick.Selected; - return true; - } - }; - - new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Prefer To Use Workshop Lua Setup") - { - Selected = GameMain.LuaCs.PreferToUseWorkshopLuaSetup, - ToolTip = "This makes Lua look first for the Lua/LuaSetup.lua located in the Workshop package instead of the one located locally.", - OnSelected = (GUITickBox tick) => - { - GameMain.LuaCs.PreferToUseWorkshopLuaSetup = tick.Selected; - return true; - } - }; - new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Disable Error GUI Overlay") { Selected = GameMain.LuaCs.DisableErrorGUIOverlay, diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml new file mode 100644 index 000000000..e2241fa2f --- /dev/null +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml index e74277984..2119fc978 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml @@ -1,4 +1,5 @@ - - \ No newline at end of file + + + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index f19a3a5c2..080a55ff9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -106,27 +106,6 @@ namespace Barotrauma private ISettingEntry _isCsEnabled; - /// - /// Whether mods marked as 'forced' or 'always load' should only be loaded if they're in the enabled mods list. - /// - public bool TreatForcedModsAsNormal - { - get => _treatForcedModsAsNormal?.Value ?? true; - internal set => _treatForcedModsAsNormal?.TrySetValue(value); - } - - private ISettingEntry _treatForcedModsAsNormal; - - /// - /// Whether the lua script runner from Workshop package should be used over the in-built version. - /// - public bool PreferToUseWorkshopLuaSetup - { - get => _preferToUseWorkshopLuaSetup?.Value ?? false; - internal set => _preferToUseWorkshopLuaSetup?.TrySetValue(value); - } - private ISettingEntry _preferToUseWorkshopLuaSetup; - /// /// Whether the popup error GUI should be hidden/suppressed. /// @@ -181,8 +160,6 @@ namespace Barotrauma { _isCsEnabled = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "IsCsEnabled", out var val1) ? val1 : throw new NullReferenceException($"{nameof(IsCsEnabled)} cannot be loaded."); - _treatForcedModsAsNormal = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "TreatForcedModsAsNormal", out var val2) ? val2 - : throw new NullReferenceException($"{nameof(TreatForcedModsAsNormal)} cannot be loaded."); _disableErrorGUIOverlay = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "DisableErrorGUIOverlay", out var val3) ? val3 : throw new NullReferenceException($"{nameof(DisableErrorGUIOverlay)} cannot be loaded."); _hideUserNamesInLogs = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "HideUserNamesInLogs", out var val4) ? val4 @@ -191,6 +168,8 @@ namespace Barotrauma : throw new NullReferenceException($"{nameof(LuaForBarotraumaSteamId)} cannot be loaded."); _restrictMessageSize = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "RestrictMessageSize", out var val7) ? val7 : throw new NullReferenceException($"{nameof(RestrictMessageSize)} cannot be loaded."); + _localDataSavePath = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "LocalDataSavePath", out var val8) ? val8 + : throw new NullReferenceException($"{nameof(LocalDataSavePath)} cannot be loaded."); } private IServicesProvider SetupServicesProvider() @@ -330,7 +309,8 @@ namespace Barotrauma if (!PackageManagementService.IsAnyPackageLoaded()) { Logger.LogResults(PackageManagementService.LoadPackagesInfo(ContentPackageManager.EnabledPackages.All.ToImmutableArray())); - LoadLuaCsConfig(); + // TODO: Enable later + //LoadLuaCsConfig(); } if (!PackageManagementService.IsAnyPackageRunning()) @@ -399,7 +379,6 @@ namespace Barotrauma void DisposeLuaCsConfig() { _isCsEnabled = null; - _treatForcedModsAsNormal = null; _disableErrorGUIOverlay = null; _hideUserNamesInLogs = null; _luaForBarotraumaSteamId = null; From ed09908c3b54cc135c22bb4217ed96304b9e018a Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 25 Jan 2026 16:50:58 -0500 Subject: [PATCH 058/288] - Added default load context type resolution. --- .../LuaCs/Services/PluginManagementService.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 9cd47e486..ba160ab66 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -119,9 +119,18 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage if (includeDefaultContext) { var type = Type.GetType(typeName, false); + if (type is not null) + { + if (isByRefType) + { + return type.MakeByRefType(); + } + + return type; + } } - - // TODO: implement by-ref type resolution + + //TODO: implement ALC resolutions. throw new NotImplementedException(); } From b626fd1e47f4106c48b281bcf67e652cf9989f5d Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 25 Jan 2026 17:04:22 -0500 Subject: [PATCH 059/288] - The GetType lag situation is craazy. --- .../LuaCs/Services/PluginManagementService.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index ba160ab66..83aa9d1d0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -130,6 +130,14 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage } } + //TODO: the lag situation here is craazy + var t = AssemblyLoadContext.All + .SelectMany(alc => alc.Assemblies) + .SelectMany(ass => ass.GetSafeTypes()) + .FirstOrDefault(tp => tp.FullName?.Equals(typeName) ?? tp.Name.Equals(typeName), null); + + return t; + //TODO: implement ALC resolutions. throw new NotImplementedException(); } From adf9303a7e26cd4f1467d7353ccf06133c8b207f Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 25 Jan 2026 17:15:54 -0500 Subject: [PATCH 060/288] - The GetType lag situation is less crazy. --- .../LuaCs/Services/PluginManagementService.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 83aa9d1d0..05b174642 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -130,16 +130,14 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage } } - //TODO: the lag situation here is craazy - var t = AssemblyLoadContext.All - .SelectMany(alc => alc.Assemblies) - .SelectMany(ass => ass.GetSafeTypes()) - .FirstOrDefault(tp => tp.FullName?.Equals(typeName) ?? tp.Name.Equals(typeName), null); - - return t; - - //TODO: implement ALC resolutions. - throw new NotImplementedException(); + foreach (var ass in AssemblyLoadContext.All.SelectMany(alc => alc.Assemblies)) + { + if (ass.GetType(typeName, false) is not { } type) + { + continue; + } + return isByRefType ? type.MakeByRefType() : type; + } } public FluentResults.Result LoadAssemblyResources(ImmutableArray resource) From 295c365a8f9eb2640a32a03d28ebcabe38d1dcaa Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 25 Jan 2026 17:16:50 -0500 Subject: [PATCH 061/288] - The return null situation is no longer crazy. --- .../SharedSource/LuaCs/Services/PluginManagementService.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 05b174642..ba9d9c651 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -136,8 +136,11 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage { continue; } + return isByRefType ? type.MakeByRefType() : type; } + + return null; } public FluentResults.Result LoadAssemblyResources(ImmutableArray resource) From 3d51abc56bb94ca410ad3ea7bdc86d8f205c8055 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 25 Jan 2026 20:26:14 -0300 Subject: [PATCH 062/288] Semi-working Lua scripts --- .../ClientSource/LuaCs/LuaCsSetup.cs | 3 +- .../Lua/CompatibilityLib.lua | 6 +- .../LuaCsForBarotrauma/Lua/DefaultHook.lua | 2 + .../LuaCsForBarotrauma/Lua/LuaSetup.lua | 10 +- .../LuaCsForBarotrauma/Lua/ModLoader.lua | 193 ------------------ .../LuaCs/Lua/LuaClasses/LuaUserData.cs | 2 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 12 +- .../LuaCs/Services/EventService.cs | 2 + .../LuaCs/Services/LoggerService.cs | 47 ++++- .../Services/LuaScriptManagementService.cs | 68 +++++- .../LuaCs/Services/PluginManagementService.cs | 2 +- .../LuaCs/Services/Safe/SafeStorageService.cs | 26 ++- 12 files changed, 154 insertions(+), 219 deletions(-) delete mode 100644 Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/ModLoader.lua diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index 35dafe4e2..ffe87afab 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -85,7 +85,8 @@ namespace Barotrauma // menus and navigation states case MainMenuScreen: case ModDownloadScreen: - case ServerListScreen: + case ServerListScreen: + SetRunState(RunState.Unloaded); SetRunState(RunState.LoadedNoExec); break; // running lobby or editor states diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/CompatibilityLib.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/CompatibilityLib.lua index 524818c5f..e266752b0 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/CompatibilityLib.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/CompatibilityLib.lua @@ -2,11 +2,11 @@ local compatibilityLib = {} -local networking = LuaUserData.RegisterType("Barotrauma.LuaCsNetworking") +-- local networking = LuaUserData.RegisterType("Barotrauma.LuaCsNetworking") -LuaUserData.AddMethod(networking, "RequestGetHTTP", Networking.HttpGet) +-- LuaUserData.AddMethod(networking, "RequestGetHTTP", Networking.HttpGet) -LuaUserData.AddMethod(networking, "RequestPostHTTP", Networking.HttpPost) +-- LuaUserData.AddMethod(networking, "RequestPostHTTP", Networking.HttpPost) compatibilityLib.CreateVector2 = Vector2.__new compatibilityLib.CreateVector3 = Vector3.__new diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultHook.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultHook.lua index 520e884ff..d78c7dc48 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultHook.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultHook.lua @@ -1,3 +1,5 @@ +if true then return end + Hook.Patch("Barotrauma.Item", "TryInteract", { "Barotrauma.Character", diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua index fc970c3b6..f324b3f7b 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua @@ -1,8 +1,8 @@ LuaSetup = {} -local path = table.pack(...)[1] +local path, resourcesToExecute = ... -package.path = {path .. "/?.lua"} +package.path = {path .. "/Lua/?.lua"} setmodulepaths(package.path) @@ -44,4 +44,8 @@ require("PostSetup") LuaSetup = nil -require("ModLoader") \ No newline at end of file +for resource in resourcesToExecute do + for path in resource.FilePaths do + dofile(path) + end +end \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/ModLoader.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/ModLoader.lua deleted file mode 100644 index 828b56205..000000000 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/ModLoader.lua +++ /dev/null @@ -1,193 +0,0 @@ -local LUA_MOD_REQUIRE_PATH = "/Lua/?.lua" -local LUA_MOD_AUTORUN_PATH = "/Lua/Autorun" -local LUA_MOD_FORCEDAUTORUN_PATH = "/Lua/ForcedAutorun" - -local function EndsWith(str, suffix) - return str:sub(-string.len(suffix)) == suffix -end - -local function GetFileName(file) - return file:match("^.+/(.+)$") -end - -local function ExecuteProtected(s, folder) - loadfile(s)(folder) -end - -local function RunFolder(folder, rootFolder, package) - local search = File.DirSearch(folder) - for i = 1, #search, 1 do - local s = search[i]:gsub("\\", "/") - - if EndsWith(s, ".lua") then - local time = os.clock() - local ok, result = pcall(ExecuteProtected, s, rootFolder) - local diff = os.clock() - time - - print(string.format(" - %s (Took %.5fms)", GetFileName(s), diff)) - if not ok then - printerror(result) - end - end - - end -end - -local function AssertTypes(expectedTypes, ...) - local args = table.pack(...) - assert( - #args == #expectedTypes, - string.format( - "Assertion failed: incorrect number of args\n\texpected = %s\n\tgot = %s", - #expectedTypes, #args - ) - ) - for i = 1, #args do - local arg = args[i] - local expectedType = expectedTypes[i] - assert( - type(arg) == expectedType, - string.format( - "Assertion failed: incorrect argument type (arg #%d)\n\texpected = %s\n\tgot = %s", - i, expectedType, type(arg) - ) - ) - end -end - -local function ExecutionQueue() - local executionQueue = {} - executionQueue.Queue = {} - - executionQueue.Process = function() - while executionQueue.Queue[1] ~= nil do - local folder, rootFolder, package = table.unpack(table.remove(executionQueue.Queue, 1)) - print(string.format("%s %s", package.Name, package.ModVersion)) - RunFolder(folder, rootFolder, package) - end - end - - executionQueue.Add = function(...) - AssertTypes({ 'string', 'string', 'userdata' }, ...) - table.insert(executionQueue.Queue, table.pack(...)) - end - - return executionQueue -end - -local QueueAutorun = ExecutionQueue() -local QueueForcedAutorun = ExecutionQueue() - -local function nocase(s) - s = string.gsub(s, "%a", function(c) - return string.format("[%s%s]", string.lower(c), string.upper(c)) - end) - return s -end - -local function ProcessPackages(packages, fn) - for pkg in packages do - if pkg then - local pkgPath = pkg.Path - :gsub("\\", "/") - :gsub(nocase("/filelist.xml"), "") - fn(pkg, pkgPath) - end - end -end - -ProcessPackages(ContentPackageManager.EnabledPackages.All, function(pkg, pkgPath) - table.insert(package.path, pkgPath .. LUA_MOD_REQUIRE_PATH) - local autorunPath = pkgPath .. LUA_MOD_AUTORUN_PATH - if File.DirectoryExists(autorunPath) then - QueueAutorun.Add(autorunPath, pkgPath, pkg) - end -end) - --- we don't want to execute workshop ForcedAutorun if we have a local Package -local executedLocalPackages = {} - -ProcessPackages(ContentPackageManager.EnabledPackages.All, function(pkg, pkgPath) - table.insert(package.path, pkgPath .. LUA_MOD_REQUIRE_PATH) - local forcedAutorunPath = pkgPath .. LUA_MOD_FORCEDAUTORUN_PATH - if File.DirectoryExists(forcedAutorunPath) then - QueueForcedAutorun.Add(forcedAutorunPath, pkgPath, pkg) - executedLocalPackages[pkg.Name] = true - end -end) - -if not LuaCsConfig.TreatForcedModsAsNormal then - ProcessPackages(ContentPackageManager.LocalPackages, function(pkg, pkgPath) - if not executedLocalPackages[pkg.Name] then - table.insert(package.path, pkgPath .. LUA_MOD_REQUIRE_PATH) - local forcedAutorunPath = pkgPath .. LUA_MOD_FORCEDAUTORUN_PATH - if File.DirectoryExists(forcedAutorunPath) then - QueueForcedAutorun.Add(forcedAutorunPath, pkgPath, pkg) - executedLocalPackages[pkg.Name] = true - end - end - end) - - ProcessPackages(ContentPackageManager.AllPackages, function(pkg, pkgPath) - if not executedLocalPackages[pkg.Name] then - table.insert(package.path, pkgPath .. LUA_MOD_REQUIRE_PATH) - local forcedAutorunPath = pkgPath .. LUA_MOD_FORCEDAUTORUN_PATH - if File.DirectoryExists(forcedAutorunPath) then - QueueForcedAutorun.Add(forcedAutorunPath, pkgPath, pkg) - end - end - end) -end - -setmodulepaths(package.path) -setmodulepaths = nil - -local allExecuted = {} -for key, value in pairs(QueueAutorun.Queue) do table.insert(allExecuted, value[3]) end -for key, value in pairs(QueueForcedAutorun.Queue) do table.insert(allExecuted, value[3]) 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") - - message.WriteUInt16(#allExecuted) - - for key, package in pairs(allExecuted) 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 - -QueueAutorun.Process() -QueueForcedAutorun.Process() - -Hook.Add("stop", "luaSetup.stop", function() - print("Stopping Lua...") -end) - -Hook.Call("loaded") \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs index cd0ecdda5..0918ff9a6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs @@ -11,7 +11,7 @@ namespace Barotrauma internal class LuaUserData { [Obsolete("Use IPluginManagementService::GetTypesByName()")] - public static Type GetType(string typeName) => throw new NotImplementedException(); //LuaCsSetup.GetType(typeName); + public static Type GetType(string typeName) => GameMain.LuaCs.PluginManagementService.GetType(typeName); //LuaCsSetup.GetType(typeName); public static IUserDataDescriptor RegisterType(string typeName) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 080a55ff9..9413289c8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -44,7 +44,7 @@ namespace Barotrauma SubscribeToLuaCsEvents(); } - bool ValidateLuaCsContent() + private bool ValidateLuaCsContent() { #if DEBUG // TODO: we just wanna boot for now @@ -57,7 +57,7 @@ namespace Barotrauma throw new NotImplementedException(); } - void SubscribeToLuaCsEvents() + private void SubscribeToLuaCsEvents() { EventService.Subscribe(this); // game state hook in EventService.Subscribe(this); @@ -283,7 +283,13 @@ namespace Barotrauma DisposeLuaCsConfig(); Logger.LogResults(PackageManagementService.UnloadAllPackages()); } - + + LuaScriptManagementService.Reset(); + PackageManagementService.Reset(); + EventService.Reset(); + + SubscribeToLuaCsEvents(); + CurrentRunState = RunState.Unloaded; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs index 80ce7628c..c3d362960 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs @@ -348,6 +348,7 @@ public class EventService : IEventService, IEventAssemblyContextUnloading _subscriptions.Clear(); _luaSubscriptionFactories.Clear(); _eventTypeNameAliases.Clear(); + _luaLegacySubscriptionFactories.Clear(); GC.SuppressFinalize(this); } @@ -357,6 +358,7 @@ public class EventService : IEventService, IEventAssemblyContextUnloading _subscriptions.Clear(); _luaSubscriptionFactories.Clear(); _eventTypeNameAliases.Clear(); + _luaLegacySubscriptionFactories.Clear(); return FluentResults.Result.Ok(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs index 3e9b9eadc..fc5e23586 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using Barotrauma.Networking; +using FluentResults; using Microsoft.Xna.Framework; using MoonSharp.Interpreter; @@ -132,6 +133,42 @@ public partial class LoggerService : ILoggerService #endif } + public void HandleException(Exception ex) + { + string errorString = ""; + switch (ex) + { + case NetRuntimeException netRuntimeException: + if (netRuntimeException.DecoratedMessage == null) + { + errorString = netRuntimeException.ToString(); + } + else + { + // FIXME: netRuntimeException.ToString() doesn't print the InnerException's stack trace... + errorString = $"{netRuntimeException.DecoratedMessage}: {netRuntimeException}"; + } + break; + case InterpreterException interpreterException: + if (interpreterException.DecoratedMessage == null) + { + errorString = interpreterException.ToString(); + } + else + { + errorString = interpreterException.DecoratedMessage; + } + break; + default: + errorString = ex.StackTrace != null + ? ex.ToString() + : $"{ex}\n{Environment.StackTrace}"; + break; + } + + LogError(errorString); + } + public void LogResults(FluentResults.Result result) { if (result == null) @@ -149,7 +186,15 @@ public partial class LoggerService : ILoggerService { foreach (var error in result.Errors) { - LogError(error.Message); + if (error is ExceptionalError exceptionalError) + { + HandleException(exceptionalError.Exception); + } + else + { + LogError(error.Message); + } + if (error.Reasons != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs index f69e2faff..33c0eb915 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs @@ -7,10 +7,12 @@ 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.Concurrent; using System.Collections.Generic; @@ -18,12 +20,12 @@ using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; -using Microsoft.Toolkit.Diagnostics; using static Barotrauma.GameSettings; namespace Barotrauma.LuaCs.Services; @@ -97,6 +99,46 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService return new FluentResults.Result().WithReasons(cacheRes.Value.SelectMany(cr => cr.Item2.Reasons)); } + private DynValue DoFile(string file, Table? globalContext = null, string? codeStringFriendly = null) + { + if (_script == null) + { + throw new Exception("Not running"); + } + + if (!LuaCsFile.CanReadFromPath(file)) + { + throw new ScriptRuntimeException($"dofile: File access to {file} not allowed."); + } + + if (!LuaCsFile.Exists(file)) + { + throw new ScriptRuntimeException($"dofile: File {file} not found."); + } + + return _script.DoFile(file, globalContext, codeStringFriendly); + } + + private DynValue LoadFile(string file, Table? globalContext = null, string? codeStringFriendly = null) + { + if (_script == null) + { + throw new Exception("Not running"); + } + + if (!LuaCsFile.CanReadFromPath(file)) + { + throw new ScriptRuntimeException($"loadfile: File access to {file} not allowed."); + } + + if (!LuaCsFile.Exists(file)) + { + throw new ScriptRuntimeException($"loadfile: File {file} not found."); + } + + return _script.LoadFile(file, globalContext, codeStringFriendly); + } + private void SetupEnvironment() { _script = new Script(CoreModules.Preset_SoftSandbox | CoreModules.Debug | CoreModules.IO | CoreModules.OS_System); @@ -115,9 +157,21 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService RegisterType(typeof(ILuaCsUtility)); RegisterType(typeof(ILuaCsTimer)); RegisterType(typeof(LuaCsFile)); + RegisterType(typeof(ILuaScriptResourceInfo)); + RegisterType(typeof(IResourceInfo)); + RegisterType(typeof(LuaUserData)); + RegisterType(typeof(IUserDataDescriptor)); new LuaConverters(_script).RegisterLuaConverters(); + var luaRequire = new LuaRequire(_script); + + _script.Globals["setmodulepaths"] = (string[] str) => ((LuaScriptLoader)_luaScriptLoader).ModulePaths = str; + + _script.Globals["dofile"] = (Func)DoFile; + _script.Globals["loadfile"] = (Func)LoadFile; + _script.Globals["require"] = (Func)luaRequire.Require; + _script.Globals["printerror"] = (DynValue o) => { LuaCsLogger.LogError(o.ToString()); }; _script.Globals["dostring"] = (Func)_script.DoString; @@ -129,6 +183,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _script.Globals["File"] = UserData.CreateStatic(); //_script.Globals["Networking"] = _luaCsNetworking; //_script.Globals["Steam"] = Steam; + _script.Globals["LuaUserData"] = UserData.CreateStatic(); _script.Globals["ExecutionNumber"] = 0; _script.Globals["CSActive"] = false; @@ -138,9 +193,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService } public FluentResults.Result ExecuteLoadedScripts(ImmutableArray executionOrder) - { - throw new NotImplementedException($"Need to implement {nameof(executionOrder)} logic."); - + { if (_isRunning) { return FluentResults.Result.Fail("Tried to execute Lua scripts without unloading first."); @@ -152,13 +205,16 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService var result = FluentResults.Result.Ok(); - foreach (ILuaScriptResourceInfo resource in _resourcesInfo) + List initializationScripts = executionOrder.Where(x => x.OwnerPackage.Name == "LuaCsForBarotrauma").ToList(); + List otherScripts = executionOrder.Except(initializationScripts).ToList(); + + foreach (ILuaScriptResourceInfo resource in initializationScripts) { foreach (ContentPath filePath in resource.FilePaths) { try { - _script?.Call(_script.LoadFile(filePath.FullPath)); + _script?.Call(_script.LoadFile(filePath.FullPath), Path.GetDirectoryName(resource.OwnerPackage.Path), otherScripts.ToList()); } catch(Exception e) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index ba9d9c651..04a180a40 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -78,7 +78,7 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage if (IsDisposed) return FluentResults.Result.Fail($"{nameof(PluginManagementService)} is disposed!"); - throw new NotImplementedException(); + return FluentResults.Result.Fail("not implemented"); } public Result> GetImplementingTypes(bool includeInterfaces = false, diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs index aa812801b..34c433f21 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs @@ -1,4 +1,11 @@ -using System; +using Barotrauma.IO; +using Barotrauma.LuaCs.Data; +using Barotrauma.Networking; +using FarseerPhysics.Common; +using FluentResults; +using FluentResults.LuaCs; +using Microsoft.Toolkit.Diagnostics; +using System; using System.Collections.Concurrent; using System.Collections.Immutable; using System.IO; @@ -6,12 +13,6 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml.Linq; -using Barotrauma.IO; -using Barotrauma.LuaCs.Data; -using FarseerPhysics.Common; -using FluentResults; -using FluentResults.LuaCs; -using Microsoft.Toolkit.Diagnostics; using Path = System.IO.Path; namespace Barotrauma.LuaCs.Services.Safe; @@ -40,6 +41,17 @@ public class SafeStorageService : StorageService, ISafeStorageService try { path = GetFullPath(path); + + if (path.StartsWith(ConfigData.WorkshopModsDirectory) + || path.StartsWith(ConfigData.LocalModsDirectory) +#if CLIENT + || path.StartsWith(ConfigData.TempDownloadsDirectory) +#endif + ) + { + return true; + } + if (!_fileListRead.ContainsKey(path)) { return false; From 60ed5496054e384add9d13e7ee4ee4e2eb7e41cd Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 25 Jan 2026 19:48:39 -0500 Subject: [PATCH 063/288] - Removed unused scheduler context. --- .../SharedSource/LuaCs/Services/ConfigService.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs index 88c1f1e32..9ee3c2062 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs @@ -172,8 +172,6 @@ public sealed partial class ConfigService : IConfigService var taskBuilder = ImmutableArray.CreateBuilder>>(); var toProcessErrors = new ConcurrentStack(); - - var taskCtx = TaskScheduler.FromCurrentSynchronizationContext(); foreach (var resource in configResources) { From f9a467453a5e658bc019f64303c3353f8b078c9d Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 26 Jan 2026 16:37:51 -0500 Subject: [PATCH 064/288] - Removed all MoveImmute situations. - Added filtering and ordering functions for package resources. --- .gitignore | 1 + .../LuaCs/Services/ConfigService.cs | 2 +- .../LuaCs/Services/EventService.cs | 2 +- .../Services/PackageManagementService.cs | 43 +++++++++++++++---- .../LuaCs/Services/PluginManagementService.cs | 18 +++++--- .../Processing/SettingsFileParserService.cs | 6 +-- .../LuaCs/Services/StorageService.cs | 4 +- .../_Interfaces/IPluginManagementService.cs | 4 +- 8 files changed, 56 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index b861f38bc..8a9348a93 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ bld/ [Rr]eleaseMac/ [Dd]ebugLinux/ [Rr]eleaseLinux/ +LocalMods/ *.o */Barotrauma*/doc/ diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs index 9ee3c2062..9aea4a8bc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs @@ -187,7 +187,7 @@ public sealed partial class ConfigService : IConfigService })); } - var taskResults = await Task.WhenAll(taskBuilder.MoveToImmutable()); + var taskResults = await Task.WhenAll(taskBuilder.ToImmutable()); if (toProcessErrors.Count > 0) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs index c3d362960..db42a1194 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs @@ -354,7 +354,7 @@ public class EventService : IEventService, IEventAssemblyContextUnloading public FluentResults.Result Reset() { - ((IService)this).CheckDisposed(); + IService.CheckDisposed(this); _subscriptions.Clear(); _luaSubscriptionFactories.Clear(); _eventTypeNameAliases.Clear(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index 126c85a24..286becd0d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; +using Barotrauma.Extensions; using Barotrauma.LuaCs.Data; using FluentResults; using Microsoft.Toolkit.Diagnostics; @@ -212,13 +213,19 @@ public sealed class PackageManagementService : IPackageManagementService // get loading order. Note: packages not in the execution order list will load first. var loadingOrderedPackages = _loadedPackages.OrderBy(pkg => executionOrder.IndexOf(pkg.Key)) .ToImmutableArray(); + var loadOrderByPackage = loadingOrderedPackages.Select(p => p.Key).ToImmutableArray(); + var toLoadPackagesIndents = loadingOrderedPackages + .SelectMany(p => p.Key.AltNames.Union(new []{ p.Key.Name }).ToIdentifiers()) + .ToImmutableHashSet(); + // NOTE: Config/Settings are instanced in LoadPackages() //lua scripts - var luaScripts = loadingOrderedPackages - .SelectMany(pkg => pkg.Value.LuaScripts.OrderBy(scr => scr.LoadPriority)) - .ToImmutableArray(); + var luaScripts = SelectCompatible(loadingOrderedPackages + .SelectMany(pkg => pkg.Value.LuaScripts) + .ToImmutableArray(), toLoadPackagesIndents, loadOrderByPackage); + if (!luaScripts.IsDefaultOrEmpty) { result.WithReasons(_luaScriptManagementService.ExecuteLoadedScripts(luaScripts).Reasons); @@ -226,9 +233,10 @@ public sealed class PackageManagementService : IPackageManagementService if (_runConfig.IsCsEnabled) { - var plugins = - loadingOrderedPackages.SelectMany(pkg => pkg.Value.Assemblies.OrderBy(scr => scr.LoadPriority)) - .ToImmutableArray(); + var plugins = SelectCompatible(loadingOrderedPackages + .SelectMany(pkg => pkg.Value.Assemblies) + .ToImmutableArray(), toLoadPackagesIndents, loadOrderByPackage); + if (!plugins.IsDefaultOrEmpty) { result.WithReasons(_pluginManagementService.LoadAssemblyResources(plugins).Reasons); @@ -239,10 +247,29 @@ public sealed class PackageManagementService : IPackageManagementService { _runningPackages[package.Key] = package.Value; } - + + if (result.IsFailed) + { + _logger.LogResults(result); + } return result; } - + + private static ImmutableArray SelectCompatible(ImmutableArray resources, ImmutableHashSet enabledPackagesIdents, ImmutableArray loadingOrder) + where T : IBaseResourceInfo + { + return resources + .Where(r => r.SupportedPlatforms.HasFlag(ModUtils.Environment.CurrentPlatform)) + .Where(r => r.SupportedTargets.HasFlag(ModUtils.Environment.CurrentTarget)) + .Where(r => !r.Optional || ( + (r.RequiredPackages.IsDefaultOrEmpty || enabledPackagesIdents.Intersect(r.RequiredPackages).Any()) + && (r.IncompatiblePackages.IsDefaultOrEmpty || enabledPackagesIdents.Intersect(r.IncompatiblePackages).None())) + ).OrderBy(r => loadingOrder.IndexOf(r.OwnerPackage)) + .ThenBy(r => r.LoadPriority) + .ToImmutableArray(); + } + + public FluentResults.Result SyncLoadedPackagesList(ImmutableArray packages) { if (packages.IsDefaultOrEmpty) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 04a180a40..1ecdb3b8b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -15,6 +15,7 @@ using FluentResults; using FluentResults.LuaCs; using ImpromptuInterface.Build; using Microsoft.CodeAnalysis; +using Microsoft.Toolkit.Diagnostics; using OneOf; namespace Barotrauma.LuaCs.Services; @@ -118,8 +119,8 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage { if (includeDefaultContext) { - var type = Type.GetType(typeName, false); - if (type is not null) + var type = Type.GetType(typeName, false, false); + if (type is not null && (includeInterfaces || !type.IsInterface)) { if (isByRefType) { @@ -132,7 +133,7 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage foreach (var ass in AssemblyLoadContext.All.SelectMany(alc => alc.Assemblies)) { - if (ass.GetType(typeName, false) is not { } type) + if (ass.GetType(typeName, false, false) is not {} type || (!includeInterfaces && type.IsInterface)) { continue; } @@ -143,11 +144,14 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage return null; } - public FluentResults.Result LoadAssemblyResources(ImmutableArray resource) + public FluentResults.Result LoadAssemblyResources(ImmutableArray resources) { -#if DEBUG - return FluentResults.Result.Fail($"{nameof(LoadAssemblyResources)}: Plugin loading not currently implemented."); -#endif + IService.CheckDisposed(this); + if (resources.IsDefaultOrEmpty) + { + ThrowHelper.ThrowArgumentNullException($"{nameof(LoadAssemblyResources)}: The resources list is empty!"); + } + throw new NotImplementedException(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/SettingsFileParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/SettingsFileParserService.cs index 3a3f631d0..a44cb285e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/SettingsFileParserService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/SettingsFileParserService.cs @@ -114,7 +114,7 @@ public sealed class SettingsFileParserService : } } - return FluentResults.Result.Ok(parsedInfo.MoveToImmutable()); + return FluentResults.Result.Ok(parsedInfo.ToImmutable()); // Helpers @@ -196,12 +196,12 @@ public sealed class SettingsFileParserService : { InternalName = profileName, OwnerPackage = res.path.ContentPackage, - ProfileValues = profileValuesBuilder.MoveToImmutable() + ProfileValues = profileValuesBuilder.ToImmutable() }); } } - return parsedInfo.MoveToImmutable(); + return parsedInfo.ToImmutable(); FluentResults.Result ReturnFail(string msg) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs index 5af427abf..cf590839b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs @@ -251,7 +251,7 @@ public class StorageService : IStorageService { builder.Add((path, LoadPackageData(path, dataLoader))); } - return builder.MoveToImmutable(); + return builder.ToImmutable(); } public ImmutableArray<(ContentPath, Result)> LoadPackageXmlFiles(ImmutableArray filePaths) @@ -317,7 +317,7 @@ public class StorageService : IStorageService { builder.Add((path, await LoadPackageDataAsync(path, dataLoader))); } - return builder.MoveToImmutable(); + return builder.ToImmutable(); } public async Task)>> LoadPackageXmlFilesAsync(ImmutableArray filePaths) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs index 81ebe8f0d..700418bfb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs @@ -35,9 +35,9 @@ public interface IPluginManagementService : IReusableService /// /// Loads the provided assembly resources in the order of their dependencies and intra-mod priority load order. /// - /// + /// /// Success/Failure and list of failed resources, if any. - FluentResults.Result LoadAssemblyResources(ImmutableArray resource); + FluentResults.Result LoadAssemblyResources(ImmutableArray resources); /// /// Creates instances of the given type and provides Property Injection and instance reference caching. Disposes of From b36224f48029dc80796f11b19240a7749442b506 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 26 Jan 2026 16:39:01 -0500 Subject: [PATCH 065/288] Removed unused data interfaces. --- .../SharedSource/LuaCs/Data/DataInterfaceImplementations.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs index 798e8734e..274d41c0c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs @@ -26,10 +26,6 @@ public record ModConfigInfo : IModConfigInfo #region DataContracts_Resources -public record AssemblyResourcesInfo(ImmutableArray Assemblies) : IAssembliesResourcesInfo; -public record LuaScriptsResourcesInfo(ImmutableArray LuaScripts) : ILuaScriptsResourcesInfo; -public record ConfigResourcesInfo(ImmutableArray Configs) : IConfigsResourcesInfo; - public record BaseResourceInfo : IBaseResourceInfo { public Platform SupportedPlatforms { get; init; } From 440cbed76ad884284a14a613c482f4047e968b36 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 26 Jan 2026 16:47:01 -0500 Subject: [PATCH 066/288] - Fixed find files in package using the wrong ContentPackage property. --- .../SharedSource/LuaCs/Services/StorageService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs index cf590839b..c78decde0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs @@ -267,8 +267,8 @@ public class StorageService : IStorageService try { var fullPath = localSubfolder.IsNullOrWhiteSpace() - ? Path.GetFullPath(package.Path) - : Path.GetFullPath(package.Path, localSubfolder); + ? Path.GetFullPath(package.Dir) + : Path.GetFullPath(package.Dir, localSubfolder); return System.IO.Directory.GetFiles(fullPath, regexFilter, searchRecursively ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly) .ToImmutableArray(); From 297f6a38cbed9233382b7e7246b66c7a8cba2480 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Mon, 26 Jan 2026 19:02:53 -0300 Subject: [PATCH 067/288] Basic legacy Lua converter --- .../Services/Processing/ModConfigService.cs | 41 +++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs index a58c47f13..279a4f31c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs @@ -225,13 +225,40 @@ public sealed class ModConfigService : IModConfigService private async Task> CreateFromLegacyAsync(ContentPackage src) { - // TODO: Implement legacy mod analysis - return new ModConfigInfo() - { - Package = src, - Assemblies = ImmutableArray.Empty, - Configs = ImmutableArray.Empty, - LuaScripts = ImmutableArray.Empty + List luaScripts = new List(); + + if (_storageService.DirectoryExists(Path.Combine(src.Path, "Lua", "Autorun")) is { IsSuccess: true, Value: true }) + { + var result = _storageService.FindFilesInPackage(src, "Lua/Autorun", "*.lua", true); + if (result.IsSuccess) + { + List contentPaths = new List(); + + foreach (var path in result.Value) + { + contentPaths.Add(ContentPath.FromRaw(src, $"%ModDir%/{Path.GetFullPath(path, src.Dir)}")); + } + + luaScripts.Add(new LuaScriptsResourceInfo() + { + IsAutorun = true, + SupportedPlatforms = Platform.Any, + SupportedTargets = Target.Any, + InternalName = "legacy", + OwnerPackage = src, + IncompatiblePackages = ImmutableArray.Empty, + RequiredPackages = ImmutableArray.Empty, + FilePaths = contentPaths.ToImmutableArray() + }); + } + } + + return new ModConfigInfo() + { + Package = src, + Assemblies = ImmutableArray.Empty, + Configs = ImmutableArray.Empty, + LuaScripts = luaScripts.ToImmutableArray() }; } } From 009957e3b643afd8fedc516ec1cc7a83cdec5916 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 27 Jan 2026 16:50:08 -0500 Subject: [PATCH 068/288] - Added alpha legacy mod support. - StorageService: Fixed ContentPackages' directories not properly resolving. - TODO: Rewrite StorageService LocalData functions to use ContentPath for resolution. --- .../LuaCs/Data/IBaseInfoDefinitions.cs | 3 +- .../Services/Processing/ModConfigService.cs | 166 ++++++++++++++---- .../LuaCs/Services/StorageService.cs | 6 +- 3 files changed, 141 insertions(+), 34 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs index 66dcdb626..4f5cb032b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs @@ -41,7 +41,8 @@ public interface IResourceInfo : IPlatformInfo { /// /// [Optional] - /// Allows you to specify the loading order for all assets of the same type (ie. styles, assemblies, etc.). + /// Specifies the loading order for all assets of the same type (ie. styles, assemblies, etc.) from + /// the same . Lower number is higher priority, see /// int LoadPriority { get; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs index 279a4f31c..2ad82d06c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs @@ -225,40 +225,144 @@ public sealed class ModConfigService : IModConfigService private async Task> CreateFromLegacyAsync(ContentPackage src) { - List luaScripts = new List(); - - if (_storageService.DirectoryExists(Path.Combine(src.Path, "Lua", "Autorun")) is { IsSuccess: true, Value: true }) - { - var result = _storageService.FindFilesInPackage(src, "Lua/Autorun", "*.lua", true); - if (result.IsSuccess) - { - List contentPaths = new List(); - - foreach (var path in result.Value) - { - contentPaths.Add(ContentPath.FromRaw(src, $"%ModDir%/{Path.GetFullPath(path, src.Dir)}")); - } - - luaScripts.Add(new LuaScriptsResourceInfo() - { - IsAutorun = true, - SupportedPlatforms = Platform.Any, - SupportedTargets = Target.Any, - InternalName = "legacy", - OwnerPackage = src, - IncompatiblePackages = ImmutableArray.Empty, - RequiredPackages = ImmutableArray.Empty, - FilePaths = contentPaths.ToImmutableArray() - }); - } - } - return new ModConfigInfo() { Package = src, - Assemblies = ImmutableArray.Empty, - Configs = ImmutableArray.Empty, - LuaScripts = luaScripts.ToImmutableArray() + Assemblies = GetAssembliesLegacy(src), + Configs = GetConfigsLegacy(src), + LuaScripts = GetLuaScriptsLegacy(src) }; + + ImmutableArray GetAssembliesLegacy(ContentPackage src) + { + var binSearchInd = new (string SubFolder, Target Targets, Platform Platforms)[] + { + ("bin/Client/Windows", Target.Client, Platform.Windows), + ("bin/Client/Linux", Target.Client, Platform.Linux), + ("bin/Client/OSX", Target.Client, Platform.OSX), + ("bin/Server/Windows", Target.Server, Platform.Windows), + ("bin/Server/Linux", Target.Server, Platform.Linux), + ("bin/Server/OSX", Target.Server, Platform.OSX) + }; + + var builder = ImmutableArray.CreateBuilder(); + + foreach (var searchPathways in binSearchInd) + { + if (_storageService.FindFilesInPackage(src, searchPathways.SubFolder, "*.dll", + true) is { IsSuccess: true, Value.IsDefaultOrEmpty: false } result) + { + builder.Add(new AssemblyResourceInfo() + { + OwnerPackage = src, + InternalName = searchPathways.SubFolder, + SupportedPlatforms = searchPathways.Platforms, + SupportedTargets = searchPathways.Targets, + LoadPriority = 0, + FilePaths = result.Value.Select(fp => ContentPath.FromRaw(src, $"%ModDir%/{Path.GetRelativePath(src.Dir, fp)}".CleanUpPathCrossPlatform())) + .ToImmutableArray(), + FriendlyName = $"{src.Name}.{searchPathways.SubFolder.Replace('/','.')}", + IncompatiblePackages = ImmutableArray.Empty, + RequiredPackages = ImmutableArray.Empty, + IsScript = false + }); + } + } + + var sharedResult = _storageService.FindFilesInPackage(src, + Path.Combine(src.Dir, "CSharp/Shared"), + "*.cs", true); + var sharedFiles = sharedResult.IsSuccess && !sharedResult.Value.IsDefaultOrEmpty + ? sharedResult.Value.Select(fp => + ContentPath.FromRaw(src, $"%ModDir%/{Path.GetRelativePath(src.Dir, fp)}".CleanUpPathCrossPlatform())) + .ToImmutableArray() + : ImmutableArray.Empty; + + var srcSearchInd = new (string SubFolder, Target Targets, Platform Platforms)[] + { + ("CSharp/Client", Target.Client, Platform.Any), + ("CSharp/Server", Target.Server, Platform.Any) + }; + + foreach (var searchPathways in srcSearchInd) + { + if (_storageService.FindFilesInPackage(src, searchPathways.SubFolder, "*.cs", + true) is { IsSuccess: true, Value.IsDefaultOrEmpty: false } result) + { + builder.Add(new AssemblyResourceInfo() + { + OwnerPackage = src, + InternalName = searchPathways.SubFolder, + SupportedPlatforms = searchPathways.Platforms, + SupportedTargets = searchPathways.Targets, + LoadPriority = 0, + FilePaths = result.Value + .Select(fp => ContentPath.FromRaw(src, + $"%ModDir%/{Path.GetRelativePath(src.Dir, fp)}".CleanUpPathCrossPlatform())) + .Concat(sharedFiles).ToImmutableArray(), + FriendlyName = IAssemblyLoaderService.InternalsAwareAssemblyName, + IncompatiblePackages = ImmutableArray.Empty, + RequiredPackages = ImmutableArray.Empty, + IsScript = true + }); + } + } + + return builder.ToImmutable(); + } + + ImmutableArray GetConfigsLegacy(ContentPackage src) + { + return ImmutableArray.Empty; + } + + ImmutableArray GetLuaScriptsLegacy(ContentPackage src) + { + var builder = ImmutableArray.CreateBuilder(); + + if (_storageService.FindFilesInPackage(src, "Lua", "*.lua", true) + is { IsSuccess: true, Value.IsDefaultOrEmpty: false } result) + { + var autorun = result.Value + .Where(fp => fp.CleanUpPathCrossPlatform().Contains("Lua/Autorun/")) + .ToImmutableArray(); + var autorunFP = autorun.Select(fp => ContentPath.FromRaw(src, + $"%ModDir%/{Path.GetRelativePath(src.Dir, fp)}".CleanUpPathCrossPlatform())) + .ToImmutableArray(); + var reg = result.Value.Except(autorun) + .Select(fp => ContentPath.FromRaw(src, + $"%ModDir%/{Path.GetRelativePath(src.Dir, fp)}".CleanUpPathCrossPlatform())) + .ToImmutableArray(); + + builder.Add(new LuaScriptsResourceInfo() + { + OwnerPackage = src, + InternalName = "LegacyAutorun", + SupportedPlatforms = Platform.Any, + SupportedTargets = Target.Any, + LoadPriority = 1, // autorun should be last to ensure that dependent code in other files are loaded first + FilePaths = autorunFP, + IncompatiblePackages = ImmutableArray.Empty, + RequiredPackages = ImmutableArray.Empty, + IsAutorun = true, + }); + + builder.Add(new LuaScriptsResourceInfo() + { + OwnerPackage = src, + InternalName = "Legacy", + SupportedPlatforms = Platform.Any, + SupportedTargets = Target.Any, + LoadPriority = 0, // should be included first to ensure that dependent code in these files are available + FilePaths = reg, + IncompatiblePackages = ImmutableArray.Empty, + RequiredPackages = ImmutableArray.Empty, + IsAutorun = false, + }); + } + + return builder.ToImmutable(); + } + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs index c78decde0..5abfdc0a2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs @@ -12,6 +12,7 @@ using Barotrauma.LuaCs.Data; using Barotrauma.Networking; using FluentResults; using FluentResults.LuaCs; +using Microsoft.CodeAnalysis; using Microsoft.Toolkit.Diagnostics; using Error = FluentResults.Error; using Path = System.IO.Path; @@ -266,9 +267,10 @@ public class StorageService : IStorageService Guard.IsNotNull(package, nameof(package)); try { + var cp = ContentPath.FromRaw(package, package.Dir); var fullPath = localSubfolder.IsNullOrWhiteSpace() - ? Path.GetFullPath(package.Dir) - : Path.GetFullPath(package.Dir, localSubfolder); + ? Path.GetFullPath(cp.FullPath) + : Path.GetFullPath(localSubfolder, cp.FullPath); return System.IO.Directory.GetFiles(fullPath, regexFilter, searchRecursively ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly) .ToImmutableArray(); From 2a1d7760e686eb166712df5e09f117fce4909a58 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 27 Jan 2026 16:53:29 -0500 Subject: [PATCH 069/288] - Enabled caching for LuaScriptLoader.cs --- .../SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs index c78f8c221..28376f513 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs @@ -19,6 +19,7 @@ namespace Barotrauma.LuaCs.Services.Safe { this._storageService = storageService; this._loggerService = loggerService; + storageService.UseCaching = true; } private readonly ISafeStorageService _storageService; From c776a5424d30ce1ad3fd600d886c5b66de90b38e Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 28 Jan 2026 19:12:45 -0500 Subject: [PATCH 070/288] Made GetType return interfaces by default. --- .../SharedSource/LuaCs/Services/PluginManagementService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 1ecdb3b8b..be2054aad 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -114,7 +114,7 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage : FluentResults.Result.Ok(builder.ToImmutable()); } - public Type GetType(string typeName, bool isByRefType = false, bool includeInterfaces = false, + public Type GetType(string typeName, bool isByRefType = false, bool includeInterfaces = true, bool includeDefaultContext = true) { if (includeDefaultContext) From 6619def3654789d3955eaf29e19b054ac6f9a72f Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Wed, 28 Jan 2026 21:41:42 -0300 Subject: [PATCH 071/288] The concurrent toolbox dictionary situation is crazy --- Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs index a96376047..f8c13c177 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/ToolBox.cs @@ -11,6 +11,7 @@ using System.Net; using System.Reflection; using System.Security.Cryptography; using System.Text; +using System.Collections.Concurrent; namespace Barotrauma { @@ -75,7 +76,7 @@ namespace Barotrauma return !corrected; } - private static readonly Dictionary cachedFileNames = new Dictionary(); + private static readonly ConcurrentDictionary cachedFileNames = new ConcurrentDictionary(); public static string CorrectFilenameCase(string filename, out bool corrected, string directory = "") { @@ -153,7 +154,7 @@ namespace Barotrauma if (i < subDirs.Length - 1) { filename += "/"; } } - cachedFileNames.Add(originalFilename, filename); + cachedFileNames.TryAdd(originalFilename, filename); return filename; } From 92232d114bf2803bce229facd533d8ee0b3d026a Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 28 Jan 2026 20:33:32 -0500 Subject: [PATCH 072/288] Ensures that ILuaCsHook will resolve to the existing event service instance. --- .../SharedSource/LuaCs/LuaCsSetup.cs | 3 ++- .../SharedSource/LuaCs/Services/ServicesProvider.cs | 13 +++++++++++++ .../LuaCs/Services/_Interfaces/IServicesProvider.cs | 9 ++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 9413289c8..bc9363642 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -20,6 +20,7 @@ using Barotrauma.Networking; using Barotrauma.Steam; using FluentResults; using ImpromptuInterface; +using LightInject; using Microsoft.Toolkit.Diagnostics; namespace Barotrauma @@ -181,7 +182,7 @@ namespace Barotrauma servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceResolver(factory => factory.GetInstance() as ILuaCsHook); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs index 65f79bb17..37e454431 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs @@ -100,6 +100,19 @@ public class ServicesProvider : IServicesProvider } } + public void RegisterServiceResolver(Func factory) where TSvcInterface : class, IService + { + try + { + _serviceLock.EnterReadLock(); + ServiceContainer.Register(f => factory(ServiceContainer)); + } + finally + { + _serviceLock.ExitReadLock(); + } + } + public void Compile() { try diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IServicesProvider.cs index cce7a24c7..901f3de4a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IServicesProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IServicesProvider.cs @@ -37,7 +37,14 @@ public interface IServicesProvider /// Args[1]: Implementing type /// event System.Action OnServiceRegistered; - + + /// + /// Registers a factory for resolving the service type. + /// + /// + /// + void RegisterServiceResolver(Func factory) where TSvcInterface : class, IService; + /// /// Runs compilation of registered services. /// From 22f587b7b9479bfbb1c4900701cf16fc3709c5e3 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 28 Jan 2026 21:05:57 -0500 Subject: [PATCH 073/288] Changed ModConfig.xml spec: RunFile => IsAutorun. --- .../LuaCs/Services/Processing/ModConfigFileParserService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigFileParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigFileParserService.cs index dd2059909..3f9b5ef25 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigFileParserService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigFileParserService.cs @@ -149,7 +149,7 @@ public sealed class ModConfigFileParserService : RequiredPackages = src.Required, IncompatiblePackages = src.Incompatible, // Type Specific - IsAutorun = src.Element.GetAttributeBool("RunFile", false) + IsAutorun = src.Element.GetAttributeBool("IsAutorun", false) }; } From ab2638b2cb035cba7e424695f09722e0fb00e55e Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Wed, 28 Jan 2026 23:37:12 -0300 Subject: [PATCH 074/288] Fix event service not clearing _luaOrphanSubscribers --- .../SharedSource/LuaCs/Services/EventService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs index db42a1194..9839afa1b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs @@ -349,6 +349,7 @@ public class EventService : IEventService, IEventAssemblyContextUnloading _luaSubscriptionFactories.Clear(); _eventTypeNameAliases.Clear(); _luaLegacySubscriptionFactories.Clear(); + _luaOrphanSubscribers.Clear(); GC.SuppressFinalize(this); } @@ -359,6 +360,7 @@ public class EventService : IEventService, IEventAssemblyContextUnloading _luaSubscriptionFactories.Clear(); _eventTypeNameAliases.Clear(); _luaLegacySubscriptionFactories.Clear(); + _luaOrphanSubscribers.Clear(); return FluentResults.Result.Ok(); } From f28749d45525ee7ba9da3a3a6b85ae85a14a5e67 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Wed, 28 Jan 2026 23:37:40 -0300 Subject: [PATCH 075/288] Missing IsAutorun --- .../BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml | 2 +- .../LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml index 2119fc978..363f7695b 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml @@ -1,5 +1,5 @@ - + diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml index 83469054f..3db2aa78f 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file From f0f09c20fa5e8fb0b59b69508b0a2cf3d010af79 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Wed, 28 Jan 2026 23:38:00 -0300 Subject: [PATCH 076/288] Plugin moment --- .../SharedSource/LuaCs/Services/PluginManagementService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index be2054aad..6bc10f0d6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -152,7 +152,7 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage ThrowHelper.ThrowArgumentNullException($"{nameof(LoadAssemblyResources)}: The resources list is empty!"); } - throw new NotImplementedException(); + return FluentResults.Result.Fail("not implemented"); } public ImmutableArray> ActivateTypeInstances(ImmutableArray types, bool serviceInjection = true, From 67d3d5f5873eb221d113a3e1f77f3694c1ade861 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Wed, 28 Jan 2026 23:41:23 -0300 Subject: [PATCH 077/288] Reimplementation of DoString with lua/cl_lua commands and fix Lua scripts not being loaded properly --- .../ClientSource/DebugConsole.cs | 15 ++++-------- .../ServerSource/DebugConsole.cs | 11 ++------- .../LuaCsForBarotrauma/Lua/LuaSetup.lua | 10 ++------ .../LuaCs/Lua/LuaClasses/LuaUserData.cs | 2 +- .../Services/LuaScriptManagementService.cs | 23 +++++++++++++++---- .../ILuaScriptManagementService.cs | 1 + 6 files changed, 28 insertions(+), 34 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index fbd680b86..38e2bb6f7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -4225,27 +4225,20 @@ namespace Barotrauma commands.Add(new Command("cl_lua", $"cl_lua: Runs a string on the client.", (string[] args) => { - throw new NotImplementedException(); - /*if (GameMain.Client != null && !GameMain.Client.HasPermission(ClientPermissions.ConsoleCommands)) + if (GameMain.Client != null && !GameMain.Client.HasPermission(ClientPermissions.ConsoleCommands)) { ThrowError("Command not permitted."); return; } - if (GameMain.LuaCs.Lua == null) + if (GameMain.LuaCs.CurrentRunState != RunState.Running) { ThrowError("LuaCs not initialized, use the console command cl_reloadluacs to force initialization."); return; } - try - { - GameMain.LuaCs.Lua.DoString(string.Join(" ", args)); - } - catch(Exception ex) - { - LuaCsLogger.HandleException(ex, LuaCsMessageOrigin.LuaMod); - }*/ + var result = GameMain.LuaCs.LuaScriptManagementService.DoString(string.Join(" ", args)); + GameMain.LuaCs.Logger.LogResults(result.ToResult()); })); commands.Add(new Command("cl_reloadlua|cl_reloadcs|cl_reloadluacs", "Re-initializes the LuaCs environment.", (string[] args) => diff --git a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs index 555d790d3..507bacc83 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs @@ -1291,15 +1291,8 @@ namespace Barotrauma commands.Add(new Command("lua", "lua: Runs a string.", (string[] args) => { - try - { - throw new NotImplementedException(); - //GameMain.LuaCs.Lua.DoString(string.Join(" ", args)); - } - catch (Exception ex) - { - LuaCsLogger.HandleException(ex, LuaCsMessageOrigin.LuaMod); - } + var result = GameMain.LuaCs.LuaScriptManagementService.DoString(string.Join(" ", args)); + GameMain.LuaCs.Logger.LogResults(result.ToResult()); })); commands.Add(new Command("reloadlua|reloadcs|reloadluacs", "Re-initializes the LuaCs environment.", (string[] args) => diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua index f324b3f7b..4b038464e 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua @@ -1,6 +1,6 @@ LuaSetup = {} -local path, resourcesToExecute = ... +local path = ... package.path = {path .. "/Lua/?.lua"} @@ -42,10 +42,4 @@ require("DefaultLib/Utils/SteamApi") require("PostSetup") -LuaSetup = nil - -for resource in resourcesToExecute do - for path in resource.FilePaths do - dofile(path) - end -end \ No newline at end of file +LuaSetup = nil \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs index 0918ff9a6..1cd7fccc2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs @@ -11,7 +11,7 @@ namespace Barotrauma internal class LuaUserData { [Obsolete("Use IPluginManagementService::GetTypesByName()")] - public static Type GetType(string typeName) => GameMain.LuaCs.PluginManagementService.GetType(typeName); //LuaCsSetup.GetType(typeName); + public static Type GetType(string typeName) => GameMain.LuaCs.PluginManagementService.GetType(typeName, includeInterfaces: true); //LuaCsSetup.GetType(typeName); public static IUserDataDescriptor RegisterType(string typeName) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs index 33c0eb915..958ab5365 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs @@ -99,6 +99,22 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService return new FluentResults.Result().WithReasons(cacheRes.Value.SelectMany(cr => cr.Item2.Reasons)); } + public FluentResults.Result DoString(string code) + { + IService.CheckDisposed(this); + if (_script == null || !IsRunning) { throw new Exception("Disposed"); } + + try + { + var result = _script.DoString(code); + return FluentResults.Result.Ok(result); + } + catch (Exception ex) + { + return FluentResults.Result.Fail(new ExceptionalError(ex)); + } + } + private DynValue DoFile(string file, Table? globalContext = null, string? codeStringFriendly = null) { if (_script == null) @@ -205,16 +221,13 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService var result = FluentResults.Result.Ok(); - List initializationScripts = executionOrder.Where(x => x.OwnerPackage.Name == "LuaCsForBarotrauma").ToList(); - List otherScripts = executionOrder.Except(initializationScripts).ToList(); - - foreach (ILuaScriptResourceInfo resource in initializationScripts) + foreach (ILuaScriptResourceInfo resource in executionOrder.Where(l => l.IsAutorun)) { foreach (ContentPath filePath in resource.FilePaths) { try { - _script?.Call(_script.LoadFile(filePath.FullPath), Path.GetDirectoryName(resource.OwnerPackage.Path), otherScripts.ToList()); + _script?.Call(_script.LoadFile(filePath.FullPath), Path.GetDirectoryName(resource.OwnerPackage.Path)); } catch(Exception e) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs index 4ca8ea401..f53cee1ed 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs @@ -17,6 +17,7 @@ public interface ILuaScriptManagementService : IReusableService #region Script_Ops object? GetGlobalTableValue(string tableName); + FluentResults.Result DoString(string code); /// /// Parses and loads script sources (code) into a memory cache without executing it. From a28a6f3320dc573b1fbf8d7f154b5c1f3f095280 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 29 Jan 2026 16:38:35 -0500 Subject: [PATCH 078/288] - Added LuaCs ordering filter. --- .../LuaCs/Services/PackageManagementService.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index 286becd0d..65ad767df 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -129,7 +129,11 @@ public sealed class PackageManagementService : IPackageManagementService IService.CheckDisposed(this); var result = new FluentResults.Result(); - var pkgConfigs = _modConfigService.CreateConfigsAsync([..packages]).ConfigureAwait(false).GetAwaiter().GetResult(); + var packages2 = packages.OrderBy(pkg => pkg.Name == "LuaCsForBarotrauma" ? 0 : 1) // always run lua cs first. + .ThenBy(packages.IndexOf) + .ToImmutableArray(); + + var pkgConfigs = _modConfigService.CreateConfigsAsync([..packages2]).ConfigureAwait(false).GetAwaiter().GetResult(); foreach (var pkgConfig in pkgConfigs) { result.WithReasons(pkgConfig.Config.Reasons); @@ -211,7 +215,9 @@ public sealed class PackageManagementService : IPackageManagementService var result = new FluentResults.Result(); // get loading order. Note: packages not in the execution order list will load first. - var loadingOrderedPackages = _loadedPackages.OrderBy(pkg => executionOrder.IndexOf(pkg.Key)) + var loadingOrderedPackages = _loadedPackages + .OrderBy(pkg => pkg.Key.Name == "LuaCsForBarotrauma" ? 0 : 1) // always run lua cs first. + .ThenBy(pkg => executionOrder.IndexOf(pkg.Key)) .ToImmutableArray(); var loadOrderByPackage = loadingOrderedPackages.Select(p => p.Key).ToImmutableArray(); var toLoadPackagesIndents = loadingOrderedPackages From 37e3a195dcc6a618426dfb38bae79c938a9b4722 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 29 Jan 2026 16:54:01 -0500 Subject: [PATCH 079/288] - Now logs results from SyncLoadedPackagesList --- Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index bc9363642..223e83ba0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -184,7 +184,8 @@ namespace Barotrauma servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceResolver(factory => factory.GetInstance() as ILuaCsHook); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceResolver(factory => factory.GetInstance()); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); @@ -252,7 +253,7 @@ namespace Barotrauma SetRunState(RunState.LoadedNoExec); } - PackageManagementService.SyncLoadedPackagesList(packages); + this.Logger.LogResults(PackageManagementService.SyncLoadedPackagesList(packages)); SetRunState(state); // restore } From 13a9bc443e4897bda892c98478b9966057bea458 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 29 Jan 2026 18:01:45 -0500 Subject: [PATCH 080/288] - Some work on PluginManagementService, IAssemblyLoaderService and IAssemblyManagementService refactors. - Fixed mod list sync not checking for zero package diff length. - Fixed the services provider not being able to inject itself as a dependcency. - Added UseInternalName data spec to ModConfig.xml - Changed Basic.Reference.Assemblies to Net80. --- Barotrauma/BarotraumaShared/Luatrauma.props | 2 +- .../Data/DataInterfaceImplementations.cs | 1 + .../LuaCs/Data/IResourceInfoDeclarations.cs | 7 + .../SharedSource/LuaCs/LuaCsSetup.cs | 2 + .../LuaCs/Services/LoggerService.cs | 4 +- .../Services/PackageManagementService.cs | 16 +- .../LuaCs/Services/PluginManagementService.cs | 414 +++++++++++------- .../Processing/ModConfigFileParserService.cs | 1 + .../LuaCs/Services/ServicesProvider.cs | 2 + .../_Interfaces/IAssemblyManagementService.cs | 15 +- .../_Interfaces/IPluginManagementService.cs | 13 +- .../LuaCs/_Plugins/AssemblyLoader.cs | 21 +- .../LuaCs/_Plugins/IAssemblyLoaderService.cs | 6 +- 13 files changed, 324 insertions(+), 180 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Luatrauma.props b/Barotrauma/BarotraumaShared/Luatrauma.props index ebc46700a..a17acc078 100644 --- a/Barotrauma/BarotraumaShared/Luatrauma.props +++ b/Barotrauma/BarotraumaShared/Luatrauma.props @@ -7,7 +7,7 @@ - + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs index 274d41c0c..514188b3f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs @@ -43,6 +43,7 @@ public record AssemblyResourceInfo : BaseResourceInfo, IAssemblyResourceInfo { public string FriendlyName { get; init; } public bool IsScript { get; init; } + public bool UseInternalAccessName { get; init; } } /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs index 366578fdb..f40850cfa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; +using System.Runtime.CompilerServices; namespace Barotrauma.LuaCs.Data; @@ -31,6 +32,12 @@ public interface IAssemblyResourceInfo : IBaseResourceInfo /// Is this entry referring to a script file collection. /// public bool IsScript { get; } + + /// + /// [Required(IsScript: true)] Whether the internal compiled assembly name should be named to enabled use of the + /// attribute. + /// + public bool UseInternalAccessName { get; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 223e83ba0..1d32ce229 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -22,6 +22,7 @@ using FluentResults; using ImpromptuInterface; using LightInject; using Microsoft.Toolkit.Diagnostics; +using AssemblyLoader = Barotrauma.LuaCs.Services.AssemblyLoader; namespace Barotrauma { @@ -185,6 +186,7 @@ namespace Barotrauma servicesProvider.RegisterServiceResolver(factory => factory.GetInstance() as ILuaCsHook); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceResolver(factory => factory.GetInstance()); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs index fc5e23586..f0bfc110a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs @@ -182,7 +182,7 @@ public partial class LoggerService : ILoggerService return; } - if (result.Errors.Any()) + if (result.IsFailed) { foreach (var error in result.Errors) { @@ -192,7 +192,7 @@ public partial class LoggerService : ILoggerService } else { - LogError(error.Message); + LogError($"FluentResults::IError: {error.Message}"); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index 65ad767df..c26a38e83 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -261,7 +261,9 @@ public sealed class PackageManagementService : IPackageManagementService return result; } - private static ImmutableArray SelectCompatible(ImmutableArray resources, ImmutableHashSet enabledPackagesIdents, ImmutableArray loadingOrder) + private static ImmutableArray SelectCompatible(ImmutableArray resources, + ImmutableHashSet enabledPackagesIdents, + ImmutableArray loadingOrder) where T : IBaseResourceInfo { return resources @@ -289,14 +291,22 @@ public sealed class PackageManagementService : IPackageManagementService var result = new FluentResults.Result(); - result.WithReasons(UnloadPackages(toRemove).Reasons); + if (!toRemove.IsDefaultOrEmpty) + { + result.WithReasons(UnloadPackages(toRemove).Reasons); + } if (result.IsFailed) { return result; } - return result.WithReasons(LoadPackagesInfo(toAdd).Reasons); + if (!toAdd.IsDefaultOrEmpty) + { + result.WithReasons(LoadPackagesInfo(toAdd).Reasons); + } + + return result; } public FluentResults.Result StopRunningPackages() diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 6bc10f0d6..63b9e3a51 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -7,114 +7,124 @@ using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Loader; +using System.Text; using System.Threading; using Barotrauma.Extensions; +using Barotrauma.IO; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Events; using FluentResults; using FluentResults.LuaCs; using ImpromptuInterface.Build; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; +using Microsoft.Extensions.Logging; using Microsoft.Toolkit.Diagnostics; using OneOf; namespace Barotrauma.LuaCs.Services; -public class PluginManagementService : IPluginManagementService, IAssemblyManagementService +public class PluginManagementService : IAssemblyManagementService { - private readonly Func _assemblyLoaderServiceFactory; - private readonly ConcurrentDictionary ResourceInfos, IAssemblyLoaderService Loader)> _packageAssemblyResources = new(); - private readonly ConcurrentDictionary> _pluginInstances = new(); - private readonly Lazy _eventService; - private readonly ConditionalWeakTable _unloadingAssemblyLoaders = new(); - private readonly ConditionalWeakTable> _assemblyTypesCache = new(); + #region CSHARP_COMPILATION_OPTIONS - public PluginManagementService( - Func assemblyLoaderServiceFactory, - Lazy eventService) - { - _assemblyLoaderServiceFactory = assemblyLoaderServiceFactory; - _eventService = eventService; - AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoadedGlobal; - } - - private void OnAssemblyLoadedGlobal(object sender, AssemblyLoadEventArgs args) - { - // cache types by name - try + private static readonly CSharpParseOptions ScriptParseOptions = CSharpParseOptions.Default + .WithPreprocessorSymbols(new[] { - var context = AssemblyLoadContext.GetLoadContext(args.LoadedAssembly); - if (context is not IAssemblyLoaderService loaderService) - return; - _eventService.Value.PublishEvent(sub => sub.OnAssemblyLoaded(args.LoadedAssembly)); - var lookupDict = new ConcurrentDictionary(); - foreach (var type in args.LoadedAssembly.GetSafeTypes()) - { - lookupDict[type.FullName ?? type.Name] = type; - } - _assemblyTypesCache.AddOrUpdate(args.LoadedAssembly, lookupDict); - } - catch (Exception e) - { - // ignored - return; - } - } +#if SERVER + "SERVER" +#elif CLIENT + "CLIENT" +#else + "UNDEFINED" +#endif +#if DEBUG + ,"DEBUG" +#endif + }); - private int _isDisposed = 0; - public bool IsDisposed - { - get => ModUtils.Threading.GetBool(ref _isDisposed); - private set => ModUtils.Threading.SetBool(ref _isDisposed, value); - } +#if WINDOWS + private const string PLATFORM_TARGET = "Windows"; +#elif OSX + private const string PLATFORM_TARGET = "OSX"; +#elif LINUX + private const string PLATFORM_TARGET = "Linux"; +#endif + +#if CLIENT + private const string ARCHITECTURE_TARGET = "Client"; +#elif SERVER + private const string ARCHITECTURE_TARGET = "Server"; +#endif + + private static readonly CSharpCompilationOptions CompilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + .WithMetadataImportOptions(MetadataImportOptions.All) +#if DEBUG + .WithOptimizationLevel(OptimizationLevel.Debug) +#else + .WithOptimizationLevel(OptimizationLevel.Release) +#endif + .WithAllowUnsafe(true); + + private static readonly SyntaxTree BaseAssemblyImports = CSharpSyntaxTree.ParseText( + new StringBuilder() + .AppendLine("using System.Reflection;") + .AppendLine("using Barotrauma;") + .AppendLine("using System.Runtime.CompilerServices;") + .AppendLine("[assembly: IgnoresAccessChecksTo(\"BarotraumaCore\")]") +#if CLIENT + .AppendLine("[assembly: IgnoresAccessChecksTo(\"Barotrauma\")]") +#elif SERVER + .AppendLine("[assembly: IgnoresAccessChecksTo(\"DedicatedServer\")]") +#endif + .ToString(), + ScriptParseOptions); + + #endregion + + #region Disposal public void Dispose() { throw new NotImplementedException(); } + public bool IsDisposed { get; } public FluentResults.Result Reset() { - if (IsDisposed) - return FluentResults.Result.Fail($"{nameof(PluginManagementService)} is disposed!"); - - return FluentResults.Result.Fail("not implemented"); + throw new NotImplementedException(); } - public Result> GetImplementingTypes(bool includeInterfaces = false, - bool includeAbstractTypes = false, bool includeDefaultContext = true) + #endregion + + private IServicesProvider _serviceProvider; + private IAssemblyLoaderService.IFactory _assemblyLoaderFactory; + private IStorageService _storageService; + private ILoggerService _logger; + private readonly ConcurrentDictionary _assemblyLoaders = new(); + private readonly AsyncReaderWriterLock _operationsLock = new(); + + public PluginManagementService( + IServicesProvider serviceProvider, + IAssemblyLoaderService.IFactory assemblyLoaderFactory, + IStorageService storageService, + ILoggerService logger) { - var builder = ImmutableArray.CreateBuilder(); - - if (this._packageAssemblyResources.Any()) - { - foreach (var resource in this._packageAssemblyResources - .Where(res => !res.Value.Loader.IsReferenceOnlyMode)) - { - builder.AddRange(resource.Value.Loader.Assemblies - .SelectMany(assembly => assembly.GetSafeTypes()) - .Where(type => type.IsAssignableTo(typeof(T))) - .Where(type => includeInterfaces || !type.IsInterface) - .Where(type => includeAbstractTypes || !type.IsAbstract)); - } - } - - if (includeDefaultContext) - { - builder.AddRange(AssemblyLoadContext.Default.Assemblies - .SelectMany(assembly => assembly.GetSafeTypes()) - .Where(type => type.IsAssignableTo(typeof(T))) - .Where(type => includeInterfaces || !type.IsInterface) - .Where(type => includeAbstractTypes || !type.IsAbstract)); - } - - return builder.Count == 0 - ? FluentResults.Result.Fail($"Failed to find any types that implement {typeof(T).Name})") - : FluentResults.Result.Ok(builder.ToImmutable()); + Guard.IsNotNull(serviceProvider, nameof(serviceProvider)); + _serviceProvider = serviceProvider; + _assemblyLoaderFactory = assemblyLoaderFactory; + _storageService = storageService; + _logger = logger; } - public Type GetType(string typeName, bool isByRefType = false, bool includeInterfaces = true, + public Result> GetImplementingTypes(bool includeInterfaces = false, bool includeAbstractTypes = false, + bool includeDefaultContext = true) + { + throw new NotImplementedException(); + } + + public Type GetType(string typeName, bool isByRefType = false, bool includeInterfaces = false, bool includeDefaultContext = true) { if (includeDefaultContext) @@ -144,91 +154,197 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage return null; } - public FluentResults.Result LoadAssemblyResources(ImmutableArray resources) - { - IService.CheckDisposed(this); - if (resources.IsDefaultOrEmpty) - { - ThrowHelper.ThrowArgumentNullException($"{nameof(LoadAssemblyResources)}: The resources list is empty!"); - } - - return FluentResults.Result.Fail("not implemented"); - } - public ImmutableArray> ActivateTypeInstances(ImmutableArray types, bool serviceInjection = true, bool hostInstanceReference = false) where T : IDisposable { + throw new NotImplementedException(); + } + + public FluentResults.Result LoadAssemblyResources(ImmutableArray resources) + { + if (resources.IsDefaultOrEmpty) + { + ThrowHelper.ThrowArgumentNullException($"{nameof(LoadAssemblyResources)} The resource list is empty.)"); + } + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + var orderedContentPacks = resources.GroupBy(res => res.OwnerPackage) + .OrderBy(res => resources.FindIndex(r2 => r2.OwnerPackage == res.Key)) + .ToImmutableArray(); + + var result = new FluentResults.Result(); + + return FluentResults.Result.Fail($"{nameof(LoadAssemblyResources)}: Not Implemented!"); + throw new NotImplementedException(); + + foreach (var contentPack in orderedContentPacks) + { + LoadBinaries(contentPack); + LoadAndCompileScriptAssemblies(contentPack); + } + + return result; + + // helper methods + void LoadBinaries(IGrouping contentPackRes) + { + var binaries = contentPackRes.Where(cRes => !cRes.IsScript) + .OrderBy(bin => bin.LoadPriority) + .SelectMany(bin => bin.FilePaths) + .ToImmutableArray(); + + if (binaries.IsDefaultOrEmpty) + { + return; + } + + var assemblyLoader = _assemblyLoaders.GetOrAdd(contentPackRes.Key, (cp) => _assemblyLoaderFactory.CreateInstance( + new IAssemblyLoaderService.LoaderInitData( + InstanceId: Guid.NewGuid(), + contentPackRes.Key.Name, + IsReferenceMode: false, + OwnerPackage: contentPackRes.Key, + OnUnload: OnAssemblyLoaderUnloading, + OnResolvingManaged: OnAssemblyLoaderResolvingManaged, + OnResolvingUnmanagedDll: OnAssemblyLoaderResolvingUnmanaged + ))); + + var dependencyPaths = binaries + .Select(bin => System.IO.Path.GetDirectoryName(bin.FullPath)) + .Distinct() + .ToImmutableArray(); + + foreach (var binResource in binaries) + { + var res = assemblyLoader.LoadAssemblyFromFile(binResource.FullPath, dependencyPaths); + result.WithReasons(res.Reasons); #if DEBUG - return ImmutableArray>.Empty; + _logger.LogResults(res.ToResult()); #endif + if (res.IsFailed) + { + _logger.LogResults(res.ToResult()); + } + } + } + + void LoadAndCompileScriptAssemblies(IGrouping contentPackRes) + { + var scripts = contentPackRes.Where(cRes => cRes.IsScript) + .OrderBy(scr => scr.LoadPriority) + .Select(scr => (scr.FriendlyName, scr.FilePaths, scr.UseInternalAccessName)) + .GroupBy(scr => scr.FriendlyName) + .ToImmutableArray(); + + if (scripts.IsDefaultOrEmpty) + { + return; + } + + var metadataReferences = GetMetadataReferences(); + + var assemblyLoader = _assemblyLoaders.GetOrAdd(contentPackRes.Key, (cp) => _assemblyLoaderFactory.CreateInstance( + new IAssemblyLoaderService.LoaderInitData( + InstanceId: Guid.NewGuid(), + contentPackRes.Key.Name, + IsReferenceMode: false, + OwnerPackage: contentPackRes.Key, + OnUnload: OnAssemblyLoaderUnloading, + OnResolvingManaged: OnAssemblyLoaderResolvingManaged, + OnResolvingUnmanagedDll: OnAssemblyLoaderResolvingUnmanaged + ))); + + // create syntax trees + var syntaxTreesBuilder = ImmutableArray.CreateBuilder(); + + foreach (var resourceInfo in contentPackRes) + { + if (resourceInfo.FilePaths.IsDefaultOrEmpty) + { + ThrowHelper.ThrowArgumentNullException($"{nameof(LoadAndCompileScriptAssemblies)} The resource list is empty for package {resourceInfo.OwnerPackage}."); + } + + var loadRes = GetSourceFilesText(resourceInfo.FilePaths); + if (loadRes.IsFailed) + { + _logger.LogResults(loadRes.ToResult()); + continue; + } + + CancellationToken token = CancellationToken.None; + + syntaxTreesBuilder.Add(SyntaxFactory.ParseSyntaxTree( + text: loadRes.Value, + options: ScriptParseOptions, + path: null, + encoding: Encoding.Default, + cancellationToken: token + )); + } + + throw new NotImplementedException(); + } + + Result GetSourceFilesText(ImmutableArray resourceInfoFilePaths) + { + if (_storageService.LoadPackageTextFiles(resourceInfoFilePaths) is not { IsDefaultOrEmpty: false } res) + { + _logger.LogError($"{nameof(GetSourceFilesText)}: Failed to load source files for ContentPackage {resourceInfoFilePaths.First().ContentPackage?.Name}."); + return FluentResults.Result.Fail($"{nameof(GetSourceFilesText)}: Failed to load source files for ContentPackage {resourceInfoFilePaths.First().ContentPackage?.Name}."); + } + + var loadRes = new FluentResults.Result(); + StringBuilder sb = new StringBuilder(); + + foreach ((ContentPath Path, Result FileResult) loadResult in res) + { + if (loadResult.FileResult.IsFailed) + { + loadRes.WithErrors(loadResult.FileResult.Errors); + continue; + } + + sb.AppendLine(loadResult.FileResult.Value); + } + + if (loadRes.IsFailed) + { + return loadRes; + } + + return sb.ToString(); + } + + IEnumerable GetMetadataReferences() + { + return Basic.Reference.Assemblies.Net80.References.All; + } + } + + private IntPtr OnAssemblyLoaderResolvingUnmanaged(Assembly arg1, string arg2) + { + throw new NotImplementedException(); + } + + private Assembly OnAssemblyLoaderResolvingManaged(IAssemblyLoaderService arg1, AssemblyName arg2) + { + throw new NotImplementedException(); + } + + private void OnAssemblyLoaderUnloading(IAssemblyLoaderService loader) + { throw new NotImplementedException(); } public FluentResults.Result UnloadManagedAssemblies() { - var res = new FluentResults.Result(); - - // cleanup managed plugins - if (_pluginInstances.Any()) - { - foreach (var packageInstances in _pluginInstances) - { - if (!packageInstances.Value.Any()) - continue; - - foreach (var disposable in packageInstances.Value) - { - try - { - disposable.Dispose(); - } - catch (Exception e) - { - res = res.WithError(new ExceptionalError(e) - .WithMetadata(MetadataType.ExceptionObject, this)); - } - } - } - _pluginInstances.Clear(); - } - - _assemblyTypesCache.Clear(); - - // cleanup running assembly contexts - if (_packageAssemblyResources.Any()) - { - foreach (var resource in _packageAssemblyResources.ToImmutableDictionary()) - { - if (resource.Value.Loader is not null) - { - try - { - resource.Value.Loader.Dispose(); - _unloadingAssemblyLoaders.AddOrUpdate(resource.Value.Loader, resource.Key); - _packageAssemblyResources.TryRemove(resource); - _packageAssemblyResources.TryAdd(resource.Key, (resource.Value.ResourceInfos, null)); - } - catch (Exception e) - { - res = res.WithError(new ExceptionalError(e) - .WithMetadata(MetadataType.ExceptionObject, this)); - } - } - } - } - - return res.WithSuccess($"Unloading of managed assemblies started successfully,"); + return FluentResults.Result.Fail($"{nameof(UnloadManagedAssemblies)}: Not Implemented."); + throw new NotImplementedException(); } public Result GetLoadedAssembly(OneOf assemblyName, in Guid[] excludedContexts) { throw new NotImplementedException(); } - - public ImmutableArray GetDefaultMetadataReferences(bool includeDefaultContext = true) - { - throw new NotImplementedException(); - } - - public ImmutableArray AssemblyLoaderServices { get; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigFileParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigFileParserService.cs index 3f9b5ef25..b35212eae 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigFileParserService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigFileParserService.cs @@ -79,6 +79,7 @@ public sealed class ModConfigFileParserService : // Type Specific FriendlyName = src.Element.GetAttributeString("FriendlyName", string.Empty), IsScript = src.Element.GetAttributeBool("IsScript", false), + UseInternalAccessName = src.Element.GetAttributeBool("UseInternalAccessName", false) }; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs index 37e454431..d0a81f425 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs @@ -21,6 +21,8 @@ public class ServicesProvider : IServicesProvider { EnablePropertyInjection = false }); + + _serviceContainerInst.Register((f) => this); } public void RegisterServiceType(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IAssemblyManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IAssemblyManagementService.cs index 2499d55f8..d82105455 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IAssemblyManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IAssemblyManagementService.cs @@ -12,7 +12,7 @@ using OneOf; namespace Barotrauma.LuaCs.Services; -public interface IAssemblyManagementService : IReusableService +public interface IAssemblyManagementService : IPluginManagementService { /// @@ -22,17 +22,4 @@ public interface IAssemblyManagementService : IReusableService /// Guids of excluded contexts. /// On Success: The assembly.
On Failure: nothing.
FluentResults.Result GetLoadedAssembly(OneOf assemblyName, in Guid[] excludedContexts); - - /// - /// Gets all for all service-managed assemblies. - /// - /// collection for all service-managed, and default if selected, assemblies, if any are found. Returns an empty collection otherwise. - ImmutableArray GetDefaultMetadataReferences(bool includeDefaultContext = true); - - /// - /// Returns all active, managed assembly loaders. - /// - ImmutableArray AssemblyLoaderServices { get; } - - } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs index 700418bfb..83cd59981 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs @@ -32,13 +32,6 @@ public interface IPluginManagementService : IReusableService /// Type GetType(string typeName, bool isByRefType = false, bool includeInterfaces = false, bool includeDefaultContext = true); - /// - /// Loads the provided assembly resources in the order of their dependencies and intra-mod priority load order. - /// - /// - /// Success/Failure and list of failed resources, if any. - FluentResults.Result LoadAssemblyResources(ImmutableArray resources); - /// /// Creates instances of the given type and provides Property Injection and instance reference caching. Disposes of /// all references that throw errors on @@ -50,6 +43,12 @@ public interface IPluginManagementService : IReusableService ImmutableArray> ActivateTypeInstances(ImmutableArray types, bool serviceInjection = true, bool hostInstanceReference = false) where T : IDisposable; + /// + /// Loads the provided assembly resources in the order of their dependencies and intra-mod priority load order. + /// + /// + /// Success/Failure and list of failed resources, if any. + FluentResults.Result LoadAssemblyResources(ImmutableArray resources); /// /// Unloads all managed , , and s. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs index a7cebbd5c..7a82c8494 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs @@ -24,6 +24,20 @@ using Path = System.IO.Path; namespace Barotrauma.LuaCs.Services; public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService { + public class Factory : IAssemblyLoaderService.IFactory + { + public IAssemblyLoaderService CreateInstance(IAssemblyLoaderService.LoaderInitData initData) + { + return new AssemblyLoader(initData); + } + + public void Dispose() + { + //stateless service + } + public bool IsDisposed => false; + } + public Guid Id { get; init; } public ContentPackage OwnerPackage { get; private set; } public bool IsReferenceOnlyMode { get; init; } @@ -55,7 +69,6 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService private int _operationsRunning; //internal - private readonly IAssemblyManagementService _assemblyManagementService; private readonly Action _onUnload; private readonly Func _onResolvingManaged; private readonly Func _onResolvingUnmanagedDll; @@ -68,7 +81,6 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService public AssemblyLoader(IAssemblyLoaderService.LoaderInitData initData) : base(isCollectible: true, name: initData.Name) { - _assemblyManagementService = initData.AssemblyManagementService; Id = initData.InstanceId; IsReferenceOnlyMode = initData.IsReferenceMode; this._onUnload = initData.OnUnload; @@ -217,7 +229,10 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService try { var p = Path.GetFullPath(path.CleanUpPath()); - _dependencyResolvers[p] = new AssemblyDependencyResolver(p); + if (!_dependencyResolvers.ContainsKey(p)) + { + _dependencyResolvers[p] = new AssemblyDependencyResolver(p); + } } catch (Exception ex) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs index 071effa1a..c3dd6983a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs @@ -13,6 +13,11 @@ namespace Barotrauma.LuaCs; public interface IAssemblyLoaderService : IService { + public interface IFactory : IService + { + IAssemblyLoaderService CreateInstance(LoaderInitData initData); + } + /// /// Constructor record for instancing. /// @@ -26,7 +31,6 @@ public interface IAssemblyLoaderService : IService /// /// public record LoaderInitData( - [Required][NotNull] IAssemblyManagementService AssemblyManagementService, [Required] Guid InstanceId, [Required][NotNull] string Name, [Required] bool IsReferenceMode, From 708fe93efefc81b8f569ceff1caf1059ce8c4f71 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Thu, 29 Jan 2026 20:26:25 -0300 Subject: [PATCH 081/288] Some extra logging and bring back LuaCsTimer --- .../LuaCs/Lua/LuaClasses/LuaCsTimer.cs | 59 +++++++++++-------- .../SharedSource/LuaCs/LuaCsSetup.cs | 1 + .../Services/LuaScriptManagementService.cs | 16 +++-- .../LuaCs/Services/PluginManagementService.cs | 2 +- 4 files changed, 46 insertions(+), 32 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsTimer.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsTimer.cs index e276ed3d2..67e23cb5c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsTimer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsTimer.cs @@ -1,4 +1,5 @@ -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs.Events; +using Barotrauma.LuaCs.Services; using Barotrauma.LuaCs.Services.Compatibility; using System; using System.Collections.Generic; @@ -6,7 +7,7 @@ using System.Diagnostics; namespace Barotrauma { - public class LuaCsTimer : ILuaCsTimer + public class LuaCsTimer : ILuaCsTimer, IEventUpdate { public static double Time => Timing.TotalTime; public static double GetTime() => Time; @@ -55,6 +56,14 @@ namespace Barotrauma private List timedActions = new List(); + private readonly IEventService _eventService; + + public LuaCsTimer(IEventService eventService) + { + _eventService = eventService; + _eventService.Subscribe(this); + } + private void AddTimer(TimedAction timedAction) { if (timedAction == null) @@ -75,7 +84,29 @@ namespace Barotrauma } } - public void Update() + public void Clear() + { + timedActions = new List(); + } + + public void Wait(LuaCsAction action, int millisecondDelay) + { + TimedAction timedAction = new TimedAction(action, millisecondDelay); + AddTimer(timedAction); + } + + public void NextFrame(LuaCsAction action) + { + TimedAction timedAction = new TimedAction(action, 0); + AddTimer(timedAction); + } + + public void Dispose() + { + _eventService.Unsubscribe(this); + } + + public void OnUpdate(double fixedDeltaTime) { lock (timedActions) { @@ -104,28 +135,6 @@ namespace Barotrauma } } - public void Clear() - { - timedActions = new List(); - } - - public void Wait(LuaCsAction action, int millisecondDelay) - { - TimedAction timedAction = new TimedAction(action, millisecondDelay); - AddTimer(timedAction); - } - - public void NextFrame(LuaCsAction action) - { - TimedAction timedAction = new TimedAction(action, 0); - AddTimer(timedAction); - } - - public void Dispose() - { - // ignored - } - public bool IsDisposed => false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 1d32ce229..740e8ab89 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -191,6 +191,7 @@ namespace Barotrauma servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); // TODO: INetworkingService servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs index 958ab5365..370c5103d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs @@ -45,6 +45,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService private readonly ILoggerService _loggerService; private readonly LuaGame _luaGame; private readonly ILuaCsHook _luaCsHook; + private readonly ILuaCsTimer _luaCsTimer; //private readonly ILuaCsNetworking _luaCsNetworking; //private readonly ILuaCsUtility _luaCsUtility; //private readonly ILuaCsTimer _luaCsTimer; @@ -54,10 +55,10 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService ILuaScriptLoader loader, ILuaScriptServicesConfig luaScriptServicesConfig, LuaGame luaGame, - ILuaCsHook luaCsHook + ILuaCsHook luaCsHook, //ILuaCsNetworking luaCsNetworking, //ILuaCsUtility luaCsUtility, - //ILuaCsTimer luaCsTimer + ILuaCsTimer luaCsTimer ) { _luaScriptLoader = loader; @@ -68,7 +69,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _luaCsHook = luaCsHook; //_luaCsNetworking = luaCsNetworking; //_luaCsUtility = luaCsUtility; - //_luaCsTimer = luaCsTimer; + _luaCsTimer = luaCsTimer; } public bool IsDisposed { get; private set; } @@ -195,7 +196,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _script.Globals["Game"] = _luaGame; _script.Globals["Hook"] = _luaCsHook; - //_script.Globals["Timer"] = _luaCsTimer; + _script.Globals["Timer"] = _luaCsTimer; _script.Globals["File"] = UserData.CreateStatic(); //_script.Globals["Networking"] = _luaCsNetworking; //_script.Globals["Steam"] = Steam; @@ -215,18 +216,21 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService return FluentResults.Result.Fail("Tried to execute Lua scripts without unloading first."); } + _loggerService.LogMessage("Executing Lua scripts"); + SetupEnvironment(); - _isRunning = true; - var result = FluentResults.Result.Ok(); + _isRunning = true; + foreach (ILuaScriptResourceInfo resource in executionOrder.Where(l => l.IsAutorun)) { foreach (ContentPath filePath in resource.FilePaths) { try { + _loggerService.LogMessage($"Run {filePath.Value}"); _script?.Call(_script.LoadFile(filePath.FullPath), Path.GetDirectoryName(resource.OwnerPackage.Path)); } catch(Exception e) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 63b9e3a51..532173247 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -93,7 +93,7 @@ public class PluginManagementService : IAssemblyManagementService public bool IsDisposed { get; } public FluentResults.Result Reset() { - throw new NotImplementedException(); + return FluentResults.Result.Fail("Not implemented"); } #endregion From 3b65ea900814f444b306cc67426cca19f5b05b67 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Thu, 29 Jan 2026 20:35:44 -0300 Subject: [PATCH 082/288] Remove unused LuaCsConfig --- .../LuaCs/Lua/LuaClasses/LuaCsUtility.cs | 292 ------------------ 1 file changed, 292 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsUtility.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsUtility.cs index fd797cfc7..596f8de3b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsUtility.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsUtility.cs @@ -246,296 +246,4 @@ namespace Barotrauma return files.ToArray(); } } - - - class LuaCsConfig - { - private enum ValueType - { - None, - Text, - Integer, - Decimal, - Boolean, - Collection, - Object, - Enum - } - - private static Type[] LoadDocTypes(XElement typesElem) - { - throw new NotImplementedException(); - /*var result = new List(); - var loadedTypes = LuaCsSetup.AssemblyManager - .GetAllTypesInLoadedAssemblies() - .ToImmutableHashSet(); - - 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); - } - } } From c6c0aadb003da44e09030007a4188abeaaae4bb1 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Thu, 29 Jan 2026 20:36:00 -0300 Subject: [PATCH 083/288] Rename LuaCsHook --- .../Lua/LuaClasses/{LuaCsHook.cs => LuaPatcher.cs} | 12 ------------ .../{LuaCsHookCompat.cs => LuaPatcherCompat.cs} | 0 2 files changed, 12 deletions(-) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/{LuaCsHook.cs => LuaPatcher.cs} (99%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/{LuaCsHookCompat.cs => LuaPatcherCompat.cs} (100%) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsHook.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaPatcher.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsHook.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaPatcher.cs index 344e091d2..d34fe2844 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsHook.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaPatcher.cs @@ -786,11 +786,6 @@ namespace Barotrauma try { - if (luaCs.PerformanceCounter.EnablePerformanceCounter) - { - performanceMeasurement.Start(); - } - var result = tuple.Item1.func(args); if (result is DynValue luaResult) @@ -813,13 +808,6 @@ namespace Barotrauma { lastResult = (T)result; } - - if (luaCs.PerformanceCounter.EnablePerformanceCounter) - { - performanceMeasurement.Stop(); - //luaCs.PerformanceCounter.SetHookElapsedTicks(name, key, performanceMeasurement.ElapsedTicks); TODO - performanceMeasurement.Reset(); - } } catch (Exception e) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsHookCompat.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaPatcherCompat.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsHookCompat.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaPatcherCompat.cs From dfb31eef1665209c37b7d6e9492d3cffffed91b3 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Thu, 29 Jan 2026 20:42:35 -0300 Subject: [PATCH 084/288] Move Lua classes to the appropriate places --- .../{Lua/LuaDocs.cs => DocsInternals.cs} | 0 .../LuaCs/Lua/LuaClasses/LuaSafeUserData.cs | 198 ------------------ .../LuaClasses}/LuaBarotraumaAdditions.cs | 0 .../Safe/LuaClasses}/LuaConverters.cs | 0 .../Safe}/LuaClasses/LuaCsLogger.cs | 0 .../Safe}/LuaClasses/LuaCsNetworking.cs | 0 .../LuaClasses/LuaCsPerformanceCounter.cs | 0 .../Safe}/LuaClasses/LuaCsSteam.cs | 0 .../Safe}/LuaClasses/LuaCsTimer.cs | 0 .../Safe}/LuaClasses/LuaCsUtility.cs | 0 .../Safe}/LuaClasses/LuaGame.cs | 0 .../Safe}/LuaClasses/LuaPatcher.cs | 0 .../Safe}/LuaClasses/LuaPatcherCompat.cs | 0 .../Safe/LuaClasses}/LuaPlatformAccessor.cs | 0 .../Safe/LuaClasses}/LuaRequire.cs | 0 .../Safe}/LuaClasses/LuaTypes.cs | 0 .../Safe}/LuaClasses/LuaUserData.cs | 0 17 files changed, 198 deletions(-) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Lua/LuaDocs.cs => DocsInternals.cs} (100%) delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaSafeUserData.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Lua => Services/Safe/LuaClasses}/LuaBarotraumaAdditions.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Lua => Services/Safe/LuaClasses}/LuaConverters.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Lua => Services/Safe}/LuaClasses/LuaCsLogger.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Lua => Services/Safe}/LuaClasses/LuaCsNetworking.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Lua => Services/Safe}/LuaClasses/LuaCsPerformanceCounter.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Lua => Services/Safe}/LuaClasses/LuaCsSteam.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Lua => Services/Safe}/LuaClasses/LuaCsTimer.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Lua => Services/Safe}/LuaClasses/LuaCsUtility.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Lua => Services/Safe}/LuaClasses/LuaGame.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Lua => Services/Safe}/LuaClasses/LuaPatcher.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Lua => Services/Safe}/LuaClasses/LuaPatcherCompat.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Lua => Services/Safe/LuaClasses}/LuaPlatformAccessor.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Lua => Services/Safe/LuaClasses}/LuaRequire.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Lua => Services/Safe}/LuaClasses/LuaTypes.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Lua => Services/Safe}/LuaClasses/LuaUserData.cs (100%) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaDocs.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/DocsInternals.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaDocs.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/DocsInternals.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaSafeUserData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaSafeUserData.cs deleted file mode 100644 index e4d91d5c1..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaSafeUserData.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Reflection; -using MoonSharp.Interpreter; -using MoonSharp.Interpreter.Interop; - -namespace Barotrauma -{ - partial class LuaSafeUserData - { - public IUserDataDescriptor this[string index] - { - get => LuaUserData.Descriptors.GetValueOrDefault(index); - } - - private static bool CanBeRegistered(string typeName) - { - if (typeName.StartsWith("Barotrauma.Lua", StringComparison.Ordinal) || - typeName.StartsWith("Barotrauma.Cs", StringComparison.Ordinal) || - typeName.StartsWith("Barotrauma.LuaCs", StringComparison.Ordinal)) - { - return false; - } - - if (typeName == "System.Single") { return true; } - - if (typeName.StartsWith("System.Collections", StringComparison.Ordinal)) - return true; - - if (typeName.StartsWith("Microsoft.Xna", StringComparison.Ordinal)) - return true; - - if (typeName.StartsWith("Barotrauma.IO", StringComparison.Ordinal)) - return false; - - if (typeName.StartsWith("Barotrauma.ToolBox", StringComparison.Ordinal)) - return false; - - if (typeName.StartsWith("Barotrauma.SaveUtil", StringComparison.Ordinal)) - return false; - - if (typeName.StartsWith("Barotrauma.", StringComparison.Ordinal)) - return true; - - return false; - } - - private static bool CanBeReRegistered(string typeName) - { - if (typeName.StartsWith("Barotrauma.Lua", StringComparison.Ordinal) || - typeName.StartsWith("Barotrauma.Cs", StringComparison.Ordinal) || - typeName.StartsWith("Barotrauma.LuaCs", StringComparison.Ordinal)) - { - return false; - } - - return true; - } - - private static bool IsAllowed(string typeName) - { - if (!CanBeReRegistered(typeName) && LuaUserData.IsRegistered(typeName)) - { - return false; - } - - if (!CanBeRegistered(typeName) && !LuaUserData.IsRegistered(typeName)) - { - return false; - } - - return true; - } - - private static void CheckAllowed(string typeName) - { - if (!IsAllowed(typeName)) - { - throw new ScriptRuntimeException($"Type {typeName} can't be registered"); - } - } - - public static Type GetType(string typeName) - { - CheckAllowed(typeName); - - return LuaUserData.GetType(typeName); - } - - public static IUserDataDescriptor RegisterType(string typeName) - { - CheckAllowed(typeName); - - return LuaUserData.RegisterType(typeName); - } - - public static IUserDataDescriptor RegisterTypeBarotrauma(string typeName) - { - return RegisterType($"Barotrauma.{typeName}"); - } - - public static void RegisterExtensionType(string typeName) - { - CheckAllowed(typeName); - LuaUserData.RegisterExtensionType(typeName); - } - - public static bool IsRegistered(string typeName) - { - return LuaUserData.IsRegistered(typeName); - } - - public static void UnregisterType(string typeName, bool deleteHistory = false) - { - LuaUserData.UnregisterType(typeName, deleteHistory); - } - public static IUserDataDescriptor RegisterGenericType(string typeName, params string[] typeNameArguements) - { - CheckAllowed(typeName); - return LuaUserData.RegisterGenericType(typeName, typeNameArguements); - } - - public static void UnregisterGenericType(string typeName, params string[] typeNameArguements) - { - LuaUserData.UnregisterGenericType(typeName, typeNameArguements); - } - - public static bool IsTargetType(object obj, string typeName) - { - return LuaUserData.IsTargetType(obj, typeName); - } - - public static string TypeOf(object obj) - { - return LuaUserData.TypeOf(obj); - } - - public static object CreateStatic(string typeName) - { - CheckAllowed(typeName); - return LuaUserData.CreateStatic(typeName); - } - - public static object CreateEnumTable(string typeName) - { - return LuaUserData.CreateEnumTable(typeName); - } - - public static void MakeFieldAccessible(IUserDataDescriptor IUUD, string fieldName) - { - LuaUserData.MakeFieldAccessible(IUUD, fieldName); - } - - public static void MakeMethodAccessible(IUserDataDescriptor IUUD, string methodName, string[] parameters = null) - { - LuaUserData.MakeMethodAccessible(IUUD, methodName, parameters); - } - - public static void MakePropertyAccessible(IUserDataDescriptor IUUD, string propertyName) - { - LuaUserData.MakePropertyAccessible(IUUD, propertyName); - } - - public static void AddMethod(IUserDataDescriptor IUUD, string methodName, object function) - { - LuaUserData.AddMethod(IUUD, methodName, function); - } - - public static void AddField(IUserDataDescriptor IUUD, string fieldName, DynValue value) - { - LuaUserData.AddField(IUUD, fieldName, value); - } - - public static void RemoveMember(IUserDataDescriptor IUUD, string memberName) - { - LuaUserData.RemoveMember(IUUD, memberName); - } - - public static bool HasMember(object obj, string memberName) - { - return LuaUserData.HasMember(obj, memberName); - } - - public static void AddCallMetaTable(object userdata) { } - - public static DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor desiredTypeDescriptor) - { - return LuaUserData.CreateUserDataFromDescriptor(scriptObject, desiredTypeDescriptor); - } - - public static DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType) - { - return LuaUserData.CreateUserDataFromType(scriptObject, desiredType); - } - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaBarotraumaAdditions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaBarotraumaAdditions.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaBarotraumaAdditions.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaBarotraumaAdditions.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaConverters.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaConverters.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaConverters.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaConverters.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsLogger.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsLogger.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsLogger.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsLogger.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsNetworking.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsNetworking.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsNetworking.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsNetworking.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsPerformanceCounter.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsPerformanceCounter.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsPerformanceCounter.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsPerformanceCounter.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsSteam.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsSteam.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsSteam.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsSteam.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsTimer.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsTimer.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsTimer.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsTimer.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsUtility.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsUtility.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaCsUtility.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsUtility.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaGame.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaGame.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaGame.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaGame.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaPatcher.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcher.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaPatcher.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcher.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaPatcherCompat.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaPatcherCompat.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaPlatformAccessor.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPlatformAccessor.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaPlatformAccessor.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPlatformAccessor.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaRequire.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaRequire.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaRequire.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaRequire.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaTypes.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaTypes.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaTypes.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaTypes.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaUserData.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaUserData.cs From 6b8a0a7dca71343dcd6b97f4fa04155343c07b08 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Thu, 29 Jan 2026 22:05:32 -0300 Subject: [PATCH 085/288] Take in account ForcedAutorun for legacy as well --- .../LuaCs/Services/Processing/ModConfigService.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs index 2ad82d06c..205d8b98e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs @@ -323,13 +323,17 @@ public sealed class ModConfigService : IModConfigService if (_storageService.FindFilesInPackage(src, "Lua", "*.lua", true) is { IsSuccess: true, Value.IsDefaultOrEmpty: false } result) { - var autorun = result.Value - .Where(fp => fp.CleanUpPathCrossPlatform().Contains("Lua/Autorun/")) + ImmutableArray cleanedResult = result.Value.Select(fp => fp.CleanUpPathCrossPlatform()).ToImmutableArray(); + + ImmutableArray autorun = cleanedResult + .Where(fp => fp.Contains("Lua/ForcedAutorun/") || fp.Contains("Lua/Autorun/")) .ToImmutableArray(); - var autorunFP = autorun.Select(fp => ContentPath.FromRaw(src, + + ImmutableArray autorunFP = autorun.Select(fp => ContentPath.FromRaw(src, $"%ModDir%/{Path.GetRelativePath(src.Dir, fp)}".CleanUpPathCrossPlatform())) .ToImmutableArray(); - var reg = result.Value.Except(autorun) + + ImmutableArray reg = cleanedResult.Except(autorun) .Select(fp => ContentPath.FromRaw(src, $"%ModDir%/{Path.GetRelativePath(src.Dir, fp)}".CleanUpPathCrossPlatform())) .ToImmutableArray(); From 4f02cb4967ab0016b16e64f7e0a6a54992212299 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Thu, 29 Jan 2026 22:20:16 -0300 Subject: [PATCH 086/288] Working Hook.Patch and old patch methods --- .../Services/Compatibility/ILuaCsHook.cs | 6 + .../LuaCs/Services/EventService.cs | 12 +- .../Services/LuaScriptManagementService.cs | 2 +- .../LuaCs/Services/PluginManagementService.cs | 6 + .../Services/Safe/LuaClasses/LuaPatcher.cs | 200 ++++-------------- .../Safe/LuaClasses/LuaPatcherCompat.cs | 17 +- .../BarotraumaTest/LuaCs/HookPatchHelpers.cs | 23 +- 7 files changed, 84 insertions(+), 182 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs index 3eb723a2e..e5a1c7b09 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs @@ -12,4 +12,10 @@ public interface ILuaCsHook : ILuaCsShim [Obsolete("Only Lua subscribers will receive events from call. Use ILuaEventService.Add() instead.")] T Call(string eventName, params object[] args); object Call(string eventName, params object[] args); + string Patch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, EventService.HookMethodType hookType = EventService.HookMethodType.Before); + string Patch(string identifier, string className, string methodName, LuaCsPatchFunc patch, EventService.HookMethodType hookType = EventService.HookMethodType.Before); + string Patch(string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, EventService.HookMethodType hookType = EventService.HookMethodType.Before); + string Patch(string className, string methodName, LuaCsPatchFunc patch, EventService.HookMethodType hookType = EventService.HookMethodType.Before); + bool RemovePatch(string identifier, string className, string methodName, string[] parameterTypes, EventService.HookMethodType hookType); + bool RemovePatch(string identifier, string className, string methodName, EventService.HookMethodType hookType); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs index 9839afa1b..8f825ae6e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs @@ -11,7 +11,7 @@ using OneOf; namespace Barotrauma.LuaCs.Services; -public class EventService : IEventService, IEventAssemblyContextUnloading +public partial class EventService : IEventService, IEventAssemblyContextUnloading { private readonly record struct TypeStringKey : IEqualityComparer, IEquatable { @@ -74,6 +74,8 @@ public class EventService : IEventService, IEventAssemblyContextUnloading { _pluginManagementService = pluginManagementService ?? throw new ArgumentNullException(nameof(pluginManagementService)); this.Subscribe(this); + + InitPatcher(); } public bool IsDisposed { get; private set; } = false; @@ -344,12 +346,8 @@ public class EventService : IEventService, IEventAssemblyContextUnloading public void Dispose() { + Reset(); IsDisposed = true; - _subscriptions.Clear(); - _luaSubscriptionFactories.Clear(); - _eventTypeNameAliases.Clear(); - _luaLegacySubscriptionFactories.Clear(); - _luaOrphanSubscribers.Clear(); GC.SuppressFinalize(this); } @@ -361,6 +359,8 @@ public class EventService : IEventService, IEventAssemblyContextUnloading _eventTypeNameAliases.Clear(); _luaLegacySubscriptionFactories.Clear(); _luaOrphanSubscribers.Clear(); + ResetPatcher(); + InitPatcher(); return FluentResults.Result.Ok(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs index 370c5103d..d15b0e682 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs @@ -169,7 +169,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService Script.GlobalOptions.ShouldPCallCatchException = (Exception ex) => { return true; }; RegisterType(typeof(LuaGame)); - RegisterType(typeof(ILuaCsHook)); + RegisterType(typeof(EventService)); RegisterType(typeof(ILuaCsNetworking)); RegisterType(typeof(ILuaCsUtility)); RegisterType(typeof(ILuaCsTimer)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 532173247..ea6fedaf6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -127,6 +127,12 @@ public class PluginManagementService : IAssemblyManagementService public Type GetType(string typeName, bool isByRefType = false, bool includeInterfaces = false, bool includeDefaultContext = true) { + if (typeName.StartsWith("out ") || typeName.StartsWith("ref ")) + { + typeName = typeName.Remove(0, 4); + isByRefType = true; + } + if (includeDefaultContext) { var type = Type.GetType(typeName, false, false); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcher.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcher.cs index d34fe2844..3f5f47cb0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcher.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcher.cs @@ -1,4 +1,11 @@ -using System; +using Barotrauma.LuaCs.Services; +using HarmonyLib; +using Microsoft.Xna.Framework; +using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Interop; +using Sigil; +using Sigil.NonGeneric; +using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; @@ -8,19 +15,16 @@ using System.Reflection; using System.Reflection.Emit; using System.Text; using System.Text.RegularExpressions; -using HarmonyLib; -using Microsoft.Xna.Framework; -using MoonSharp.Interpreter; -using MoonSharp.Interpreter.Interop; -using Sigil; -using Sigil.NonGeneric; namespace Barotrauma { public delegate void LuaCsAction(params object[] args); public delegate object LuaCsFunc(params object[] args); - public delegate DynValue LuaCsPatchFunc(object instance, LuaCsHook.ParameterTable ptable); + public delegate DynValue LuaCsPatchFunc(object instance, EventService.ParameterTable ptable); +} +namespace Barotrauma.LuaCs.Services +{ internal static class SigilExtensions { /// @@ -410,7 +414,7 @@ namespace Barotrauma } } - public partial class LuaCsHook + partial class EventService { public enum HookMethodType { @@ -536,13 +540,11 @@ namespace Barotrauma private Lazy patchModuleBuilder; - private readonly Dictionary> hookFunctions = new Dictionary>(); - private readonly Dictionary registeredPatches = new Dictionary(); private LuaCsSetup luaCs; - private static LuaCsHook instance; + private static EventService instance; private struct MethodKey : IEquatable { @@ -582,21 +584,17 @@ namespace Barotrauma }; } - internal LuaCsHook(LuaCsSetup luaCs) + public void InitPatcher() { instance = this; - this.luaCs = luaCs; - } - public void Initialize() - { harmony = new Harmony("LuaCsForBarotrauma"); patchModuleBuilder = new Lazy(CreateModuleBuilder); UserData.RegisterType(); - var hookType = UserData.RegisterType(); + var hookType = UserData.RegisterType(); var hookDesc = (StandardUserDataDescriptor)hookType; - typeof(LuaCsHook).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).ToList().ForEach(m => { + typeof(EventService).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).ToList().ForEach(m => { if ( m.Name.Contains("HookMethod") || m.Name.Contains("UnhookMethod") || @@ -609,6 +607,29 @@ namespace Barotrauma }); } + public void ResetPatcher() + { + harmony?.UnpatchSelf(); + + foreach (var (_, patch) in registeredPatches) + { + // Remove references stored in our dynamic types so the generated + // assembly can be garbage-collected. + patch.HarmonyPrefixMethod.DeclaringType + .GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static) + .SetValue(null, null); + patch.HarmonyPostfixMethod.DeclaringType + .GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static) + .SetValue(null, null); + } + + registeredPatches.Clear(); + patchModuleBuilder = null; + + compatHookPrefixMethods.Clear(); + compatHookPostfixMethods.Clear(); + } + private ModuleBuilder CreateModuleBuilder() { var assemblyName = $"LuaCsHookPatch-{Guid.NewGuid():N}"; @@ -689,143 +710,6 @@ namespace Barotrauma return moduleBuilder; } - public void Add(string name, LuaCsFunc func, ACsMod owner = null) => Add(name, name, func, owner); - - public void Add(string name, string identifier, LuaCsFunc func, ACsMod owner = null) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - if (identifier == null) throw new ArgumentNullException(nameof(identifier)); - if (func == null) throw new ArgumentNullException(nameof(func)); - - name = NormalizeIdentifier(name); - identifier = NormalizeIdentifier(identifier); - - if (!hookFunctions.ContainsKey(name)) - { - hookFunctions.Add(name, new Dictionary()); - } - - hookFunctions[name][identifier] = (new LuaCsHookCallback(name, identifier, func), owner); - } - - public bool Exists(string name, string identifier) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - if (identifier == null) throw new ArgumentNullException(nameof(identifier)); - - name = NormalizeIdentifier(name); - identifier = NormalizeIdentifier(identifier); - - if (!hookFunctions.ContainsKey(name)) - { - return false; - } - - return hookFunctions[name].ContainsKey(identifier); - } - - public void Remove(string name, string identifier) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - if (identifier == null) throw new ArgumentNullException(nameof(identifier)); - - name = NormalizeIdentifier(name); - identifier = NormalizeIdentifier(identifier); - - if (hookFunctions.ContainsKey(name) && hookFunctions[name].ContainsKey(identifier)) - { - hookFunctions[name].Remove(identifier); - } - } - - public void Clear() - { - harmony?.UnpatchSelf(); - - foreach (var (_, patch) in registeredPatches) - { - // Remove references stored in our dynamic types so the generated - // assembly can be garbage-collected. - patch.HarmonyPrefixMethod.DeclaringType - .GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static) - .SetValue(null, null); - patch.HarmonyPostfixMethod.DeclaringType - .GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static) - .SetValue(null, null); - } - - hookFunctions.Clear(); - registeredPatches.Clear(); - patchModuleBuilder = null; - - compatHookPrefixMethods.Clear(); - compatHookPostfixMethods.Clear(); - } - - private Stopwatch performanceMeasurement = new Stopwatch(); - - [MoonSharpHidden] - public T Call(string name, params object[] args) - { - if (name == null) throw new ArgumentNullException(name); - if (args == null) args = new object[0]; - - name = NormalizeIdentifier(name); - if (!hookFunctions.ContainsKey(name)) return default; - - T lastResult = default; - - var hooks = hookFunctions[name].ToArray(); - foreach ((string key, var tuple) in hooks) - { - if (tuple.Item2 != null && tuple.Item2.IsDisposed) - { - hookFunctions[name].Remove(key); - continue; - } - - try - { - var result = tuple.Item1.func(args); - - if (result is DynValue luaResult) - { - if (luaResult.Type == DataType.Tuple) - { - bool replaceNil = luaResult.Tuple.Length > 1 && luaResult.Tuple[1].CastToBool(); - - if (!luaResult.Tuple[0].IsNil() || replaceNil) - { - lastResult = luaResult.ToObject(); - } - } - else if (!luaResult.IsNil()) - { - lastResult = luaResult.ToObject(); - } - } - else - { - lastResult = (T)result; - } - } - catch (Exception e) - { - var argsSb = new StringBuilder(); - foreach (var arg in args) - { - argsSb.Append(arg + " "); - } - LuaCsLogger.LogError($"Error in Hook '{name}'->'{key}', with args '{argsSb}':\n{e}", LuaCsMessageOrigin.Unknown); - LuaCsLogger.HandleException(e, LuaCsMessageOrigin.Unknown); - } - } - - return lastResult; - } - - public object Call(string name, params object[] args) => Call(name, args); - private static MethodBase ResolveMethod(string className, string methodName, string[] parameters) { var classType = GameMain.LuaCs.PluginManagementService.GetType(className); @@ -984,8 +868,8 @@ namespace Barotrauma // IL: var patchExists = instance.registeredPatches.TryGetValue(patchKey, out MethodPatches patches) var patchExists = il.DeclareLocal("patchExists"); var patches = il.DeclareLocal("patches"); - il.LoadField(typeof(LuaCsHook).GetField(nameof(instance), BindingFlags.NonPublic | BindingFlags.Static)); - il.LoadField(typeof(LuaCsHook).GetField(nameof(registeredPatches), BindingFlags.NonPublic | BindingFlags.Instance)); + il.LoadField(typeof(EventService).GetField(nameof(instance), BindingFlags.NonPublic | BindingFlags.Static)); + il.LoadField(typeof(EventService).GetField(nameof(registeredPatches), BindingFlags.NonPublic | BindingFlags.Instance)); il.LoadLocal(patchKey); il.LoadLocalAddress(patches); // out parameter il.Call(typeof(Dictionary).GetMethod("TryGetValue")); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs index 957f77ba9..99b6bb68f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs @@ -1,4 +1,6 @@ -using System; +global using LuaCsHook = Barotrauma.LuaCs.Services.EventService; + +using System; using System.Linq; using System.Reflection; using HarmonyLib; @@ -10,8 +12,11 @@ namespace Barotrauma { // XXX: this can't be renamed because of backward compatibility with C# mods public delegate object LuaCsPatch(object self, Dictionary args); +} - partial class LuaCsHook +namespace Barotrauma.LuaCs.Services +{ + partial class EventService { private Dictionary> compatHookPrefixMethods = new Dictionary>(); private Dictionary> compatHookPostfixMethods = new Dictionary>(); @@ -115,10 +120,10 @@ namespace Barotrauma if (result != null) __result = result; } - private static MethodInfo _miHookLuaCsPatchPrefix = typeof(LuaCsHook).GetMethod("HookLuaCsPatchPrefix", BindingFlags.NonPublic | BindingFlags.Static); - private static MethodInfo _miHookLuaCsPatchPostfix = typeof(LuaCsHook).GetMethod("HookLuaCsPatchPostfix", BindingFlags.NonPublic | BindingFlags.Static); - private static MethodInfo _miHookLuaCsPatchRetPrefix = typeof(LuaCsHook).GetMethod("HookLuaCsPatchRetPrefix", BindingFlags.NonPublic | BindingFlags.Static); - private static MethodInfo _miHookLuaCsPatchRetPostfix = typeof(LuaCsHook).GetMethod("HookLuaCsPatchRetPostfix", BindingFlags.NonPublic | BindingFlags.Static); + private static MethodInfo _miHookLuaCsPatchPrefix = typeof(EventService).GetMethod("HookLuaCsPatchPrefix", BindingFlags.NonPublic | BindingFlags.Static); + private static MethodInfo _miHookLuaCsPatchPostfix = typeof(EventService).GetMethod("HookLuaCsPatchPostfix", BindingFlags.NonPublic | BindingFlags.Static); + private static MethodInfo _miHookLuaCsPatchRetPrefix = typeof(EventService).GetMethod("HookLuaCsPatchRetPrefix", BindingFlags.NonPublic | BindingFlags.Static); + private static MethodInfo _miHookLuaCsPatchRetPostfix = typeof(EventService).GetMethod("HookLuaCsPatchRetPostfix", BindingFlags.NonPublic | BindingFlags.Static); // TODO: deprecate this public void HookMethod(string identifier, MethodBase method, LuaCsCompatPatchFunc patch, HookMethodType hookType = HookMethodType.Before, ACsMod owner = null) diff --git a/Barotrauma/BarotraumaTest/LuaCs/HookPatchHelpers.cs b/Barotrauma/BarotraumaTest/LuaCs/HookPatchHelpers.cs index 49037a3c3..8aaeb45c0 100644 --- a/Barotrauma/BarotraumaTest/LuaCs/HookPatchHelpers.cs +++ b/Barotrauma/BarotraumaTest/LuaCs/HookPatchHelpers.cs @@ -1,6 +1,7 @@ extern alias Client; -using Client::Barotrauma; +using Client::Barotrauma.LuaCs.Services; +using Client::Barotrauma; using MoonSharp.Interpreter; using System; using System.Collections.Concurrent; @@ -63,14 +64,14 @@ namespace TestProject.LuaCs string methodName, string[]? parameters, string function, - LuaCsHook.HookMethodType patchType) + EventService.HookMethodType patchType) { var args = BuildHookPatchArgsList(patchId, className, methodName, parameters); args.Add(function); args.Add(patchType switch { - LuaCsHook.HookMethodType.Before => "Hook.HookMethodType.Before", - LuaCsHook.HookMethodType.After => "Hook.HookMethodType.After", + EventService.HookMethodType.Before => "Hook.HookMethodType.Before", + EventService.HookMethodType.After => "Hook.HookMethodType.After", _ => throw new NotImplementedException(), }); throw new NotImplementedException(); @@ -83,13 +84,13 @@ namespace TestProject.LuaCs string className, string methodName, string[]? parameters, - LuaCsHook.HookMethodType patchType) + EventService.HookMethodType patchType) { var args = BuildHookPatchArgsList(patchId, className, methodName, parameters); args.Add(patchType switch { - LuaCsHook.HookMethodType.Before => "Hook.HookMethodType.Before", - LuaCsHook.HookMethodType.After => "Hook.HookMethodType.After", + EventService.HookMethodType.Before => "Hook.HookMethodType.Before", + EventService.HookMethodType.After => "Hook.HookMethodType.After", _ => throw new NotImplementedException(), }); throw new NotImplementedException(); @@ -103,7 +104,7 @@ namespace TestProject.LuaCs function(instance, ptable) {body} end - ", LuaCsHook.HookMethodType.Before); + ", EventService.HookMethodType.Before); Assert.Equal(DataType.String, returnValue.Type); return new(returnValue.String, () => luaCs.RemovePrefix(returnValue.String, methodName, parameters)); } @@ -115,7 +116,7 @@ namespace TestProject.LuaCs function(instance, ptable) {body} end - ", LuaCsHook.HookMethodType.After); + ", EventService.HookMethodType.After); Assert.Equal(DataType.String, returnValue.Type); return new(returnValue.String, () => luaCs.RemovePostfix(returnValue.String, methodName, parameters)); } @@ -123,7 +124,7 @@ namespace TestProject.LuaCs public static bool RemovePrefix(this LuaCsSetup luaCs, string patchId, string methodName, string[]? parameters = null) { var className = typeof(T).FullName!; - var returnValue = luaCs.DoHookRemovePatch(patchId, className, methodName, parameters, LuaCsHook.HookMethodType.Before); + var returnValue = luaCs.DoHookRemovePatch(patchId, className, methodName, parameters, EventService.HookMethodType.Before); Assert.Equal(DataType.Boolean, returnValue.Type); return returnValue.Boolean; } @@ -131,7 +132,7 @@ namespace TestProject.LuaCs public static bool RemovePostfix(this LuaCsSetup luaCs, string patchId, string methodName, string[]? parameters = null) { var className = typeof(T).FullName!; - var returnValue = luaCs.DoHookRemovePatch(patchId, className, methodName, parameters, LuaCsHook.HookMethodType.After); + var returnValue = luaCs.DoHookRemovePatch(patchId, className, methodName, parameters, EventService.HookMethodType.After); Assert.Equal(DataType.Boolean, returnValue.Type); return returnValue.Boolean; } From 5421c7df4ff6f633b051cf1a8e02e4e6434e0b52 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 30 Jan 2026 16:38:55 -0500 Subject: [PATCH 087/288] - Made SyncPackages function always complete the unload->reload process. - Basic assembly loading is completed (alpha), unloading/disposal not yet supported. --- .../Services/PackageManagementService.cs | 7 +- .../LuaCs/Services/PluginManagementService.cs | 125 +++++++++++------- 2 files changed, 77 insertions(+), 55 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index c26a38e83..09cdb0a32 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -296,16 +296,11 @@ public sealed class PackageManagementService : IPackageManagementService result.WithReasons(UnloadPackages(toRemove).Reasons); } - if (result.IsFailed) - { - return result; - } - if (!toAdd.IsDefaultOrEmpty) { result.WithReasons(LoadPackagesInfo(toAdd).Reasons); } - + return result; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index ea6fedaf6..8293805c6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -181,9 +181,6 @@ public class PluginManagementService : IAssemblyManagementService var result = new FluentResults.Result(); - return FluentResults.Result.Fail($"{nameof(LoadAssemblyResources)}: Not Implemented!"); - throw new NotImplementedException(); - foreach (var contentPack in orderedContentPacks) { LoadBinaries(contentPack); @@ -237,13 +234,13 @@ public class PluginManagementService : IAssemblyManagementService void LoadAndCompileScriptAssemblies(IGrouping contentPackRes) { - var scripts = contentPackRes.Where(cRes => cRes.IsScript) + var scriptsGrp = contentPackRes.Where(cRes => cRes.IsScript) + .Select(scr => (scr.OwnerPackage, scr.FriendlyName, scr.FilePaths, scr.UseInternalAccessName, scr.LoadPriority)) .OrderBy(scr => scr.LoadPriority) - .Select(scr => (scr.FriendlyName, scr.FilePaths, scr.UseInternalAccessName)) .GroupBy(scr => scr.FriendlyName) .ToImmutableArray(); - if (scripts.IsDefaultOrEmpty) + if (scriptsGrp.IsDefaultOrEmpty) { return; } @@ -262,69 +259,87 @@ public class PluginManagementService : IAssemblyManagementService ))); // create syntax trees - var syntaxTreesBuilder = ImmutableArray.CreateBuilder(); - foreach (var resourceInfo in contentPackRes) + foreach (var scripts in scriptsGrp) { - if (resourceInfo.FilePaths.IsDefaultOrEmpty) + var syntaxTreesBuilder = ImmutableArray.CreateBuilder(); + + bool hasInternalsAwareBeenAdded = false; + bool compileWithInternalName = true; + + foreach (var resourceInfo in scripts) { - ThrowHelper.ThrowArgumentNullException($"{nameof(LoadAndCompileScriptAssemblies)} The resource list is empty for package {resourceInfo.OwnerPackage}."); + if (!hasInternalsAwareBeenAdded && resourceInfo.UseInternalAccessName) + { + hasInternalsAwareBeenAdded = true; + syntaxTreesBuilder.Add(BaseAssemblyImports); + } + + if (resourceInfo.FilePaths.IsDefaultOrEmpty) + { + ThrowHelper.ThrowArgumentNullException($"{nameof(LoadAndCompileScriptAssemblies)} The resource list is empty for package {resourceInfo.OwnerPackage}."); + } + + foreach (var resourcePath in resourceInfo.FilePaths) + { + var loadRes = GetSourceFilesText(resourcePath); + if (loadRes.IsFailed) + { + _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; + + syntaxTreesBuilder.Add(SyntaxFactory.ParseSyntaxTree( + text: loadRes.Value, + options: ScriptParseOptions, + path: null, + encoding: Encoding.Default, + cancellationToken: token + )); + } } - var loadRes = GetSourceFilesText(resourceInfo.FilePaths); - if (loadRes.IsFailed) + if (syntaxTreesBuilder.Count < 1) { - _logger.LogResults(loadRes.ToResult()); continue; } - CancellationToken token = CancellationToken.None; +#if DEBUG + _logger.Log($"[DEBUG] Compiling assembly for {scripts.Key}, in ContentPackage {contentPackRes.Key.Name}"); +#endif - syntaxTreesBuilder.Add(SyntaxFactory.ParseSyntaxTree( - text: loadRes.Value, - options: ScriptParseOptions, - path: null, - encoding: Encoding.Default, - cancellationToken: token - )); + result.WithReasons(assemblyLoader.CompileScriptAssembly( + assemblyName: scripts.Key, + compileWithInternalAccess: compileWithInternalName, + syntaxTrees: syntaxTreesBuilder.ToImmutable(), + metadataReferences: metadataReferences.ToImmutableArray(), + compilationOptions: CompilationOptions) + .Reasons); } - - throw new NotImplementedException(); } - Result GetSourceFilesText(ImmutableArray resourceInfoFilePaths) + Result GetSourceFilesText(ContentPath resourceInfoFilePath) { - if (_storageService.LoadPackageTextFiles(resourceInfoFilePaths) is not { IsDefaultOrEmpty: false } res) + if (_storageService.LoadPackageText(resourceInfoFilePath) is not { IsFailed: false } res) { - _logger.LogError($"{nameof(GetSourceFilesText)}: Failed to load source files for ContentPackage {resourceInfoFilePaths.First().ContentPackage?.Name}."); - return FluentResults.Result.Fail($"{nameof(GetSourceFilesText)}: Failed to load source files for ContentPackage {resourceInfoFilePaths.First().ContentPackage?.Name}."); + _logger.LogError($"{nameof(GetSourceFilesText)}: Failed to load source file for ContentPackage {resourceInfoFilePath.ContentPackage?.Name}."); + return FluentResults.Result.Fail($"{nameof(GetSourceFilesText)}: Failed to load source files for ContentPackage {resourceInfoFilePath.ContentPackage?.Name}."); } - var loadRes = new FluentResults.Result(); - StringBuilder sb = new StringBuilder(); - - foreach ((ContentPath Path, Result FileResult) loadResult in res) - { - if (loadResult.FileResult.IsFailed) - { - loadRes.WithErrors(loadResult.FileResult.Errors); - continue; - } - - sb.AppendLine(loadResult.FileResult.Value); - } - - if (loadRes.IsFailed) - { - return loadRes; - } - - return sb.ToString(); + return res; } IEnumerable GetMetadataReferences() { - return Basic.Reference.Assemblies.Net80.References.All; + return Basic.Reference.Assemblies.Net80.References.All + .Union(AppDomain.CurrentDomain.GetAssemblies() + .Where(ass => !ass.Location.IsNullOrWhiteSpace()) + .Select(ass => MetadataReference.CreateFromFile(ass.Location))); } } @@ -345,7 +360,19 @@ public class PluginManagementService : IAssemblyManagementService public FluentResults.Result UnloadManagedAssemblies() { - return FluentResults.Result.Fail($"{nameof(UnloadManagedAssemblies)}: Not Implemented."); + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + if (_assemblyLoaders.Count == 0) + { + return FluentResults.Result.Ok(); + } + + foreach (var loaderService in _assemblyLoaders) + { + + } + throw new NotImplementedException(); } From 9b9529107c61ffabe6e8e8e0f449ddf8f6bbf714 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sat, 31 Jan 2026 22:02:16 -0500 Subject: [PATCH 088/288] Added limited multithreaded compatibility. Still requires locks. --- .../LuaCs/Services/EventService.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs index 8f825ae6e..38743666d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -56,10 +57,10 @@ public partial class EventService : IEventService, IEventAssemblyContextUnloadin /// ---- Key: Either string identifier or subscriber instance pointer
/// ---- Value: Subscriber delegate
/// - private readonly Dictionary, IEvent>> _subscriptions = new(); - private readonly Dictionary _eventTypeNameAliases = new(); + private readonly ConcurrentDictionary, IEvent>> _subscriptions = new(); + private readonly ConcurrentDictionary _eventTypeNameAliases = new(); private readonly Lazy _pluginManagementService; - private readonly Dictionary>> _luaSubscriptionFactories = new(); + private readonly ConcurrentDictionary>> _luaSubscriptionFactories = new(); /// /// A collection of factories to produce subscribers from a single lua function handle. For legacy Add() API. /// @@ -192,7 +193,7 @@ public partial class EventService : IEventService, IEventAssemblyContextUnloadin return FluentResults.Result.Ok().WithReason(new Success($"The event {type.Name} is already registered.")); try { - _luaSubscriptionFactories.Add(type, (ident, funcDict) => + _luaSubscriptionFactories.TryAdd(type, (ident, funcDict) => { var runner = T.GetLuaRunner(funcDict); var dict = _subscriptions.TryGetOrSet(type, () => new Dictionary, IEvent>()); @@ -214,7 +215,7 @@ public partial class EventService : IEventService, IEventAssemblyContextUnloadin public FluentResults.Result UnregisterSafeEvent() where T : IEvent { ((IService)this).CheckDisposed(); - _luaSubscriptionFactories.Remove(typeof(T)); + _luaSubscriptionFactories.TryRemove(typeof(T), out _); if (!_subscriptions.TryGetValue(typeof(T), out var dict)) return FluentResults.Result.Ok(); dict.Values.Where(value => value.IsLuaRunner()).ToImmutableArray().ForEach(Unsubscribe); @@ -257,7 +258,7 @@ public partial class EventService : IEventService, IEventAssemblyContextUnloadin public void RemoveEventAlias(string alias) { - _eventTypeNameAliases.Remove(alias); + _eventTypeNameAliases.TryRemove(alias, out _); } public void RemoveAllEventAliases() where T : IEvent @@ -266,7 +267,7 @@ public partial class EventService : IEventService, IEventAssemblyContextUnloadin .Where(kvp => kvp.Value.IsNullOrWhiteSpace() || kvp.Value == typeof(T).Name) .Select(kvp => kvp.Key).ToImmutableArray()) { - _eventTypeNameAliases.Remove(keys); + _eventTypeNameAliases.TryRemove(keys, out _); } } @@ -296,7 +297,7 @@ public partial class EventService : IEventService, IEventAssemblyContextUnloadin public void ClearAllEventSubscribers() where T : IEvent { - _subscriptions.Remove(typeof(T)); + _subscriptions.TryRemove(typeof(T), out _); if (typeof(IEventAssemblyContextUnloading) == typeof(T)) { this.Subscribe(this); @@ -377,8 +378,8 @@ public partial class EventService : IEventService, IEventAssemblyContextUnloadin continue; foreach (var type in types) { - _subscriptions.Remove(type); - _luaSubscriptionFactories.Remove(type); + _subscriptions.TryRemove(type, out _); + _luaSubscriptionFactories.TryRemove(type, out _); } } } From 5777b64a1894e6e5eb79ca1fa06a06cd0caaa848 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 1 Feb 2026 00:44:25 -0300 Subject: [PATCH 089/288] Move LuaUserData and registration into a proper service --- .../Lua/DefaultLib/LibClient.lua | 15 +- .../Lua/DefaultLib/LibServer.lua | 4 +- .../Lua/DefaultLib/LibShared.lua | 6 +- .../Lua/DefaultLib/Utils/SteamApi.lua | 2 + .../Lua/DefaultRegister/RegisterClient.lua | 150 ------ .../Lua/DefaultRegister/RegisterServer.lua | 20 - .../Lua/DefaultRegister/RegisterShared.lua | 479 ------------------ .../LuaCsForBarotrauma/Lua/LuaSetup.lua | 15 +- .../LuaCsForBarotrauma/Lua/LuaUserData.lua | 97 ---- .../LuaCsForBarotrauma/Lua/PostSetup.lua | 13 - .../SharedSource/LuaCs/LuaCsSetup.cs | 4 + .../Services/LuaScriptManagementService.cs | 68 +-- .../Services/Safe/DefaultLuaRegistrar.cs | 177 +++++++ .../Services/Safe/LuaClasses/LuaUserData.cs | 367 -------------- .../LuaCs/Services/Safe/LuaUserDataService.cs | 440 ++++++++++++++++ .../Services/Safe/SafeLuaUserDataService.cs | 234 +++++++++ .../ILuaScriptManagementService.cs | 6 - 17 files changed, 908 insertions(+), 1189 deletions(-) delete mode 100644 Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultRegister/RegisterClient.lua delete mode 100644 Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultRegister/RegisterServer.lua delete mode 100644 Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultRegister/RegisterShared.lua delete mode 100644 Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaUserData.lua delete mode 100644 Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/PostSetup.lua create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/DefaultLuaRegistrar.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaUserData.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaUserDataService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeLuaUserDataService.cs diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/LibClient.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/LibClient.lua index eb94d18f5..a6a6a1562 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/LibClient.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/LibClient.lua @@ -1,8 +1,8 @@ local defaultLib = {} -local CreateStatic = LuaSetup.LuaUserData.CreateStatic -local CreateEnum = LuaSetup.LuaUserData.CreateEnumTable -local AddCallMetaTable = LuaSetup.LuaUserData.AddCallMetaTable +local CreateStatic = LuaUserData.CreateStatic +local CreateEnum = LuaUserData.CreateEnumTable +local AddCallMetaTable = LuaUserData.AddCallMetaTable local localizedStrings = { "LocalizedString", "LimitLString", "WrappedLString", "AddedPunctuationLString", "CapitalizeLString", "ConcatLString", "FallbackLString", "FormattedLString", "InputTypeLString", "JoinLString", "LowerLString", "RawLString", "ReplaceLString", "ServerMsgLString", "SplitLString", "TagLString", "TrimLString", "UpperLString", "StripRichTagsLString", @@ -79,13 +79,12 @@ defaultLib["GUI"] = { GUIStyle = CreateStatic("Barotrauma.GUIStyle", true), } +local guiFallback = defaultLib["GUI"].GUI + setmetatable(defaultLib["GUI"], { - __index = function (table, key) - return defaultLib["GUI"].GUI[key] + __index = function(_, key) + return guiFallback[key] end }) -AddCallMetaTable(defaultLib["GUI"].VideoPlayer.VideoSettings) -AddCallMetaTable(defaultLib["GUI"].VideoPlayer.TextSettings) - return defaultLib \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/LibServer.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/LibServer.lua index 68bfe97ef..1076e90f6 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/LibServer.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/LibServer.lua @@ -1,7 +1,7 @@ local defaultLib = {} -local CreateStatic = LuaSetup.LuaUserData.CreateStatic -local CreateEnum = LuaSetup.LuaUserData.CreateEnumTable +local CreateStatic = LuaUserData.CreateStatic +local CreateEnum = LuaUserData.CreateEnumTable local localizedStrings = { "LocalizedString", "AddedPunctuationLString", "CapitalizeLString", "ConcatLString", "FallbackLString", "FormattedLString", "InputTypeLString", "JoinLString", "LowerLString", "RawLString", "ReplaceLString", "ServerMsgLString", "SplitLString", "TagLString", "TrimLString", "UpperLString", "StripRichTagsLString", diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/LibShared.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/LibShared.lua index 1fde8456b..db8469158 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/LibShared.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/LibShared.lua @@ -1,8 +1,8 @@ local defaultLib = {} -local AddCallMetaTable = LuaSetup.LuaUserData.AddCallMetaTable -local CreateStatic = LuaSetup.LuaUserData.CreateStatic -local CreateEnum = LuaSetup.LuaUserData.CreateEnumTable +local AddCallMetaTable = LuaUserData.AddCallMetaTable +local CreateStatic = LuaUserData.CreateStatic +local CreateEnum = LuaUserData.CreateEnumTable defaultLib["SByte"] = CreateStatic("Barotrauma.LuaSByte", true) defaultLib["Byte"] = CreateStatic("Barotrauma.LuaByte", true) diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/Utils/SteamApi.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/Utils/SteamApi.lua index 4e9608f60..564d39bc7 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/Utils/SteamApi.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultLib/Utils/SteamApi.lua @@ -1,3 +1,5 @@ +if true then return end + local descriptor = LuaUserData.RegisterType("Barotrauma.LuaCsSteam") LuaUserData.AddMethod(descriptor, "GetWorkshopCollection", function (id, callback) diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultRegister/RegisterClient.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultRegister/RegisterClient.lua deleted file mode 100644 index 6f074747b..000000000 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultRegister/RegisterClient.lua +++ /dev/null @@ -1,150 +0,0 @@ -local Register = LuaSetup.LuaUserData.RegisterType -local RegisterBarotrauma = LuaSetup.LuaUserData.RegisterTypeBarotrauma - -local localizedStrings = { - "LocalizedString", "LimitLString", "WrappedLString", "AddedPunctuationLString", "CapitalizeLString", "ConcatLString", "FallbackLString", "FormattedLString", "InputTypeLString", "JoinLString", "LowerLString", "RawLString", "ReplaceLString", "ServerMsgLString", "SplitLString", "TagLString", "TrimLString", "UpperLString", "StripRichTagsLString", -} - -for key, value in pairs(localizedStrings) do - RegisterBarotrauma(value) -end - -RegisterBarotrauma("EditorScreen") -RegisterBarotrauma("SubEditorScreen") -RegisterBarotrauma("EventEditorScreen") -RegisterBarotrauma("CharacterEditor.CharacterEditorScreen") -RegisterBarotrauma("SpriteEditorScreen") -RegisterBarotrauma("LevelEditorScreen") - -RegisterBarotrauma("Networking.ClientPeer") -RegisterBarotrauma("Networking.GameClient") -RegisterBarotrauma("Networking.VoipCapture") - -RegisterBarotrauma("Media.Video") - -RegisterBarotrauma("SoundsFile") -RegisterBarotrauma("SoundPrefab") -RegisterBarotrauma("PrefabCollection`1") -RegisterBarotrauma("PrefabSelector`1") -RegisterBarotrauma("BackgroundMusic") -RegisterBarotrauma("GUISound") -RegisterBarotrauma("DamageSound") - -RegisterBarotrauma("Sounds.SoundManager") -RegisterBarotrauma("Sounds.OggSound") -RegisterBarotrauma("Sounds.VideoSound") -RegisterBarotrauma("Sounds.VoipSound") -RegisterBarotrauma("Sounds.SoundChannel") -RegisterBarotrauma("Sounds.SoundBuffers") -RegisterBarotrauma("RoundSound") -RegisterBarotrauma("CharacterSound") -RegisterBarotrauma("SoundPlayer") -RegisterBarotrauma("Items.Components.ItemSound") - -RegisterBarotrauma("Sounds.LowpassFilter") -RegisterBarotrauma("Sounds.HighpassFilter") -RegisterBarotrauma("Sounds.BandpassFilter") -RegisterBarotrauma("Sounds.NotchFilter") -RegisterBarotrauma("Sounds.LowShelfFilter") -RegisterBarotrauma("Sounds.HighShelfFilter") -RegisterBarotrauma("Sounds.PeakFilter") - -RegisterBarotrauma("Particles.ParticleManager") -RegisterBarotrauma("Particles.Particle") -RegisterBarotrauma("Particles.ParticleEmitterProperties") -RegisterBarotrauma("Particles.ParticleEmitter") -RegisterBarotrauma("Particles.ParticlePrefab") - -RegisterBarotrauma("Lights.LightManager") -RegisterBarotrauma("Lights.LightSource") -RegisterBarotrauma("Lights.LightSourceParams") - -RegisterBarotrauma("LevelWallVertexBuffer") -RegisterBarotrauma("LevelRenderer") -RegisterBarotrauma("WaterRenderer") -RegisterBarotrauma("WaterVertexData") - -RegisterBarotrauma("ChatBox") -RegisterBarotrauma("GUICanvas") -RegisterBarotrauma("Anchor") -RegisterBarotrauma("Alignment") -RegisterBarotrauma("Pivot") -RegisterBarotrauma("Key") -RegisterBarotrauma("PlayerInput") -RegisterBarotrauma("ScalableFont") - -Register("Microsoft.Xna.Framework.Graphics.Effect") -Register("Microsoft.Xna.Framework.Graphics.EffectParameterCollection") -Register("Microsoft.Xna.Framework.Graphics.EffectParameter") - -Register("Microsoft.Xna.Framework.Graphics.SpriteBatch") -Register("Microsoft.Xna.Framework.Graphics.Texture2D") -Register("EventInput.KeyboardDispatcher") -Register("EventInput.KeyEventArgs") -Register("Microsoft.Xna.Framework.Input.Keys") -Register("Microsoft.Xna.Framework.Input.KeyboardState") - -RegisterBarotrauma("TextureLoader") -RegisterBarotrauma("Sprite") -RegisterBarotrauma("GUI") -RegisterBarotrauma("GUIStyle") -RegisterBarotrauma("GUIComponent") -RegisterBarotrauma("GUILayoutGroup") -RegisterBarotrauma("GUITextBox") -RegisterBarotrauma("GUITextBlock") -RegisterBarotrauma("GUIButton") -RegisterBarotrauma("RectTransform") -RegisterBarotrauma("GUIFrame") -RegisterBarotrauma("GUITickBox") -RegisterBarotrauma("GUIImage") -RegisterBarotrauma("GUIListBox") -RegisterBarotrauma("GUIScrollBar") -RegisterBarotrauma("GUIDropDown") -RegisterBarotrauma("GUINumberInput") -RegisterBarotrauma("GUIMessage") -RegisterBarotrauma("GUIMessageBox") -RegisterBarotrauma("GUIColorPicker") -RegisterBarotrauma("GUIProgressBar") -RegisterBarotrauma("GUICustomComponent") -RegisterBarotrauma("GUIScissorComponent") -RegisterBarotrauma("GUIComponentStyle") -RegisterBarotrauma("GUIFontPrefab") -RegisterBarotrauma("GUIFont") -RegisterBarotrauma("GUISpritePrefab") -RegisterBarotrauma("GUISprite") -RegisterBarotrauma("GUISpriteSheetPrefab") -RegisterBarotrauma("GUISpriteSheet") -RegisterBarotrauma("GUICursorPrefab") -RegisterBarotrauma("GUICursor") -RegisterBarotrauma("GUIRadioButtonGroup") -RegisterBarotrauma("GUIDragHandle") -RegisterBarotrauma("GUIContextMenu") -RegisterBarotrauma("ContextMenuOption") -RegisterBarotrauma("VideoPlayer") -RegisterBarotrauma("CreditsPlayer") -RegisterBarotrauma("SlideshowPlayer") -RegisterBarotrauma("SerializableEntityEditor") -RegisterBarotrauma("CircuitBoxWireRenderer") -RegisterBarotrauma("CircuitBoxLabel") -RegisterBarotrauma("CircuitBoxMouseDragSnapshotHandler") -RegisterBarotrauma("CircuitBoxUI") - -RegisterBarotrauma("SettingsMenu") -RegisterBarotrauma("TabMenu") -RegisterBarotrauma("Widget") -RegisterBarotrauma("UpgradeStore") -RegisterBarotrauma("VotingInterface") -RegisterBarotrauma("MedicalClinicUI") -RegisterBarotrauma("LoadingScreen") -RegisterBarotrauma("HUD") -RegisterBarotrauma("HUDLayoutSettings") -RegisterBarotrauma("HUDProgressBar") -RegisterBarotrauma("Graph") -RegisterBarotrauma("HRManagerUI") -RegisterBarotrauma("SubmarineSelection") -RegisterBarotrauma("Store") -RegisterBarotrauma("UISprite") -RegisterBarotrauma("ParamsEditor") - -RegisterBarotrauma("Inventory+SlotReference") -RegisterBarotrauma("VisualSlot") diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultRegister/RegisterServer.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultRegister/RegisterServer.lua deleted file mode 100644 index 0de67a7f4..000000000 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultRegister/RegisterServer.lua +++ /dev/null @@ -1,20 +0,0 @@ -local Register = LuaSetup.LuaUserData.RegisterType -local RegisterBarotrauma = LuaSetup.LuaUserData.RegisterTypeBarotrauma - - -local localizedStrings = { - "LocalizedString", "AddedPunctuationLString", "CapitalizeLString", "ConcatLString", "FallbackLString", "FormattedLString", "InputTypeLString", "JoinLString", "LowerLString", "RawLString", "ReplaceLString", "ServerMsgLString", "SplitLString", "TagLString", "TrimLString", "UpperLString", "StripRichTagsLString", -} - -for key, value in pairs(localizedStrings) do - RegisterBarotrauma(value) -end - -Register("Steamworks.SteamServer") - -RegisterBarotrauma("Character+TeamChangeEventData") - -RegisterBarotrauma("Networking.GameServer") - -RegisterBarotrauma("Networking.ServerPeer") -RegisterBarotrauma("Networking.FileSender") diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultRegister/RegisterShared.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultRegister/RegisterShared.lua deleted file mode 100644 index fc5ee9b2c..000000000 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultRegister/RegisterShared.lua +++ /dev/null @@ -1,479 +0,0 @@ -local Register = LuaSetup.LuaUserData.RegisterType -local RegisterExtension = LuaSetup.LuaUserData.RegisterExtensionType -local RegisterBarotrauma = LuaSetup.LuaUserData.RegisterTypeBarotrauma - -Register("System.TimeSpan") -Register("System.Exception") -Register("System.Console") -Register("System.Exception") - -RegisterBarotrauma("Success`2") -RegisterBarotrauma("Failure`2") - -RegisterBarotrauma("LuaSByte") -RegisterBarotrauma("LuaByte") -RegisterBarotrauma("LuaInt16") -RegisterBarotrauma("LuaUInt16") -RegisterBarotrauma("LuaInt32") -RegisterBarotrauma("LuaUInt32") -RegisterBarotrauma("LuaInt64") -RegisterBarotrauma("LuaUInt64") -RegisterBarotrauma("LuaSingle") -RegisterBarotrauma("LuaDouble") - -RegisterBarotrauma("GameMain") -RegisterBarotrauma("Networking.BanList") -RegisterBarotrauma("Networking.BannedPlayer") - -RegisterBarotrauma("Range`1") - -RegisterBarotrauma("RichString") -RegisterBarotrauma("Identifier") -RegisterBarotrauma("LanguageIdentifier") - -RegisterBarotrauma("Job") -RegisterBarotrauma("JobPrefab") -RegisterBarotrauma("JobVariant") - -Register("Voronoi2.DoubleVector2") -Register("Voronoi2.Site") -Register("Voronoi2.Edge") -Register("Voronoi2.Halfedge") -Register("Voronoi2.VoronoiCell") -Register("Voronoi2.GraphEdge") - -RegisterBarotrauma("WayPoint") -RegisterBarotrauma("Level") -RegisterBarotrauma("LevelData") -RegisterBarotrauma("Level+InterestingPosition") -RegisterBarotrauma("LevelGenerationParams") -RegisterBarotrauma("LevelObjectManager") -RegisterBarotrauma("LevelObject") -RegisterBarotrauma("LevelObjectPrefab") -RegisterBarotrauma("LevelTrigger") -RegisterBarotrauma("CaveGenerationParams") -RegisterBarotrauma("CaveGenerator") -RegisterBarotrauma("OutpostGenerationParams") -RegisterBarotrauma("OutpostGenerator") -RegisterBarotrauma("OutpostModuleInfo") -RegisterBarotrauma("BeaconStationInfo") -RegisterBarotrauma("NPCSet") -RegisterBarotrauma("RuinGeneration.Ruin") -RegisterBarotrauma("RuinGeneration.RuinGenerationParams") -RegisterBarotrauma("LevelWall") -RegisterBarotrauma("DestructibleLevelWall") -RegisterBarotrauma("Biome") -RegisterBarotrauma("Map") -RegisterBarotrauma("Networking.RespawnManager") -RegisterBarotrauma("Networking.RespawnManager+TeamSpecificState") - -RegisterBarotrauma("Character") -RegisterBarotrauma("CharacterPrefab") -RegisterBarotrauma("CharacterInfo") -RegisterBarotrauma("CharacterInfoPrefab") -RegisterBarotrauma("CharacterInfo+HeadPreset") -RegisterBarotrauma("CharacterInfo+HeadInfo") -RegisterBarotrauma("CharacterHealth") -RegisterBarotrauma("CharacterHealth+LimbHealth") -RegisterBarotrauma("DamageModifier") -RegisterBarotrauma("CharacterInventory") -RegisterBarotrauma("CharacterParams") -RegisterBarotrauma("CharacterParams+AIParams") -RegisterBarotrauma("CharacterParams+TargetParams") -RegisterBarotrauma("CharacterParams+InventoryParams") -RegisterBarotrauma("CharacterParams+HealthParams") -RegisterBarotrauma("CharacterParams+ParticleParams") -RegisterBarotrauma("CharacterParams+SoundParams") -RegisterBarotrauma("SteeringManager") -RegisterBarotrauma("IndoorsSteeringManager") -RegisterBarotrauma("SteeringPath") -RegisterBarotrauma("CreatureMetrics") - -RegisterBarotrauma("Item") -RegisterBarotrauma("DeconstructItem") -RegisterBarotrauma("PurchasedItem") -RegisterBarotrauma("PurchasedItemSwap") -RegisterBarotrauma("PurchasedUpgrade") -RegisterBarotrauma("SoldItem") -RegisterBarotrauma("StartItem") -RegisterBarotrauma("StartItemSet") -RegisterBarotrauma("RelatedItem") -RegisterBarotrauma("UpgradeManager") -RegisterBarotrauma("CargoManager") -RegisterBarotrauma("HireManager") -RegisterBarotrauma("FabricationRecipe") -RegisterBarotrauma("PreferredContainer") -RegisterBarotrauma("SwappableItem") -RegisterBarotrauma("FabricationRecipe+RequiredItemByIdentifier") -RegisterBarotrauma("FabricationRecipe+RequiredItemByTag") -RegisterBarotrauma("Submarine") - -RegisterBarotrauma("Networking.AccountInfo") -RegisterBarotrauma("Networking.AccountId") -RegisterBarotrauma("Networking.SteamId") -RegisterBarotrauma("Networking.EpicAccountId") -RegisterBarotrauma("Networking.Address") -RegisterBarotrauma("Networking.UnknownAddress") -RegisterBarotrauma("Networking.P2PAddress") -RegisterBarotrauma("Networking.EosP2PAddress") -RegisterBarotrauma("Networking.SteamP2PAddress") -RegisterBarotrauma("Networking.PipeAddress") -RegisterBarotrauma("Networking.LidgrenAddress") -RegisterBarotrauma("Networking.Endpoint") -RegisterBarotrauma("Networking.SteamP2PEndpoint") -RegisterBarotrauma("Networking.PipeEndpoint") -RegisterBarotrauma("Networking.LidgrenEndpoint") - -RegisterBarotrauma("INetSerializableStruct") -RegisterBarotrauma("Networking.Client") -RegisterBarotrauma("Networking.TempClient") -RegisterBarotrauma("Networking.NetworkConnection") -RegisterBarotrauma("Networking.LidgrenConnection") -RegisterBarotrauma("Networking.SteamP2PConnection") -RegisterBarotrauma("Networking.VoipQueue") -RegisterBarotrauma("Networking.ChatMessage") - -RegisterBarotrauma("AnimController") -RegisterBarotrauma("HumanoidAnimController") -RegisterBarotrauma("FishAnimController") -RegisterBarotrauma("Limb") -RegisterBarotrauma("Ragdoll") -RegisterBarotrauma("RagdollParams") - -RegisterBarotrauma("AfflictionPrefab") -RegisterBarotrauma("Affliction") -RegisterBarotrauma("AttackResult") -RegisterBarotrauma("Attack") -RegisterBarotrauma("Entity") -RegisterBarotrauma("EntityGrid") -RegisterBarotrauma("EntitySpawner") -RegisterBarotrauma("MapEntity") -RegisterBarotrauma("MapEntityPrefab") -RegisterBarotrauma("CauseOfDeath") -RegisterBarotrauma("Hull") -RegisterBarotrauma("WallSection") -RegisterBarotrauma("Structure") -RegisterBarotrauma("Gap") -RegisterBarotrauma("PhysicsBody") -RegisterBarotrauma("AbilityFlags") -RegisterBarotrauma("ItemPrefab") -RegisterBarotrauma("ItemAssemblyPrefab") -RegisterBarotrauma("InputType") - -RegisterBarotrauma("FireSource") -RegisterBarotrauma("SerializableProperty") -LuaUserData.MakeFieldAccessible(RegisterBarotrauma("StatusEffect"), "user") -RegisterBarotrauma("DurationListElement") -RegisterBarotrauma("PropertyConditional") -RegisterBarotrauma("DelayedListElement") -RegisterBarotrauma("DelayedEffect") - - -RegisterBarotrauma("ContentPackageManager") -RegisterBarotrauma("ContentPackageManager+PackageSource") -RegisterBarotrauma("ContentPackageManager+EnabledPackages") -RegisterBarotrauma("ContentPackage") -RegisterBarotrauma("RegularPackage") -RegisterBarotrauma("CorePackage") -RegisterBarotrauma("ContentXElement") -RegisterBarotrauma("ContentPath") -RegisterBarotrauma("ContentPackageId") -RegisterBarotrauma("SteamWorkshopId") -RegisterBarotrauma("Md5Hash") - -RegisterBarotrauma("AfflictionsFile") -RegisterBarotrauma("BackgroundCreaturePrefabsFile") -RegisterBarotrauma("BallastFloraFile") -RegisterBarotrauma("BeaconStationFile") -RegisterBarotrauma("CaveGenerationParametersFile") -RegisterBarotrauma("CharacterFile") -RegisterBarotrauma("ContentFile") -RegisterBarotrauma("CorpsesFile") -RegisterBarotrauma("DecalsFile") -RegisterBarotrauma("EnemySubmarineFile") -RegisterBarotrauma("EventManagerSettingsFile") -RegisterBarotrauma("FactionsFile") -RegisterBarotrauma("ItemAssemblyFile") -RegisterBarotrauma("ItemFile") -RegisterBarotrauma("JobsFile") -RegisterBarotrauma("LevelGenerationParametersFile") -RegisterBarotrauma("LevelObjectPrefabsFile") -RegisterBarotrauma("LocationTypesFile") -RegisterBarotrauma("MapGenerationParametersFile") -RegisterBarotrauma("MissionsFile") -RegisterBarotrauma("NPCConversationsFile") -RegisterBarotrauma("NPCPersonalityTraitsFile") -RegisterBarotrauma("NPCSetsFile") -RegisterBarotrauma("OrdersFile") -RegisterBarotrauma("OtherFile") -RegisterBarotrauma("OutpostConfigFile") -RegisterBarotrauma("OutpostFile") -RegisterBarotrauma("OutpostModuleFile") -RegisterBarotrauma("ParticlesFile") -RegisterBarotrauma("RandomEventsFile") -RegisterBarotrauma("RuinConfigFile") -RegisterBarotrauma("ServerExecutableFile") -RegisterBarotrauma("SkillSettingsFile") -RegisterBarotrauma("SoundsFile") -RegisterBarotrauma("StartItemsFile") -RegisterBarotrauma("StructureFile") -RegisterBarotrauma("SubmarineFile") -RegisterBarotrauma("TalentsFile") -RegisterBarotrauma("TalentTreesFile") -RegisterBarotrauma("TextFile") -RegisterBarotrauma("TutorialsFile") -RegisterBarotrauma("UIStyleFile") -RegisterBarotrauma("UpgradeModulesFile") -RegisterBarotrauma("WreckAIConfigFile") -RegisterBarotrauma("WreckFile") - -Register("System.Xml.Linq.XElement") -Register("System.Xml.Linq.XName") -Register("System.Xml.Linq.XAttribute") -Register("System.Xml.Linq.XContainer") -Register("System.Xml.Linq.XDocument") -Register("System.Xml.Linq.XNode") - - -RegisterBarotrauma("SubmarineBody") -RegisterBarotrauma("Explosion") -RegisterBarotrauma("Networking.ServerSettings") -RegisterBarotrauma("Networking.ServerSettings+SavedClientPermission") -RegisterBarotrauma("Inventory") -RegisterBarotrauma("ItemInventory") -RegisterBarotrauma("Inventory+ItemSlot") -RegisterBarotrauma("FireSource") -RegisterBarotrauma("AutoItemPlacer") -RegisterBarotrauma("CircuitBoxConnection") -RegisterBarotrauma("CircuitBoxComponent") -RegisterBarotrauma("CircuitBoxNode") -RegisterBarotrauma("CircuitBoxWire") -RegisterBarotrauma("CircuitBoxInputOutputNode") -RegisterBarotrauma("CircuitBoxSelectable") -RegisterBarotrauma("CircuitBoxSizes") - -local componentsToRegister = { "DockingPort", "Door", "GeneticMaterial", "Growable", "Holdable", "LevelResource", "ItemComponent", "ItemLabel", "LightComponent", "Controller", "Deconstructor", "Engine", "Fabricator", "OutpostTerminal", "Pump", "Reactor", "Steering", "PowerContainer", "Projectile", "Repairable", "Rope", "Scanner", "ButtonTerminal", "ConnectionPanel", "CustomInterface", "MemoryComponent", "Terminal", "WifiComponent", "Wire", "TriggerComponent", "ElectricalDischarger", "EntitySpawnerComponent", "ProducedItem", "VineTile", "GrowthSideExtension", "IdCard", "MeleeWeapon", "Pickable", "AbilityItemPickingTime", "Propulsion", "RangedWeapon", "AbilityRangedWeapon", "RepairTool", "Sprayer", "Throwable", "ItemContainer", "AbilityItemContainer", "Ladder", "LimbPos", "AbilityDeconstructedItem", "AbilityItemCreationMultiplier", "AbilityItemDeconstructedInventory", "MiniMap", "OxygenGenerator", "Sonar", "SonarTransducer", "Vent", "NameTag", "Planter", "Powered", "PowerTransfer", "Quality", "RemoteController", "AdderComponent", "AndComponent", "ArithmeticComponent", "ColorComponent", "ConcatComponent", "Connection", "CircuitBox", "DelayComponent", "DivideComponent", "EqualsComponent", "ExponentiationComponent", "FunctionComponent", "GreaterComponent", "ModuloComponent", "MotionSensor", "MultiplyComponent", "NotComponent", "OrComponent", "OscillatorComponent", "OxygenDetector", "RegExFindComponent", "RelayComponent", "SignalCheckComponent", "SmokeDetector", "StringComponent", "SubtractComponent", "TrigonometricFunctionComponent", "WaterDetector", "XorComponent", "StatusHUD", "Turret", "Wearable", -"GridInfo", "PowerSourceGroup" -} - -for key, value in pairs(componentsToRegister) do - RegisterBarotrauma("Items.Components." .. value) -end - -LuaUserData.MakeFieldAccessible(RegisterBarotrauma("Items.Components.CustomInterface"), "customInterfaceElementList") -RegisterBarotrauma("Items.Components.CustomInterface+CustomInterfaceElement") - -RegisterBarotrauma("WearableSprite") - -RegisterBarotrauma("AIController") -RegisterBarotrauma("EnemyAIController") -RegisterBarotrauma("HumanAIController") -RegisterBarotrauma("AICharacter") -RegisterBarotrauma("AITarget") -RegisterBarotrauma("AITargetMemory") -RegisterBarotrauma("AIChatMessage") -RegisterBarotrauma("AIObjectiveManager") -RegisterBarotrauma("WreckAI") -RegisterBarotrauma("WreckAIConfig") - -RegisterBarotrauma("AIObjectiveChargeBatteries") -RegisterBarotrauma("AIObjective") -RegisterBarotrauma("AIObjectiveCleanupItem") -RegisterBarotrauma("AIObjectiveCleanupItems") -RegisterBarotrauma("AIObjectiveCombat") -RegisterBarotrauma("AIObjectiveContainItem") -RegisterBarotrauma("AIObjectiveDeconstructItem") -RegisterBarotrauma("AIObjectiveDeconstructItems") -RegisterBarotrauma("AIObjectiveEscapeHandcuffs") -RegisterBarotrauma("AIObjectiveExtinguishFire") -RegisterBarotrauma("AIObjectiveExtinguishFires") -RegisterBarotrauma("AIObjectiveFightIntruders") -RegisterBarotrauma("AIObjectiveFindDivingGear") -RegisterBarotrauma("AIObjectiveFindSafety") -RegisterBarotrauma("AIObjectiveFixLeak") -RegisterBarotrauma("AIObjectiveFixLeaks") -RegisterBarotrauma("AIObjectiveGetItem") -RegisterBarotrauma("AIObjectiveGoTo") -RegisterBarotrauma("AIObjectiveIdle") -RegisterBarotrauma("AIObjectiveOperateItem") -RegisterBarotrauma("AIObjectivePumpWater") -RegisterBarotrauma("AIObjectiveRepairItem") -RegisterBarotrauma("AIObjectiveRepairItems") -RegisterBarotrauma("AIObjectiveRescue") -RegisterBarotrauma("AIObjectiveRescueAll") -RegisterBarotrauma("AIObjectiveReturn") - -RegisterBarotrauma("Order") -RegisterBarotrauma("OrderPrefab") -RegisterBarotrauma("OrderTarget") - -RegisterBarotrauma("TalentPrefab") -RegisterBarotrauma("TalentOption") -RegisterBarotrauma("TalentSubTree") -RegisterBarotrauma("TalentTree") -RegisterBarotrauma("CharacterTalent") -RegisterBarotrauma("Upgrade") -RegisterBarotrauma("UpgradeCategory") -RegisterBarotrauma("UpgradePrefab") -RegisterBarotrauma("UpgradeManager") - -RegisterBarotrauma("Screen") -RegisterBarotrauma("GameScreen") -RegisterBarotrauma("GameSession") -RegisterBarotrauma("GameSettings") -RegisterBarotrauma("CrewManager") -RegisterBarotrauma("KarmaManager") - -RegisterBarotrauma("GameMode") -RegisterBarotrauma("MissionMode") -RegisterBarotrauma("PvPMode") -RegisterBarotrauma("Mission") -RegisterBarotrauma("AbandonedOutpostMission") -RegisterBarotrauma("EliminateTargetsMission") -RegisterBarotrauma("EndMission") -RegisterBarotrauma("BeaconMission") -RegisterBarotrauma("CargoMission") -RegisterBarotrauma("CombatMission") -RegisterBarotrauma("EscortMission") -RegisterBarotrauma("GoToMission") -RegisterBarotrauma("MineralMission") -RegisterBarotrauma("MonsterMission") -RegisterBarotrauma("NestMission") -RegisterBarotrauma("PirateMission") -RegisterBarotrauma("SalvageMission") -RegisterBarotrauma("ScanMission") -RegisterBarotrauma("MissionPrefab") -RegisterBarotrauma("CampaignMode") -RegisterBarotrauma("CoOpMode") -RegisterBarotrauma("MultiPlayerCampaign") -RegisterBarotrauma("Radiation") - -RegisterBarotrauma("CampaignMetadata") -RegisterBarotrauma("Wallet") - -RegisterBarotrauma("Faction") -RegisterBarotrauma("FactionPrefab") -RegisterBarotrauma("Reputation") - -RegisterBarotrauma("Location") -RegisterBarotrauma("LocationConnection") -RegisterBarotrauma("LocationType") -RegisterBarotrauma("LocationTypeChange") - -RegisterBarotrauma("DebugConsole") -RegisterBarotrauma("DebugConsole+Command") - -RegisterBarotrauma("TextManager") -RegisterBarotrauma("TextPack") - -local descriptor = RegisterBarotrauma("NetLobbyScreen") - -if SERVER then - LuaUserData.MakeFieldAccessible(descriptor, "subs") -end - -RegisterBarotrauma("EventManager") -RegisterBarotrauma("EventManagerSettings") -RegisterBarotrauma("Event") -RegisterBarotrauma("ArtifactEvent") -RegisterBarotrauma("MonsterEvent") -RegisterBarotrauma("ScriptedEvent") -RegisterBarotrauma("MalfunctionEvent") -RegisterBarotrauma("EventSet") -RegisterBarotrauma("EventPrefab") - -RegisterBarotrauma("Networking.NetConfig") -RegisterBarotrauma("Networking.IWriteMessage") -RegisterBarotrauma("Networking.IReadMessage") -RegisterBarotrauma("Networking.NetEntityEvent") -RegisterBarotrauma("Networking.INetSerializable") -Register("Lidgren.Network.NetIncomingMessage") -Register("Lidgren.Network.NetConnection") -Register("System.Net.IPEndPoint") -Register("System.Net.IPAddress") - -RegisterBarotrauma("Skill") -RegisterBarotrauma("SkillPrefab") -RegisterBarotrauma("SkillSettings") - -RegisterBarotrauma("TraitorManager") -RegisterBarotrauma("TraitorEvent") -RegisterBarotrauma("TraitorEventPrefab") -RegisterBarotrauma("TraitorManager+TraitorResults") - -Register("FarseerPhysics.Dynamics.Body") -Register("FarseerPhysics.Dynamics.World") -Register("FarseerPhysics.Dynamics.Fixture") -Register("FarseerPhysics.ConvertUnits") -Register("FarseerPhysics.Collision.AABB") -Register("FarseerPhysics.Collision.ContactFeature") -Register("FarseerPhysics.Collision.ManifoldPoint") -Register("FarseerPhysics.Collision.ContactID") -Register("FarseerPhysics.Collision.Manifold") -Register("FarseerPhysics.Collision.RayCastInput") -Register("FarseerPhysics.Collision.ClipVertex") -Register("FarseerPhysics.Collision.RayCastOutput") -Register("FarseerPhysics.Collision.EPAxis") -Register("FarseerPhysics.Collision.ReferenceFace") -Register("FarseerPhysics.Collision.Collision") - -RegisterBarotrauma("Physics") - -local toolBox = RegisterBarotrauma("ToolBox") -if CLIENT then - LuaUserData.RemoveMember(toolBox, "OpenFileWithShell") -end - -RegisterBarotrauma("Camera") -RegisterBarotrauma("Key") - -RegisterBarotrauma("PrefabCollection`1") - -RegisterBarotrauma("PrefabSelector`1") - -RegisterBarotrauma("Pair`2") - -RegisterBarotrauma("Items.Components.Signal") -RegisterBarotrauma("SubmarineInfo") - -RegisterBarotrauma("MapCreatures.Behavior.BallastFloraBehavior") -RegisterBarotrauma("MapCreatures.Behavior.BallastFloraBranch") - -RegisterBarotrauma("PetBehavior") -RegisterBarotrauma("SwarmBehavior") -RegisterBarotrauma("LatchOntoAI") - -RegisterBarotrauma("Decal") -RegisterBarotrauma("DecalPrefab") -RegisterBarotrauma("DecalManager") - -RegisterBarotrauma("PriceInfo") - -RegisterBarotrauma("Voting") - -Register("Microsoft.Xna.Framework.Vector2") -Register("Microsoft.Xna.Framework.Vector3") -Register("Microsoft.Xna.Framework.Vector4") -Register("Microsoft.Xna.Framework.Color") -Register("Microsoft.Xna.Framework.Point") -Register("Microsoft.Xna.Framework.Rectangle") -Register("Microsoft.Xna.Framework.Matrix") - -local friend = Register("Steamworks.Friend") - -LuaUserData.RemoveMember(friend, "InviteToGame") -LuaUserData.RemoveMember(friend, "SendMessage") - -local workshopItem = Register("Steamworks.Ugc.Item") - -LuaUserData.RemoveMember(workshopItem, "Subscribe") -LuaUserData.RemoveMember(workshopItem, "DownloadAsync") -LuaUserData.RemoveMember(workshopItem, "Unsubscribe") -LuaUserData.RemoveMember(workshopItem, "AddFavorite") -LuaUserData.RemoveMember(workshopItem, "RemoveFavorite") -LuaUserData.RemoveMember(workshopItem, "Vote") -LuaUserData.RemoveMember(workshopItem, "GetUserVote") -LuaUserData.RemoveMember(workshopItem, "Edit") - -RegisterExtension("Barotrauma.MathUtils") -RegisterExtension("Barotrauma.XMLExtensions") \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua index 4b038464e..d8c531649 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua @@ -6,17 +6,6 @@ package.path = {path .. "/Lua/?.lua"} setmodulepaths(package.path) --- Setup Libraries -LuaSetup.LuaUserData = LuaUserData - -require("DefaultRegister/RegisterShared") - -if SERVER then - require("DefaultRegister/RegisterServer") -else - require("DefaultRegister/RegisterClient") -end - local function AddTableToGlobal(tbl) for k, v in pairs(tbl) do _G[k] = v @@ -35,11 +24,11 @@ AddTableToGlobal(require("CompatibilityLib")) require("DefaultHook") +Descriptors = LuaSetup.LuaUserData + require("DefaultLib/Utils/Math") require("DefaultLib/Utils/String") require("DefaultLib/Utils/Util") require("DefaultLib/Utils/SteamApi") -require("PostSetup") - LuaSetup = nil \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaUserData.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaUserData.lua deleted file mode 100644 index 387fc7f2b..000000000 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaUserData.lua +++ /dev/null @@ -1,97 +0,0 @@ -local clrLuaUserData = LuaUserData -local luaUserData = {} - -luaUserData.Descriptors = {} - -LuaSetup.LuaUserData = luaUserData - -luaUserData.IsRegistered = clrLuaUserData.IsRegistered -luaUserData.UnregisterType = clrLuaUserData.UnregisterType -luaUserData.RegisterGenericType = clrLuaUserData.RegisterGenericType -luaUserData.RegisterExtensionType = clrLuaUserData.RegisterExtensionType -luaUserData.UnregisterGenericType = clrLuaUserData.UnregisterGenericType -luaUserData.IsTargetType = clrLuaUserData.IsTargetType -luaUserData.TypeOf = clrLuaUserData.TypeOf -luaUserData.GetType = clrLuaUserData.GetType -luaUserData.CreateEnumTable = clrLuaUserData.CreateEnumTable -luaUserData.MakeFieldAccessible = clrLuaUserData.MakeFieldAccessible -luaUserData.MakeMethodAccessible = clrLuaUserData.MakeMethodAccessible -luaUserData.MakePropertyAccessible = clrLuaUserData.MakePropertyAccessible -luaUserData.AddMethod = clrLuaUserData.AddMethod -luaUserData.AddField = clrLuaUserData.AddField -luaUserData.RemoveMember = clrLuaUserData.RemoveMember -luaUserData.CreateUserDataFromDescriptor = clrLuaUserData.CreateUserDataFromDescriptor -luaUserData.CreateUserDataFromType = clrLuaUserData.CreateUserDataFromType -luaUserData.HasMember = clrLuaUserData.HasMember - -luaUserData.RegisterType = function(typeName) - local success, result = pcall(clrLuaUserData.RegisterType, typeName) - - if not success then - error(result, 2) - end - - luaUserData.Descriptors[typeName] = result - - return result -end - -luaUserData.RegisterTypeBarotrauma = function(typeName) - typeName = "Barotrauma." .. typeName - local success, result = pcall(luaUserData.RegisterType, typeName) - - if not success then - error(result, 2) - end - - return result -end - -luaUserData.AddCallMetaTable = function (userdata) - if userdata == nil then - error("Attempted to add a call metatable to a nil value.", 2) - end - - if not LuaUserData.HasMember(userdata, ".ctor") then - error("Attempted to add a call metatable to a userdata that does not have a constructor.", 2) - end - - debug.setmetatable(userdata, { - __call = function(obj, ...) - if userdata == nil then - error("userdata was nil.", 2) - end - - local success, result = pcall(userdata.__new, ...) - - - if not success then - error(result, 2) - end - - return result - end - }) -end - -luaUserData.CreateStatic = function(typeName) - if type(typeName) ~= "string" then - error("Expected a string for typeName, got " .. type(typeName) .. ".", 2) - end - - local success, result = pcall(clrLuaUserData.CreateStatic, typeName) - - if not success then - error(result, 2) - end - - if result == nil then - return - end - - if LuaUserData.HasMember(result, ".ctor") then - luaUserData.AddCallMetaTable(result) - end - - return result -end \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/PostSetup.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/PostSetup.lua deleted file mode 100644 index d18dbc685..000000000 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/PostSetup.lua +++ /dev/null @@ -1,13 +0,0 @@ -if not CSActive then - LuaUserDataIUUD = LuaUserData.RegisterType("Barotrauma.LuaSafeUserData") - LuaUserData = LuaUserData.CreateStatic("Barotrauma.LuaSafeUserData"); - - for k, v in pairs(debug) do - if k ~= "getmetatable" and k ~= "setmetatable" and k ~= "traceback" then - debug[k] = nil - end - end -end - -Descriptors = LuaUserData.__new() -LuaUserDataIUUD = nil \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 740e8ab89..8bb0d26ae 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -189,6 +189,10 @@ namespace Barotrauma servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceResolver(factory => factory.GetInstance()); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs index d15b0e682..97564b9be 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs @@ -17,16 +17,10 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; -using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.IO; using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading; using System.Threading.Tasks; -using static Barotrauma.GameSettings; namespace Barotrauma.LuaCs.Services; @@ -39,20 +33,26 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService private List _resourcesInfo = new List(); private readonly AsyncReaderWriterLock _operationsLock = new (); - + + private readonly ILuaUserDataService _userDataService; + private readonly ISafeLuaUserDataService _safeUserDataService; + private readonly ILuaScriptLoader _luaScriptLoader; private readonly ILuaScriptServicesConfig _luaScriptServicesConfig; private readonly ILoggerService _loggerService; private readonly LuaGame _luaGame; private readonly ILuaCsHook _luaCsHook; private readonly ILuaCsTimer _luaCsTimer; + private readonly IDefaultLuaRegistrar _defaultLuaRegistrar; //private readonly ILuaCsNetworking _luaCsNetworking; //private readonly ILuaCsUtility _luaCsUtility; - //private readonly ILuaCsTimer _luaCsTimer; public LuaScriptManagementService( - ILoggerService loggerService, - ILuaScriptLoader loader, + ILoggerService loggerService, + ILuaScriptLoader loader, + ILuaUserDataService userDataService, + ISafeLuaUserDataService safeUserDataService, + IDefaultLuaRegistrar defaultLuaRegistrar, ILuaScriptServicesConfig luaScriptServicesConfig, LuaGame luaGame, ILuaCsHook luaCsHook, @@ -62,6 +62,9 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService ) { _luaScriptLoader = loader; + _userDataService = userDataService; + _safeUserDataService = safeUserDataService; + _defaultLuaRegistrar = defaultLuaRegistrar; _luaScriptServicesConfig = luaScriptServicesConfig; _loggerService = loggerService; @@ -168,16 +171,15 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService Script.GlobalOptions.ShouldPCallCatchException = (Exception ex) => { return true; }; - RegisterType(typeof(LuaGame)); - RegisterType(typeof(EventService)); - RegisterType(typeof(ILuaCsNetworking)); - RegisterType(typeof(ILuaCsUtility)); - RegisterType(typeof(ILuaCsTimer)); - RegisterType(typeof(LuaCsFile)); - RegisterType(typeof(ILuaScriptResourceInfo)); - RegisterType(typeof(IResourceInfo)); - RegisterType(typeof(LuaUserData)); - RegisterType(typeof(IUserDataDescriptor)); + UserData.RegisterType(typeof(LuaGame)); + UserData.RegisterType(typeof(EventService)); + UserData.RegisterType(typeof(ILuaCsNetworking)); + UserData.RegisterType(typeof(ILuaCsUtility)); + UserData.RegisterType(typeof(ILuaCsTimer)); + UserData.RegisterType(typeof(LuaCsFile)); + UserData.RegisterType(typeof(ILuaScriptResourceInfo)); + UserData.RegisterType(typeof(IResourceInfo)); + UserData.RegisterType(typeof(IUserDataDescriptor)); new LuaConverters(_script).RegisterLuaConverters(); @@ -193,20 +195,31 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _script.Globals["dostring"] = (Func)_script.DoString; _script.Globals["load"] = (Func)_script.LoadString; - _script.Globals["Game"] = _luaGame; _script.Globals["Hook"] = _luaCsHook; _script.Globals["Timer"] = _luaCsTimer; _script.Globals["File"] = UserData.CreateStatic(); //_script.Globals["Networking"] = _luaCsNetworking; //_script.Globals["Steam"] = Steam; - _script.Globals["LuaUserData"] = UserData.CreateStatic(); + + if (GameMain.LuaCs.IsCsEnabled) + { + UserData.RegisterType(typeof(LuaUserDataService)); + _script.Globals["LuaUserData"] = _userDataService; + } + else + { + UserData.RegisterType(typeof(SafeLuaUserDataService)); + _script.Globals["LuaUserData"] = _safeUserDataService; + } _script.Globals["ExecutionNumber"] = 0; _script.Globals["CSActive"] = false; _script.Globals["SERVER"] = LuaCsSetup.IsServer; _script.Globals["CLIENT"] = LuaCsSetup.IsClient; + + _defaultLuaRegistrar.RegisterAll(); } public FluentResults.Result ExecuteLoadedScripts(ImmutableArray executionOrder) @@ -291,24 +304,17 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService public FluentResults.Result Reset() { + _userDataService.Reset(); return DisposeAllPackageResources(); } public void Dispose() { + _userDataService.Dispose(); _luaScriptLoader.Dispose(); IsDisposed = true; } - public IUserDataDescriptor RegisterType(Type type) - { - return UserData.RegisterType(type); - } - public void UnregisterType(Type type) - { - UserData.UnregisterType(type, true); - } - public object? GetGlobalTableValue(string tableName) { if (!IsRunning) { return null; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/DefaultLuaRegistrar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/DefaultLuaRegistrar.cs new file mode 100644 index 000000000..c7bc3c518 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/DefaultLuaRegistrar.cs @@ -0,0 +1,177 @@ +using Barotrauma.Networking; +using MoonSharp.Interpreter; +using Sigil; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Barotrauma.LuaCs.Services; + +public interface IDefaultLuaRegistrar : IService +{ + public void RegisterAll(); +} + +public class DefaultLuaRegistrar : IDefaultLuaRegistrar +{ + public bool IsDisposed { get; private set; } + + private readonly ILuaUserDataService _userDataService; + private readonly ISafeLuaUserDataService _safeUserDataService; + private readonly ILoggerService _loggerService; + + public DefaultLuaRegistrar(ILoggerService loggerService, ILuaUserDataService userDataService, ISafeLuaUserDataService safeUserDataService) + { + _userDataService = userDataService; + _safeUserDataService = safeUserDataService; + _loggerService = loggerService; + } + + private void RegisterShared() + { + _userDataService.RegisterType("System.TimeSpan"); + _userDataService.RegisterType("System.Exception"); + _userDataService.RegisterType("System.Console"); + _userDataService.RegisterType("System.Exception"); + + _userDataService.RegisterType("Barotrauma.Success`2"); + _userDataService.RegisterType("Barotrauma.Failure`2"); + _userDataService.RegisterType("Barotrauma.Range`1"); + + List assembliesToScan = [typeof(DefaultLuaRegistrar).Assembly, typeof(Identifier).Assembly, typeof(Microsoft.Xna.Framework.Vector2).Assembly]; + + foreach (var type in assembliesToScan.SelectMany(a => a.GetTypes())) + { + if (type.IsEnum || type.IsDefined(typeof(CompilerGeneratedAttribute)) || !_safeUserDataService.IsAllowed(type.FullName)) + { + continue; + } + + _loggerService.LogMessage($"Registered {type.FullName}"); + + _userDataService.RegisterType(type.FullName); + } + + + _userDataService.RegisterType("Barotrauma.LuaSByte"); + _userDataService.RegisterType("Barotrauma.LuaByte"); + _userDataService.RegisterType("Barotrauma.LuaInt16"); + _userDataService.RegisterType("Barotrauma.LuaUInt16"); + _userDataService.RegisterType("Barotrauma.LuaInt32"); + _userDataService.RegisterType("Barotrauma.LuaUInt32"); + _userDataService.RegisterType("Barotrauma.LuaInt64"); + _userDataService.RegisterType("Barotrauma.LuaUInt64"); + _userDataService.RegisterType("Barotrauma.LuaSingle"); + _userDataService.RegisterType("Barotrauma.LuaDouble"); + + _userDataService.RegisterType("Barotrauma.Level+InterestingPosition"); + _userDataService.RegisterType("Barotrauma.Networking.RespawnManager+TeamSpecificState"); + + _userDataService.RegisterType("Barotrauma.CharacterParams+AIParams"); + _userDataService.RegisterType("Barotrauma.CharacterParams+TargetParams"); + _userDataService.RegisterType("Barotrauma.CharacterParams+InventoryParams"); + _userDataService.RegisterType("Barotrauma.CharacterParams+HealthParams"); + _userDataService.RegisterType("Barotrauma.CharacterParams+ParticleParams"); + _userDataService.RegisterType("Barotrauma.CharacterParams+SoundParams"); + + _userDataService.RegisterType("Barotrauma.FabricationRecipe+RequiredItemByIdentifier"); + _userDataService.RegisterType("Barotrauma.FabricationRecipe+RequiredItemByTag"); + + _userDataService.MakeFieldAccessible(_userDataService.RegisterType("Barotrauma.StatusEffect"), "user"); + + + _userDataService.RegisterType("Barotrauma.ContentPackageManager+PackageSource"); + _userDataService.RegisterType("Barotrauma.ContentPackageManager+EnabledPackages"); + + _userDataService.RegisterType("System.Xml.Linq.XElement"); + _userDataService.RegisterType("System.Xml.Linq.XName"); + _userDataService.RegisterType("System.Xml.Linq.XAttribute"); + _userDataService.RegisterType("System.Xml.Linq.XContainer"); + _userDataService.RegisterType("System.Xml.Linq.XDocument"); + _userDataService.RegisterType("System.Xml.Linq.XNode"); + + + _userDataService.RegisterType("Barotrauma.Networking.ServerSettings+SavedClientPermission"); + _userDataService.RegisterType("Barotrauma.Inventory+ItemSlot"); + + + _userDataService.MakeFieldAccessible(_userDataService.RegisterType("Barotrauma.Items.Components.CustomInterface"), "customInterfaceElementList"); + _userDataService.RegisterType("Barotrauma.Items.Components.CustomInterface+CustomInterfaceElement"); + + _userDataService.RegisterType("Barotrauma.DebugConsole+Command"); + + { + var descriptor = _userDataService.RegisterType("Barotrauma.NetLobbyScreen"); + +#if SERVER + _userDataService.MakeFieldAccessible(descriptor, "subs"); +#endif + } + + _userDataService.RegisterType("FarseerPhysics.Dynamics.Body"); + _userDataService.RegisterType("FarseerPhysics.Dynamics.World"); + _userDataService.RegisterType("FarseerPhysics.Dynamics.Fixture"); + _userDataService.RegisterType("FarseerPhysics.ConvertUnits"); + _userDataService.RegisterType("FarseerPhysics.Collision.AABB"); + _userDataService.RegisterType("FarseerPhysics.Collision.ContactFeature"); + _userDataService.RegisterType("FarseerPhysics.Collision.ManifoldPoint"); + _userDataService.RegisterType("FarseerPhysics.Collision.ContactID"); + _userDataService.RegisterType("FarseerPhysics.Collision.Manifold"); + _userDataService.RegisterType("FarseerPhysics.Collision.RayCastInput"); + _userDataService.RegisterType("FarseerPhysics.Collision.ClipVertex"); + _userDataService.RegisterType("FarseerPhysics.Collision.RayCastOutput"); + _userDataService.RegisterType("FarseerPhysics.Collision.EPAxis"); + _userDataService.RegisterType("FarseerPhysics.Collision.ReferenceFace"); + _userDataService.RegisterType("FarseerPhysics.Collision.Collision"); + + _userDataService.RegisterType("Barotrauma.PrefabCollection`1"); + _userDataService.RegisterType("Barotrauma.PrefabSelector`1"); + _userDataService.RegisterType("Barotrauma.Pair`2"); + + var toolBox = UserData.RegisterType(typeof(ToolBox)); +#if CLIENT + _userDataService.RemoveMember(toolBox, "OpenFileWithShell"); +#endif + } + +#if CLIENT + private void RegisterClient() + { + _userDataService.RegisterType("Microsoft.Xna.Framework.Graphics.Effect"); + _userDataService.RegisterType("Microsoft.Xna.Framework.Graphics.EffectParameterCollection"); + _userDataService.RegisterType("Microsoft.Xna.Framework.Graphics.EffectParameter"); + + _userDataService.RegisterType("Microsoft.Xna.Framework.Graphics.SpriteBatch"); + _userDataService.RegisterType("Microsoft.Xna.Framework.Graphics.Texture2D"); + _userDataService.RegisterType("EventInput.KeyboardDispatcher"); + _userDataService.RegisterType("EventInput.KeyEventArgs"); + _userDataService.RegisterType("Microsoft.Xna.Framework.Input.Keys"); + _userDataService.RegisterType("Microsoft.Xna.Framework.Input.KeyboardState"); + + + _userDataService.RegisterType("Barotrauma.Inventory+SlotReference"); + } +#elif SERVER + private void RegisterServer() + { + _userDataService.RegisterType("Barotrauma.Character+TeamChangeEventData"); + } +#endif + + public void RegisterAll() + { + RegisterShared(); +#if CLIENT + RegisterClient(); +#elif SERVER + RegisterServer(); +#endif + } + + public void Dispose() + { + IsDisposed = true; + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaUserData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaUserData.cs deleted file mode 100644 index 1cd7fccc2..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaUserData.cs +++ /dev/null @@ -1,367 +0,0 @@ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using MoonSharp.Interpreter; -using MoonSharp.Interpreter.Interop; - -namespace Barotrauma -{ - internal class LuaUserData - { - [Obsolete("Use IPluginManagementService::GetTypesByName()")] - public static Type GetType(string typeName) => GameMain.LuaCs.PluginManagementService.GetType(typeName, includeInterfaces: true); //LuaCsSetup.GetType(typeName); - - public static IUserDataDescriptor RegisterType(string typeName) - { - Type type = GetType(typeName); - - if (type == null) - { - throw new ScriptRuntimeException($"tried to register a type that doesn't exist: {typeName}."); - } - - return UserData.RegisterType(type); - } - - public static void RegisterExtensionType(string typeName) - { - Type type = GetType(typeName); - - if (type == null) - { - throw new ScriptRuntimeException($"tried to register a type that doesn't exist: {typeName}."); - } - - UserData.RegisterExtensionType(type); - } - - public static bool IsRegistered(string typeName) - { - Type type = GetType(typeName); - - if (type == null) - { - return false; - } - - return UserData.GetDescriptorForType(type, true) != null; - } - - public static void UnregisterType(string typeName, bool deleteHistory = false) - { - Type type = GetType(typeName); - - if (type == null) - { - throw new ScriptRuntimeException($"tried to unregister a type that doesn't exist: {typeName}."); - } - - UserData.UnregisterType(type, deleteHistory); - } - public static IUserDataDescriptor RegisterGenericType(string typeName, params string[] typeNameArguements) - { - Type type = GetType(typeName); - Type[] typeArguements = typeNameArguements.Select(x => GetType(x)).ToArray(); - Type genericType = type.MakeGenericType(typeArguements); - return UserData.RegisterType(genericType); - } - - public static void UnregisterGenericType(string typeName, params string[] typeNameArguements) - { - Type type = GetType(typeName); - Type[] typeArguements = typeNameArguements.Select(x => GetType(x)).ToArray(); - Type genericType = type.MakeGenericType(typeArguements); - UserData.UnregisterType(genericType); - } - - public static bool IsTargetType(object obj, string typeName) - { - if (obj == null) { throw new ScriptRuntimeException("userdata is nil"); } - Type targetType = GetType(typeName); - if (targetType == null) { throw new ScriptRuntimeException("target type not found"); } - - Type type = obj is Type ? (Type)obj : obj.GetType(); - return targetType.IsAssignableFrom(type); - } - - public static string TypeOf(object obj) - { - if (obj == null) { throw new ScriptRuntimeException("userdata is nil"); } - - return obj.GetType().FullName; - } - - public static object CreateStatic(string typeName) - { - Type type = GetType(typeName); - - if (type == null) - { - throw new ScriptRuntimeException($"tried to create a static userdata of a type that doesn't exist: {typeName}."); - } - - MethodInfo method = typeof(UserData).GetMethod(nameof(UserData.CreateStatic), 1, new Type[0]); - MethodInfo generic = method.MakeGenericMethod(type); - return generic.Invoke(null, null); - } - - public static object CreateEnumTable(string typeName) - { - Type type = GetType(typeName); - - if (type == null) - { - throw new ScriptRuntimeException($"tried to create an enum table with a type that doesn't exist:: {typeName}."); - } - - Dictionary result = new Dictionary(); - - foreach (var value in Enum.GetValues(type)) - { - string name = Enum.GetName(type, value); - - result[name] = value; - } - - return result; - } - - private static FieldInfo FindFieldRecursively(Type type, string fieldName) - { - var field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); - - if (field == null && type.BaseType != null) - { - return FindFieldRecursively(type.BaseType, fieldName); - } - - return field; - } - - public static void MakeFieldAccessible(IUserDataDescriptor IUUD, string fieldName) - { - if (IUUD == null) - { - throw new ScriptRuntimeException($"tried to use a UserDataDescriptor that is null to make {fieldName} accessible."); - } - - var descriptor = (StandardUserDataDescriptor)IUUD; - FieldInfo field = FindFieldRecursively(IUUD.Type, fieldName); - - if (field == null) - { - throw new ScriptRuntimeException($"tried to make field '{fieldName}' accessible, but the field doesn't exist."); - } - - descriptor.RemoveMember(fieldName); - descriptor.AddMember(fieldName, new FieldMemberDescriptor(field, InteropAccessMode.Default)); - } - - private static MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null) - { - MethodInfo method; - - if (types == null) - { - method = type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); - } - else - { - method = type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static, types); - } - - if (method == null && type.BaseType != null) - { - return FindMethodRecursively(type.BaseType, methodName, types); - } - - return method; - } - - public static void MakeMethodAccessible(IUserDataDescriptor IUUD, string methodName, string[] parameters = null) - { - if (IUUD == null) - { - throw new ScriptRuntimeException($"tried to use a UserDataDescriptor that is null to make {methodName} accessible."); - } - - Type[] parameterTypes = null; - - - if (parameters != null) - { - parameterTypes = new Type[parameters.Length]; - - for (int i = 0; i < parameters.Length; i++) - { - Type type = LuaUserData.GetType(parameters[i]); - if (type == null) - { - throw new ScriptRuntimeException($"invalid parameter type '{parameters[i]}'"); - } - parameterTypes[i] = type; - } - } - - var descriptor = (StandardUserDataDescriptor)IUUD; - - MethodBase method; - - try - { - method = FindMethodRecursively(IUUD.Type, methodName, parameterTypes); - } - catch (AmbiguousMatchException ex) - { - throw new ScriptRuntimeException("ambiguous method signature."); - } - - if (method == null) - { - throw new ScriptRuntimeException($"tried to make method '{methodName}' accessible, but the method doesn't exist."); - } - - descriptor.AddMember(methodName, new MethodMemberDescriptor(method, InteropAccessMode.Default)); - } - - private static PropertyInfo FindPropertyRecursively(Type type, string propertyName) - { - var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); - - if (property == null && type.BaseType != null) - { - return FindPropertyRecursively(type.BaseType, propertyName); - } - - return property; - } - - public static void MakePropertyAccessible(IUserDataDescriptor IUUD, string propertyName) - { - if (IUUD == null) - { - throw new ScriptRuntimeException($"tried to use a UserDataDescriptor that is null to make {propertyName} accessible."); - } - - var descriptor = (StandardUserDataDescriptor)IUUD; - PropertyInfo property = FindPropertyRecursively(IUUD.Type, propertyName); - - if (property == null) - { - throw new ScriptRuntimeException($"tried to make property '{propertyName}' accessible, but the property doesn't exist."); - } - - descriptor.RemoveMember(propertyName); - descriptor.AddMember(propertyName, new PropertyMemberDescriptor(property, InteropAccessMode.Default, property.GetGetMethod(true), property.GetSetMethod(true))); - } - - public static void AddMethod(IUserDataDescriptor IUUD, string methodName, object function) - { - if (IUUD == null) - { - throw new ScriptRuntimeException($"tried to use a UserDataDescriptor that is null to add method {methodName}."); - } - - var descriptor = (StandardUserDataDescriptor)IUUD; - - descriptor.RemoveMember(methodName); - descriptor.AddMember(methodName, new ObjectCallbackMemberDescriptor(methodName, (object arg1, ScriptExecutionContext arg2, CallbackArguments arg3) => - { - /*if (GameMain.LuaCs != null) - return GameMain.LuaCs.CallLuaFunction(function, arg3.GetArray()); - return null;*/ - throw new NotImplementedException(); - })); - } - - public static void AddField(IUserDataDescriptor IUUD, string fieldName, DynValue value) - { - if (IUUD == null) - { - throw new ScriptRuntimeException($"tried to use a UserDataDescriptor that is null to add field {fieldName}."); - } - - var descriptor = (StandardUserDataDescriptor)IUUD; - descriptor.RemoveMember(fieldName); - descriptor.AddMember(fieldName, new DynValueMemberDescriptor(fieldName, value)); - } - - public static void RemoveMember(IUserDataDescriptor IUUD, string memberName) - { - if (IUUD == null) - { - throw new ScriptRuntimeException($"tried to use a UserDataDescriptor that is null to remove the member {memberName}."); - } - - var descriptor = (StandardUserDataDescriptor)IUUD; - descriptor.RemoveMember(memberName); - } - - public static bool HasMember(object obj, string memberName) - { - if (obj == null) { throw new ScriptRuntimeException("object is nil"); } - - Type type; - if (obj is Type) - { - type = (Type)obj; - } - else if(obj is IUserDataDescriptor descriptor) - { - type = descriptor.Type; - - if (((StandardUserDataDescriptor)descriptor).HasMember(memberName)) - { - return true; - } - } - else - { - type = obj.GetType(); - } - - if (type.GetMember(memberName).Length == 0) - { - return false; - } - - return true; - } - - - /// - /// See . - /// - /// Lua value to convert and wrap in a userdata. - /// Descriptor of the type of the object to convert the Lua value to. Uses MoonSharp ScriptToClr converters. - /// A userdata that wraps the Lua value converted to an object of the desired type as described by . - public static DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor desiredTypeDescriptor) - { - return UserData.Create(scriptObject.ToObject(desiredTypeDescriptor.Type), desiredTypeDescriptor); - } - - /// - /// Converts a Lua value to a CLR object of a desired type and wraps it in a userdata. - /// If the type is not registered, then a new will be created and used. - /// The goal of this method is to allow Lua scripts to create userdata to wrap certain data without having to register types. - /// Wrapping the value in a userdata preserves the original type during script-to-CLR conversions. - /// A Lua script needs to pass a List`1 to a CLR method expecting System.Object, MoonSharp gets - /// in the way by converting the List`1 to a MoonSharp.Interpreter.Table and breaking everything. - /// Registering the List`1 type can break other scripts relying on default converters, so instead - /// it is better to manually wrap the List`1 object into a userdata. - /// - /// - /// Lua value to convert and wrap in a userdata. - /// Type describing the CLR type of the object to convert the Lua value to. - /// A userdata that wraps the Lua value converted to an object of the desired type. - public static DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType) - { - IUserDataDescriptor descriptor = UserData.GetDescriptorForType(desiredType, true); - descriptor ??= new StandardUserDataDescriptor(desiredType, InteropAccessMode.Default); - return CreateUserDataFromDescriptor(scriptObject, descriptor); - } - } -} - diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaUserDataService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaUserDataService.cs new file mode 100644 index 000000000..51a20c41d --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaUserDataService.cs @@ -0,0 +1,440 @@ +using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Interop; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Barotrauma.LuaCs.Services; + +public interface ILuaUserDataService : IReusableService +{ + IReadOnlyDictionary Descriptors { get; } + IUserDataDescriptor RegisterType(string typeName); + void RegisterExtensionType(string typeName); + bool IsRegistered(string typeName); + void UnregisterType(string typeName, bool deleteHistory = false); + object CreateStatic(string typeName); + bool IsTargetType(object obj, string typeName); + string TypeOf(object obj); + object CreateEnumTable(string typeName); + void MakeFieldAccessible(IUserDataDescriptor IUUD, string fieldName); + void MakeMethodAccessible(IUserDataDescriptor IUUD, string methodName, string[] parameters = null); + void MakePropertyAccessible(IUserDataDescriptor IUUD, string propertyName); + void AddMethod(IUserDataDescriptor IUUD, string methodName, object function); + void AddField(IUserDataDescriptor IUUD, string fieldName, DynValue value); + void RemoveMember(IUserDataDescriptor IUUD, string memberName); + bool HasMember(object obj, string memberName); + /// + /// See . + /// + /// Lua value to convert and wrap in a userdata. + /// Descriptor of the type of the object to convert the Lua value to. Uses MoonSharp ScriptToClr converters. + /// A userdata that wraps the Lua value converted to an object of the desired type as described by . + DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor desiredTypeDescriptor); + + /// + /// Converts a Lua value to a CLR object of a desired type and wraps it in a userdata. + /// If the type is not registered, then a new will be created and used. + /// The goal of this method is to allow Lua scripts to create userdata to wrap certain data without having to register types. + /// Wrapping the value in a userdata preserves the original type during script-to-CLR conversions. + /// A Lua script needs to pass a List`1 to a CLR method expecting System.Object, MoonSharp gets + /// in the way by converting the List`1 to a MoonSharp.Interpreter.Table and breaking everything. + /// Registering the List`1 type can break other scripts relying on default converters, so instead + /// it is better to manually wrap the List`1 object into a userdata. + /// + /// + /// Lua value to convert and wrap in a userdata. + /// Type describing the CLR type of the object to convert the Lua value to. + /// A userdata that wraps the Lua value converted to an object of the desired type. + DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType); + + void AddCallMetaTable(object userdata); +} + +public class LuaUserDataService : ILuaUserDataService +{ + public bool IsDisposed { get; private set; } + + public IReadOnlyDictionary Descriptors => descriptors; + private ConcurrentDictionary descriptors; + + private readonly IPluginManagementService _pluginManagementService; + + public LuaUserDataService(IPluginManagementService pluginManagementService) + { + descriptors = new ConcurrentDictionary(); + _pluginManagementService = pluginManagementService; + } + + public IUserDataDescriptor this[string key] + { + get + { + return descriptors.GetValueOrDefault(key); + } + } + + private Type GetType(string typeName) => _pluginManagementService.GetType(typeName, includeInterfaces: true); + + public IUserDataDescriptor RegisterType(string typeName) + { + Type type = GetType(typeName); + + if (type == null) + { + throw new ScriptRuntimeException($"tried to register a type that doesn't exist: {typeName}."); + } + + return UserData.RegisterType(type, new CallableUserDataDescriptor(type)); + } + + public void RegisterExtensionType(string typeName) + { + Type type = GetType(typeName); + + if (type == null) + { + throw new ScriptRuntimeException($"tried to register a type that doesn't exist: {typeName}."); + } + + UserData.RegisterExtensionType(type); + } + + public bool IsRegistered(string typeName) + { + Type type = GetType(typeName); + + if (type == null) + { + return false; + } + + return UserData.GetDescriptorForType(type, true) != null; + } + + public void UnregisterType(string typeName, bool deleteHistory = false) + { + Type type = GetType(typeName); + + if (type == null) + { + throw new ScriptRuntimeException($"tried to unregister a type that doesn't exist: {typeName}."); + } + + UserData.UnregisterType(type, deleteHistory); + } + + public bool IsTargetType(object obj, string typeName) + { + if (obj == null) { throw new ScriptRuntimeException("userdata is nil"); } + Type targetType = GetType(typeName); + if (targetType == null) { throw new ScriptRuntimeException("target type not found"); } + + Type type = obj is Type ? (Type)obj : obj.GetType(); + return targetType.IsAssignableFrom(type); + } + + public string TypeOf(object obj) + { + if (obj == null) { throw new ScriptRuntimeException("userdata is nil"); } + + return obj.GetType().FullName; + } + + public object CreateEnumTable(string typeName) + { + Type type = GetType(typeName); + + if (type == null) + { + throw new ScriptRuntimeException($"tried to create an enum table with a type that doesn't exist:: {typeName}."); + } + + Dictionary result = new Dictionary(); + + foreach (var value in Enum.GetValues(type)) + { + string name = Enum.GetName(type, value); + + result[name] = value; + } + + return result; + } + + public object CreateStatic(string typeName) + { + Type type = GetType(typeName); + + if (type == null) + { + throw new ScriptRuntimeException($"tried to create a static userdata of a type that doesn't exist: {typeName}."); + } + + MethodInfo method = typeof(UserData).GetMethod(nameof(UserData.CreateStatic), 1, new Type[0]); + MethodInfo generic = method.MakeGenericMethod(type); + return generic.Invoke(null, null); + } + + private FieldInfo FindFieldRecursively(Type type, string fieldName) + { + var field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + + if (field == null && type.BaseType != null) + { + return FindFieldRecursively(type.BaseType, fieldName); + } + + return field; + } + + public void MakeFieldAccessible(IUserDataDescriptor IUUD, string fieldName) + { + if (IUUD == null) + { + throw new ScriptRuntimeException($"tried to use a UserDataDescriptor that is null to make {fieldName} accessible."); + } + + var descriptor = (StandardUserDataDescriptor)IUUD; + FieldInfo field = FindFieldRecursively(IUUD.Type, fieldName); + + if (field == null) + { + throw new ScriptRuntimeException($"tried to make field '{fieldName}' accessible, but the field doesn't exist."); + } + + descriptor.RemoveMember(fieldName); + descriptor.AddMember(fieldName, new FieldMemberDescriptor(field, InteropAccessMode.Default)); + } + + private MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null) + { + MethodInfo method; + + if (types == null) + { + method = type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + } + else + { + method = type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static, types); + } + + if (method == null && type.BaseType != null) + { + return FindMethodRecursively(type.BaseType, methodName, types); + } + + return method; + } + + public void MakeMethodAccessible(IUserDataDescriptor IUUD, string methodName, string[] parameters = null) + { + if (IUUD == null) + { + throw new ScriptRuntimeException($"tried to use a UserDataDescriptor that is null to make {methodName} accessible."); + } + + Type[] parameterTypes = null; + + + if (parameters != null) + { + parameterTypes = new Type[parameters.Length]; + + for (int i = 0; i < parameters.Length; i++) + { + Type type = GetType(parameters[i]); + if (type == null) + { + throw new ScriptRuntimeException($"invalid parameter type '{parameters[i]}'"); + } + parameterTypes[i] = type; + } + } + + var descriptor = (StandardUserDataDescriptor)IUUD; + + MethodBase method; + + try + { + method = FindMethodRecursively(IUUD.Type, methodName, parameterTypes); + } + catch (AmbiguousMatchException ex) + { + throw new ScriptRuntimeException("ambiguous method signature."); + } + + if (method == null) + { + throw new ScriptRuntimeException($"tried to make method '{methodName}' accessible, but the method doesn't exist."); + } + + descriptor.AddMember(methodName, new MethodMemberDescriptor(method, InteropAccessMode.Default)); + } + + private PropertyInfo FindPropertyRecursively(Type type, string propertyName) + { + var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + + if (property == null && type.BaseType != null) + { + return FindPropertyRecursively(type.BaseType, propertyName); + } + + return property; + } + + public void MakePropertyAccessible(IUserDataDescriptor IUUD, string propertyName) + { + if (IUUD == null) + { + throw new ScriptRuntimeException($"tried to use a UserDataDescriptor that is null to make {propertyName} accessible."); + } + + var descriptor = (StandardUserDataDescriptor)IUUD; + PropertyInfo property = FindPropertyRecursively(IUUD.Type, propertyName); + + if (property == null) + { + throw new ScriptRuntimeException($"tried to make property '{propertyName}' accessible, but the property doesn't exist."); + } + + descriptor.RemoveMember(propertyName); + descriptor.AddMember(propertyName, new PropertyMemberDescriptor(property, InteropAccessMode.Default, property.GetGetMethod(true), property.GetSetMethod(true))); + } + + public void AddMethod(IUserDataDescriptor IUUD, string methodName, object function) + { + if (IUUD == null) + { + throw new ScriptRuntimeException($"tried to use a UserDataDescriptor that is null to add method {methodName}."); + } + + var descriptor = (StandardUserDataDescriptor)IUUD; + + descriptor.RemoveMember(methodName); + descriptor.AddMember(methodName, new ObjectCallbackMemberDescriptor(methodName, (object arg1, ScriptExecutionContext arg2, CallbackArguments arg3) => + { + /*if (GameMain.LuaCs != null) + return GameMain.LuaCs.CallLuaFunction(function, arg3.GetArray()); + return null;*/ + throw new NotImplementedException(); + })); + } + + public void AddField(IUserDataDescriptor IUUD, string fieldName, DynValue value) + { + if (IUUD == null) + { + throw new ScriptRuntimeException($"tried to use a UserDataDescriptor that is null to add field {fieldName}."); + } + + var descriptor = (StandardUserDataDescriptor)IUUD; + descriptor.RemoveMember(fieldName); + descriptor.AddMember(fieldName, new DynValueMemberDescriptor(fieldName, value)); + } + + public void RemoveMember(IUserDataDescriptor IUUD, string memberName) + { + if (IUUD == null) + { + throw new ScriptRuntimeException($"tried to use a UserDataDescriptor that is null to remove the member {memberName}."); + } + + var descriptor = (StandardUserDataDescriptor)IUUD; + descriptor.RemoveMember(memberName); + } + + public bool HasMember(object obj, string memberName) + { + if (obj == null) { throw new ScriptRuntimeException("object is nil"); } + + Type type; + if (obj is Type) + { + type = (Type)obj; + } + else if (obj is IUserDataDescriptor descriptor) + { + type = descriptor.Type; + + if (((StandardUserDataDescriptor)descriptor).HasMember(memberName)) + { + return true; + } + } + else + { + type = obj.GetType(); + } + + if (type.GetMember(memberName).Length == 0) + { + return false; + } + + return true; + } + + + public DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor desiredTypeDescriptor) + { + return UserData.Create(scriptObject.ToObject(desiredTypeDescriptor.Type), desiredTypeDescriptor); + } + + public DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType) + { + IUserDataDescriptor descriptor = UserData.GetDescriptorForType(desiredType, true); + descriptor ??= new StandardUserDataDescriptor(desiredType, InteropAccessMode.Default); + return CreateUserDataFromDescriptor(scriptObject, descriptor); + } + + public void AddCallMetaTable(object userdata) { } + + public void Dispose() + { + IsDisposed = true; + descriptors.Clear(); + } + + public FluentResults.Result Reset() + { + descriptors.Clear(); + return FluentResults.Result.Ok(); + } +} + +sealed class CallableUserDataDescriptor : StandardUserDataDescriptor +{ + public CallableUserDataDescriptor(Type type) + : base(type, InteropAccessMode.Default) + { + } + + public override DynValue MetaIndex(Script script, object obj, string metaname) + { + if (metaname == "__call") + { + return DynValue.NewCallback((ctx, args) => + { + var self = args[0]; + + var ctor = base.Index(script, obj, DynValue.NewString("__new"), true); + + if (ctor == null || ctor.IsNil()) + { + throw new ScriptRuntimeException("Attempted to call userdata without __new."); + } + + var callArgs = args.GetArray().Skip(1).ToArray(); + return script.Call(ctor, callArgs); + }); + } + + return base.MetaIndex(script, obj, metaname); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeLuaUserDataService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeLuaUserDataService.cs new file mode 100644 index 000000000..9f2dc0130 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeLuaUserDataService.cs @@ -0,0 +1,234 @@ +using Barotrauma; +using Barotrauma.LuaCs.Services; +using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Interop; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Barotrauma.LuaCs.Services; + +public interface ISafeLuaUserDataService : IService +{ + bool IsAllowed(string typeName); + IUserDataDescriptor RegisterType(string typeName); + void RegisterExtensionType(string typeName); + bool IsRegistered(string typeName); + void UnregisterType(string typeName, bool deleteHistory = false); + object CreateStatic(string typeName); + bool IsTargetType(object obj, string typeName); + string TypeOf(object obj); + object CreateEnumTable(string typeName); + void MakeFieldAccessible(IUserDataDescriptor IUUD, string fieldName); + void MakeMethodAccessible(IUserDataDescriptor IUUD, string methodName, string[] parameters = null); + void MakePropertyAccessible(IUserDataDescriptor IUUD, string propertyName); + void AddMethod(IUserDataDescriptor IUUD, string methodName, object function); + void AddField(IUserDataDescriptor IUUD, string fieldName, DynValue value); + void RemoveMember(IUserDataDescriptor IUUD, string memberName); + bool HasMember(object obj, string memberName); + /// + /// See . + /// + /// Lua value to convert and wrap in a userdata. + /// Descriptor of the type of the object to convert the Lua value to. Uses MoonSharp ScriptToClr converters. + /// A userdata that wraps the Lua value converted to an object of the desired type as described by . + DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor desiredTypeDescriptor); + + /// + /// Converts a Lua value to a CLR object of a desired type and wraps it in a userdata. + /// If the type is not registered, then a new will be created and used. + /// The goal of this method is to allow Lua scripts to create userdata to wrap certain data without having to register types. + /// Wrapping the value in a userdata preserves the original type during script-to-CLR conversions. + /// A Lua script needs to pass a List`1 to a CLR method expecting System.Object, MoonSharp gets + /// in the way by converting the List`1 to a MoonSharp.Interpreter.Table and breaking everything. + /// Registering the List`1 type can break other scripts relying on default converters, so instead + /// it is better to manually wrap the List`1 object into a userdata. + /// + /// + /// Lua value to convert and wrap in a userdata. + /// Type describing the CLR type of the object to convert the Lua value to. + /// A userdata that wraps the Lua value converted to an object of the desired type. + DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType); + void AddCallMetaTable(object userdata); +} + +public class SafeLuaUserDataService : ISafeLuaUserDataService +{ + private readonly ILuaUserDataService _userDataService; + + public bool IsDisposed { get; private set; } + + public SafeLuaUserDataService(ILuaUserDataService userDataService) + { + _userDataService = userDataService; + } + + public IUserDataDescriptor this[string key] + { + get + { + return _userDataService.Descriptors.GetValueOrDefault(key); + } + } + + private bool CanBeRegistered(string typeName) + { + if (typeName.StartsWith("Barotrauma.Lua", StringComparison.Ordinal) || + typeName.StartsWith("Barotrauma.Cs", StringComparison.Ordinal) || + typeName.StartsWith("Barotrauma.LuaCs", StringComparison.Ordinal)) + { + return false; + } + + if (typeName == "System.Single") { return true; } + + if (typeName.StartsWith("System.Collections", StringComparison.Ordinal)) + return true; + + if (typeName.StartsWith("Microsoft.Xna", StringComparison.Ordinal)) + return true; + + if (typeName.StartsWith("Barotrauma.IO", StringComparison.Ordinal)) + return false; + + if (typeName.StartsWith("Barotrauma.ToolBox", StringComparison.Ordinal)) + return false; + + if (typeName.StartsWith("Barotrauma.SaveUtil", StringComparison.Ordinal)) + return false; + + if (typeName.StartsWith("Barotrauma.", StringComparison.Ordinal)) + return true; + + return false; + } + + private bool CanBeReRegistered(string typeName) + { + if (typeName.StartsWith("Barotrauma.Lua", StringComparison.Ordinal) || + typeName.StartsWith("Barotrauma.Cs", StringComparison.Ordinal) || + typeName.StartsWith("Barotrauma.LuaCs", StringComparison.Ordinal)) + { + return false; + } + + return true; + } + + public bool IsAllowed(string typeName) + { + if (!CanBeReRegistered(typeName) && IsRegistered(typeName)) + { + return false; + } + + if (!CanBeRegistered(typeName)) + { + return false; + } + + return true; + } + + private void CheckAllowed(string typeName) + { + if (!IsAllowed(typeName)) + { + throw new ScriptRuntimeException($"Type {typeName} can't be registered"); + } + } + + public IUserDataDescriptor RegisterType(string typeName) + { + CheckAllowed(typeName); + return _userDataService.RegisterType(typeName); + } + + public void RegisterExtensionType(string typeName) + { + CheckAllowed(typeName); + _userDataService.RegisterExtensionType(typeName); + } + + public bool IsRegistered(string typeName) + { + return _userDataService.IsRegistered(typeName); + } + + public void UnregisterType(string typeName, bool deleteHistory = false) + { + IsAllowed(typeName); + _userDataService.UnregisterType(typeName, deleteHistory); + } + public object CreateStatic(string typeName) + { + return _userDataService.CreateStatic(typeName); + } + + public bool IsTargetType(object obj, string typeName) + { + return _userDataService.IsTargetType(obj, typeName); + } + + public string TypeOf(object obj) + { + return _userDataService.TypeOf(obj); + } + + public object CreateEnumTable(string typeName) + { + return _userDataService.CreateEnumTable(typeName); + } + + public void MakeFieldAccessible(IUserDataDescriptor IUUD, string fieldName) + { + _userDataService.MakeFieldAccessible(IUUD, fieldName); + } + + public void MakeMethodAccessible(IUserDataDescriptor IUUD, string methodName, string[] parameters = null) + { + _userDataService.MakeMethodAccessible(IUUD, methodName, parameters); + } + + public void MakePropertyAccessible(IUserDataDescriptor IUUD, string propertyName) + { + _userDataService.MakePropertyAccessible(IUUD, propertyName); + } + + public void AddMethod(IUserDataDescriptor IUUD, string methodName, object function) + { + _userDataService.AddMethod(IUUD, methodName, function); + } + + public void AddField(IUserDataDescriptor IUUD, string fieldName, DynValue value) + { + _userDataService.AddField(IUUD, fieldName, value); + } + + public void RemoveMember(IUserDataDescriptor IUUD, string memberName) + { + _userDataService.RemoveMember(IUUD, memberName); + } + + public bool HasMember(object obj, string memberName) + { + return _userDataService.HasMember(obj, memberName); + } + + public DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor desiredTypeDescriptor) + { + return _userDataService.CreateUserDataFromDescriptor(scriptObject, desiredTypeDescriptor); + } + + public DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType) + { + return _userDataService.CreateUserDataFromType(scriptObject, desiredType); + } + + public void AddCallMetaTable(object userdata) { } + + public void Dispose() + { + IsDisposed = true; + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs index f53cee1ed..d05a8e046 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs @@ -58,10 +58,4 @@ public interface ILuaScriptManagementService : IReusableService #endregion - #region Type_Registration - - IUserDataDescriptor RegisterType(Type type); - void UnregisterType(Type type); - - #endregion } From cb171d350d2bfcd4f924ea90351a60d383cb0b23 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 1 Feb 2026 06:29:49 -0500 Subject: [PATCH 090/288] Alpha PluginManagementService, plugin loading functionality implemented. --- .../SharedSource/LuaCs/IEvents.cs | 5 + .../Services/PackageManagementService.cs | 11 +- .../LuaCs/Services/PluginManagementService.cs | 254 +++++++++++++++++- .../_Interfaces/IPluginManagementService.cs | 11 +- .../LuaCs/_Plugins/AssemblyLoader.cs | 2 +- 5 files changed, 260 insertions(+), 23 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index d3c6ac779..4f98edf13 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -234,4 +234,9 @@ public interface IEventAssemblyContextUnloading : IEvent loaderService); } +public interface IEventAssemblyUnloading : IEvent +{ + void OnAssemblyUnloading(Assembly assembly); +} + #endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index 09cdb0a32..608256034 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -246,6 +246,8 @@ public sealed class PackageManagementService : IPackageManagementService if (!plugins.IsDefaultOrEmpty) { result.WithReasons(_pluginManagementService.LoadAssemblyResources(plugins).Reasons); + result.WithReasons(_pluginManagementService.ActivatePluginInstances( + plugins.Select(p => p.OwnerPackage).ToImmutableArray(), false).Reasons); } } @@ -271,10 +273,11 @@ public sealed class PackageManagementService : IPackageManagementService .Where(r => r.SupportedTargets.HasFlag(ModUtils.Environment.CurrentTarget)) .Where(r => !r.Optional || ( (r.RequiredPackages.IsDefaultOrEmpty || enabledPackagesIdents.Intersect(r.RequiredPackages).Any()) - && (r.IncompatiblePackages.IsDefaultOrEmpty || enabledPackagesIdents.Intersect(r.IncompatiblePackages).None())) - ).OrderBy(r => loadingOrder.IndexOf(r.OwnerPackage)) - .ThenBy(r => r.LoadPriority) - .ToImmutableArray(); + && (r.IncompatiblePackages.IsDefaultOrEmpty || enabledPackagesIdents.Intersect(r.IncompatiblePackages).None()))) + .OrderBy(r => r.Optional ? 1 : 0) // optional content last + .ThenBy(r => loadingOrder.IndexOf(r.OwnerPackage)) + .ThenBy(r => r.LoadPriority) + .ToImmutableArray(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 8293805c6..393d15705 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -16,6 +16,7 @@ using Barotrauma.LuaCs.Events; using FluentResults; using FluentResults.LuaCs; using ImpromptuInterface.Build; +using LightInject; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; @@ -102,20 +103,49 @@ public class PluginManagementService : IAssemblyManagementService private IAssemblyLoaderService.IFactory _assemblyLoaderFactory; private IStorageService _storageService; private ILoggerService _logger; + private IEventService _eventService; + private IConfigService _configService; + private ILuaScriptManagementService _luaScriptManagementService; private readonly ConcurrentDictionary _assemblyLoaders = new(); + private readonly ConcurrentDictionary> _pluginInstances = new(); + private readonly ConditionalWeakTable _unloadingAssemblyLoaders = new(); private readonly AsyncReaderWriterLock _operationsLock = new(); + private ServiceContainer _pluginInjectorContainer; public PluginManagementService( IServicesProvider serviceProvider, IAssemblyLoaderService.IFactory assemblyLoaderFactory, IStorageService storageService, - ILoggerService logger) + ILoggerService logger, + IEventService eventService, + ILuaScriptManagementService luaScriptManagementService, + IConfigService configService) { Guard.IsNotNull(serviceProvider, nameof(serviceProvider)); _serviceProvider = serviceProvider; _assemblyLoaderFactory = assemblyLoaderFactory; _storageService = storageService; _logger = logger; + _eventService = eventService; + _luaScriptManagementService = luaScriptManagementService; + _configService = configService; + } + + private ServiceContainer CreatePluginServiceContainer() + { + var container = new ServiceContainer(new ContainerOptions() + { + EnablePropertyInjection = true, + + }); + + container.Register(fac => _logger, new PerContainerLifetime()); + container.Register(fac => _storageService, new PerContainerLifetime()); + container.Register(fac => _eventService, new PerContainerLifetime()); + container.Register(fac => _luaScriptManagementService, new PerContainerLifetime()); + container.Register(fac => _configService, new PerContainerLifetime()); + + return container; } public Result> GetImplementingTypes(bool includeInterfaces = false, bool includeAbstractTypes = false, @@ -160,12 +190,118 @@ public class PluginManagementService : IAssemblyManagementService return null; } - public ImmutableArray> ActivateTypeInstances(ImmutableArray types, bool serviceInjection = true, - bool hostInstanceReference = false) where T : IDisposable + public FluentResults.Result ActivatePluginInstances(ImmutableArray executionOrder, bool excludeAlreadyRunningPackages = true) { - throw new NotImplementedException(); + if (executionOrder.IsDefaultOrEmpty) + { + ThrowHelper.ThrowArgumentNullException($"{nameof(ActivatePluginInstances)}: The ececution list provided is empty."); + } + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + if (_assemblyLoaders.IsEmpty) + { + return FluentResults.Result.Ok(); + } + + var results = new FluentResults.Result(); + + var toLoad = _assemblyLoaders + .Where(al => executionOrder.Contains(al.Key)) + .Where(al => !excludeAlreadyRunningPackages || !_pluginInstances.ContainsKey(al.Key)) + .SelectMany(al => al.Value.Assemblies.Select(ass => (al.Key, ass))) + .SelectMany(kvp => kvp.ass.GetSafeTypes() + .Where(type => + type is { IsInterface: false, IsAbstract: false, IsGenericType: false } + && type.IsAssignableTo(typeof(IAssemblyPlugin))) + .Select(type => (kvp.Key, type))) + .GroupBy(kvp => kvp.Key, kvp => kvp.type) + .OrderBy(exeGrp => executionOrder.IndexOf(exeGrp.Key)) + .ToImmutableArray(); + + if (toLoad.Length == 0) + { + return FluentResults.Result.Ok(); + } + + var loadedPackagePlugins = + ImmutableArray.CreateBuilder<(ContentPackage Package, ImmutableArray Plugins)>(); + _pluginInjectorContainer ??= CreatePluginServiceContainer(); + + foreach (var packageTypes in toLoad) + { + var loadedTypes = ImmutableArray.CreateBuilder(); + foreach (var pluginType in packageTypes) + { + try + { + var plugin = (IAssemblyPlugin)Activator.CreateInstance(pluginType); + _pluginInjectorContainer.InjectProperties(plugin); + _pluginInjectorContainer.Register(pluginType, fac => plugin); + loadedTypes.Add(plugin); + } + catch (Exception e) + { + results.WithError(new ExceptionalError(e)); + continue; + } + } + loadedPackagePlugins.Add((packageTypes.Key, loadedTypes.ToImmutable())); + } + + var packPluginGroups = loadedPackagePlugins.ToImmutable(); + foreach (var packagePluginGrp in packPluginGroups) + { + if (_pluginInstances.TryGetValue(packagePluginGrp.Package, out var plugins)) + { + _pluginInstances[packagePluginGrp.Package] = plugins.Concat(packagePluginGrp.Plugins).ToImmutableArray(); + continue; + } + + _pluginInstances[packagePluginGrp.Package] = packagePluginGrp.Plugins; + } + + var pluginsToInit = packPluginGroups.SelectMany(ppg => ppg.Plugins).ToImmutableArray(); + + foreach (var plugin in pluginsToInit) + { + results.WithReasons(PluginInitRunner(plugin, p => p.PreInitPatching()).Reasons); + } + + _eventService.PublishEvent(sub => sub.PreInitPatching()); + + foreach (var plugin in pluginsToInit) + { + results.WithReasons(PluginInitRunner(plugin, p => p.Initialize()).Reasons); + } + + _eventService.PublishEvent(sub => sub.Initialize()); + + foreach (var plugin in pluginsToInit) + { + results.WithReasons(PluginInitRunner(plugin, p => p.OnLoadCompleted()).Reasons); + } + + _eventService.PublishEvent(sub => sub.OnLoadCompleted()); + + return results; + + // helper + FluentResults.Result PluginInitRunner(IAssemblyPlugin plugin, Action action) + { + try + { + action(plugin); + return FluentResults.Result.Ok(); + } + catch (Exception e) + { + return FluentResults.Result.Fail(new ExceptionalError(e)); + } + } } - + + public FluentResults.Result LoadAssemblyResources(ImmutableArray resources) { if (resources.IsDefaultOrEmpty) @@ -185,11 +321,15 @@ public class PluginManagementService : IAssemblyManagementService { LoadBinaries(contentPack); LoadAndCompileScriptAssemblies(contentPack); + foreach (var ass in _assemblyLoaders[contentPack.Key].Assemblies) + { + ReflectionUtils.AddNonAbstractAssemblyTypes(ass); + } } return result; - // helper methods + // --- helper methods void LoadBinaries(IGrouping contentPackRes) { var binaries = contentPackRes.Where(cRes => !cRes.IsScript) @@ -336,6 +476,9 @@ public class PluginManagementService : IAssemblyManagementService IEnumerable GetMetadataReferences() { +#if !DEBUG + throw new NotImplementedException($"Needs to use publicized barotrauma assemblies."); +#endif return Basic.Reference.Assemblies.Net80.References.All .Union(AppDomain.CurrentDomain.GetAssemblies() .Where(ass => !ass.Location.IsNullOrWhiteSpace()) @@ -348,14 +491,44 @@ public class PluginManagementService : IAssemblyManagementService throw new NotImplementedException(); } - private Assembly OnAssemblyLoaderResolvingManaged(IAssemblyLoaderService arg1, AssemblyName arg2) + private Assembly OnAssemblyLoaderResolvingManaged(IAssemblyLoaderService requestingLoader, AssemblyName searchName) { - throw new NotImplementedException(); + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + foreach (var loader in _assemblyLoaders.Where(kvp => kvp.Value != requestingLoader) + .Select(kvp => kvp.Value).ToImmutableArray()) + { + if (loader.IsReferenceOnlyMode || !loader.Assemblies.Any()) + { + continue; + } + + foreach (var assembly in loader.Assemblies) + { + if (assembly.GetName().Equals(searchName)) + { + return assembly; + } + } + } + + return null; } private void OnAssemblyLoaderUnloading(IAssemblyLoaderService loader) { - throw new NotImplementedException(); + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + + if (!loader.Assemblies.Any()) + { + return; + } + + foreach (var assembly in loader.Assemblies) + { + _eventService.PublishEvent(sub => sub.OnAssemblyUnloading(assembly)); + } } public FluentResults.Result UnloadManagedAssemblies() @@ -367,13 +540,72 @@ public class PluginManagementService : IAssemblyManagementService { return FluentResults.Result.Ok(); } + + var results = new FluentResults.Result(); + + results.WithReasons(UnsafeDisposeManagedTypeInstances().Reasons); + + ReflectionUtils.ResetCache(); + bool[] targetGcGeneration = new bool[GC.MaxGeneration]; + + for (int i = 0; i < targetGcGeneration.Length; i++) + { + targetGcGeneration[i] = false; + } + foreach (var loaderService in _assemblyLoaders) { - + try + { + loaderService.Value.Dispose(); + targetGcGeneration[GC.GetGeneration(loaderService.Value)] = true; + _unloadingAssemblyLoaders.Add(loaderService.Value, loaderService.Key); + } + catch (Exception e) + { + results.WithError(new ExceptionalError(e)); + } } - throw new NotImplementedException(); + _assemblyLoaders.Clear(); + + for (int i = 0; i < targetGcGeneration.Length; i++) + { + if (!targetGcGeneration[i]) + { + GC.Collect(i, GCCollectionMode.Aggressive, true); + } + } + + return results; + } + + private FluentResults.Result UnsafeDisposeManagedTypeInstances() + { + var results = new FluentResults.Result(); + _pluginInjectorContainer = null; + if (_pluginInstances.IsEmpty) + { + return FluentResults.Result.Ok(); + } + + foreach (var instance in _pluginInstances.SelectMany(kvp => kvp.Value)) + { + try + { + instance.Dispose(); + } + catch (Exception e) + { + results.WithError(new ExceptionalError(e)); + continue; + } + } + + _pluginInstances.Clear(); + + return results; } public Result GetLoadedAssembly(OneOf assemblyName, in Guid[] excludedContexts) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs index 83cd59981..711e69580 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs @@ -33,15 +33,12 @@ public interface IPluginManagementService : IReusableService Type GetType(string typeName, bool isByRefType = false, bool includeInterfaces = false, bool includeDefaultContext = true); /// - /// Creates instances of the given type and provides Property Injection and instance reference caching. Disposes of - /// all references that throw errors on + /// /// - /// List of Types - /// - /// + /// + /// /// - ImmutableArray> ActivateTypeInstances(ImmutableArray types, bool serviceInjection = true, - bool hostInstanceReference = false) where T : IDisposable; + FluentResults.Result ActivatePluginInstances(ImmutableArray executionOrder, bool excludeAlreadyRunningPackages = true); /// /// Loads the provided assembly resources in the order of their dependencies and intra-mod priority load order. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs index 7a82c8494..87f920d70 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs @@ -551,6 +551,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService IsDisposed = true; this.Unload(); this.DisposeInternal(); + GC.SuppressFinalize(this); } ~AssemblyLoader() @@ -582,7 +583,6 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService base.Unloading -= OnUnload; this._dependencyResolvers.Clear(); this._loadedAssemblyData.Clear(); - GC.SuppressFinalize(this); } protected override Assembly Load(AssemblyName assemblyName) From bb8869268ed36835895b13f43849716172f3bc0e Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Sun, 1 Feb 2026 18:48:40 -0500 Subject: [PATCH 091/288] - Refactored the EventService interfaces and event system, incomplete. --- .../SharedSource/LuaCs/IEvents.cs | 6 ++-- .../Services/Compatibility/ILuaCsHook.cs | 12 ++++---- .../LuaCs/Services/Safe/ILuaEventService.cs | 28 +++++++++++++++---- .../Services/_Interfaces/IEventService.cs | 5 ---- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index 4f98edf13..c65f4051b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -24,9 +24,7 @@ public interface IEvent : IEvent where T : IEvent { static virtual T GetLuaRunner(IDictionary luaFunc) { - // throw error if not overriden since we don't have 'static abstract'. - // Implementers must provide the runner. - throw new NotImplementedException(); + throw new InvalidOperationException($"Lua runners forbidden for {typeof(T).Name}"); } } @@ -179,7 +177,7 @@ public interface IEventPluginInitialize : IEvent static IEventPluginInitialize IEvent.GetLuaRunner(IDictionary luaFunc) => new { IsLuaRunner = Return.Arguments(() => true), - OnInitialize = ReturnVoid.Arguments(() => luaFunc[nameof(Initialize)]()) + Initialize = ReturnVoid.Arguments(() => luaFunc[nameof(Initialize)]()) }.ActLike(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs index e5a1c7b09..5697a51df 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs @@ -4,14 +4,16 @@ namespace Barotrauma.LuaCs.Services.Compatibility; public interface ILuaCsHook : ILuaCsShim { + // Event Services [Obsolete("ACsMod is deprecated. Use ILuaEventService.Add() instead.")] - void Add(string eventName, string identifier, LuaCsFunc callback, ACsMod mod = null); + void Add(string eventName, string identifier, LuaCsFunc callback); [Obsolete("ACsMod is deprecated. Use ILuaEventService.Add() instead.")] - void Add(string eventName, LuaCsFunc callback, ACsMod mod = null); - bool Exists(string eventName, string identifier); - [Obsolete("Only Lua subscribers will receive events from call. Use ILuaEventService.Add() instead.")] - T Call(string eventName, params object[] args); + void Add(string eventName, LuaCsFunc callback); + // 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); + + // Hook/Method Patching string Patch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, EventService.HookMethodType hookType = EventService.HookMethodType.Before); string Patch(string identifier, string className, string methodName, LuaCsPatchFunc patch, EventService.HookMethodType hookType = EventService.HookMethodType.Before); string Patch(string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, EventService.HookMethodType hookType = EventService.HookMethodType.Before); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs index 9a2f0d02b..e5dd9178d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs @@ -7,7 +7,13 @@ namespace Barotrauma.LuaCs.Services.Safe; public interface ILuaSafeEventService : ILuaService, ILuaCsHook { - void Subscribe(string interfaceName, string identifier, IDictionary callbacks); + /// + /// Subscribes lua scripts via for the given interface. + /// + /// + /// + /// A 'method name'=='signature action' dictionary matching the interface method list. + void Subscribe(string identifier, IDictionary callbacks); /// /// Removes a subscriber from an event that subscribed under the given identifier. /// @@ -17,14 +23,24 @@ public interface ILuaSafeEventService : ILuaService, ILuaCsHook /// /// Send an event to all subscribers to an interface. /// - /// Name of the interface (must be registered with Lua). - /// Execution runner, the subscriber is provided as the first argument in the lua runner. + /// Interface type. + /// Execution runner, the subscriber is provided as the first argument in the lua runner. /// - void PublishLuaEvent(string interfaceName, LuaCsFunc runner); + void PublishLuaEvent(LuaCsFunc subscriberRunner); + + /// + /// Defines the target method name for legacy to target on new + /// interfaces. + /// + /// The legacy event name. + /// . + /// The event interface type. + /// Operation success. + /// The is null or empty. + public FluentResults.Result RegisterLuaEventAlias(string luaEventName, string targetMethod) where T : IEvent; } public interface ILuaEventService : ILuaSafeEventService { - public FluentResults.Result RegisterSafeEvent() where T : IEvent; - public FluentResults.Result UnregisterSafeEvent() where T : IEvent; + } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IEventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IEventService.cs index 08d612944..1dc2117d1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IEventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IEventService.cs @@ -8,11 +8,6 @@ namespace Barotrauma.LuaCs.Services; public interface IEventService : IReusableService, ILuaEventService { - FluentResults.Result SetLegacyLuaRunnerFactory(Func runnerFactory) where T : IEvent; - void RemoveLegacyLuaRunnerFactory() where T : IEvent; - void SetAliasToEvent(string alias) where T : IEvent; - void RemoveEventAlias(string alias); - void RemoveAllEventAliases() where T : IEvent; /// /// /// From 7e541cef3de5690e536af59f9b37843c600bf729 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Sun, 1 Feb 2026 18:48:40 -0500 Subject: [PATCH 092/288] - Removed ACsMod.cs --- .../SharedSource/LuaCs/_Plugins/ACsMod.cs | 64 ------------------- 1 file changed, 64 deletions(-) delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs deleted file mode 100644 index 76dfac73f..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace Barotrauma -{ - [Obsolete("Make your class implement IAssemblyPlugin instead.")] - public abstract class ACsMod : IAssemblyPlugin - { - private static List mods = new List(); - public static List LoadedMods { get => mods; } - - private const string MOD_STORE = "LocalMods/.modstore"; - public static string GetStoreFolder() where T : ACsMod - { - if (!Directory.Exists(MOD_STORE)) Directory.CreateDirectory(MOD_STORE); - var modFolder = $"{MOD_STORE}/{typeof(T)}"; - if (!Directory.Exists(modFolder)) Directory.CreateDirectory(modFolder); - return modFolder; - } - - public bool IsDisposed { get; private set; } - - /// Mod initialization - public ACsMod() - { - IsDisposed = false; - LoadedMods.Add(this); - } - - /// - /// Called as soon as plugin loading begins, use this for internal setup only. - /// - public virtual void Initialize() { } - - /// - /// Called once all plugins have completed Initialization. Put cross-mod code here. - /// - public virtual void OnLoadCompleted() { } - - /// - /// [NotImplemented] Called before vanilla content is loaded. Use to patch Barotrauma classes before they're - /// instantiated. - /// - public void PreInitPatching() { } - - public virtual void Dispose() - { - try - { - Stop(); - } - catch (Exception e) - { - LuaCsLogger.HandleException(e, LuaCsMessageOrigin.CSharpMod); - } - - LoadedMods.Remove(this); - IsDisposed = true; - } - - public abstract void Stop(); - } -} From b325a01eeaf93ad8c09d8783b683146223762d6f Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Mon, 2 Feb 2026 02:38:10 -0500 Subject: [PATCH 093/288] Rewrote the EventService. --- .../LuaCs/Services/EventService.cs | 479 +++++++----------- .../LuaCs/Services/Safe/ILuaEventService.cs | 4 +- .../Services/_Interfaces/IEventService.cs | 4 +- 3 files changed, 195 insertions(+), 292 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs index 38743666d..42a025c2c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs @@ -8,11 +8,12 @@ using Barotrauma.LuaCs.Events; using Barotrauma.LuaCs.Services.Compatibility; using FluentResults; using FluentResults.LuaCs; +using Microsoft.Toolkit.Diagnostics; using OneOf; namespace Barotrauma.LuaCs.Services; -public partial class EventService : IEventService, IEventAssemblyContextUnloading +public partial class EventService : IEventService { private readonly record struct TypeStringKey : IEqualityComparer, IEquatable { @@ -49,338 +50,240 @@ public partial class EventService : IEventService, IEventAssemblyContextUnloadin public static implicit operator TypeStringKey(Type type) => new(type); public static implicit operator TypeStringKey(string typeName) => new(typeName); } - /// - /// Contains subscriber delegates by event and identifier. - /// Structure:
- /// - Key: Type or String, TypeName == String Equality.
- /// - Value: Dictionary
- /// ---- Key: Either string identifier or subscriber instance pointer
- /// ---- Value: Subscriber delegate
- ///
- private readonly ConcurrentDictionary, IEvent>> _subscriptions = new(); - private readonly ConcurrentDictionary _eventTypeNameAliases = new(); - private readonly Lazy _pluginManagementService; - private readonly ConcurrentDictionary>> _luaSubscriptionFactories = new(); - /// - /// A collection of factories to produce subscribers from a single lua function handle. For legacy Add() API. - /// - private readonly Dictionary> _luaLegacySubscriptionFactories = new(); - /// - /// A collection of lua event subscribers from Add() that had neither a valid event name nor an event alias pointing to one. - /// Only actionable via Call(). - /// - private readonly Dictionary> _luaOrphanSubscribers = new(); - public EventService(Lazy pluginManagementService) - { - _pluginManagementService = pluginManagementService ?? throw new ArgumentNullException(nameof(pluginManagementService)); - this.Subscribe(this); + private readonly AsyncReaderWriterLock _operationsLock = new(); + private readonly ConcurrentDictionary, IEvent>> _subscribers = new(); + private readonly ConcurrentDictionary RunnerFactory)> _luaAliasEventFactory = new(); + private readonly ConcurrentDictionary> _luaLegacyEventsSubscribers = new(); - InitPatcher(); - } + #region Disposal - public bool IsDisposed { get; private set; } = false; - - #region Compatibility - - [Obsolete("ACsMod is deprecated. Use ILuaEventService.Add() instead.")] - void ILuaCsHook.Add(string eventName, string identifier, LuaCsFunc callback, ACsMod mod = null) + public void Dispose() { - Add(eventName, identifier, callback); - } - [Obsolete("ACsMod is deprecated. Use ILuaEventService.Add() instead.")] - void ILuaCsHook.Add(string eventName, LuaCsFunc callback, ACsMod mod = null) - { - Add(eventName, callback); - } - - public bool Exists(string eventName, string identifier) - { - ((IService)this).CheckDisposed(); - if (_subscriptions.ContainsKey(eventName) && _subscriptions[eventName].ContainsKey(identifier)) - return true; - if (_luaOrphanSubscribers.ContainsKey(eventName)) - return true; - return false; - } - - [Obsolete("Part of the legacy events API, only works for Lua-only custom events.")] - public T Call(string eventName, params object[] args) - { - ((IService)this).CheckDisposed(); - if (!_luaOrphanSubscribers.TryGetValue(eventName, out var dict)) - return default; - T returnValue = default; - foreach (var sub in dict.Values) + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) { - try - { - var r = sub(args); - if (r != default) - returnValue = (T)r; - } - catch - { - continue; - } + return; } - return returnValue; + + _luaLegacyEventsSubscribers.Clear(); + _luaAliasEventFactory.Clear(); + _subscribers.Clear(); + } + + private int _isDisposed; + + public bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } + public FluentResults.Result Reset() + { + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + _luaLegacyEventsSubscribers.Clear(); + _luaAliasEventFactory.Clear(); + _subscribers.Clear(); + return FluentResults.Result.Ok(); } - - [Obsolete("Part of the legacy events API, only works for Lua-only custom events.")] - public object Call(string eventName, params object[] args) => Call(eventName, args); #endregion + #region LuaEventSystem + public void Add(string eventName, string identifier, LuaCsFunc callback) { - var eventKey = eventName; - if (_eventTypeNameAliases.TryGetValue(eventName, out var aliasType)) - eventKey = aliasType; - if (_luaLegacySubscriptionFactories.TryGetValue(eventKey, out var factory)) + Guard.IsNotNullOrWhiteSpace(eventName, nameof(eventName)); + Guard.IsNotNullOrWhiteSpace(identifier, nameof(identifier)); + Guard.IsNotNull(callback, nameof(callback)); + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + if (_luaAliasEventFactory.TryGetValue(eventName, out var eventFunc)) { - factory(identifier, callback); - return; + var eventSubs = _subscribers.GetOrAdd(eventFunc.Event, key => new ConcurrentDictionary, IEvent>()); + eventSubs[identifier] = eventFunc.RunnerFactory(callback); + } + else + { + var eventSubs = _luaLegacyEventsSubscribers.GetOrAdd(eventName, key => new ConcurrentDictionary()); + eventSubs[identifier] = callback; } - _luaOrphanSubscribers.TryGetOrSet(eventName, () => new Dictionary()) - .Add(identifier.IsNullOrWhiteSpace() ? string.Empty : identifier, callback); } public void Add(string eventName, LuaCsFunc callback) { - Add(eventName, string.Empty, callback); - } - - public void Remove(string eventName, string identifier) - { - if (_luaOrphanSubscribers.TryGetValue(eventName, out var dict)) - dict.Remove(identifier); - if (_subscriptions.TryGetValue(eventName, out var dict2)) - dict2.Remove(identifier); - } - - public void PublishLuaEvent(string interfaceName, LuaCsFunc runner) - { - ((IService)this).CheckDisposed(); - if (interfaceName.IsNullOrWhiteSpace()) - return; - if (!_subscriptions.TryGetValue(interfaceName, out var dict)) - return; - - var type = _subscriptions - .Select(x => x.Key) - .FirstOrNull(x => x.Type?.Name == interfaceName)?.Type; - - var errors = new Queue(); - foreach (var eventSub in dict.Values) - { - try - { - runner(type is null ? eventSub : Convert.ChangeType(eventSub, type)); // cast if possible - } - catch - { - continue; - } - } + // random ident, we hope for no conflicts :barodev:. + Add(eventName, Random.Shared.NextInt64().ToString() ,callback); } - public FluentResults.Result RegisterSafeEvent() where T : IEvent + public object Call(string eventName, params object[] args) { - ((IService)this).CheckDisposed(); - var type = typeof(T); - if (_luaSubscriptionFactories.ContainsKey(type)) - return FluentResults.Result.Ok().WithReason(new Success($"The event {type.Name} is already registered.")); - try + Guard.IsNotNullOrWhiteSpace(eventName, nameof(eventName)); + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + if (!_luaLegacyEventsSubscribers.TryGetValue(eventName, out var eventSubscribers) + || eventSubscribers.IsEmpty) { - _luaSubscriptionFactories.TryAdd(type, (ident, funcDict) => - { - var runner = T.GetLuaRunner(funcDict); - var dict = _subscriptions.TryGetOrSet(type, () => new Dictionary, IEvent>()); - if (!ident.IsNullOrWhiteSpace()) - dict[ident] = runner; - else - dict[runner] = runner; - }); - return FluentResults.Result.Ok(); - } - catch (NullReferenceException e) - { - return FluentResults.Result.Fail(new Error($"The lua runner for {type.Name} is not registered.") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, type)); - } - } - - public FluentResults.Result UnregisterSafeEvent() where T : IEvent - { - ((IService)this).CheckDisposed(); - _luaSubscriptionFactories.TryRemove(typeof(T), out _); - if (!_subscriptions.TryGetValue(typeof(T), out var dict)) - return FluentResults.Result.Ok(); - dict.Values.Where(value => value.IsLuaRunner()).ToImmutableArray().ForEach(Unsubscribe); - return FluentResults.Result.Ok(); - } - - // lua subscribe - public void Subscribe(string interfaceName, string identifier, IDictionary callbacks) - { - ((IService)this).CheckDisposed(); - if (_luaSubscriptionFactories.TryGetValue(interfaceName, out var subFactory)) - subFactory(identifier, callbacks); - } - - public FluentResults.Result SetLegacyLuaRunnerFactory(Func runnerFactory) where T : IEvent - { - var type = typeof(T); - if (!_luaSubscriptionFactories.TryGetValue(type, out var dict)) - return FluentResults.Result.Fail(new Error($"Tried to add legacy lua factory for an event not registered for lua subscriptions.")); - - _luaLegacySubscriptionFactories[type] = (ident, func) => - { - var runner = runnerFactory(func); - _subscriptions.TryGetOrSet(type, () => new Dictionary, IEvent>())[ident] = runner; - }; - return FluentResults.Result.Ok(); - } - - public void RemoveLegacyLuaRunnerFactory() where T : IEvent - { - _luaLegacySubscriptionFactories.Remove(typeof(T)); - } - - public void SetAliasToEvent(string alias) where T : IEvent - { - if (alias.IsNullOrWhiteSpace()) - return; - _eventTypeNameAliases[alias] = typeof(T).Name; - } - - public void RemoveEventAlias(string alias) - { - _eventTypeNameAliases.TryRemove(alias, out _); - } - - public void RemoveAllEventAliases() where T : IEvent - { - foreach (var keys in _eventTypeNameAliases - .Where(kvp => kvp.Value.IsNullOrWhiteSpace() || kvp.Value == typeof(T).Name) - .Select(kvp => kvp.Key).ToImmutableArray()) - { - _eventTypeNameAliases.TryRemove(keys, out _); - } - } - - public FluentResults.Result Subscribe(T subscriber) where T : IEvent - { - ((IService)this).CheckDisposed(); - var eventType = typeof(T); - var dict = _subscriptions.TryGetOrSet(eventType, () => new Dictionary, IEvent>()); - if (dict.ContainsKey(OneOf.FromT1(subscriber))) - { - return FluentResults.Result.Fail( - new Error($"The subscriber for {eventType.Name} is already registered to the event.") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, subscriber)); - } - dict[subscriber] = subscriber; - return FluentResults.Result.Ok(); - } - - public void Unsubscribe(T subscriber) where T : IEvent - { - ((IService)this).CheckDisposed(); - if (!_subscriptions.TryGetValue(typeof(T), out var dict)) - return; - dict.Remove(OneOf.FromT1(subscriber)); - } - - public void ClearAllEventSubscribers() where T : IEvent - { - _subscriptions.TryRemove(typeof(T), out _); - if (typeof(IEventAssemblyContextUnloading) == typeof(T)) - { - this.Subscribe(this); - } - } - public void ClearAllSubscribers() - { - _subscriptions.Clear(); - this.Subscribe(this); - } - - public FluentResults.Result PublishEvent(Action action) where T : IEvent - { - ((IService)this).CheckDisposed(); - var eventType = typeof(T); - if (!_subscriptions.TryGetValue(eventType, out var dict)) - { - return FluentResults.Result.Fail(new Error($"The event {eventType.Name} is not registered.") - .WithMetadata(MetadataType.ExceptionObject, this)); + return null; } - var errors = new Queue(); - foreach (var eventSub in dict.Values) + object returnValue = null; + + foreach (var subscriber in eventSubscribers) { try { - action((T)eventSub); + returnValue = subscriber.Value.Invoke(args); } catch (Exception e) { #if DEBUG - throw; //make errors apparent + throw; #endif - errors.Enqueue(new Error($"Error while executing runner for {eventType.Name} on type {eventSub.GetType().Name}.") - .WithMetadata(MetadataType.ExceptionObject, this) - .WithMetadata(MetadataType.RootObject, eventSub) - .WithMetadata(MetadataType.ExceptionDetails, e.Message) - .WithMetadata(MetadataType.StackTrace, e.StackTrace)); } } - - var result = errors.Count > 0 ? FluentResults.Result.Fail($"Errors while executing event type {eventType.Name}") : FluentResults.Result.Ok(); - while (errors.Count > 0) - result = result.WithError(errors.Dequeue()); - return result; + + return returnValue; } - public void Dispose() - { - Reset(); - IsDisposed = true; - GC.SuppressFinalize(this); - } - - public FluentResults.Result Reset() + public void Subscribe(string identifier, IDictionary callbacks) where T : IEvent { + Guard.IsNotNullOrWhiteSpace(identifier, nameof(identifier)); + Guard.IsNotNull(callbacks, nameof(callbacks)); + Guard.IsNotEmpty(callbacks, nameof(callbacks)); + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); - _subscriptions.Clear(); - _luaSubscriptionFactories.Clear(); - _eventTypeNameAliases.Clear(); - _luaLegacySubscriptionFactories.Clear(); - _luaOrphanSubscribers.Clear(); - ResetPatcher(); - InitPatcher(); + + var eventSubs = _subscribers.GetOrAdd(typeof(T), key => new ConcurrentDictionary, IEvent>()); + eventSubs[identifier] = T.GetLuaRunner(callbacks); + } + + 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(); + + if (!_subscribers.TryGetValue(eventName, out var evtSubscribers)) + { + return; + } + + evtSubscribers.TryRemove(identifier, out _); + } + + public void PublishLuaEvent(LuaCsFunc subscriberRunner) where T : IEvent + { + this.PublishEvent(sub => subscriberRunner(sub)); + } + + public FluentResults.Result RegisterLuaEventAlias(string luaEventName, string targetMethod) where T : IEvent + { + Guard.IsNotNullOrWhiteSpace(luaEventName, nameof(luaEventName)); + Guard.IsNotNullOrWhiteSpace(targetMethod, nameof(targetMethod)); + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + if (_luaAliasEventFactory.ContainsKey(luaEventName)) + { +#if DEBUG + ThrowHelper.ThrowInvalidOperationException($"{nameof(RegisterLuaEventAlias)}: An alias already exists for the event of {luaEventName}."); +#endif + return FluentResults.Result.Fail($"{nameof(RegisterLuaEventAlias)}: An alias already exists for the event of {luaEventName}."); + } + + var eventRunnerFactory = (LuaCsFunc function) => (IEvent)T.GetLuaRunner(new Dictionary + { + { targetMethod, function } + }); + + _luaAliasEventFactory[luaEventName] = (Event: typeof(T), RunnerFactory: eventRunnerFactory); + // create the group + _subscribers.GetOrAdd(typeof(T), key => new ConcurrentDictionary, IEvent>()); return FluentResults.Result.Ok(); } - public void OnAssemblyUnloading(WeakReference loaderService) + #endregion + + public FluentResults.Result Subscribe(T subscriber) where T : class, IEvent { - if (!loaderService.TryGetTarget(out var loader)) - return; - foreach (var assembly in loader.Assemblies) + Guard.IsNotNull(subscriber, nameof(subscriber)); + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + var eventSubs = + _subscribers.GetOrAdd(typeof(T), (type) => new ConcurrentDictionary, IEvent>()); + + if (eventSubs.ContainsKey(subscriber)) { - var types = assembly.GetSafeTypes() - .Where(t => typeof(IEvent).IsAssignableFrom(t)) - .ToImmutableArray(); - if (!types.Any()) - continue; - foreach (var type in types) + ThrowHelper.ThrowInvalidOperationException($"{nameof(Subscribe)}: The instance is already registered!"); + } + + return eventSubs.TryAdd(subscriber, subscriber) + ? FluentResults.Result.Ok() + : FluentResults.Result.Fail($"{nameof(Subscribe)}: Failed to add subscriber."); + } + + public void Unsubscribe(T subscriber) where T : class, IEvent + { + Guard.IsNotNull(subscriber, nameof(subscriber)); + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + if (!_subscribers.TryGetValue(typeof(T), out var evtSubscribers)) + { + return; + } + + evtSubscribers.TryRemove(subscriber, out _); + } + + public void ClearAllEventSubscribers() where T : IEvent + { + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + _subscribers.TryRemove(typeof(T), out _); + } + + public void ClearAllSubscribers() + { + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + _subscribers.Clear(); + } + + public FluentResults.Result PublishEvent(Action action) where T : IEvent + { + Guard.IsNotNull(action, nameof(action)); + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + if (!_subscribers.TryGetValue(typeof(T), out var subs) || subs.IsEmpty) + { + return FluentResults.Result.Ok(); + } + + var results = new FluentResults.Result(); + + foreach (var sub in subs) + { + try { - _subscriptions.TryRemove(type, out _); - _luaSubscriptionFactories.TryRemove(type, out _); + action.Invoke((T)sub.Value); + } + catch (Exception e) + { + results.WithError(new ExceptionalError(e)); +#if DEBUG + throw; +#endif + continue; } } + + return results; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs index e5dd9178d..50d470965 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs @@ -13,7 +13,7 @@ public interface ILuaSafeEventService : ILuaService, ILuaCsHook /// /// /// A 'method name'=='signature action' dictionary matching the interface method list. - void Subscribe(string identifier, IDictionary callbacks); + void Subscribe(string identifier, IDictionary callbacks) where T : IEvent; /// /// Removes a subscriber from an event that subscribed under the given identifier. /// @@ -26,7 +26,7 @@ public interface ILuaSafeEventService : ILuaService, ILuaCsHook /// Interface type. /// Execution runner, the subscriber is provided as the first argument in the lua runner. /// - void PublishLuaEvent(LuaCsFunc subscriberRunner); + void PublishLuaEvent(LuaCsFunc subscriberRunner) where T : IEvent; /// /// Defines the target method name for legacy to target on new diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IEventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IEventService.cs index 1dc2117d1..a5ef5ce67 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IEventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IEventService.cs @@ -14,13 +14,13 @@ public interface IEventService : IReusableService, ILuaEventService /// /// /// - FluentResults.Result Subscribe(T subscriber) where T : IEvent; + FluentResults.Result Subscribe(T subscriber) where T : class, IEvent; /// /// /// /// /// - void Unsubscribe(T subscriber) where T : IEvent; + void Unsubscribe(T subscriber) where T : class, IEvent; /// /// Clears all subscribers for a given event type and removes any registration to the type. /// From 0cab7954f8bf32cde7729f3b9552c251329c61ee Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Mon, 2 Feb 2026 02:57:34 -0500 Subject: [PATCH 094/288] Fixed immediate errors (untested). --- .../Services/Compatibility/ILuaCsHook.cs | 1 + .../LuaCs/Services/EventService.cs | 5 +++++ .../Safe/LuaClasses/LuaPatcherCompat.cs | 22 +++++++++---------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs index 5697a51df..9a67bddf3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs @@ -12,6 +12,7 @@ public interface ILuaCsHook : ILuaCsShim // 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); + T Call(string eventName, params object[] args); // Hook/Method Patching string Patch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, EventService.HookMethodType hookType = EventService.HookMethodType.Before); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs index 42a025c2c..e583b1501 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs @@ -149,6 +149,11 @@ public partial class EventService : IEventService return returnValue; } + public T Call(string eventName, params object[] args) + { + return (T)Call(eventName, args); + } + public void Subscribe(string identifier, IDictionary callbacks) where T : IEvent { Guard.IsNotNullOrWhiteSpace(identifier, nameof(identifier)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs index 99b6bb68f..8b59b830e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs @@ -18,8 +18,8 @@ namespace Barotrauma.LuaCs.Services { partial class EventService { - private Dictionary> compatHookPrefixMethods = new Dictionary>(); - private Dictionary> compatHookPostfixMethods = new Dictionary>(); + private Dictionary> compatHookPrefixMethods = new Dictionary>(); + private Dictionary> compatHookPostfixMethods = new Dictionary>(); private static void _hookLuaCsPatch(MethodBase __originalMethod, object[] __args, object __instance, out object result, HookMethodType hookType) { @@ -28,7 +28,7 @@ namespace Barotrauma.LuaCs.Services try { var funcAddr = ((long)__originalMethod.MethodHandle.GetFunctionPointer()); - HashSet<(string, LuaCsCompatPatchFunc, ACsMod)> methodSet = null; + HashSet<(string, LuaCsCompatPatchFunc, IAssemblyPlugin)> methodSet = null; switch (hookType) { case HookMethodType.Before: @@ -50,10 +50,10 @@ namespace Barotrauma.LuaCs.Services args.Add(@params[i].Name, __args[i]); } - var outOfSocpe = new HashSet<(string, LuaCsCompatPatchFunc, ACsMod)>(); + var outOfSocpe = new HashSet<(string, LuaCsCompatPatchFunc, IAssemblyPlugin)>(); foreach (var tuple in methodSet) { - if (tuple.Item3 != null && tuple.Item3.IsDisposed) + if (tuple.Item3 != null) { outOfSocpe.Add(tuple); } @@ -126,7 +126,7 @@ namespace Barotrauma.LuaCs.Services private static MethodInfo _miHookLuaCsPatchRetPostfix = typeof(EventService).GetMethod("HookLuaCsPatchRetPostfix", BindingFlags.NonPublic | BindingFlags.Static); // TODO: deprecate this - public void HookMethod(string identifier, MethodBase method, LuaCsCompatPatchFunc patch, HookMethodType hookType = HookMethodType.Before, ACsMod owner = null) + public void HookMethod(string identifier, MethodBase method, LuaCsCompatPatchFunc patch, HookMethodType hookType = HookMethodType.Before, IAssemblyPlugin owner = null) { if (identifier == null || method == null || patch == null) { @@ -155,7 +155,7 @@ namespace Barotrauma.LuaCs.Services } } - if (compatHookPrefixMethods.TryGetValue(funcAddr, out HashSet<(string, LuaCsCompatPatchFunc, ACsMod)> methodSet)) + if (compatHookPrefixMethods.TryGetValue(funcAddr, out HashSet<(string, LuaCsCompatPatchFunc, IAssemblyPlugin)> methodSet)) { if (identifier != "") { @@ -166,7 +166,7 @@ namespace Barotrauma.LuaCs.Services } else if (patch != null) { - compatHookPrefixMethods.Add(funcAddr, new HashSet<(string, LuaCsCompatPatchFunc, ACsMod)>() { (identifier, patch, owner) }); + compatHookPrefixMethods.Add(funcAddr, new HashSet<(string, LuaCsCompatPatchFunc, IAssemblyPlugin)>() { (identifier, patch, owner) }); } } @@ -187,7 +187,7 @@ namespace Barotrauma.LuaCs.Services } } - if (compatHookPostfixMethods.TryGetValue(funcAddr, out HashSet<(string, LuaCsCompatPatchFunc, ACsMod)> methodSet)) + if (compatHookPostfixMethods.TryGetValue(funcAddr, out HashSet<(string, LuaCsCompatPatchFunc, IAssemblyPlugin)> methodSet)) { if (identifier != "") { @@ -198,7 +198,7 @@ namespace Barotrauma.LuaCs.Services } else if (patch != null) { - compatHookPostfixMethods.Add(funcAddr, new HashSet<(string, LuaCsCompatPatchFunc, ACsMod)>() { (identifier, patch, owner) }); + compatHookPostfixMethods.Add(funcAddr, new HashSet<(string, LuaCsCompatPatchFunc, IAssemblyPlugin)>() { (identifier, patch, owner) }); } } } @@ -224,7 +224,7 @@ namespace Barotrauma.LuaCs.Services { var funcAddr = (long)method.MethodHandle.GetFunctionPointer(); - Dictionary> methods; + Dictionary> methods; if (hookType == HookMethodType.Before) methods = compatHookPrefixMethods; else if (hookType == HookMethodType.After) methods = compatHookPostfixMethods; else throw null; From 024b07d5f485ad8f0a71c64bc0db578b54c002ea Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Mon, 2 Feb 2026 02:59:43 -0500 Subject: [PATCH 095/288] Added logging to the EventService. --- .../SharedSource/LuaCs/Services/EventService.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs index e583b1501..c4c96d7c6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs @@ -51,6 +51,7 @@ public partial class EventService : IEventService public static implicit operator TypeStringKey(string typeName) => new(typeName); } + private ILoggerService _loggerService; private readonly AsyncReaderWriterLock _operationsLock = new(); private readonly ConcurrentDictionary, IEvent>> _subscribers = new(); private readonly ConcurrentDictionary RunnerFactory)> _luaAliasEventFactory = new(); @@ -73,6 +74,11 @@ public partial class EventService : IEventService private int _isDisposed; + public EventService(ILoggerService loggerService) + { + _loggerService = loggerService; + } + public bool IsDisposed { get => ModUtils.Threading.GetBool(ref _isDisposed); @@ -140,6 +146,7 @@ public partial class EventService : IEventService } catch (Exception e) { + _loggerService.LogError(e.Message); #if DEBUG throw; #endif @@ -282,6 +289,7 @@ public partial class EventService : IEventService catch (Exception e) { results.WithError(new ExceptionalError(e)); + _loggerService.LogError(e.Message); #if DEBUG throw; #endif From 36bed09bde50daa4f78fde6e41f4110b6f397ed2 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 2 Feb 2026 16:26:38 -0500 Subject: [PATCH 096/288] - Fixed stack ovewrflow from ServicesProvider (???). --- .../SharedSource/LuaCs/LuaCsSetup.cs | 6 +++- .../Services/PackageManagementService.cs | 4 +-- .../LuaCs/Services/PluginManagementService.cs | 34 ++++++++----------- .../LuaCs/Services/ServicesProvider.cs | 2 +- .../_Interfaces/IPackageManagementService.cs | 2 +- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 8bb0d26ae..18ade84cd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -330,7 +330,11 @@ namespace Barotrauma if (!PackageManagementService.IsAnyPackageRunning()) { - Logger.LogResults(PackageManagementService.ExecuteLoadedPackages(ContentPackageManager.EnabledPackages.All.ToImmutableArray())); +#if DEBUG + Logger.LogResults(PackageManagementService.ExecuteLoadedPackages(ContentPackageManager.EnabledPackages.All.ToImmutableArray(), true)); +#else + Logger.LogResults(PackageManagementService.ExecuteLoadedPackages(ContentPackageManager.EnabledPackages.All.ToImmutableArray(), IsCsEnabled)); +#endif } CurrentRunState = RunState.Running; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs index 608256034..e2878bb58 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs @@ -189,7 +189,7 @@ public sealed class PackageManagementService : IPackageManagementService } } - public FluentResults.Result ExecuteLoadedPackages(ImmutableArray executionOrder) + public FluentResults.Result ExecuteLoadedPackages(ImmutableArray executionOrder, bool executeCsAssemblies) { using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); using var executeLock = _executionLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); @@ -237,7 +237,7 @@ public sealed class PackageManagementService : IPackageManagementService result.WithReasons(_luaScriptManagementService.ExecuteLoadedScripts(luaScripts).Reasons); } - if (_runConfig.IsCsEnabled) + if (executeCsAssemblies) { var plugins = SelectCompatible(loadingOrderedPackages .SelectMany(pkg => pkg.Value.Assemblies) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 393d15705..85b3e35c5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -99,13 +99,12 @@ public class PluginManagementService : IAssemblyManagementService #endregion - private IServicesProvider _serviceProvider; private IAssemblyLoaderService.IFactory _assemblyLoaderFactory; private IStorageService _storageService; private ILoggerService _logger; - private IEventService _eventService; - private IConfigService _configService; - private ILuaScriptManagementService _luaScriptManagementService; + private Lazy _eventService; + private Lazy _configService; + private Lazy _luaScriptManagementService; private readonly ConcurrentDictionary _assemblyLoaders = new(); private readonly ConcurrentDictionary> _pluginInstances = new(); private readonly ConditionalWeakTable _unloadingAssemblyLoaders = new(); @@ -113,16 +112,13 @@ public class PluginManagementService : IAssemblyManagementService private ServiceContainer _pluginInjectorContainer; public PluginManagementService( - IServicesProvider serviceProvider, IAssemblyLoaderService.IFactory assemblyLoaderFactory, IStorageService storageService, ILoggerService logger, - IEventService eventService, - ILuaScriptManagementService luaScriptManagementService, - IConfigService configService) + Lazy eventService, + Lazy luaScriptManagementService, + Lazy configService) { - Guard.IsNotNull(serviceProvider, nameof(serviceProvider)); - _serviceProvider = serviceProvider; _assemblyLoaderFactory = assemblyLoaderFactory; _storageService = storageService; _logger = logger; @@ -139,11 +135,11 @@ public class PluginManagementService : IAssemblyManagementService }); - container.Register(fac => _logger, new PerContainerLifetime()); - container.Register(fac => _storageService, new PerContainerLifetime()); - container.Register(fac => _eventService, new PerContainerLifetime()); - container.Register(fac => _luaScriptManagementService, new PerContainerLifetime()); - container.Register(fac => _configService, new PerContainerLifetime()); + container.Register(fac => _logger); + container.Register(fac => _storageService); + container.Register(fac => _eventService.Value); + container.Register(fac => _luaScriptManagementService.Value); + container.Register(fac => _configService.Value); return container; } @@ -268,21 +264,21 @@ public class PluginManagementService : IAssemblyManagementService results.WithReasons(PluginInitRunner(plugin, p => p.PreInitPatching()).Reasons); } - _eventService.PublishEvent(sub => sub.PreInitPatching()); + _eventService.Value.PublishEvent(sub => sub.PreInitPatching()); foreach (var plugin in pluginsToInit) { results.WithReasons(PluginInitRunner(plugin, p => p.Initialize()).Reasons); } - _eventService.PublishEvent(sub => sub.Initialize()); + _eventService.Value.PublishEvent(sub => sub.Initialize()); foreach (var plugin in pluginsToInit) { results.WithReasons(PluginInitRunner(plugin, p => p.OnLoadCompleted()).Reasons); } - _eventService.PublishEvent(sub => sub.OnLoadCompleted()); + _eventService.Value.PublishEvent(sub => sub.OnLoadCompleted()); return results; @@ -527,7 +523,7 @@ public class PluginManagementService : IAssemblyManagementService foreach (var assembly in loader.Assemblies) { - _eventService.PublishEvent(sub => sub.OnAssemblyUnloading(assembly)); + _eventService.Value.PublishEvent(sub => sub.OnAssemblyUnloading(assembly)); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs index d0a81f425..71fcb54b2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs @@ -22,7 +22,7 @@ public class ServicesProvider : IServicesProvider EnablePropertyInjection = false }); - _serviceContainerInst.Register((f) => this); + //_serviceContainerInst.Register((f) => this); } public void RegisterServiceType(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs index 2c6f2bc39..b36bf93a4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs @@ -14,7 +14,7 @@ public interface IPackageManagementService : IReusableService { public FluentResults.Result LoadPackageInfo(ContentPackage package); public FluentResults.Result LoadPackagesInfo(ImmutableArray packages); - public FluentResults.Result ExecuteLoadedPackages(ImmutableArray executionOrder); + public FluentResults.Result ExecuteLoadedPackages(ImmutableArray executionOrder, bool executeCsAssemblies); public FluentResults.Result SyncLoadedPackagesList(ImmutableArray packages); public FluentResults.Result StopRunningPackages(); public FluentResults.Result UnloadPackage(ContentPackage package); From 7b529bce5716fa45c51428532fc6b6a42ed5bfda Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 2 Feb 2026 16:32:36 -0500 Subject: [PATCH 097/288] Revert "- Removed ACsMod.cs" This reverts commit 54a3e2e5de0cff38ea4fc3750d2eb90f5ea73fe9. --- .../SharedSource/LuaCs/_Plugins/ACsMod.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs new file mode 100644 index 000000000..76dfac73f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Barotrauma +{ + [Obsolete("Make your class implement IAssemblyPlugin instead.")] + public abstract class ACsMod : IAssemblyPlugin + { + private static List mods = new List(); + public static List LoadedMods { get => mods; } + + private const string MOD_STORE = "LocalMods/.modstore"; + public static string GetStoreFolder() where T : ACsMod + { + if (!Directory.Exists(MOD_STORE)) Directory.CreateDirectory(MOD_STORE); + var modFolder = $"{MOD_STORE}/{typeof(T)}"; + if (!Directory.Exists(modFolder)) Directory.CreateDirectory(modFolder); + return modFolder; + } + + public bool IsDisposed { get; private set; } + + /// Mod initialization + public ACsMod() + { + IsDisposed = false; + LoadedMods.Add(this); + } + + /// + /// Called as soon as plugin loading begins, use this for internal setup only. + /// + public virtual void Initialize() { } + + /// + /// Called once all plugins have completed Initialization. Put cross-mod code here. + /// + public virtual void OnLoadCompleted() { } + + /// + /// [NotImplemented] Called before vanilla content is loaded. Use to patch Barotrauma classes before they're + /// instantiated. + /// + public void PreInitPatching() { } + + public virtual void Dispose() + { + try + { + Stop(); + } + catch (Exception e) + { + LuaCsLogger.HandleException(e, LuaCsMessageOrigin.CSharpMod); + } + + LoadedMods.Remove(this); + IsDisposed = true; + } + + public abstract void Stop(); + } +} From 244c0fbec37c60b00d05a22022bc0523fcae6246 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 2 Feb 2026 16:47:46 -0500 Subject: [PATCH 098/288] Made ACsMod even more useless so hopefully people stop using it... --- .../SharedSource/LuaCs/_Plugins/ACsMod.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs index 76dfac73f..d453dfbba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs @@ -8,9 +8,11 @@ namespace Barotrauma public abstract class ACsMod : IAssemblyPlugin { private static List mods = new List(); + [Obsolete("$This does nothing. Stop using it!")] public static List LoadedMods { get => mods; } private const string MOD_STORE = "LocalMods/.modstore"; + [Obsolete("$This does nothing. Stop using it!")] public static string GetStoreFolder() where T : ACsMod { if (!Directory.Exists(MOD_STORE)) Directory.CreateDirectory(MOD_STORE); @@ -19,14 +21,7 @@ namespace Barotrauma return modFolder; } - public bool IsDisposed { get; private set; } - - /// Mod initialization - public ACsMod() - { - IsDisposed = false; - LoadedMods.Add(this); - } + public bool IsDisposed { get; private set; } = false; /// /// Called as soon as plugin loading begins, use this for internal setup only. @@ -54,8 +49,6 @@ namespace Barotrauma { LuaCsLogger.HandleException(e, LuaCsMessageOrigin.CSharpMod); } - - LoadedMods.Remove(this); IsDisposed = true; } From 2eb593f4615e931c2737c5f13dbec5ef059033eb Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 2 Feb 2026 19:50:41 -0500 Subject: [PATCH 099/288] - Debugging LuaCsHook compat issues. --- .../LuaCs/Services/Compatibility/ILuaCsHook.cs | 10 ++++++++++ .../LuaCs/Services/PluginManagementService.cs | 4 ++++ .../LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs | 9 ++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs index 9a67bddf3..343a73856 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs @@ -1,4 +1,6 @@ using System; +using System.Reflection; +using LuaCsCompatPatchFunc = Barotrauma.LuaCsPatch; namespace Barotrauma.LuaCs.Services.Compatibility; @@ -21,4 +23,12 @@ public interface ILuaCsHook : ILuaCsShim string Patch(string className, string methodName, LuaCsPatchFunc patch, EventService.HookMethodType hookType = EventService.HookMethodType.Before); bool RemovePatch(string identifier, string className, string methodName, string[] parameterTypes, EventService.HookMethodType hookType); bool RemovePatch(string identifier, string className, string methodName, EventService.HookMethodType hookType); + + void HookMethod(string identifier, MethodBase method, LuaCsCompatPatchFunc patch, HookMethodType hookType = HookMethodType.Before, IAssemblyPlugin owner = null); + + + public enum HookMethodType + { + Before, After + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 85b3e35c5..75fe54d4d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -70,8 +70,12 @@ public class PluginManagementService : IAssemblyManagementService private static readonly SyntaxTree BaseAssemblyImports = CSharpSyntaxTree.ParseText( new StringBuilder() + .AppendLine("global using LuaCsHook = Barotrauma.LuaCs.Services.Compatibility.ILuaCsHook;") .AppendLine("using System.Reflection;") .AppendLine("using Barotrauma;") + .AppendLine("using Barotrauma.LuaCs;") + .AppendLine("using Barotrauma.LuaCs.Services;") + .AppendLine("using Barotrauma.LuaCs.Services.Compatibility;") .AppendLine("using System.Runtime.CompilerServices;") .AppendLine("[assembly: IgnoresAccessChecksTo(\"BarotraumaCore\")]") #if CLIENT diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs index 8b59b830e..be821e83f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs @@ -1,10 +1,12 @@ -global using LuaCsHook = Barotrauma.LuaCs.Services.EventService; +//global using LuaCsHook = Barotrauma.LuaCs.Services.EventService; +global using LuaCsHook = Barotrauma.LuaCs.Services.Compatibility.ILuaCsHook; using System; using System.Linq; using System.Reflection; using HarmonyLib; using System.Collections.Generic; +using Barotrauma.LuaCs.Services.Compatibility; using MoonSharp.Interpreter; using LuaCsCompatPatchFunc = Barotrauma.LuaCsPatch; @@ -126,6 +128,11 @@ namespace Barotrauma.LuaCs.Services private static MethodInfo _miHookLuaCsPatchRetPostfix = typeof(EventService).GetMethod("HookLuaCsPatchRetPostfix", BindingFlags.NonPublic | BindingFlags.Static); // TODO: deprecate this + + public void HookMethod(string identifier, MethodBase method, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookType = ILuaCsHook.HookMethodType.Before, IAssemblyPlugin owner = null) + { + throw new NotImplementedException(); + } public void HookMethod(string identifier, MethodBase method, LuaCsCompatPatchFunc patch, HookMethodType hookType = HookMethodType.Before, IAssemblyPlugin owner = null) { if (identifier == null || method == null || patch == null) From 06348d3ba5e11283b5c7950ebaecd9986fdfe976 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 2 Feb 2026 20:45:09 -0500 Subject: [PATCH 100/288] It works. Except (HookMethod->Harmony: L189) is throwing NRE. --- .../Services/Compatibility/ILuaCsHook.cs | 12 ++--- .../LuaCs/Services/PluginManagementService.cs | 2 +- .../Services/Processing/ModConfigService.cs | 1 + .../Services/Safe/LuaClasses/LuaPatcher.cs | 47 +++++++++---------- .../Safe/LuaClasses/LuaPatcherCompat.cs | 40 +++++++--------- .../LuaCs/_Plugins/IAssemblyLoaderService.cs | 10 ++++ .../BarotraumaTest/LuaCs/HookPatchHelpers.cs | 23 ++++----- 7 files changed, 69 insertions(+), 66 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs index 343a73856..e596135e7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs @@ -17,12 +17,12 @@ public interface ILuaCsHook : ILuaCsShim T Call(string eventName, params object[] args); // Hook/Method Patching - string Patch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, EventService.HookMethodType hookType = EventService.HookMethodType.Before); - string Patch(string identifier, string className, string methodName, LuaCsPatchFunc patch, EventService.HookMethodType hookType = EventService.HookMethodType.Before); - string Patch(string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, EventService.HookMethodType hookType = EventService.HookMethodType.Before); - string Patch(string className, string methodName, LuaCsPatchFunc patch, EventService.HookMethodType hookType = EventService.HookMethodType.Before); - bool RemovePatch(string identifier, string className, string methodName, string[] parameterTypes, EventService.HookMethodType hookType); - bool RemovePatch(string identifier, string className, string methodName, EventService.HookMethodType hookType); + string Patch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before); + string Patch(string identifier, string className, string methodName, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before); + string Patch(string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before); + string Patch(string className, string methodName, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before); + bool RemovePatch(string identifier, string className, string methodName, string[] parameterTypes, HookMethodType hookType); + bool RemovePatch(string identifier, string className, string methodName, HookMethodType hookType); void HookMethod(string identifier, MethodBase method, LuaCsCompatPatchFunc patch, HookMethodType hookType = HookMethodType.Before, IAssemblyPlugin owner = null); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 75fe54d4d..49c31e4cc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -477,7 +477,7 @@ public class PluginManagementService : IAssemblyManagementService IEnumerable GetMetadataReferences() { #if !DEBUG - throw new NotImplementedException($"Needs to use publicized barotrauma assemblies."); + throw new NotImplementedException($"Needs to use publicized barotrauma assemblies and cache metadata."); #endif return Basic.Reference.Assemblies.Net80.References.All .Union(AppDomain.CurrentDomain.GetAssemblies() diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs index 205d8b98e..5ce1aff99 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs @@ -303,6 +303,7 @@ public sealed class ModConfigService : IModConfigService FriendlyName = IAssemblyLoaderService.InternalsAwareAssemblyName, IncompatiblePackages = ImmutableArray.Empty, RequiredPackages = ImmutableArray.Empty, + UseInternalAccessName = true, IsScript = true }); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcher.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcher.cs index 3f5f47cb0..f45c73e4e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcher.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcher.cs @@ -416,11 +416,6 @@ namespace Barotrauma.LuaCs.Services partial class EventService { - public enum HookMethodType - { - Before, After - } - private class LuaCsHookCallback { public string name; @@ -805,7 +800,7 @@ namespace Barotrauma.LuaCs.Services // If you need to debug this: // - use https://sharplab.io ; it's a very useful for resource for writing IL by hand. // - use il.NewMessage("") or il.WriteLine("") to see where the IL crashes at runtime. - private MethodInfo CreateDynamicHarmonyPatch(string identifier, MethodBase original, HookMethodType hookType) + private MethodInfo CreateDynamicHarmonyPatch(string identifier, MethodBase original, LuaCsHook.HookMethodType hookType) { var parameters = new List { @@ -840,9 +835,9 @@ namespace Barotrauma.LuaCs.Services var luaCsField = typeBuilder.DefineField(FIELD_LUACS, typeof(LuaCsSetup), FieldAttributes.Public | FieldAttributes.Static); - var methodName = hookType == HookMethodType.Before ? "HarmonyPrefix" : "HarmonyPostfix"; + var methodName = hookType == LuaCsHook.HookMethodType.Before ? "HarmonyPrefix" : "HarmonyPostfix"; var il = Emit.BuildMethod( - returnType: hookType == HookMethodType.Before ? typeof(bool) : typeof(void), + returnType: hookType == LuaCsHook.HookMethodType.Before ? typeof(bool) : typeof(void), parameterTypes: parameters.Select(x => x.HarmonyPatchParamType).ToArray(), type: typeBuilder, name: methodName, @@ -911,7 +906,7 @@ namespace Barotrauma.LuaCs.Services il.NewObject(typeof(ParameterTable), typeof(Dictionary)); il.StoreLocal(ptable); - if (hasReturnType && hookType == HookMethodType.After) + if (hasReturnType && hookType == LuaCsHook.HookMethodType.After) { // IL: ptable.OriginalReturnValue = __result; il.LoadLocal(ptable); @@ -924,7 +919,7 @@ namespace Barotrauma.LuaCs.Services var enumerator = il.DeclareLocal>("enumerator"); il.LoadLocal(patches); il.CallVirtual(typeof(PatchedMethod).GetMethod( - name: hookType == HookMethodType.Before + name: hookType == LuaCsHook.HookMethodType.Before ? nameof(PatchedMethod.GetPrefixEnumerator) : nameof(PatchedMethod.GetPostfixEnumerator), bindingAttr: BindingFlags.Public | BindingFlags.Instance)); @@ -1073,7 +1068,7 @@ namespace Barotrauma.LuaCs.Services il.EndExceptionBlock(exceptionBlock); // Only prefixes return a bool - if (hookType == HookMethodType.Before) + if (hookType == LuaCsHook.HookMethodType.Before) { il.LoadLocal(harmonyReturnValue); } @@ -1090,7 +1085,7 @@ namespace Barotrauma.LuaCs.Services return type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); } - private string Patch(string identifier, MethodBase method, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before) + private string Patch(string identifier, MethodBase method, LuaCsPatchFunc patch, LuaCsHook.HookMethodType hookType = LuaCsHook.HookMethodType.Before) { if (method == null) throw new ArgumentNullException(nameof(method)); if (patch == null) throw new ArgumentNullException(nameof(patch)); @@ -1102,13 +1097,13 @@ namespace Barotrauma.LuaCs.Services var patchKey = MethodKey.Create(method); if (!registeredPatches.TryGetValue(patchKey, out var methodPatches)) { - var harmonyPrefix = CreateDynamicHarmonyPatch(identifier, method, HookMethodType.Before); - var harmonyPostfix = CreateDynamicHarmonyPatch(identifier, method, HookMethodType.After); + var harmonyPrefix = CreateDynamicHarmonyPatch(identifier, method, LuaCsHook.HookMethodType.Before); + var harmonyPostfix = CreateDynamicHarmonyPatch(identifier, method, LuaCsHook.HookMethodType.After); harmony.Patch(method, prefix: new HarmonyMethod(harmonyPrefix), postfix: new HarmonyMethod(harmonyPostfix)); methodPatches = registeredPatches[patchKey] = new PatchedMethod(harmonyPrefix, harmonyPostfix); } - if (hookType == HookMethodType.Before) + if (hookType == LuaCsHook.HookMethodType.Before) { if (methodPatches.Prefixes.Remove(identifier)) { @@ -1121,7 +1116,7 @@ namespace Barotrauma.LuaCs.Services PatchFunc = patch, }); } - else if (hookType == HookMethodType.After) + else if (hookType == LuaCsHook.HookMethodType.After) { if (methodPatches.Postfixes.Remove(identifier)) { @@ -1138,31 +1133,31 @@ namespace Barotrauma.LuaCs.Services return identifier; } - public string Patch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before) + public string Patch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, LuaCsHook.HookMethodType hookType = LuaCsHook.HookMethodType.Before) { var method = ResolveMethod(className, methodName, parameterTypes); return Patch(identifier, method, patch, hookType); } - public string Patch(string identifier, string className, string methodName, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before) + public string Patch(string identifier, string className, string methodName, LuaCsPatchFunc patch, LuaCsHook.HookMethodType hookType = LuaCsHook.HookMethodType.Before) { var method = ResolveMethod(className, methodName, null); return Patch(identifier, method, patch, hookType); } - public string Patch(string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before) + public string Patch(string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, LuaCsHook.HookMethodType hookType = LuaCsHook.HookMethodType.Before) { var method = ResolveMethod(className, methodName, parameterTypes); return Patch(null, method, patch, hookType); } - public string Patch(string className, string methodName, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before) + public string Patch(string className, string methodName, LuaCsPatchFunc patch, LuaCsHook.HookMethodType hookType = LuaCsHook.HookMethodType.Before) { var method = ResolveMethod(className, methodName, null); return Patch(null, method, patch, hookType); } - private bool RemovePatch(string identifier, MethodBase method, HookMethodType hookType) + private bool RemovePatch(string identifier, MethodBase method, LuaCsHook.HookMethodType hookType) { if (identifier == null) throw new ArgumentNullException(nameof(identifier)); identifier = NormalizeIdentifier(identifier); @@ -1175,19 +1170,19 @@ namespace Barotrauma.LuaCs.Services return hookType switch { - HookMethodType.Before => methodPatches.Prefixes.Remove(identifier), - HookMethodType.After => methodPatches.Postfixes.Remove(identifier), - _ => throw new ArgumentException($"Invalid {nameof(HookMethodType)} enum value.", nameof(hookType)), + LuaCsHook.HookMethodType.Before => methodPatches.Prefixes.Remove(identifier), + LuaCsHook.HookMethodType.After => methodPatches.Postfixes.Remove(identifier), + _ => throw new ArgumentException($"Invalid {nameof(LuaCsHook.HookMethodType)} enum value.", nameof(hookType)), }; } - public bool RemovePatch(string identifier, string className, string methodName, string[] parameterTypes, HookMethodType hookType) + public bool RemovePatch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsHook.HookMethodType hookType) { var method = ResolveMethod(className, methodName, parameterTypes); return RemovePatch(identifier, method, hookType); } - public bool RemovePatch(string identifier, string className, string methodName, HookMethodType hookType) + public bool RemovePatch(string identifier, string className, string methodName, LuaCsHook.HookMethodType hookType) { var method = ResolveMethod(className, methodName, null); return RemovePatch(identifier, method, hookType); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs index be821e83f..8b0d93306 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs @@ -23,7 +23,7 @@ namespace Barotrauma.LuaCs.Services private Dictionary> compatHookPrefixMethods = new Dictionary>(); private Dictionary> compatHookPostfixMethods = new Dictionary>(); - private static void _hookLuaCsPatch(MethodBase __originalMethod, object[] __args, object __instance, out object result, HookMethodType hookType) + private static void _hookLuaCsPatch(MethodBase __originalMethod, object[] __args, object __instance, out object result, ILuaCsHook.HookMethodType hookType) { result = null; @@ -33,14 +33,14 @@ namespace Barotrauma.LuaCs.Services HashSet<(string, LuaCsCompatPatchFunc, IAssemblyPlugin)> methodSet = null; switch (hookType) { - case HookMethodType.Before: + case ILuaCsHook.HookMethodType.Before: instance.compatHookPrefixMethods.TryGetValue(funcAddr, out methodSet); break; - case HookMethodType.After: + case ILuaCsHook.HookMethodType.After: instance.compatHookPostfixMethods.TryGetValue(funcAddr, out methodSet); break; default: - throw new ArgumentException($"Invalid {nameof(HookMethodType)} enum value.", nameof(hookType)); + throw new ArgumentException($"Invalid {nameof(ILuaCsHook.HookMethodType)} enum value.", nameof(hookType)); } if (methodSet != null) @@ -98,16 +98,16 @@ namespace Barotrauma.LuaCs.Services private static bool HookLuaCsPatchPrefix(MethodBase __originalMethod, object[] __args, object __instance) { - _hookLuaCsPatch(__originalMethod, __args, __instance, out object result, HookMethodType.Before); + _hookLuaCsPatch(__originalMethod, __args, __instance, out object result, ILuaCsHook.HookMethodType.Before); return result == null; } private static void HookLuaCsPatchPostfix(MethodBase __originalMethod, object[] __args, object __instance) => - _hookLuaCsPatch(__originalMethod, __args, __instance, out object _, HookMethodType.After); + _hookLuaCsPatch(__originalMethod, __args, __instance, out object _, ILuaCsHook.HookMethodType.After); private static bool HookLuaCsPatchRetPrefix(MethodBase __originalMethod, object[] __args, ref object __result, object __instance) { - _hookLuaCsPatch(__originalMethod, __args, __instance, out object result, HookMethodType.Before); + _hookLuaCsPatch(__originalMethod, __args, __instance, out object result, ILuaCsHook.HookMethodType.Before); if (result != null) { __result = result; @@ -118,7 +118,7 @@ namespace Barotrauma.LuaCs.Services private static void HookLuaCsPatchRetPostfix(MethodBase __originalMethod, object[] __args, ref object __result, object __instance) { - _hookLuaCsPatch(__originalMethod, __args, __instance, out object result, HookMethodType.After); + _hookLuaCsPatch(__originalMethod, __args, __instance, out object result, ILuaCsHook.HookMethodType.After); if (result != null) __result = result; } @@ -130,10 +130,6 @@ namespace Barotrauma.LuaCs.Services // TODO: deprecate this public void HookMethod(string identifier, MethodBase method, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookType = ILuaCsHook.HookMethodType.Before, IAssemblyPlugin owner = null) - { - throw new NotImplementedException(); - } - public void HookMethod(string identifier, MethodBase method, LuaCsCompatPatchFunc patch, HookMethodType hookType = HookMethodType.Before, IAssemblyPlugin owner = null) { if (identifier == null || method == null || patch == null) { @@ -145,7 +141,7 @@ namespace Barotrauma.LuaCs.Services var funcAddr = ((long)method.MethodHandle.GetFunctionPointer()); var patches = Harmony.GetPatchInfo(method); - if (hookType == HookMethodType.Before) + if (hookType == ILuaCsHook.HookMethodType.Before) { if (method is MethodInfo mi && mi.ReturnType != typeof(void)) { @@ -177,7 +173,7 @@ namespace Barotrauma.LuaCs.Services } } - else if (hookType == HookMethodType.After) + else if (hookType == ILuaCsHook.HookMethodType.After) { if (method is MethodInfo mi && mi.ReturnType != typeof(void)) { @@ -209,7 +205,7 @@ namespace Barotrauma.LuaCs.Services } } } - protected void HookMethod(string identifier, string className, string methodName, string[] parameterNames, LuaCsCompatPatchFunc patch, HookMethodType hookMethodType = HookMethodType.Before) + protected void HookMethod(string identifier, string className, string methodName, string[] parameterNames, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before) { var method = ResolveMethod(className, methodName, parameterNames); if (method == null) return; @@ -219,26 +215,26 @@ namespace Barotrauma.LuaCs.Services } HookMethod(identifier, method, patch, hookMethodType); } - protected void HookMethod(string identifier, string className, string methodName, LuaCsCompatPatchFunc patch, HookMethodType hookMethodType = HookMethodType.Before) => + protected void HookMethod(string identifier, string className, string methodName, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before) => HookMethod(identifier, className, methodName, null, patch, hookMethodType); - protected void HookMethod(string className, string methodName, LuaCsCompatPatchFunc patch, HookMethodType hookMethodType = HookMethodType.Before) => + protected void HookMethod(string className, string methodName, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before) => HookMethod("", className, methodName, null, patch, hookMethodType); - protected void HookMethod(string className, string methodName, string[] parameterNames, LuaCsCompatPatchFunc patch, HookMethodType hookMethodType = HookMethodType.Before) => + protected void HookMethod(string className, string methodName, string[] parameterNames, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before) => HookMethod("", className, methodName, parameterNames, patch, hookMethodType); - public void UnhookMethod(string identifier, MethodBase method, HookMethodType hookType = HookMethodType.Before) + public void UnhookMethod(string identifier, MethodBase method, ILuaCsHook.HookMethodType hookType = ILuaCsHook.HookMethodType.Before) { var funcAddr = (long)method.MethodHandle.GetFunctionPointer(); Dictionary> methods; - if (hookType == HookMethodType.Before) methods = compatHookPrefixMethods; - else if (hookType == HookMethodType.After) methods = compatHookPostfixMethods; + if (hookType == ILuaCsHook.HookMethodType.Before) methods = compatHookPrefixMethods; + else if (hookType == ILuaCsHook.HookMethodType.After) methods = compatHookPostfixMethods; else throw null; if (methods.ContainsKey(funcAddr)) methods[funcAddr]?.RemoveWhere(t => t.Item1 == identifier); } - protected void UnhookMethod(string identifier, string className, string methodName, string[] parameterNames, HookMethodType hookType = HookMethodType.Before) + protected void UnhookMethod(string identifier, string className, string methodName, string[] parameterNames, ILuaCsHook.HookMethodType hookType = ILuaCsHook.HookMethodType.Before) { var method = ResolveMethod(className, methodName, parameterNames); if (method == null) return; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs index c3dd6983a..9c113ed4b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs @@ -11,6 +11,16 @@ using Microsoft.CodeAnalysis.CSharp; namespace Barotrauma.LuaCs; +[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] +public sealed class IgnoresAccessChecksToAttribute : Attribute +{ + public string AssemblyName { get; } + public IgnoresAccessChecksToAttribute(string assemblyName) + { + AssemblyName = assemblyName; + } +} + public interface IAssemblyLoaderService : IService { public interface IFactory : IService diff --git a/Barotrauma/BarotraumaTest/LuaCs/HookPatchHelpers.cs b/Barotrauma/BarotraumaTest/LuaCs/HookPatchHelpers.cs index 8aaeb45c0..213744534 100644 --- a/Barotrauma/BarotraumaTest/LuaCs/HookPatchHelpers.cs +++ b/Barotrauma/BarotraumaTest/LuaCs/HookPatchHelpers.cs @@ -1,5 +1,5 @@ extern alias Client; - +extern alias Server; using Client::Barotrauma.LuaCs.Services; using Client::Barotrauma; using MoonSharp.Interpreter; @@ -8,6 +8,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Text; using System.Threading; +using Server::Barotrauma.LuaCs.Services.Compatibility; using Xunit; namespace TestProject.LuaCs @@ -64,14 +65,14 @@ namespace TestProject.LuaCs string methodName, string[]? parameters, string function, - EventService.HookMethodType patchType) + ILuaCsHook.HookMethodType patchType) { var args = BuildHookPatchArgsList(patchId, className, methodName, parameters); args.Add(function); args.Add(patchType switch { - EventService.HookMethodType.Before => "Hook.HookMethodType.Before", - EventService.HookMethodType.After => "Hook.HookMethodType.After", + ILuaCsHook.HookMethodType.Before => "Hook.HookMethodType.Before", + ILuaCsHook.HookMethodType.After => "Hook.HookMethodType.After", _ => throw new NotImplementedException(), }); throw new NotImplementedException(); @@ -84,13 +85,13 @@ namespace TestProject.LuaCs string className, string methodName, string[]? parameters, - EventService.HookMethodType patchType) + ILuaCsHook.HookMethodType patchType) { var args = BuildHookPatchArgsList(patchId, className, methodName, parameters); args.Add(patchType switch { - EventService.HookMethodType.Before => "Hook.HookMethodType.Before", - EventService.HookMethodType.After => "Hook.HookMethodType.After", + ILuaCsHook.HookMethodType.Before => "Hook.HookMethodType.Before", + ILuaCsHook.HookMethodType.After => "Hook.HookMethodType.After", _ => throw new NotImplementedException(), }); throw new NotImplementedException(); @@ -104,7 +105,7 @@ namespace TestProject.LuaCs function(instance, ptable) {body} end - ", EventService.HookMethodType.Before); + ", ILuaCsHook.HookMethodType.Before); Assert.Equal(DataType.String, returnValue.Type); return new(returnValue.String, () => luaCs.RemovePrefix(returnValue.String, methodName, parameters)); } @@ -116,7 +117,7 @@ namespace TestProject.LuaCs function(instance, ptable) {body} end - ", EventService.HookMethodType.After); + ", ILuaCsHook.HookMethodType.After); Assert.Equal(DataType.String, returnValue.Type); return new(returnValue.String, () => luaCs.RemovePostfix(returnValue.String, methodName, parameters)); } @@ -124,7 +125,7 @@ namespace TestProject.LuaCs public static bool RemovePrefix(this LuaCsSetup luaCs, string patchId, string methodName, string[]? parameters = null) { var className = typeof(T).FullName!; - var returnValue = luaCs.DoHookRemovePatch(patchId, className, methodName, parameters, EventService.HookMethodType.Before); + var returnValue = luaCs.DoHookRemovePatch(patchId, className, methodName, parameters, ILuaCsHook.HookMethodType.Before); Assert.Equal(DataType.Boolean, returnValue.Type); return returnValue.Boolean; } @@ -132,7 +133,7 @@ namespace TestProject.LuaCs public static bool RemovePostfix(this LuaCsSetup luaCs, string patchId, string methodName, string[]? parameters = null) { var className = typeof(T).FullName!; - var returnValue = luaCs.DoHookRemovePatch(patchId, className, methodName, parameters, EventService.HookMethodType.After); + var returnValue = luaCs.DoHookRemovePatch(patchId, className, methodName, parameters, ILuaCsHook.HookMethodType.After); Assert.Equal(DataType.Boolean, returnValue.Type); return returnValue.Boolean; } From ea602f6d2f1949c60a19d90f3d70f2a109527e25 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 2 Feb 2026 20:54:48 -0500 Subject: [PATCH 101/288] Woof --- .../LuaCs/Services/PluginManagementService.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 49c31e4cc..11814a326 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -151,7 +151,24 @@ public class PluginManagementService : IAssemblyManagementService public Result> GetImplementingTypes(bool includeInterfaces = false, bool includeAbstractTypes = false, bool includeDefaultContext = true) { +#if !DEBUG throw new NotImplementedException(); +#endif + var builder = ImmutableArray.CreateBuilder(); + + foreach (var ass in AppDomain.CurrentDomain.GetAssemblies()) + { + foreach (var type in ass.GetSafeTypes()) + { + if ((includeInterfaces || !type.IsInterface) + && (includeAbstractTypes || !type.IsAbstract)) + { + builder.Add(type); + } + } + } + + return builder.ToImmutable(); } public Type GetType(string typeName, bool isByRefType = false, bool includeInterfaces = false, @@ -488,6 +505,7 @@ public class PluginManagementService : IAssemblyManagementService private IntPtr OnAssemblyLoaderResolvingUnmanaged(Assembly arg1, string arg2) { + // TODO: Implement extern assembly lookup for Native/Unmanaged Assemblies. throw new NotImplementedException(); } From 70dd602bcf3bf82df823042aff35aa7276ddeb78 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Tue, 3 Feb 2026 19:37:47 -0300 Subject: [PATCH 102/288] Move the Lua IL patching bullshit to a separate service --- .../SharedSource/LuaCs/LuaCsSetup.cs | 1 + .../Services/Compatibility/ILuaCsHook.cs | 15 +- .../LuaCs/Services/EventService.cs | 62 ++- .../LuaCs/Services/Safe/ILuaPatcher.cs | 16 + .../Safe/{LuaClasses => }/LuaPatcherCompat.cs | 15 +- .../LuaPatcher.cs => LuaPatcherService.cs} | 510 +++--------------- .../SharedSource/LuaCs/SigilExtensions.cs | 399 ++++++++++++++ 7 files changed, 547 insertions(+), 471 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPatcher.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/{LuaClasses => }/LuaPatcherCompat.cs (95%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/{LuaClasses/LuaPatcher.cs => LuaPatcherService.cs} (66%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/SigilExtensions.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 18ade84cd..c2b75b325 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -190,6 +190,7 @@ namespace Barotrauma servicesProvider.RegisterServiceResolver(factory => factory.GetInstance()); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs index e596135e7..6ef5d256e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs @@ -1,10 +1,9 @@ using System; using System.Reflection; -using LuaCsCompatPatchFunc = Barotrauma.LuaCsPatch; namespace Barotrauma.LuaCs.Services.Compatibility; -public interface ILuaCsHook : ILuaCsShim +public interface ILuaCsHook : ILuaPatcher, ILuaCsShim { // Event Services [Obsolete("ACsMod is deprecated. Use ILuaEventService.Add() instead.")] @@ -15,18 +14,8 @@ public interface ILuaCsHook : ILuaCsShim //bool Exists(string eventName, string identifier); object Call(string eventName, params object[] args); T Call(string eventName, params object[] args); - - // Hook/Method Patching - string Patch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before); - string Patch(string identifier, string className, string methodName, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before); - string Patch(string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before); - string Patch(string className, string methodName, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before); - bool RemovePatch(string identifier, string className, string methodName, string[] parameterTypes, HookMethodType hookType); - bool RemovePatch(string identifier, string className, string methodName, HookMethodType hookType); - void HookMethod(string identifier, MethodBase method, LuaCsCompatPatchFunc patch, HookMethodType hookType = HookMethodType.Before, IAssemblyPlugin owner = null); - - + // Needs to be here instead of ILuaPatcher for compatiility purposes public enum HookMethodType { Before, After diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs index c4c96d7c6..e8141201c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs @@ -1,15 +1,18 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using Barotrauma.Extensions; +using Barotrauma.Extensions; using Barotrauma.LuaCs.Events; using Barotrauma.LuaCs.Services.Compatibility; using FluentResults; using FluentResults.LuaCs; +using HarmonyLib; using Microsoft.Toolkit.Diagnostics; using OneOf; +using RestSharp; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; namespace Barotrauma.LuaCs.Services; @@ -51,13 +54,14 @@ public partial class EventService : IEventService public static implicit operator TypeStringKey(string typeName) => new(typeName); } - private ILoggerService _loggerService; + private readonly ILoggerService _loggerService; + private readonly ILuaPatcher _luaPatcher; private readonly AsyncReaderWriterLock _operationsLock = new(); private readonly ConcurrentDictionary, IEvent>> _subscribers = new(); private readonly ConcurrentDictionary RunnerFactory)> _luaAliasEventFactory = new(); private readonly ConcurrentDictionary> _luaLegacyEventsSubscribers = new(); - #region Disposal + #region LifeCycle public void Dispose() { @@ -70,13 +74,15 @@ public partial class EventService : IEventService _luaLegacyEventsSubscribers.Clear(); _luaAliasEventFactory.Clear(); _subscribers.Clear(); + _luaPatcher.Dispose(); } private int _isDisposed; - public EventService(ILoggerService loggerService) + public EventService(ILoggerService loggerService, ILuaPatcher luaPatcher) { _loggerService = loggerService; + _luaPatcher = luaPatcher; } public bool IsDisposed @@ -91,6 +97,7 @@ public partial class EventService : IEventService _luaLegacyEventsSubscribers.Clear(); _luaAliasEventFactory.Clear(); _subscribers.Clear(); + _luaPatcher.Reset(); return FluentResults.Result.Ok(); } @@ -299,4 +306,41 @@ public partial class EventService : IEventService return results; } + + #region LuaPatcherAdapter + public string Patch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, LuaCsHook.HookMethodType hookType = LuaCsHook.HookMethodType.Before) + { + return _luaPatcher.Patch(identifier, className, methodName, parameterTypes, patch, hookType); + } + + public string Patch(string identifier, string className, string methodName, LuaCsPatchFunc patch, LuaCsHook.HookMethodType hookType = LuaCsHook.HookMethodType.Before) + { + return _luaPatcher.Patch(identifier, className, methodName, patch, hookType); + } + + public string Patch(string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, LuaCsHook.HookMethodType hookType = LuaCsHook.HookMethodType.Before) + { + return _luaPatcher.Patch(className, methodName, parameterTypes, patch, hookType); + } + + public string Patch(string className, string methodName, LuaCsPatchFunc patch, LuaCsHook.HookMethodType hookType = LuaCsHook.HookMethodType.Before) + { + return _luaPatcher.Patch(className, methodName, patch, hookType); + } + + public bool RemovePatch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsHook.HookMethodType hookType) + { + return _luaPatcher.RemovePatch(className, methodName, methodName, parameterTypes, hookType); + } + + public bool RemovePatch(string identifier, string className, string methodName, LuaCsHook.HookMethodType hookType) + { + return _luaPatcher.RemovePatch(className, methodName, methodName, hookType); + } + + public void HookMethod(string identifier, MethodBase method, LuaCsPatch patch, LuaCsHook.HookMethodType hookType = LuaCsHook.HookMethodType.Before, IAssemblyPlugin owner = null) + { + _luaPatcher.HookMethod(identifier, method, patch, hookType, owner); + } + #endregion } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPatcher.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPatcher.cs new file mode 100644 index 000000000..eba27adba --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPatcher.cs @@ -0,0 +1,16 @@ +using System.Reflection; +using static Barotrauma.LuaCs.Services.Compatibility.ILuaCsHook; +using LuaCsCompatPatchFunc = Barotrauma.LuaCsPatch; + +namespace Barotrauma.LuaCs.Services; + +public interface ILuaPatcher : IReusableService +{ + string Patch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before); + string Patch(string identifier, string className, string methodName, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before); + string Patch(string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before); + string Patch(string className, string methodName, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before); + bool RemovePatch(string identifier, string className, string methodName, string[] parameterTypes, HookMethodType hookType); + bool RemovePatch(string identifier, string className, string methodName, HookMethodType hookType); + void HookMethod(string identifier, MethodBase method, LuaCsCompatPatchFunc patch, HookMethodType hookType = HookMethodType.Before, IAssemblyPlugin owner = null); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaPatcherCompat.cs similarity index 95% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaPatcherCompat.cs index 8b0d93306..b41111378 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcherCompat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaPatcherCompat.cs @@ -1,5 +1,4 @@ -//global using LuaCsHook = Barotrauma.LuaCs.Services.EventService; -global using LuaCsHook = Barotrauma.LuaCs.Services.Compatibility.ILuaCsHook; +global using LuaCsHook = Barotrauma.LuaCs.Services.Compatibility.ILuaCsHook; using System; using System.Linq; @@ -18,8 +17,10 @@ namespace Barotrauma namespace Barotrauma.LuaCs.Services { - partial class EventService + partial class LuaPatcherService { + private static LuaPatcherService instance; + private Dictionary> compatHookPrefixMethods = new Dictionary>(); private Dictionary> compatHookPostfixMethods = new Dictionary>(); @@ -122,10 +123,10 @@ namespace Barotrauma.LuaCs.Services if (result != null) __result = result; } - private static MethodInfo _miHookLuaCsPatchPrefix = typeof(EventService).GetMethod("HookLuaCsPatchPrefix", BindingFlags.NonPublic | BindingFlags.Static); - private static MethodInfo _miHookLuaCsPatchPostfix = typeof(EventService).GetMethod("HookLuaCsPatchPostfix", BindingFlags.NonPublic | BindingFlags.Static); - private static MethodInfo _miHookLuaCsPatchRetPrefix = typeof(EventService).GetMethod("HookLuaCsPatchRetPrefix", BindingFlags.NonPublic | BindingFlags.Static); - private static MethodInfo _miHookLuaCsPatchRetPostfix = typeof(EventService).GetMethod("HookLuaCsPatchRetPostfix", BindingFlags.NonPublic | BindingFlags.Static); + private static MethodInfo _miHookLuaCsPatchPrefix = typeof(LuaPatcherService).GetMethod("HookLuaCsPatchPrefix", BindingFlags.NonPublic | BindingFlags.Static); + private static MethodInfo _miHookLuaCsPatchPostfix = typeof(LuaPatcherService).GetMethod("HookLuaCsPatchPostfix", BindingFlags.NonPublic | BindingFlags.Static); + private static MethodInfo _miHookLuaCsPatchRetPrefix = typeof(LuaPatcherService).GetMethod("HookLuaCsPatchRetPrefix", BindingFlags.NonPublic | BindingFlags.Static); + private static MethodInfo _miHookLuaCsPatchRetPostfix = typeof(LuaPatcherService).GetMethod("HookLuaCsPatchRetPostfix", BindingFlags.NonPublic | BindingFlags.Static); // TODO: deprecate this diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcher.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaPatcherService.cs similarity index 66% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcher.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaPatcherService.cs index f45c73e4e..5d51769f7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPatcher.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaPatcherService.cs @@ -20,401 +20,12 @@ namespace Barotrauma { public delegate void LuaCsAction(params object[] args); public delegate object LuaCsFunc(params object[] args); - public delegate DynValue LuaCsPatchFunc(object instance, EventService.ParameterTable ptable); + public delegate DynValue LuaCsPatchFunc(object instance, LuaPatcherService.ParameterTable ptable); } namespace Barotrauma.LuaCs.Services { - internal static class SigilExtensions - { - /// - /// Puts a type on the stack, as a object instead of a - /// runtime type token. - /// - /// The IL emitter. - /// The type to put on the stack. - public static void LoadType(this Emit il, Type type) - { - if (type == null) throw new ArgumentNullException(nameof(type)); - il.LoadConstant(type); // ldtoken - // This converts the type token into a Type object - il.Call(typeof(Type).GetMethod( - name: nameof(Type.GetTypeFromHandle), - bindingAttr: BindingFlags.Public | BindingFlags.Static, - binder: null, - types: new Type[] { typeof(RuntimeTypeHandle) }, - modifiers: null)); - } - - /// - /// Converts the value on the stack to . - /// - /// The IL emitter. - /// The type of the value on the stack. - public static void ToObject(this Emit il, Type type) - { - if (type == null) throw new ArgumentNullException(nameof(type)); - il.DerefIfByRef(ref type); - if (type.IsValueType) - { - il.Box(type); - } - else if (type != typeof(object)) - { - il.CastClass(); - } - } - - /// - /// Deferences the value on stack if the provided type is ByRef. - /// - /// The IL emitter. - /// The type to check if ByRef. - public static void DerefIfByRef(this Emit il, Type type) => il.DerefIfByRef(ref type); - - /// - /// Deferences the value on stack if the provided type is ByRef. - /// - /// The IL emitter. - /// The type to check if ByRef. - public static void DerefIfByRef(this Emit il, ref Type type) - { - if (type == null) throw new ArgumentNullException(nameof(type)); - if (type.IsByRef) - { - type = type.GetElementType(); - if (type.IsValueType) - { - il.LoadObject(type); - } - else - { - il.LoadIndirect(type); - } - } - } - - // Copied from https://github.com/evilfactory/moonsharp/blob/5264656c6442e783f3c75082cce69a93d66d4cc0/src/MoonSharp.Interpreter/Interop/Converters/ScriptToClrConversions.cs#L79-L99 - private static MethodInfo GetImplicitOperatorMethod(Type baseType, Type targetType) - { - try - { - return Expression.Convert(Expression.Parameter(baseType, null), targetType).Method; - } - catch - { - if (baseType.BaseType != null) - { - return GetImplicitOperatorMethod(baseType.BaseType, targetType); - } - - if (targetType.BaseType != null) - { - return GetImplicitOperatorMethod(baseType, targetType.BaseType); - } - - return null; - } - } - - /// - /// Loads a local variable and casts it to the target type. - /// - /// The IL emitter. - /// The value to cast. Must be of type . - /// The type to cast into. - public static void LoadLocalAndCast(this Emit il, Local value, Type targetType) - { - if (value == null) throw new ArgumentNullException(nameof(value)); - if (targetType == null) throw new ArgumentNullException(nameof(targetType)); - if (value.LocalType != typeof(object)) - { - throw new ArgumentException($"Expected local type {typeof(object)}; got {value.LocalType}.", nameof(value)); - } - - var guid = Guid.NewGuid().ToString("N"); - - if (targetType.IsByRef) - { - targetType = targetType.GetElementType(); - } - - // IL: var baseType = value.GetType(); - var baseType = il.DeclareLocal(typeof(Type), $"cast_baseType_{guid}"); - il.LoadLocal(value); - il.Call(typeof(object).GetMethod("GetType")); - il.StoreLocal(baseType); - - // IL: var implicitOperatorMethod = SigilExtensions.GetImplicitOperatorMethod(baseType, ); - var implicitOperatorMethod = il.DeclareLocal(typeof(MethodInfo), $"cast_implicitOperatorMethod_{guid}"); - il.LoadLocal(baseType); - il.LoadType(targetType); - il.Call(typeof(SigilExtensions).GetMethod(nameof(GetImplicitOperatorMethod), BindingFlags.NonPublic | BindingFlags.Static)); - il.StoreLocal(implicitOperatorMethod); - - // IL: castValue; - var castValue = il.DeclareLocal(targetType, $"cast_castValue_{guid}"); - - // IL: if (implicitConversionMethod != null) - il.LoadLocal(implicitOperatorMethod); - il.Branch((il) => - { - // IL: var methodInvokeParams = new object[1]; - var methodInvokeParams = il.DeclareLocal(typeof(object[]), $"cast_methodInvokeParams_{guid}"); - il.LoadConstant(1); - il.NewArray(typeof(object)); - il.StoreLocal(methodInvokeParams); - - // IL: methodInvokeParams[0] = value; - il.LoadLocal(methodInvokeParams); - il.LoadConstant(0); - il.LoadLocal(value); - il.StoreElement(); - - // IL: castValue = ()implicitConversionMethod.Invoke(null, methodInvokeParams); - il.LoadLocal(implicitOperatorMethod); - il.LoadNull(); // first parameter is null because implicit cast operators are static - il.LoadLocal(methodInvokeParams); - il.Call(typeof(MethodInfo).GetMethod("Invoke", new[] { typeof(object), typeof(object[]) })); - if (targetType.IsValueType) - { - il.UnboxAny(targetType); - } - else - { - il.CastClass(targetType); - } - il.StoreLocal(castValue); - }, - (il) => - { - // IL: castValue = ()value; - il.LoadLocal(value); - if (targetType.IsValueType) - { - il.UnboxAny(targetType); - } - else - { - il.CastClass(targetType); - } - il.StoreLocal(castValue); - }); - - il.LoadLocal(castValue); - } - - /// - /// Emits a call to . - /// - /// The IL emitter. - /// The string format. - /// The local variables passed to string.Format. - public static void FormatString(this Emit il, string format, params Local[] args) - { - if (format == null) throw new ArgumentNullException(nameof(format)); - if (args == null) throw new ArgumentNullException(nameof(args)); - - var guid = Guid.NewGuid().ToString("N"); - - var listType = typeof(List<>).MakeGenericType(typeof(object)); - var list = il.DeclareLocal(listType, $"formatString_list_{guid}"); - il.NewObject(listType); - il.StoreLocal(list); - - foreach (var arg in args) - { - il.LoadLocal(list); - il.LoadLocal(arg); - il.ToObject(arg.LocalType); - il.CallVirtual(listType.GetMethod("Add", new[] { typeof(object) })); - } - - var arr = il.DeclareLocal($"formatString_arr_{guid}"); - il.LoadLocal(list); - il.CallVirtual(listType.GetMethod("ToArray", new Type[0])); - il.StoreLocal(arr); - - il.LoadConstant(format); - il.LoadLocal(arr); - il.Call(typeof(string).GetMethod("Format", new[] { typeof(string), typeof(object[]) })); - } - - /// - /// Emits a call to . - /// - /// The IL emitter. - /// The message to print. - public static void NewMessage(this Emit il, string message) - { - var newMessage = typeof(DebugConsole).GetMethod( - name: nameof(DebugConsole.NewMessage), - bindingAttr: BindingFlags.Public | BindingFlags.Static, - binder: null, - types: new Type[] { typeof(string), typeof(Color?), typeof(bool) }, - modifiers: null); - il.LoadConstant(message); - il.Call(typeof(Color).GetProperty(nameof(Color.LightBlue), BindingFlags.Public | BindingFlags.Static).GetGetMethod()); - il.LoadConstant(false); - il.Call(newMessage); - } - - /// - /// Emits a call to , - /// using the string on the stack. - /// - /// The IL emitter. - public static void NewMessage(this Emit il) - { - var newMessage = typeof(DebugConsole).GetMethod( - name: nameof(DebugConsole.NewMessage), - bindingAttr: BindingFlags.Public | BindingFlags.Static, - binder: null, - types: new Type[] { typeof(string), typeof(Color?), typeof(bool) }, - modifiers: null); - il.Call(typeof(Color).GetProperty(nameof(Color.LightBlue), BindingFlags.Public | BindingFlags.Static).GetGetMethod()); - il.LoadConstant(false); - il.Call(newMessage); - } - - /// - /// Emits a foreach loop that iterates over an local variable. - /// - /// The type of elements in the enumerable. - /// The IL emitter. - /// The enumerable. - /// The body of code to run on each iteration. - public static void ForEachEnumerable(this Emit il, Local enumerable, Action action) - { - if (enumerable == null) throw new ArgumentNullException(nameof(enumerable)); - if (action == null) throw new ArgumentNullException(nameof(action)); - if (!typeof(IEnumerable).IsAssignableFrom(enumerable.LocalType)) - { - throw new ArgumentException($"Expected local type {typeof(IEnumerator)}; got {enumerable.LocalType}.", nameof(enumerable)); - } - - var guid = Guid.NewGuid().ToString("N"); - - var enumerator = il.DeclareLocal>($"forEachEnumerable_enumerator_{guid}"); - il.LoadLocal(enumerable); - il.CallVirtual(typeof(IEnumerable).GetMethod("GetEnumerator")); - il.StoreLocal(enumerator); - ForEachEnumerator(il, enumerator, action); - } - - /// - /// Emits a foreach loop that iterates over an local variable. - /// - /// The type of elements in the enumerable. - /// The IL emitter. - /// The enumerator. - /// The body of code to run on each iteration. - public static void ForEachEnumerator(this Emit il, Local enumerator, Action action) - { - if (enumerator == null) throw new ArgumentNullException(nameof(enumerator)); - if (action == null) throw new ArgumentNullException(nameof(action)); - if (!typeof(IEnumerator).IsAssignableFrom(enumerator.LocalType)) - { - throw new ArgumentException($"Expected local type {typeof(IEnumerator)}; got {enumerator.LocalType}.", nameof(enumerator)); - } - - var guid = Guid.NewGuid().ToString("N"); - var labelLoopStart = il.DefineLabel($"forEach_loopStart_{guid}"); - var labelMoveNext = il.DefineLabel($"forEach_moveNext_{guid}"); - var labelLeave = il.DefineLabel($"forEach_leave_{guid}"); - - il.BeginExceptionBlock(out var exceptionBlock); - il.Branch(labelMoveNext); // MoveNext() needs to be called at least once before iterating - il.MarkLabel(labelLoopStart); - - // IL: var current = enumerator.Current; - var current = il.DeclareLocal($"forEachEnumerator_current_{guid}"); - il.LoadLocal(enumerator); - il.CallVirtual(enumerator.LocalType.GetProperty("Current").GetGetMethod()); - il.StoreLocal(current); - - action(il, current, labelLeave); - - il.MarkLabel(labelMoveNext); - il.LoadLocal(enumerator); - il.CallVirtual(typeof(IEnumerator).GetMethod("MoveNext")); - il.BranchIfTrue(labelLoopStart); // loop if MoveNext() returns true - - // IL: finally { enumerator.Dispose(); } - il.BeginFinallyBlock(exceptionBlock, out var finallyBlock); - il.LoadLocal(enumerator); - il.CallVirtual(typeof(IDisposable).GetMethod("Dispose")); - il.EndFinallyBlock(finallyBlock); - - il.EndExceptionBlock(exceptionBlock); - - il.MarkLabel(labelLeave); - } - - /// - /// Emits a branch that only executes if the last value on the stack - /// is truthy (e.g. non-null references, 1, etc). - /// - /// The IL emitter. - /// The body of code to run if the value is truthy. - public static void If(this Emit il, Action action) - { - if (action == null) throw new ArgumentNullException(nameof(action)); - il.Branch(@if: action); - } - - /// - /// Emits a branch that only executes if the last value on the stack - /// is falsy (e.g. null references, 0, etc). - /// - /// The IL emitter. - /// The body of code to run if the value is falsy. - public static void IfNot(this Emit il, Action action) - { - if (action == null) throw new ArgumentNullException(nameof(action)); - il.Branch(@else: action); - } - - /// - /// Emits two branches that diverge based on a condition -- analogous - /// to an if-else statement. If either - /// or are omitted, it behaves the same as - /// - /// and . - /// - /// The IL emitter. - /// The body of code to run if the value is truthy. - /// The body of code to run if the value is falsy. - public static void Branch(this Emit il, Action @if = null, Action @else = null) - { - if (@if == null && @else == null) throw new ArgumentException("At least one of the two branches must be defined."); - - var guid = Guid.NewGuid().ToString("N"); - var labelEnd = il.DefineLabel($"branch_end_{guid}"); - if (@if != null && @else != null) - { - var labelElse = il.DefineLabel($"branch_else_{guid}"); - il.BranchIfFalse(labelElse); - @if(il); - il.Branch(labelEnd); - il.MarkLabel(labelElse); - @else(il); - } - else if (@if != null) - { - il.BranchIfFalse(labelEnd); - @if(il); - } - else - { - il.BranchIfTrue(labelEnd); - @else(il); - } - il.MarkLabel(labelEnd); - } - } - - partial class EventService + public partial class LuaPatcherService : ILuaPatcher { private class LuaCsHookCallback { @@ -511,36 +122,6 @@ namespace Barotrauma.LuaCs.Services public Dictionary ModifiedParameters { get; } = new Dictionary(); } - private static readonly string[] prohibitedHooks = - { - "Barotrauma.Lua", - "Barotrauma.Cs", - "Barotrauma.ContentPackageManager", - }; - - private static void ValidatePatchTarget(MethodBase method) - { - if (prohibitedHooks.Any(h => method.DeclaringType.FullName.StartsWith(h))) - { - throw new ArgumentException("Hooks into the modding environment are prohibited."); - } - } - - private static string NormalizeIdentifier(string identifier) - { - return identifier?.Trim().ToLowerInvariant(); - } - - private Harmony harmony; - - private Lazy patchModuleBuilder; - - private readonly Dictionary registeredPatches = new Dictionary(); - - private LuaCsSetup luaCs; - - private static EventService instance; - private struct MethodKey : IEquatable { public ModuleHandle ModuleHandle { get; set; } @@ -579,7 +160,19 @@ namespace Barotrauma.LuaCs.Services }; } - public void InitPatcher() + private static readonly string[] prohibitedHooks = + { + "Barotrauma.Lua", + "Barotrauma.Cs", + "Barotrauma.ContentPackageManager", + }; + + + private Harmony harmony; + private Lazy patchModuleBuilder; + private readonly Dictionary registeredPatches = new Dictionary(); + + public LuaPatcherService() { instance = this; @@ -587,6 +180,9 @@ namespace Barotrauma.LuaCs.Services patchModuleBuilder = new Lazy(CreateModuleBuilder); UserData.RegisterType(); + + // whats this for? + /* var hookType = UserData.RegisterType(); var hookDesc = (StandardUserDataDescriptor)hookType; typeof(EventService).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).ToList().ForEach(m => { @@ -600,29 +196,20 @@ namespace Barotrauma.LuaCs.Services hookDesc.AddMember(m.Name, new MethodMemberDescriptor(m, InteropAccessMode.Default)); } }); + */ } - public void ResetPatcher() + private static void ValidatePatchTarget(MethodBase method) { - harmony?.UnpatchSelf(); - - foreach (var (_, patch) in registeredPatches) + if (prohibitedHooks.Any(h => method.DeclaringType.FullName.StartsWith(h))) { - // Remove references stored in our dynamic types so the generated - // assembly can be garbage-collected. - patch.HarmonyPrefixMethod.DeclaringType - .GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static) - .SetValue(null, null); - patch.HarmonyPostfixMethod.DeclaringType - .GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static) - .SetValue(null, null); + throw new ArgumentException("Hooks into the modding environment are prohibited."); } + } - registeredPatches.Clear(); - patchModuleBuilder = null; - - compatHookPrefixMethods.Clear(); - compatHookPostfixMethods.Clear(); + private static string NormalizeIdentifier(string identifier) + { + return identifier?.Trim().ToLowerInvariant(); } private ModuleBuilder CreateModuleBuilder() @@ -797,6 +384,8 @@ namespace Barotrauma.LuaCs.Services private const string FIELD_LUACS = "LuaCs"; + public bool IsDisposed { get; private set; } + // If you need to debug this: // - use https://sharplab.io ; it's a very useful for resource for writing IL by hand. // - use il.NewMessage("") or il.WriteLine("") to see where the IL crashes at runtime. @@ -863,8 +452,8 @@ namespace Barotrauma.LuaCs.Services // IL: var patchExists = instance.registeredPatches.TryGetValue(patchKey, out MethodPatches patches) var patchExists = il.DeclareLocal("patchExists"); var patches = il.DeclareLocal("patches"); - il.LoadField(typeof(EventService).GetField(nameof(instance), BindingFlags.NonPublic | BindingFlags.Static)); - il.LoadField(typeof(EventService).GetField(nameof(registeredPatches), BindingFlags.NonPublic | BindingFlags.Instance)); + il.LoadField(typeof(LuaPatcherService).GetField(nameof(instance), BindingFlags.NonPublic | BindingFlags.Static)); + il.LoadField(typeof(LuaPatcherService).GetField(nameof(registeredPatches), BindingFlags.NonPublic | BindingFlags.Instance)); il.LoadLocal(patchKey); il.LoadLocalAddress(patches); // out parameter il.Call(typeof(Dictionary).GetMethod("TryGetValue")); @@ -1081,7 +670,7 @@ namespace Barotrauma.LuaCs.Services } var type = typeBuilder.CreateType(); - type.GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static).SetValue(null, luaCs); + type.GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static).SetValue(null, GameMain.LuaCs); return type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); } @@ -1187,5 +776,42 @@ namespace Barotrauma.LuaCs.Services var method = ResolveMethod(className, methodName, null); return RemovePatch(identifier, method, hookType); } + + private void ClearAll() + { + harmony?.UnpatchSelf(); + + foreach (var (_, patch) in registeredPatches) + { + // Remove references stored in our dynamic types so the generated + // assembly can be garbage-collected. + patch.HarmonyPrefixMethod.DeclaringType + .GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static) + .SetValue(null, null); + patch.HarmonyPostfixMethod.DeclaringType + .GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static) + .SetValue(null, null); + } + + registeredPatches.Clear(); + patchModuleBuilder = null; + + compatHookPrefixMethods.Clear(); + compatHookPostfixMethods.Clear(); + } + + public void Dispose() + { + IsDisposed = true; + + ClearAll(); + } + + public FluentResults.Result Reset() + { + ClearAll(); + + return FluentResults.Result.Ok(); + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/SigilExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/SigilExtensions.cs new file mode 100644 index 000000000..81143ffce --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/SigilExtensions.cs @@ -0,0 +1,399 @@ +using Microsoft.Xna.Framework; +using Sigil; +using Sigil.NonGeneric; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; + +namespace Barotrauma.LuaCs; + +internal static class SigilExtensions +{ + /// + /// Puts a type on the stack, as a object instead of a + /// runtime type token. + /// + /// The IL emitter. + /// The type to put on the stack. + public static void LoadType(this Emit il, Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + il.LoadConstant(type); // ldtoken + // This converts the type token into a Type object + il.Call(typeof(Type).GetMethod( + name: nameof(Type.GetTypeFromHandle), + bindingAttr: BindingFlags.Public | BindingFlags.Static, + binder: null, + types: new Type[] { typeof(RuntimeTypeHandle) }, + modifiers: null)); + } + + /// + /// Converts the value on the stack to . + /// + /// The IL emitter. + /// The type of the value on the stack. + public static void ToObject(this Emit il, Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + il.DerefIfByRef(ref type); + if (type.IsValueType) + { + il.Box(type); + } + else if (type != typeof(object)) + { + il.CastClass(); + } + } + + /// + /// Deferences the value on stack if the provided type is ByRef. + /// + /// The IL emitter. + /// The type to check if ByRef. + public static void DerefIfByRef(this Emit il, Type type) => il.DerefIfByRef(ref type); + + /// + /// Deferences the value on stack if the provided type is ByRef. + /// + /// The IL emitter. + /// The type to check if ByRef. + public static void DerefIfByRef(this Emit il, ref Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + if (type.IsByRef) + { + type = type.GetElementType(); + if (type.IsValueType) + { + il.LoadObject(type); + } + else + { + il.LoadIndirect(type); + } + } + } + + // Copied from https://github.com/evilfactory/moonsharp/blob/5264656c6442e783f3c75082cce69a93d66d4cc0/src/MoonSharp.Interpreter/Interop/Converters/ScriptToClrConversions.cs#L79-L99 + private static MethodInfo GetImplicitOperatorMethod(Type baseType, Type targetType) + { + try + { + return Expression.Convert(Expression.Parameter(baseType, null), targetType).Method; + } + catch + { + if (baseType.BaseType != null) + { + return GetImplicitOperatorMethod(baseType.BaseType, targetType); + } + + if (targetType.BaseType != null) + { + return GetImplicitOperatorMethod(baseType, targetType.BaseType); + } + + return null; + } + } + + /// + /// Loads a local variable and casts it to the target type. + /// + /// The IL emitter. + /// The value to cast. Must be of type . + /// The type to cast into. + public static void LoadLocalAndCast(this Emit il, Local value, Type targetType) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + if (targetType == null) throw new ArgumentNullException(nameof(targetType)); + if (value.LocalType != typeof(object)) + { + throw new ArgumentException($"Expected local type {typeof(object)}; got {value.LocalType}.", nameof(value)); + } + + var guid = Guid.NewGuid().ToString("N"); + + if (targetType.IsByRef) + { + targetType = targetType.GetElementType(); + } + + // IL: var baseType = value.GetType(); + var baseType = il.DeclareLocal(typeof(Type), $"cast_baseType_{guid}"); + il.LoadLocal(value); + il.Call(typeof(object).GetMethod("GetType")); + il.StoreLocal(baseType); + + // IL: var implicitOperatorMethod = SigilExtensions.GetImplicitOperatorMethod(baseType, ); + var implicitOperatorMethod = il.DeclareLocal(typeof(MethodInfo), $"cast_implicitOperatorMethod_{guid}"); + il.LoadLocal(baseType); + il.LoadType(targetType); + il.Call(typeof(SigilExtensions).GetMethod(nameof(GetImplicitOperatorMethod), BindingFlags.NonPublic | BindingFlags.Static)); + il.StoreLocal(implicitOperatorMethod); + + // IL: castValue; + var castValue = il.DeclareLocal(targetType, $"cast_castValue_{guid}"); + + // IL: if (implicitConversionMethod != null) + il.LoadLocal(implicitOperatorMethod); + il.Branch((il) => + { + // IL: var methodInvokeParams = new object[1]; + var methodInvokeParams = il.DeclareLocal(typeof(object[]), $"cast_methodInvokeParams_{guid}"); + il.LoadConstant(1); + il.NewArray(typeof(object)); + il.StoreLocal(methodInvokeParams); + + // IL: methodInvokeParams[0] = value; + il.LoadLocal(methodInvokeParams); + il.LoadConstant(0); + il.LoadLocal(value); + il.StoreElement(); + + // IL: castValue = ()implicitConversionMethod.Invoke(null, methodInvokeParams); + il.LoadLocal(implicitOperatorMethod); + il.LoadNull(); // first parameter is null because implicit cast operators are static + il.LoadLocal(methodInvokeParams); + il.Call(typeof(MethodInfo).GetMethod("Invoke", new[] { typeof(object), typeof(object[]) })); + if (targetType.IsValueType) + { + il.UnboxAny(targetType); + } + else + { + il.CastClass(targetType); + } + il.StoreLocal(castValue); + }, + (il) => + { + // IL: castValue = ()value; + il.LoadLocal(value); + if (targetType.IsValueType) + { + il.UnboxAny(targetType); + } + else + { + il.CastClass(targetType); + } + il.StoreLocal(castValue); + }); + + il.LoadLocal(castValue); + } + + /// + /// Emits a call to . + /// + /// The IL emitter. + /// The string format. + /// The local variables passed to string.Format. + public static void FormatString(this Emit il, string format, params Local[] args) + { + if (format == null) throw new ArgumentNullException(nameof(format)); + if (args == null) throw new ArgumentNullException(nameof(args)); + + var guid = Guid.NewGuid().ToString("N"); + + var listType = typeof(List<>).MakeGenericType(typeof(object)); + var list = il.DeclareLocal(listType, $"formatString_list_{guid}"); + il.NewObject(listType); + il.StoreLocal(list); + + foreach (var arg in args) + { + il.LoadLocal(list); + il.LoadLocal(arg); + il.ToObject(arg.LocalType); + il.CallVirtual(listType.GetMethod("Add", new[] { typeof(object) })); + } + + var arr = il.DeclareLocal($"formatString_arr_{guid}"); + il.LoadLocal(list); + il.CallVirtual(listType.GetMethod("ToArray", new Type[0])); + il.StoreLocal(arr); + + il.LoadConstant(format); + il.LoadLocal(arr); + il.Call(typeof(string).GetMethod("Format", new[] { typeof(string), typeof(object[]) })); + } + + /// + /// Emits a call to . + /// + /// The IL emitter. + /// The message to print. + public static void NewMessage(this Emit il, string message) + { + var newMessage = typeof(DebugConsole).GetMethod( + name: nameof(DebugConsole.NewMessage), + bindingAttr: BindingFlags.Public | BindingFlags.Static, + binder: null, + types: new Type[] { typeof(string), typeof(Color?), typeof(bool) }, + modifiers: null); + il.LoadConstant(message); + il.Call(typeof(Color).GetProperty(nameof(Color.LightBlue), BindingFlags.Public | BindingFlags.Static).GetGetMethod()); + il.LoadConstant(false); + il.Call(newMessage); + } + + /// + /// Emits a call to , + /// using the string on the stack. + /// + /// The IL emitter. + public static void NewMessage(this Emit il) + { + var newMessage = typeof(DebugConsole).GetMethod( + name: nameof(DebugConsole.NewMessage), + bindingAttr: BindingFlags.Public | BindingFlags.Static, + binder: null, + types: new Type[] { typeof(string), typeof(Color?), typeof(bool) }, + modifiers: null); + il.Call(typeof(Color).GetProperty(nameof(Color.LightBlue), BindingFlags.Public | BindingFlags.Static).GetGetMethod()); + il.LoadConstant(false); + il.Call(newMessage); + } + + /// + /// Emits a foreach loop that iterates over an local variable. + /// + /// The type of elements in the enumerable. + /// The IL emitter. + /// The enumerable. + /// The body of code to run on each iteration. + public static void ForEachEnumerable(this Emit il, Local enumerable, Action action) + { + if (enumerable == null) throw new ArgumentNullException(nameof(enumerable)); + if (action == null) throw new ArgumentNullException(nameof(action)); + if (!typeof(IEnumerable).IsAssignableFrom(enumerable.LocalType)) + { + throw new ArgumentException($"Expected local type {typeof(IEnumerator)}; got {enumerable.LocalType}.", nameof(enumerable)); + } + + var guid = Guid.NewGuid().ToString("N"); + + var enumerator = il.DeclareLocal>($"forEachEnumerable_enumerator_{guid}"); + il.LoadLocal(enumerable); + il.CallVirtual(typeof(IEnumerable).GetMethod("GetEnumerator")); + il.StoreLocal(enumerator); + ForEachEnumerator(il, enumerator, action); + } + + /// + /// Emits a foreach loop that iterates over an local variable. + /// + /// The type of elements in the enumerable. + /// The IL emitter. + /// The enumerator. + /// The body of code to run on each iteration. + public static void ForEachEnumerator(this Emit il, Local enumerator, Action action) + { + if (enumerator == null) throw new ArgumentNullException(nameof(enumerator)); + if (action == null) throw new ArgumentNullException(nameof(action)); + if (!typeof(IEnumerator).IsAssignableFrom(enumerator.LocalType)) + { + throw new ArgumentException($"Expected local type {typeof(IEnumerator)}; got {enumerator.LocalType}.", nameof(enumerator)); + } + + var guid = Guid.NewGuid().ToString("N"); + var labelLoopStart = il.DefineLabel($"forEach_loopStart_{guid}"); + var labelMoveNext = il.DefineLabel($"forEach_moveNext_{guid}"); + var labelLeave = il.DefineLabel($"forEach_leave_{guid}"); + + il.BeginExceptionBlock(out var exceptionBlock); + il.Branch(labelMoveNext); // MoveNext() needs to be called at least once before iterating + il.MarkLabel(labelLoopStart); + + // IL: var current = enumerator.Current; + var current = il.DeclareLocal($"forEachEnumerator_current_{guid}"); + il.LoadLocal(enumerator); + il.CallVirtual(enumerator.LocalType.GetProperty("Current").GetGetMethod()); + il.StoreLocal(current); + + action(il, current, labelLeave); + + il.MarkLabel(labelMoveNext); + il.LoadLocal(enumerator); + il.CallVirtual(typeof(IEnumerator).GetMethod("MoveNext")); + il.BranchIfTrue(labelLoopStart); // loop if MoveNext() returns true + + // IL: finally { enumerator.Dispose(); } + il.BeginFinallyBlock(exceptionBlock, out var finallyBlock); + il.LoadLocal(enumerator); + il.CallVirtual(typeof(IDisposable).GetMethod("Dispose")); + il.EndFinallyBlock(finallyBlock); + + il.EndExceptionBlock(exceptionBlock); + + il.MarkLabel(labelLeave); + } + + /// + /// Emits a branch that only executes if the last value on the stack + /// is truthy (e.g. non-null references, 1, etc). + /// + /// The IL emitter. + /// The body of code to run if the value is truthy. + public static void If(this Emit il, Action action) + { + if (action == null) throw new ArgumentNullException(nameof(action)); + il.Branch(@if: action); + } + + /// + /// Emits a branch that only executes if the last value on the stack + /// is falsy (e.g. null references, 0, etc). + /// + /// The IL emitter. + /// The body of code to run if the value is falsy. + public static void IfNot(this Emit il, Action action) + { + if (action == null) throw new ArgumentNullException(nameof(action)); + il.Branch(@else: action); + } + + /// + /// Emits two branches that diverge based on a condition -- analogous + /// to an if-else statement. If either + /// or are omitted, it behaves the same as + /// + /// and . + /// + /// The IL emitter. + /// The body of code to run if the value is truthy. + /// The body of code to run if the value is falsy. + public static void Branch(this Emit il, Action @if = null, Action @else = null) + { + if (@if == null && @else == null) throw new ArgumentException("At least one of the two branches must be defined."); + + var guid = Guid.NewGuid().ToString("N"); + var labelEnd = il.DefineLabel($"branch_end_{guid}"); + if (@if != null && @else != null) + { + var labelElse = il.DefineLabel($"branch_else_{guid}"); + il.BranchIfFalse(labelElse); + @if(il); + il.Branch(labelEnd); + il.MarkLabel(labelElse); + @else(il); + } + else if (@if != null) + { + il.BranchIfFalse(labelEnd); + @if(il); + } + else + { + il.BranchIfTrue(labelEnd); + @else(il); + } + il.MarkLabel(labelEnd); + } +} From 02a7338ab8bf499bda4dbdc8790609dbf444087b Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 3 Feb 2026 17:49:10 -0500 Subject: [PATCH 103/288] Removed duplicate rawrrs --- .../LuaCs/_Plugins/IAssemblyLoaderService.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs index 9c113ed4b..c3dd6983a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs @@ -11,16 +11,6 @@ using Microsoft.CodeAnalysis.CSharp; namespace Barotrauma.LuaCs; -[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] -public sealed class IgnoresAccessChecksToAttribute : Attribute -{ - public string AssemblyName { get; } - public IgnoresAccessChecksToAttribute(string assemblyName) - { - AssemblyName = assemblyName; - } -} - public interface IAssemblyLoaderService : IService { public interface IFactory : IService From fd037153ee872636a9bed871860e86fe6401cc7a Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 3 Feb 2026 18:53:31 -0500 Subject: [PATCH 104/288] - Fixed recursion deadlock due to the EventService.Reset() being called during event publishing. --- .../ClientSource/LuaCs/LuaCsSetup.cs | 67 ++++++++------ .../LuaCs/Services/PluginManagementService.cs | 91 ++++++++++++++----- 2 files changed, 105 insertions(+), 53 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index ffe87afab..6c5be4e53 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -80,38 +80,45 @@ namespace Barotrauma /// The new game screen. public partial void OnScreenSelected(Screen screen) { - switch (screen) + /*Note: This logic needs to be run after the triggering event so that recursion scenarios (ie. resetting the EventService) + do not occur, so we delay it by one game tick.*/ + CoroutineManager.Invoke(() => { - // menus and navigation states - case MainMenuScreen: - case ModDownloadScreen: - case ServerListScreen: - SetRunState(RunState.Unloaded); - SetRunState(RunState.LoadedNoExec); - break; - // running lobby or editor states - case CampaignEndScreen: - case CharacterEditorScreen: - case EventEditorScreen: - case GameScreen: - case LevelEditorScreen: - case NetLobbyScreen: - case ParticleEditorScreen: - case RoundSummaryScreen: - case SpriteEditorScreen: - case SubEditorScreen: - case TestScreen: // notes: TestScreen is a Linux edge case editor screen and is deprecated. - if (CheckCsEnabled() && this.CurrentRunState >= RunState.Running) - { + switch (screen) + { + // menus and navigation states + case MainMenuScreen: + case ModDownloadScreen: + case ServerListScreen: + SetRunState(RunState.Unloaded); SetRunState(RunState.LoadedNoExec); - } - SetRunState(RunState.Running); - break; - default: - Logger.LogError($"{nameof(LuaCsSetup)}: Received an unknown screen {screen?.GetType().Name ?? "'null screen'"}. Retarding load state to 'unloaded'."); - SetRunState(RunState.Unloaded); - break; - } + break; + // running lobby or editor states + case CampaignEndScreen: + case CharacterEditorScreen: + case EventEditorScreen: + case GameScreen: + case LevelEditorScreen: + case NetLobbyScreen: + case ParticleEditorScreen: + case RoundSummaryScreen: + case SpriteEditorScreen: + case SubEditorScreen: + case TestScreen: // notes: TestScreen is a Linux edge case editor screen and is deprecated. + if (CheckCsEnabled() && this.CurrentRunState >= RunState.Running) + { + SetRunState(RunState.LoadedNoExec); + } + + SetRunState(RunState.Running); + break; + default: + Logger.LogError( + $"{nameof(LuaCsSetup)}: Received an unknown screen {screen?.GetType().Name ?? "'null screen'"}. Retarding load state to 'unloaded'."); + SetRunState(RunState.Unloaded); + break; + } + }, delay: 0f); // min is one tick delay. } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 11814a326..9ab837140 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -9,6 +9,7 @@ using System.Runtime.CompilerServices; using System.Runtime.Loader; using System.Text; using System.Threading; +using System.Xml.Serialization; using Barotrauma.Extensions; using Barotrauma.IO; using Barotrauma.LuaCs.Data; @@ -92,13 +93,75 @@ public class PluginManagementService : IAssemblyManagementService public void Dispose() { - throw new NotImplementedException(); + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) + { + return; + } + + UnsafeDisposeResourcesInternal(); + _assemblyLoaderFactory = null; + _storageService = null; + _eventService = null; + _logger = null; + _configService = null; + _luaScriptManagementService = null; + + GC.SuppressFinalize(this); } - public bool IsDisposed { get; } + private void UnsafeDisposeResourcesInternal() + { + foreach (var packPlugin in _pluginInstances.SelectMany(kvp => kvp.Value.Select(pluginInst => (kvp.Key, pluginInst)))) + { + try + { + packPlugin.pluginInst.Dispose(); + } + catch (Exception e) + { + _logger.LogError($"Error while disposing plugin for ContentPackage {packPlugin.Key.Name}: \n{e.Message}"); + } + } + _pluginInstances.Clear(); + _pluginInjectorContainer.Dispose(); + _pluginInjectorContainer = null; + + foreach (var loader in _assemblyLoaders) + { + try + { + loader.Value.Dispose(); + _unloadingAssemblyLoaders.Add(loader.Value, loader.Key); + } + catch (Exception e) + { + _logger.LogError($"Failed to dispose of {nameof(IAssemblyLoaderService)} for ContentPackage {loader.Key.Name}: \n{e.Message}"); + if (loader.Value.Assemblies.Any()) + { + foreach (var ass in loader.Value.Assemblies) + { + _logger.LogWarning($"{nameof(PluginManagementService)}: Fallback manual unsubscription of assemblies: {ass.GetName()}"); + ReflectionUtils.RemoveAssemblyFromCache(ass); + } + } + } + } + _assemblyLoaders.Clear(); + } + + private int _isDisposed = 0; + public bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } public FluentResults.Result Reset() { - return FluentResults.Result.Fail("Not implemented"); + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + UnsafeDisposeResourcesInternal(); + return FluentResults.Result.Ok(); } #endregion @@ -536,8 +599,6 @@ public class PluginManagementService : IAssemblyManagementService private void OnAssemblyLoaderUnloading(IAssemblyLoaderService loader) { - using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); - if (!loader.Assemblies.Any()) { return; @@ -545,7 +606,7 @@ public class PluginManagementService : IAssemblyManagementService foreach (var assembly in loader.Assemblies) { - _eventService.Value.PublishEvent(sub => sub.OnAssemblyUnloading(assembly)); + _eventService?.Value?.PublishEvent(sub => sub.OnAssemblyUnloading(assembly)); } } @@ -564,20 +625,11 @@ public class PluginManagementService : IAssemblyManagementService results.WithReasons(UnsafeDisposeManagedTypeInstances().Reasons); ReflectionUtils.ResetCache(); - - bool[] targetGcGeneration = new bool[GC.MaxGeneration]; - - for (int i = 0; i < targetGcGeneration.Length; i++) - { - targetGcGeneration[i] = false; - } - foreach (var loaderService in _assemblyLoaders) { try { loaderService.Value.Dispose(); - targetGcGeneration[GC.GetGeneration(loaderService.Value)] = true; _unloadingAssemblyLoaders.Add(loaderService.Value, loaderService.Key); } catch (Exception e) @@ -587,14 +639,7 @@ public class PluginManagementService : IAssemblyManagementService } _assemblyLoaders.Clear(); - - for (int i = 0; i < targetGcGeneration.Length; i++) - { - if (!targetGcGeneration[i]) - { - GC.Collect(i, GCCollectionMode.Aggressive, true); - } - } + GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true); return results; } From 4cf4b1604b59d8c0782d37c99e4e9e4b82608ad3 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 3 Feb 2026 18:55:30 -0500 Subject: [PATCH 105/288] Fixed some NREs. --- .../SharedSource/LuaCs/Services/PluginManagementService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 9ab837140..b6144435c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -124,7 +124,7 @@ public class PluginManagementService : IAssemblyManagementService } } _pluginInstances.Clear(); - _pluginInjectorContainer.Dispose(); + _pluginInjectorContainer?.Dispose(); _pluginInjectorContainer = null; foreach (var loader in _assemblyLoaders) @@ -136,12 +136,12 @@ public class PluginManagementService : IAssemblyManagementService } catch (Exception e) { - _logger.LogError($"Failed to dispose of {nameof(IAssemblyLoaderService)} for ContentPackage {loader.Key.Name}: \n{e.Message}"); + _logger?.LogError($"Failed to dispose of {nameof(IAssemblyLoaderService)} for ContentPackage {loader.Key.Name}: \n{e.Message}"); if (loader.Value.Assemblies.Any()) { foreach (var ass in loader.Value.Assemblies) { - _logger.LogWarning($"{nameof(PluginManagementService)}: Fallback manual unsubscription of assemblies: {ass.GetName()}"); + _logger?.LogWarning($"{nameof(PluginManagementService)}: Fallback manual unsubscription of assemblies: {ass.GetName()}"); ReflectionUtils.RemoveAssemblyFromCache(ass); } } From cf251451ed82aaa91ae85a867436f7c07ed8826d Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Tue, 3 Feb 2026 21:06:13 -0300 Subject: [PATCH 106/288] Fix EventService.Call not implemented correctly --- .../LuaCs/Services/EventService.cs | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs index e8141201c..bf7a04271 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs @@ -5,6 +5,7 @@ using FluentResults; using FluentResults.LuaCs; using HarmonyLib; using Microsoft.Toolkit.Diagnostics; +using MoonSharp.Interpreter; using OneOf; using RestSharp; using System; @@ -132,6 +133,11 @@ public partial class EventService : IEventService } public object Call(string eventName, params object[] args) + { + return Call(eventName, args); + } + + public T Call(string eventName, params object[] args) { Guard.IsNotNullOrWhiteSpace(eventName, nameof(eventName)); using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); @@ -140,16 +146,36 @@ public partial class EventService : IEventService if (!_luaLegacyEventsSubscribers.TryGetValue(eventName, out var eventSubscribers) || eventSubscribers.IsEmpty) { - return null; + return default; } - object returnValue = null; + T returnValue = default; foreach (var subscriber in eventSubscribers) { try { - returnValue = subscriber.Value.Invoke(args); + object result = subscriber.Value.Invoke(args); + if (result is DynValue luaResult) + { + if (luaResult.Type == DataType.Tuple) + { + bool replaceNil = luaResult.Tuple.Length > 1 && luaResult.Tuple[1].CastToBool(); + + if (!luaResult.Tuple[0].IsNil() || replaceNil) + { + returnValue = luaResult.ToObject(); + } + } + else if (!luaResult.IsNil()) + { + returnValue = luaResult.ToObject(); + } + } + else + { + returnValue = (T)result; + } } catch (Exception e) { @@ -163,11 +189,6 @@ public partial class EventService : IEventService return returnValue; } - public T Call(string eventName, params object[] args) - { - return (T)Call(eventName, args); - } - public void Subscribe(string identifier, IDictionary callbacks) where T : IEvent { Guard.IsNotNullOrWhiteSpace(identifier, nameof(identifier)); From 80555ef933957c2b040f947733e8a2a514fe2059 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Tue, 3 Feb 2026 21:06:28 -0300 Subject: [PATCH 107/288] IT WORKS!!!!!!!!!!!!!!!!!!!! --- .../LuaCs/Services/Safe/LuaPatcherCompat.cs | 57 ++++++++----------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaPatcherCompat.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaPatcherCompat.cs index b41111378..bb5a79331 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaPatcherCompat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaPatcherCompat.cs @@ -21,8 +21,8 @@ namespace Barotrauma.LuaCs.Services { private static LuaPatcherService instance; - private Dictionary> compatHookPrefixMethods = new Dictionary>(); - private Dictionary> compatHookPostfixMethods = new Dictionary>(); + private Dictionary> compatHookPrefixMethods = new Dictionary>(); + private Dictionary> compatHookPostfixMethods = new Dictionary>(); private static void _hookLuaCsPatch(MethodBase __originalMethod, object[] __args, object __instance, out object result, ILuaCsHook.HookMethodType hookType) { @@ -31,7 +31,7 @@ namespace Barotrauma.LuaCs.Services try { var funcAddr = ((long)__originalMethod.MethodHandle.GetFunctionPointer()); - HashSet<(string, LuaCsCompatPatchFunc, IAssemblyPlugin)> methodSet = null; + HashSet<(string, LuaCsCompatPatchFunc)> methodSet = null; switch (hookType) { case ILuaCsHook.HookMethodType.Before: @@ -53,40 +53,31 @@ namespace Barotrauma.LuaCs.Services args.Add(@params[i].Name, __args[i]); } - var outOfSocpe = new HashSet<(string, LuaCsCompatPatchFunc, IAssemblyPlugin)>(); foreach (var tuple in methodSet) { - if (tuple.Item3 != null) + var _result = tuple.Item2(__instance, args); + if (_result != null) { - outOfSocpe.Add(tuple); - } - else - { - var _result = tuple.Item2(__instance, args); - if (_result != null) + if (_result is DynValue res) { - if (_result is DynValue res) + if (!res.IsNil()) { - if (!res.IsNil()) + if (__originalMethod is MethodInfo mi && mi.ReturnType != typeof(void)) { - if (__originalMethod is MethodInfo mi && mi.ReturnType != typeof(void)) - { - result = res.ToObject(mi.ReturnType); - } - else - { - result = res.ToObject(); - } + result = res.ToObject(mi.ReturnType); + } + else + { + result = res.ToObject(); } } - else - { - result = _result; - } + } + else + { + result = _result; } } } - foreach (var tuple in outOfSocpe) { methodSet.Remove(tuple); } } } catch (Exception ex) @@ -159,18 +150,18 @@ namespace Barotrauma.LuaCs.Services } } - if (compatHookPrefixMethods.TryGetValue(funcAddr, out HashSet<(string, LuaCsCompatPatchFunc, IAssemblyPlugin)> methodSet)) + if (compatHookPrefixMethods.TryGetValue(funcAddr, out HashSet<(string, LuaCsCompatPatchFunc)> methodSet)) { if (identifier != "") { methodSet.RemoveWhere(tuple => tuple.Item1 == identifier); } - methodSet.Add((identifier, patch, owner)); + methodSet.Add((identifier, patch)); } else if (patch != null) { - compatHookPrefixMethods.Add(funcAddr, new HashSet<(string, LuaCsCompatPatchFunc, IAssemblyPlugin)>() { (identifier, patch, owner) }); + compatHookPrefixMethods.Add(funcAddr, new HashSet<(string, LuaCsCompatPatchFunc)>() { (identifier, patch) }); } } @@ -191,18 +182,18 @@ namespace Barotrauma.LuaCs.Services } } - if (compatHookPostfixMethods.TryGetValue(funcAddr, out HashSet<(string, LuaCsCompatPatchFunc, IAssemblyPlugin)> methodSet)) + if (compatHookPostfixMethods.TryGetValue(funcAddr, out HashSet<(string, LuaCsCompatPatchFunc)> methodSet)) { if (identifier != "") { methodSet.RemoveWhere(tuple => tuple.Item1 == identifier); } - methodSet.Add((identifier, patch, owner)); + methodSet.Add((identifier, patch)); } else if (patch != null) { - compatHookPostfixMethods.Add(funcAddr, new HashSet<(string, LuaCsCompatPatchFunc, IAssemblyPlugin)>() { (identifier, patch, owner) }); + compatHookPostfixMethods.Add(funcAddr, new HashSet<(string, LuaCsCompatPatchFunc)>() { (identifier, patch) }); } } } @@ -228,7 +219,7 @@ namespace Barotrauma.LuaCs.Services { var funcAddr = (long)method.MethodHandle.GetFunctionPointer(); - Dictionary> methods; + Dictionary> methods; if (hookType == ILuaCsHook.HookMethodType.Before) methods = compatHookPrefixMethods; else if (hookType == ILuaCsHook.HookMethodType.After) methods = compatHookPostfixMethods; else throw null; From cae3741953d105083596909e9910ff28b54d7ef7 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 3 Feb 2026 19:43:49 -0500 Subject: [PATCH 108/288] Made most of the networking interfaces public. --- .../LuaCs/Services/ConfigService.cs | 2 +- .../LuaCs/Configuration/SettingEntry.cs | 4 +- .../LuaCs/Networking/INetworkSyncEntity.cs | 4 +- .../LuaCs/Networking/NetInterfaceCompat.cs | 161 ------------------ .../Networking/Primitives/AccountInfo.cs | 4 +- .../Primitives/Endpoint/Endpoint.cs | 2 +- .../Primitives/Message/IReadMessage.cs | 2 +- .../Primitives/Message/IWriteMessage.cs | 2 +- .../NetworkConnection/NetworkConnection.cs | 4 +- 9 files changed, 12 insertions(+), 173 deletions(-) delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs index ab00224e5..b9de7b917 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs @@ -7,7 +7,7 @@ using FluentResults; namespace Barotrauma.LuaCs.Services; -public partial class ConfigService +public sealed partial class ConfigService { public ImmutableArray GetDisplayableConfigs() { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingEntry.cs index 37d7d5e9f..4c1193e2d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingEntry.cs @@ -73,12 +73,12 @@ public class SettingEntry : ISettingEntry where T : IEquatable public Guid InstanceId { get; } public NetSync SyncType { get; } public ClientPermissions WritePermissions { get; } - public void ReadNetMessage(INetReadMessage message) + public void ReadNetMessage(IReadMessage message) { throw new NotImplementedException(); } - public void WriteNetMessage(INetWriteMessage message) + public void WriteNetMessage(IWriteMessage message) { throw new NotImplementedException(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetworkSyncEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetworkSyncEntity.cs index e0cb5713d..3a93e787b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetworkSyncEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetworkSyncEntity.cs @@ -28,14 +28,14 @@ public interface INetworkSyncEntity /// machine. /// /// Wrapper for the internal type: - void ReadNetMessage(INetReadMessage message); + void ReadNetMessage(IReadMessage message); /// /// Called when a network send-event involving this entity is triggered. Any data expected to be read by the recipient /// network object on the other instance(s) should be written to the packet. /// /// Wrapper for the internal type: - void WriteNetMessage(INetWriteMessage message); + void WriteNetMessage(IWriteMessage message); } /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs deleted file mode 100644 index 161bf3e1c..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/NetInterfaceCompat.cs +++ /dev/null @@ -1,161 +0,0 @@ -using Barotrauma.Networking; -using Microsoft.Xna.Framework; - -namespace Barotrauma.LuaCs.Services; - -#region Wrapper-IWriteMessage - -/// -/// Literally just exists because Barotrauma.IWriteMessage is internal only. -/// -public interface INetWriteMessage -{ - internal IWriteMessage Message { get; } - internal INetWriteMessage SetMessage(IWriteMessage msg); - - void WriteBoolean(bool val) => Message.WriteBoolean(val); - - void WritePadBits() => Message.WritePadBits(); - - void WriteByte(byte val) => Message.WriteByte(val); - - void WriteInt16(short val) => Message.WriteInt16(val); - - void WriteUInt16(ushort val) => Message.WriteUInt16(val); - - void WriteInt32(int val) => Message.WriteInt32(val); - - void WriteUInt32(uint val) => Message.WriteUInt32(val); - - void WriteInt64(long val) => Message.WriteInt64(val); - - void WriteUInt64(ulong val) => Message.WriteUInt64(val); - - void WriteSingle(float val) => Message.WriteSingle(val); - - void WriteDouble(double val) => Message.WriteDouble(val); - - void WriteColorR8G8B8(Color val) => Message.WriteColorR8G8B8(val); - - void WriteColorR8G8B8A8(Color val) => Message.WriteColorR8G8B8A8(val); - - void WriteVariableUInt32(uint val) => Message.WriteVariableUInt32(val); - - void WriteString(string val) => Message.WriteString(val); - - void WriteIdentifier(Identifier val) => Message.WriteIdentifier(val); - - void WriteRangedInteger(int val, int min, int max) => Message.WriteRangedInteger(val, min, max); - - void WriteRangedSingle(float val, float min, float max, int bitCount) => - Message.WriteRangedSingle(val, min, max, bitCount); - - void WriteBytes(byte[] val, int startIndex, int length) => Message.WriteBytes(val, startIndex, length); - - byte[] PrepareForSending(bool compressPastThreshold, out bool isCompressed, out int outLength) => - Message.PrepareForSending(compressPastThreshold, out isCompressed, out outLength); - - int BitPosition - { - get => Message.BitPosition; - set => Message.BitPosition = value; - } - - int BytePosition => Message.BytePosition; - - byte[] Buffer => Message.Buffer; - - int LengthBits - { - get => Message.LengthBits; - set => Message.LengthBits = value; - } - - int LengthBytes => Message.LengthBytes; -} - -#endregion - -#region Wrapper-IReadMessage - -/// -/// Literally just exists because Barotrauma.IReadMessage is internal only. -/// -public interface INetReadMessage -{ - internal IReadMessage Message { get; } - internal INetReadMessage SetMessage(IReadMessage msg); - - bool ReadBoolean() => Message.ReadBoolean(); - void ReadPadBits() => Message.ReadPadBits(); - byte ReadByte() => Message.ReadByte(); - byte PeekByte() => Message.PeekByte(); - ushort ReadUInt16() => Message.ReadUInt16(); - short ReadInt16() => Message.ReadInt16(); - uint ReadUInt32() => Message.ReadUInt32(); - int ReadInt32() => Message.ReadInt32(); - ulong ReadUInt64() => Message.ReadUInt64(); - long ReadInt64() => Message.ReadInt64(); - float ReadSingle() => Message.ReadSingle(); - double ReadDouble() => Message.ReadDouble(); - uint ReadVariableUInt32() => Message.ReadVariableUInt32(); - string ReadString() => Message.ReadString(); - Identifier ReadIdentifier() => Message.ReadIdentifier(); - Color ReadColorR8G8B8() => Message.ReadColorR8G8B8(); - Color ReadColorR8G8B8A8() => Message.ReadColorR8G8B8A8(); - int ReadRangedInteger(int min, int max) => Message.ReadRangedInteger(min, max); - float ReadRangedSingle(float min, float max, int bitCount) => Message.ReadRangedSingle(min, max, bitCount); - byte[] ReadBytes(int numberOfBytes) => Message.ReadBytes(numberOfBytes); - int BitPosition - { - get => Message.BitPosition; - set => Message.BitPosition = value; - } - int BytePosition => Message.BytePosition; - byte[] Buffer => Message.Buffer; - int LengthBits - { - get => Message.LengthBits; - set => Message.LengthBits = value; - } - int LengthBytes => Message.LengthBytes; -} - -#endregion - -#region HelperImplementations - -public class NetWriteMessage : INetWriteMessage -{ - private IWriteMessage Message { get; set; } - - IWriteMessage INetWriteMessage.Message => Message; - - INetWriteMessage INetWriteMessage.SetMessage(IWriteMessage msg) - { - Message = msg; - return this; - } -} - -internal static class NetHelperExtensions -{ - internal static INetWriteMessage ToNetWriteMessage(this IWriteMessage msg) => - ((INetWriteMessage)new NetWriteMessage()).SetMessage(msg); - internal static INetReadMessage ToNetReadMessage(this IReadMessage msg) => - ((INetReadMessage)new NetReadMessage()).SetMessage(msg); -} - -public class NetReadMessage : INetReadMessage -{ - private IReadMessage Message { get; set; } - IReadMessage INetReadMessage.Message => Message; - - INetReadMessage INetReadMessage.SetMessage(IReadMessage msg) - { - Message = msg; - return this; - } -} - -#endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/AccountInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/AccountInfo.cs index 5ef184891..1c9ff1907 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/AccountInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/AccountInfo.cs @@ -5,7 +5,7 @@ using System.Linq; namespace Barotrauma.Networking { [NetworkSerialize] - readonly struct AccountInfo : INetSerializableStruct + public readonly struct AccountInfo : INetSerializableStruct { public static readonly AccountInfo None = new AccountInfo(Option.None()); @@ -48,4 +48,4 @@ namespace Barotrauma.Networking public static bool operator !=(AccountInfo a, AccountInfo b) => !(a == b); } -} \ No newline at end of file +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Endpoint/Endpoint.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Endpoint/Endpoint.cs index f1599e654..ad6ef6e00 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Endpoint/Endpoint.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Endpoint/Endpoint.cs @@ -2,7 +2,7 @@ namespace Barotrauma.Networking { - abstract class Endpoint + public abstract class Endpoint { public abstract string StringRepresentation { get; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IReadMessage.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IReadMessage.cs index 2bfc7eec4..6259e7678 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IReadMessage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IReadMessage.cs @@ -4,7 +4,7 @@ using System.Text; namespace Barotrauma.Networking { - interface IReadMessage + public interface IReadMessage { bool ReadBoolean(); void ReadPadBits(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IWriteMessage.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IWriteMessage.cs index b5721153d..47580b4bb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IWriteMessage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Message/IWriteMessage.cs @@ -2,7 +2,7 @@ namespace Barotrauma.Networking { - interface IWriteMessage + public interface IWriteMessage { void WriteBoolean(bool val); void WritePadBits(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkConnection/NetworkConnection.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkConnection/NetworkConnection.cs index 040321b59..2189a166a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkConnection/NetworkConnection.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/NetworkConnection/NetworkConnection.cs @@ -8,14 +8,14 @@ namespace Barotrauma.Networking Disconnected = 0x2 } - abstract class NetworkConnection : NetworkConnection where T : Endpoint + public abstract class NetworkConnection : NetworkConnection where T : Endpoint { protected NetworkConnection(T endpoint) : base(endpoint) { } public new T Endpoint => (base.Endpoint as T)!; } - abstract class NetworkConnection + public abstract class NetworkConnection { public static double TimeoutThresholdNotInGame => GameMain.NetworkMember?.ServerSettings?.TimeoutThresholdNotInGame ?? 60.0; //full minute for timeout because loading screens can take quite a while public static double TimeoutThresholdInGame => GameMain.NetworkMember?.ServerSettings?.TimeoutThresholdInGame ?? 10.0; From 9cc20a03c0f869ae54e17cac765df7732b345360 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 3 Feb 2026 19:48:35 -0500 Subject: [PATCH 109/288] Fixed networking references errors. --- .../LuaCs/Services/NetworkingService.cs | 4 +-- .../LuaCs/Services/NetworkingService.cs | 4 +-- .../LuaCs/Configuration/SettingList.cs | 4 +-- .../LuaCs/Services/ConfigInitializers.cs | 7 ++-- .../LuaCs/Services/NetworkingService.cs | 32 ++++++------------- .../_Interfaces/INetworkingService.cs | 2 +- 6 files changed, 21 insertions(+), 32 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs index 654e2c817..623bef305 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs @@ -50,7 +50,7 @@ partial class NetworkingService : INetworkingService throw new NotImplementedException(); } - public INetWriteMessage Start(Guid netId) + public IWriteMessage Start(Guid netId) { var message = new WriteOnlyMessage(); @@ -67,7 +67,7 @@ partial class NetworkingService : INetworkingService message.WriteBytes(netId.ToByteArray(), 0, 16); } - return message.ToNetWriteMessage(); + return message; } public void RequestId(Guid netId) diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs index 683558d35..c5789827b 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs @@ -15,7 +15,7 @@ partial class NetworkingService : INetworkingService private ushort currentId = 0; - public INetWriteMessage Start(Guid netId) + public IWriteMessage Start(Guid netId) { var message = new WriteOnlyMessage(); @@ -32,7 +32,7 @@ partial class NetworkingService : INetworkingService message.WriteBytes(netId.ToByteArray(), 0, 16); } - return message.ToNetWriteMessage(); + return message; } public void NetMessageReceived(IReadMessage netMessage, ClientPacketHeader header, Client client = null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingList.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingList.cs index 973a14abe..313c4f9b3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingList.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingList.cs @@ -82,12 +82,12 @@ public class SettingList : ISettingList where T : IEquatable public Guid InstanceId { get; } public NetSync SyncType { get; } public ClientPermissions WritePermissions { get; } - public void ReadNetMessage(INetReadMessage message) + public void ReadNetMessage(IReadMessage message) { throw new NotImplementedException(); } - public void WriteNetMessage(INetWriteMessage message) + public void WriteNetMessage(IWriteMessage message) { throw new NotImplementedException(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigInitializers.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigInitializers.cs index 388923d64..a4db95e52 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigInitializers.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigInitializers.cs @@ -2,6 +2,7 @@ using System.Numerics; using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; +using Barotrauma.Networking; using FluentResults; using Microsoft.Xna.Framework; using Vector2 = Microsoft.Xna.Framework.Vector2; @@ -27,15 +28,15 @@ public class ConfigInitializers : IService public bool IsDisposed => false; private Result> CreateConfigEntry(IConfigInfo configInfo, - Action, INetReadMessage> readHandler, - Action, INetWriteMessage> writeHandler) + Action, IReadMessage> readHandler, + Action, IWriteMessage> writeHandler) where T : IEquatable { throw new NotImplementedException(); } private Result> CreateConfigList(IConfigInfo configInfo, - Action, INetReadMessage> readHandler, Action, INetWriteMessage> writeHandler) + Action, IReadMessage> readHandler, Action, IWriteMessage> writeHandler) where T : IEquatable { throw new NotImplementedException(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs index f4abb97d7..463821d96 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs @@ -46,28 +46,6 @@ internal partial class NetworkingService : INetworkingService #endif } - public void RegisterNetVar(INetworkSyncEntity netVar) - { - netVars[netVar.InstanceId] = netVar; - - netReceives[netVar.InstanceId] = (IReadMessage netMessage) => - { - INetReadMessage internalMind = new NetReadMessage(); - internalMind.SetMessage(netMessage); - netVar.ReadNetMessage(internalMind); - }; - } - - public void SendNetVar(INetworkSyncEntity netVar) - { - if (netVars.ContainsKey(netVar.InstanceId)) - { - INetWriteMessage message = Start(netVar.InstanceId); - netVar.WriteNetMessage(message); - Send(message.Message); - } - } - public void Receive(Guid netId, NetMessageReceived callback) { #if SERVER @@ -125,4 +103,14 @@ internal partial class NetworkingService : INetworkingService { IsDisposed = true; } + + public void RegisterNetVar(INetworkSyncEntity netVar) + { + throw new NotImplementedException(); + } + + public void SendNetVar(INetworkSyncEntity netVar) + { + throw new NotImplementedException(); + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs index c89b5e63e..5d39e5e20 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs @@ -13,7 +13,7 @@ internal partial interface INetworkingService : IReusableService, ILuaCsNetworki bool IsActive { get; } bool IsSynchronized { get; } - public INetWriteMessage Start(Guid netId); + public IWriteMessage Start(Guid netId); public void Receive(Guid netId, NetMessageReceived action); #if SERVER public void Send(IWriteMessage netMessage, NetworkConnection connection = null, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable); From 863ee23583c9fea4399135f49ddb77e29ea7e4d7 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 4 Feb 2026 21:52:29 -0500 Subject: [PATCH 110/288] - Some work on config service. --- .../LuaCs/Configuration/ISettingControl.cs | 1 - .../LuaCs/Configuration/ISettingBase.cs | 18 - .../LuaCs/Configuration/ISettingEntry.cs | 12 - .../LuaCs/Configuration/ISettingEnum.cs | 9 - .../LuaCs/Configuration/ISettingList.cs | 11 - .../LuaCs/Configuration/ISettingRangeEntry.cs | 13 - .../LuaCs/Configuration/ISettingTypeDef.cs | 90 +++++ .../LuaCs/Configuration/SettingBase.cs | 60 ++++ .../LuaCs/Configuration/SettingEntry.cs | 299 +++++++++++++--- .../LuaCs/Configuration/SettingList.cs | 94 ----- .../SharedSource/LuaCs/LuaCsSetup.cs | 24 +- .../LuaCs/Networking/INetworkSyncEntity.cs | 9 +- .../LuaCs/Services/ConfigInitializers.cs | 323 ------------------ .../LuaCs/Services/NetworkingService.cs | 5 + .../_Interfaces/INetworkingService.cs | 1 + 15 files changed, 420 insertions(+), 549 deletions(-) delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingBase.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingEntry.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingEnum.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingList.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingRangeEntry.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingTypeDef.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingBase.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingList.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigInitializers.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/ISettingControl.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/ISettingControl.cs index 9907af9cc..f478dc50b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/ISettingControl.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/ISettingControl.cs @@ -6,7 +6,6 @@ public interface ISettingControl : ISettingBase { event Action OnDown; KeyOrMouse Value { get; } - bool IsAssignable(KeyOrMouse value); bool TrySetValue(KeyOrMouse value); bool IsDown(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingBase.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingBase.cs deleted file mode 100644 index 6adc49f31..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingBase.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Xml.Linq; -using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services; -using Barotrauma.Networking; - -namespace Barotrauma.LuaCs.Configuration; - -public partial interface ISettingBase : IDataInfo, IEquatable, IDisposable -{ - Type GetValueType(); - string GetStringValue(); - bool TrySetValue(OneOf.OneOf value); - bool IsAssignable(OneOf.OneOf value); - event Func, bool> IsNewValueValid; - event Action OnValueChanged; - OneOf.OneOf GetSerializableValue(); -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingEntry.cs deleted file mode 100644 index a6e545543..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingEntry.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using Barotrauma.LuaCs.Services; - -namespace Barotrauma.LuaCs.Configuration; - -public interface ISettingEntry : ISettingBase, INetworkSyncEntity where T : IEquatable -{ - T Value { get; } - bool TrySetValue(T value); - bool IsAssignable(T value); - new event Action> OnValueChanged; -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingEnum.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingEnum.cs deleted file mode 100644 index 83e9604f0..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingEnum.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; -using Barotrauma.LuaCs.Services; - -namespace Barotrauma.LuaCs.Configuration; - -public interface ISettingEnum : ISettingBase, INetworkSyncEntity -{ - -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingList.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingList.cs deleted file mode 100644 index ee74412cb..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingList.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using Barotrauma.LuaCs.Services; - -namespace Barotrauma.LuaCs.Configuration; - -public interface ISettingList : ISettingEntry, INetworkSyncEntity where T : IEquatable -{ - IReadOnlyList Options { get; } - new event Action> OnValueChanged; -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingRangeEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingRangeEntry.cs deleted file mode 100644 index 57eb47fab..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingRangeEntry.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Barotrauma.LuaCs.Configuration; - -public interface ISettingRangeEntry : ISettingEntry where T : IConvertible, IEquatable -{ - T MinValue { get; } - T MaxValue { get; } - - int GetStepCount(); - float GetRangeMin(); - float GetRangeMax(); -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingTypeDef.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingTypeDef.cs new file mode 100644 index 000000000..0f2534db6 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingTypeDef.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Xml.Linq; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Services; +using Barotrauma.Networking; + +namespace Barotrauma.LuaCs.Configuration; + +public interface ISettingBase : IDataInfo, IEquatable, IDisposable +{ + /// + /// Settings production factory. Should be implemented by all types and registered with the Dependency Injector. + /// + /// An interface type derived from . + public interface IFactory where T : ISettingBase + { + /// + /// Creates an instance of the given type. + /// + /// Configuration information. + /// Called before a new value is assigned. Returns a boolean whether to allow + /// the value to be changed to the one given. + /// + T CreateInstance([NotNull]IConfigInfo configInfo, Func, bool> valueChangePredicate); + } + + #if CLIENT + IConfigDisplayInfo GetDisplayInfo(); + #endif + Type GetValueType(); + string GetStringValue(); + string GetDefaultStringValue(); + bool TrySetValue(OneOf.OneOf value); + event Action OnValueChanged; + OneOf.OneOf GetSerializableValue(); +} + +/// +/// Creates a setting representing a value of the given . Must be a compatible listed type.
+///
+/// +/// Compatible Types:
+/// Any primitive type:
+/// -
+/// -
+/// -
+/// -
+/// -
+/// -
+/// -
+/// -
+/// -
+/// -
+/// Extension types and Enums:
+/// -
+/// -
+///
+public interface ISettingBase : ISettingBase where T : IEquatable, IConvertible +{ + [NotNull] + T Value { get; } + [NotNull] + T DefaultValue { get; } + bool TrySetValue(T value); +} + +/// +/// Creates a setting representing a value of the given with a minimum and maximum value. +/// Must be a type compatible with . +/// +/// The value type. See +public interface ISettingRangeBase : ISettingBase where T : IEquatable, IConvertible +{ + T MinValue { get; } + T MaxValue { get; } + int IncrementalSteps { get; } +} + +/// +/// Creates a setting representing a value of the given with a distinct list of selectable values. +/// Must be a type compatible with . +/// +/// The value type. See +public interface ISettingList : ISettingBase where T : IEquatable, IConvertible +{ + IReadOnlyList Options { get; } + IReadOnlyList StringOptions { get; } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingBase.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingBase.cs new file mode 100644 index 000000000..ca7fbdcc0 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingBase.cs @@ -0,0 +1,60 @@ +using System; +using System.Xml.Linq; +using Barotrauma.LuaCs.Data; +using OneOf; + +namespace Barotrauma.LuaCs.Configuration; + +public abstract class SettingBase : ISettingBase +{ + protected SettingBase(IConfigInfo configInfo) + { + ConfigInfo = configInfo; + } + + protected IConfigInfo ConfigInfo { get; private set; } + + public string InternalName => ConfigInfo.InternalName; + public ContentPackage OwnerPackage => ConfigInfo.OwnerPackage; + + #if CLIENT + public IConfigDisplayInfo GetDisplayInfo() => ConfigInfo; + #endif + + public virtual bool Equals(ISettingBase other) + { + return other is not null && ( + ReferenceEquals(this, other) || !IsDisposed && + OwnerPackage == other.OwnerPackage && + InternalName.Equals(other.InternalName)); + } + + private int _isDisposed = 0; + protected virtual bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } + + public virtual void Dispose() + { + if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) + { + return; + } + + ConfigInfo = null; + OnValueChanged = null; + GC.SuppressFinalize(this); + } + + // -- Must be implemented + + public abstract Type GetValueType(); + public abstract string GetStringValue(); + public abstract string GetDefaultStringValue(); + + public abstract bool TrySetValue(OneOf value); + public event Action OnValueChanged; + public abstract OneOf GetSerializableValue(); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingEntry.cs index 4c1193e2d..040a3a065 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingEntry.cs @@ -1,85 +1,274 @@ using System; +using System.Runtime.CompilerServices; using System.Xml.Linq; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Services; using Barotrauma.Networking; +using Microsoft.Toolkit.Diagnostics; using OneOf; namespace Barotrauma.LuaCs.Configuration; -public class SettingEntry : ISettingEntry where T : IEquatable +public class SettingEntry : SettingBase, ISettingBase, INetworkSyncEntity where T : IEquatable, IConvertible { - public string InternalName { get; } - public ContentPackage OwnerPackage { get; } - public bool Equals(ISettingBase other) + public class Factory : ISettingBase.IFactory> { - throw new NotImplementedException(); + public ISettingBase CreateInstance(IConfigInfo configInfo, Func, bool> valueChangePredicate) + { + Guard.IsNotNull(configInfo, nameof(configInfo)); + return new SettingEntry(configInfo, valueChangePredicate); + } + } + + public SettingEntry(IConfigInfo configInfo, + Func, bool> valueChangePredicate) + : base(configInfo) + { + if (!( + typeof(T).IsEnum || + typeof(T).IsPrimitive || + typeof(T) == typeof(string))) + { + ThrowHelper.ThrowArgumentException($"{nameof(ISettingBase)}: The type of {nameof(T)} is not an allowed type."); + } + ValueChangePredicate = valueChangePredicate; + + try + { + Value = (T)Convert.ChangeType(ConfigInfo.Element.GetAttributeString("Value", null), typeof(T)); + } + catch (Exception e) when (e is InvalidCastException or ArgumentNullException) + { + Value = default(T); + } + + try + { + DefaultValue = (T)Convert.ChangeType(ConfigInfo.Element.GetAttributeString("Value", null), typeof(T)); + } + catch (Exception e) when (e is InvalidCastException or ArgumentNullException) + { + DefaultValue = default(T); + } } - public void Dispose() + protected Func, bool> ValueChangePredicate; + public T Value { get; protected set; } + + public T DefaultValue { get; protected set; } + + public virtual bool TrySetValue(T value) { - throw new NotImplementedException(); + if (value is null) + { + return false; + } + + if (ValueChangePredicate != null && !ValueChangePredicate(value)) + { + return false; + } + + Value = value; + return true; } - public Type GetValueType() + public override Type GetValueType() => typeof(T); + + public override string GetStringValue() => Value.ToString(); + + public override string GetDefaultStringValue() => DefaultValue.ToString(); + + public override bool TrySetValue(OneOf value) { - throw new NotImplementedException(); + bool isFailed = false; + var typeConvertedValue = value.Match( + (string val) => + { + try + { + return (T)Convert.ChangeType(val, typeof(T)); + } + catch (Exception e) + { + // ignored + isFailed = true; + return default(T); + } + }, + (XElement val) => + { + try + { + return (T)Convert.ChangeType(val.GetAttributeString("Value", null), typeof(T)); + } + catch (Exception e) + { + isFailed = true; + return default(T); + } + }); + + return isFailed || TrySetValue(typeConvertedValue); } - public string GetStringValue() + public override OneOf GetSerializableValue() => Value.ToString(); + + // -- Networking + protected IEntityNetworkingService NetworkingService; + public ulong InstanceId => NetworkingService?.GetNetworkIdForInstance(this) ?? 0ul; + public void SetNetworkOwner(IEntityNetworkingService networkingService) { - throw new NotImplementedException(); + NetworkingService = networkingService; + if (NetworkingService is null) + { + return; + } + NetworkingService.RegisterNetVar(this); } - public bool TrySetValue(OneOf value) - { - throw new NotImplementedException(); - } - - public bool IsAssignable(OneOf value) - { - throw new NotImplementedException(); - } - - public event Func, bool> IsNewValueValid; - public T Value { get; } - public bool TrySetValue(T value) - { - throw new NotImplementedException(); - } - - public bool IsAssignable(T value) - { - throw new NotImplementedException(); - } - - event Action> ISettingEntry.OnValueChanged - { - add => throw new NotImplementedException(); - remove => throw new NotImplementedException(); - } - - event Action ISettingBase.OnValueChanged - { - add => throw new NotImplementedException(); - remove => throw new NotImplementedException(); - } - - public OneOf GetSerializableValue() - { - throw new NotImplementedException(); - } - - public Guid InstanceId { get; } - public NetSync SyncType { get; } - public ClientPermissions WritePermissions { get; } + public NetSync SyncType => ConfigInfo.NetSync; + // needs to be added IConfigInfo + public ClientPermissions WritePermissions => throw new NotImplementedException(); public void ReadNetMessage(IReadMessage message) { - throw new NotImplementedException(); + if (SyncType == NetSync.None || NetworkingService is null) + { + return; + } + + try + { + if (typeof(T).IsEnum) + { + TrySetValue((T)(object)message.ReadInt32()); + } + + // No...there's no better way to do this... + var typeCode = Type.GetTypeCode(typeof(T)); + switch (typeCode) + { + case TypeCode.Boolean: + TrySetValue((T)Convert.ChangeType(message.ReadBoolean(), typeCode)); + return; + case TypeCode.Byte: + TrySetValue((T)Convert.ChangeType(message.ReadByte(), typeCode)); + return; + // SByte not supported by interface + case TypeCode.SByte: + TrySetValue((T)Convert.ChangeType(message.ReadInt16(), typeCode)); + return; + case TypeCode.Int16: + TrySetValue((T)Convert.ChangeType(message.ReadInt16(), typeCode)); + return; + case TypeCode.Char: + case TypeCode.UInt16: + TrySetValue((T)Convert.ChangeType(message.ReadUInt16(), typeCode)); + return; + case TypeCode.Int32: + TrySetValue((T)Convert.ChangeType(message.ReadInt32(), typeCode)); + return; + case TypeCode.UInt32: + TrySetValue((T)Convert.ChangeType(message.ReadUInt32(), typeCode)); + return; + case TypeCode.Int64: + TrySetValue((T)Convert.ChangeType(message.ReadInt64(), typeCode)); + return; + case TypeCode.UInt64: + TrySetValue((T)Convert.ChangeType(message.ReadUInt64(), typeCode)); + return; + case TypeCode.Single: + TrySetValue((T)Convert.ChangeType(message.ReadSingle(), typeCode)); + return; + case TypeCode.Double: + TrySetValue((T)Convert.ChangeType(message.ReadDouble(), typeCode)); + return; + case TypeCode.String: + TrySetValue((T)Convert.ChangeType(message.ReadString(), typeCode)); + return; + case TypeCode.Decimal: + default: + ThrowHelper.ThrowNotSupportedException($"{nameof(SettingEntry)}: The type {typeof(T).Name} is not supported."); + break; + } + } + catch (Exception e) + { + // Suppress unless we're testing. +#if DEBUG + throw; +#endif + } } public void WriteNetMessage(IWriteMessage message) { - throw new NotImplementedException(); + if (SyncType == NetSync.None || NetworkingService is null) + { + return; + } + + try + { + if (typeof(T).IsEnum) + { + message.WriteInt32((int)((IConvertible)Value)); + } + + // No...there's no better way to do this... + var typeCode = Type.GetTypeCode(typeof(T)); + switch (typeCode) + { + case TypeCode.Boolean: + message.WriteBoolean((bool)Convert.ChangeType(Value, typeCode)!); + return; + case TypeCode.Byte: + message.WriteByte((byte)Convert.ChangeType(Value, typeCode)!); + return; + // SByte not supported by interface + case TypeCode.SByte: + message.WriteInt16((short)Convert.ChangeType(Value, typeCode)!); + return; + case TypeCode.Int16: + message.WriteInt16((short)Convert.ChangeType(Value, typeCode)!); + return; + case TypeCode.Char: + case TypeCode.UInt16: + message.WriteUInt16((ushort)Convert.ChangeType(Value, typeCode)!); + return; + case TypeCode.Int32: + message.WriteInt32((int)Convert.ChangeType(Value, typeCode)!); + return; + case TypeCode.UInt32: + message.WriteUInt32((uint)Convert.ChangeType(Value, typeCode)!); + return; + case TypeCode.Int64: + message.WriteInt64((long)Convert.ChangeType(Value, typeCode)!); + return; + case TypeCode.UInt64: + message.WriteUInt64((ulong)Convert.ChangeType(Value, typeCode)!); + return; + case TypeCode.Single: + message.WriteSingle((float)Convert.ChangeType(Value, typeCode)!); + return; + case TypeCode.Double: + message.WriteDouble((double)Convert.ChangeType(Value, typeCode)!); + return; + case TypeCode.String: + message.WriteString((string)Convert.ChangeType(Value, typeCode)!); + return; + case TypeCode.Decimal: + default: + ThrowHelper.ThrowNotSupportedException($"{nameof(SettingEntry)}: The type {typeof(T).Name} is not supported."); + break; + } + } + catch (Exception e) + { + // Suppress unless we're testing. +#if DEBUG + throw; +#endif + } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingList.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingList.cs deleted file mode 100644 index 313c4f9b3..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingList.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Xml.Linq; -using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services; -using Barotrauma.Networking; -using OneOf; - -namespace Barotrauma.LuaCs.Configuration; - -public class SettingList : ISettingList where T : IEquatable -{ - public string InternalName { get; } - public ContentPackage OwnerPackage { get; } - public bool Equals(ISettingBase other) - { - throw new NotImplementedException(); - } - - public void Dispose() - { - throw new NotImplementedException(); - } - - public Type GetValueType() - { - throw new NotImplementedException(); - } - - public string GetStringValue() - { - throw new NotImplementedException(); - } - - public bool TrySetValue(OneOf value) - { - throw new NotImplementedException(); - } - - public bool IsAssignable(OneOf value) - { - throw new NotImplementedException(); - } - - public event Func, bool> IsNewValueValid; - public T Value { get; } - public bool TrySetValue(T value) - { - throw new NotImplementedException(); - } - - public bool IsAssignable(T value) - { - throw new NotImplementedException(); - } - - event Action> ISettingList.OnValueChanged - { - add => throw new NotImplementedException(); - remove => throw new NotImplementedException(); - } - - public IReadOnlyList Options { get; } - - event Action> ISettingEntry.OnValueChanged - { - add => throw new NotImplementedException(); - remove => throw new NotImplementedException(); - } - - event Action ISettingBase.OnValueChanged - { - add => throw new NotImplementedException(); - remove => throw new NotImplementedException(); - } - - public OneOf GetSerializableValue() - { - throw new NotImplementedException(); - } - - public Guid InstanceId { get; } - public NetSync SyncType { get; } - public ClientPermissions WritePermissions { get; } - public void ReadNetMessage(IReadMessage message) - { - throw new NotImplementedException(); - } - - public void WriteNetMessage(IWriteMessage message) - { - throw new NotImplementedException(); - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index c2b75b325..0e044d6f9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -106,7 +106,7 @@ namespace Barotrauma internal set => _isCsEnabled?.TrySetValue(value); } - private ISettingEntry _isCsEnabled; + private SettingEntry _isCsEnabled; /// /// Whether the popup error GUI should be hidden/suppressed. @@ -116,7 +116,7 @@ namespace Barotrauma get => _disableErrorGUIOverlay?.Value ?? false; internal set => _disableErrorGUIOverlay?.TrySetValue(value); } - private ISettingEntry _disableErrorGUIOverlay; + private SettingEntry _disableErrorGUIOverlay; /// /// Whether usernames are anonymized or show in logs. @@ -126,7 +126,7 @@ namespace Barotrauma get => _hideUserNamesInLogs?.Value ?? false; internal set => _hideUserNamesInLogs?.TrySetValue(value); } - private ISettingEntry _hideUserNamesInLogs; + private SettingEntry _hideUserNamesInLogs; /// /// The SteamId of the Workshop LuaCs CPackage in use, if available. @@ -136,7 +136,7 @@ namespace Barotrauma get => _luaForBarotraumaSteamId?.Value ?? 0; internal set => _luaForBarotraumaSteamId?.TrySetValue(value); } - private ISettingEntry _luaForBarotraumaSteamId; + private SettingEntry _luaForBarotraumaSteamId; /// /// TODO: @evilfactory@users.noreply.github.com @@ -146,7 +146,7 @@ namespace Barotrauma get => _restrictMessageSize?.Value ?? false; internal set => _restrictMessageSize?.TrySetValue(value); } - private ISettingEntry _restrictMessageSize; + private SettingEntry _restrictMessageSize; /// /// The local save path for all local data storage for mods. @@ -156,21 +156,21 @@ namespace Barotrauma get => _localDataSavePath?.Value ?? Path.Combine(Directory.GetCurrentDirectory(), "/Data/Mods"); internal set => _localDataSavePath?.TrySetValue(value); } - private ISettingEntry _localDataSavePath; + private SettingEntry _localDataSavePath; void LoadLuaCsConfig() { - _isCsEnabled = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "IsCsEnabled", out var val1) ? val1 + _isCsEnabled = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "IsCsEnabled", out var val1) ? val1 : throw new NullReferenceException($"{nameof(IsCsEnabled)} cannot be loaded."); - _disableErrorGUIOverlay = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "DisableErrorGUIOverlay", out var val3) ? val3 + _disableErrorGUIOverlay = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "DisableErrorGUIOverlay", out var val3) ? val3 : throw new NullReferenceException($"{nameof(DisableErrorGUIOverlay)} cannot be loaded."); - _hideUserNamesInLogs = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "HideUserNamesInLogs", out var val4) ? val4 + _hideUserNamesInLogs = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "HideUserNamesInLogs", out var val4) ? val4 : throw new NullReferenceException($"{nameof(HideUserNamesInLogs)} cannot be loaded."); - _luaForBarotraumaSteamId = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "LuaForBarotraumaSteamId", out var val5) ? val5 + _luaForBarotraumaSteamId = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "LuaForBarotraumaSteamId", out var val5) ? val5 : throw new NullReferenceException($"{nameof(LuaForBarotraumaSteamId)} cannot be loaded."); - _restrictMessageSize = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "RestrictMessageSize", out var val7) ? val7 + _restrictMessageSize = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "RestrictMessageSize", out var val7) ? val7 : throw new NullReferenceException($"{nameof(RestrictMessageSize)} cannot be loaded."); - _localDataSavePath = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "LocalDataSavePath", out var val8) ? val8 + _localDataSavePath = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "LocalDataSavePath", out var val8) ? val8 : throw new NullReferenceException($"{nameof(LocalDataSavePath)} cannot be loaded."); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetworkSyncEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetworkSyncEntity.cs index 3a93e787b..1847dc2ef 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetworkSyncEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetworkSyncEntity.cs @@ -11,7 +11,14 @@ public interface INetworkSyncEntity /// /// Network-synchronized object ID. Used for networking send/receive message events. /// - Guid InstanceId { get; } + ulong InstanceId { get; } + + /// + /// Sets the that is currently managing this instance. The + /// is retrieved from here. + /// + /// The networking service managing this instance or null to deregister. + void SetNetworkOwner(IEntityNetworkingService networkingService); /// /// Synchronization type. See for more information. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigInitializers.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigInitializers.cs deleted file mode 100644 index a4db95e52..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigInitializers.cs +++ /dev/null @@ -1,323 +0,0 @@ -using System; -using System.Numerics; -using Barotrauma.LuaCs.Configuration; -using Barotrauma.LuaCs.Data; -using Barotrauma.Networking; -using FluentResults; -using Microsoft.Xna.Framework; -using Vector2 = Microsoft.Xna.Framework.Vector2; -using Vector3 = Microsoft.Xna.Framework.Vector3; -using Vector4 = Microsoft.Xna.Framework.Vector4; - -namespace Barotrauma.LuaCs.Services; - -public class ConfigInitializers : IService -{ - // parameterless .ctor - public ConfigInitializers() - { - } - - public void Dispose() - { - // stateless service - return; - } - - // stateless service - public bool IsDisposed => false; - - private Result> CreateConfigEntry(IConfigInfo configInfo, - Action, IReadMessage> readHandler, - Action, IWriteMessage> writeHandler) - where T : IEquatable - { - throw new NotImplementedException(); - } - - private Result> CreateConfigList(IConfigInfo configInfo, - Action, IReadMessage> readHandler, Action, IWriteMessage> writeHandler) - where T : IEquatable - { - throw new NotImplementedException(); - } - - public void RegisterTypeInitializers(IConfigService configService) - { - if (configService == null) - throw new ArgumentNullException($"{nameof(RegisterTypeInitializers)}: {nameof(IConfigService)} is null."); - - /*configService.RegisterTypeInitializer>(this.CreateConfigBool); - configService.RegisterTypeInitializer>(this.CreateConfigSbyte); - configService.RegisterTypeInitializer>(this.CreateConfigByte); - configService.RegisterTypeInitializer>(this.CreateConfigShort); - configService.RegisterTypeInitializer>(this.CreateConfigUShort); - configService.RegisterTypeInitializer>(this.CreateConfigInt32); - configService.RegisterTypeInitializer>(this.CreateConfigUInt32); - configService.RegisterTypeInitializer>(this.CreateConfigInt64); - configService.RegisterTypeInitializer>(this.CreateConfigUInt64); - configService.RegisterTypeInitializer>(this.CreateConfigFloat32); - configService.RegisterTypeInitializer>(this.CreateConfigFloat64); - configService.RegisterTypeInitializer>(this.CreateConfigFloat128); - configService.RegisterTypeInitializer>(this.CreateConfigChar); - configService.RegisterTypeInitializer>(this.CreateConfigString); - configService.RegisterTypeInitializer>(this.CreateConfigColor); - configService.RegisterTypeInitializer>(this.CreateConfigVector2); - configService.RegisterTypeInitializer>(this.CreateConfigVector3); - configService.RegisterTypeInitializer>(this.CreateConfigVector4);*/ - } - - - #region InitializerWrappers_NetworkInjected - - private void AssignValueConditional(T val, ISettingEntry inst) where T : IEquatable - { -#if SERVER - if (inst.SyncType is NetSync.None or NetSync.ServerAuthority) - throw new InvalidOperationException($"[Server] Tried to assign a net value to a type that does not support sync: {inst.SyncType}. Name: {inst.InternalName}, Package: {inst.OwnerPackage.Name}"); - inst.TrySetValue(val); -#else - if (inst.SyncType is NetSync.None or NetSync.ClientOneWay) - throw new InvalidOperationException($"[Client] Tried to assign a net value to a type that does not support sync: {inst.SyncType}. Name: {inst.InternalName}, Package: {inst.OwnerPackage.Name}"); - inst.TrySetValue(val); -#endif - } - - private Result> CreateConfigBool(IConfigInfo configInfo) - { - return CreateConfigEntry(configInfo, (inst, readMsg) => - { - AssignValueConditional(readMsg.ReadBoolean(), inst); - - }, (inst, writeMsg) => - { - writeMsg.WriteBoolean(inst.Value); - }); - } - - private Result> CreateConfigSbyte(IConfigInfo configInfo) - { - return CreateConfigEntry(configInfo, (inst, readMsg) => - { - AssignValueConditional((sbyte)readMsg.ReadInt16(), inst); - - }, (inst, writeMsg) => - { - writeMsg.WriteInt16((short)inst.Value); - }); - } - - private Result> CreateConfigByte(IConfigInfo configInfo) - { - return CreateConfigEntry(configInfo, (inst, readMsg) => - { - AssignValueConditional((byte)readMsg.ReadUInt16(), inst); - - }, (inst, writeMsg) => - { - writeMsg.WriteUInt16((byte)inst.Value); - }); - } - - private Result> CreateConfigShort(IConfigInfo configInfo) - { - return CreateConfigEntry(configInfo, (inst, readMsg) => - { - AssignValueConditional(readMsg.ReadInt16(), inst); - - }, (inst, writeMsg) => - { - writeMsg.WriteInt16(inst.Value); - }); - } - - private Result> CreateConfigUShort(IConfigInfo configInfo) - { - return CreateConfigEntry(configInfo, (inst, readMsg) => - { - AssignValueConditional(readMsg.ReadUInt16(), inst); - - }, (inst, writeMsg) => - { - writeMsg.WriteUInt16(inst.Value); - }); - } - - private Result> CreateConfigInt32(IConfigInfo configInfo) - { - return CreateConfigEntry(configInfo, (inst, readMsg) => - { - AssignValueConditional(readMsg.ReadInt32(), inst); - - }, (inst, writeMsg) => - { - writeMsg.WriteInt32(inst.Value); - }); - } - - private Result> CreateConfigUInt32(IConfigInfo configInfo) - { - return CreateConfigEntry(configInfo, (inst, readMsg) => - { - AssignValueConditional(readMsg.ReadUInt32(), inst); - - }, (inst, writeMsg) => - { - writeMsg.WriteUInt32(inst.Value); - }); - } - - private Result> CreateConfigInt64(IConfigInfo configInfo) - { - return CreateConfigEntry(configInfo, (inst, readMsg) => - { - AssignValueConditional(readMsg.ReadInt64(), inst); - - }, (inst, writeMsg) => - { - writeMsg.WriteInt64(inst.Value); - }); - } - - private Result> CreateConfigUInt64(IConfigInfo configInfo) - { - return CreateConfigEntry(configInfo, (inst, readMsg) => - { - AssignValueConditional(readMsg.ReadUInt64(), inst); - - }, (inst, writeMsg) => - { - writeMsg.WriteUInt64(inst.Value); - }); - } - - private Result> CreateConfigFloat32(IConfigInfo configInfo) - { - return CreateConfigEntry(configInfo, (inst, readMsg) => - { - AssignValueConditional(readMsg.ReadSingle(), inst); - - }, (inst, writeMsg) => - { - writeMsg.WriteSingle(inst.Value); - }); - } - - private Result> CreateConfigFloat64(IConfigInfo configInfo) - { - return CreateConfigEntry(configInfo, (inst, readMsg) => - { - AssignValueConditional(readMsg.ReadDouble(), inst); - - }, (inst, writeMsg) => - { - writeMsg.WriteDouble(inst.Value); - }); - } - - private Result> CreateConfigFloat128(IConfigInfo configInfo) - { - return CreateConfigEntry(configInfo, (inst, readMsg) => - { - var decimalArr = new int[4]; - decimalArr[0] = readMsg.ReadInt32(); - decimalArr[1] = readMsg.ReadInt32(); - decimalArr[2] = readMsg.ReadInt32(); - decimalArr[3] = readMsg.ReadInt32(); - AssignValueConditional(new decimal(decimalArr), inst); - - }, (inst, writeMsg) => - { - var decimalArr = Decimal.GetBits(inst.Value); - writeMsg.WriteInt32(decimalArr[0]); - writeMsg.WriteInt32(decimalArr[1]); - writeMsg.WriteInt32(decimalArr[2]); - writeMsg.WriteInt32(decimalArr[3]); - }); - } - - private Result> CreateConfigChar(IConfigInfo configInfo) - { - return CreateConfigEntry(configInfo, (inst, readMsg) => - { - AssignValueConditional((char)readMsg.ReadUInt16(), inst); - - }, (inst, writeMsg) => - { - writeMsg.WriteUInt16(inst.Value); - }); - } - - private Result> CreateConfigString(IConfigInfo configInfo) - { - return CreateConfigEntry(configInfo, (inst, readMsg) => - { - AssignValueConditional(readMsg.ReadString(), inst); - - }, (inst, writeMsg) => - { - writeMsg.WriteString(inst.Value); - }); - } - - private Result> CreateConfigColor(IConfigInfo configInfo) - { - return CreateConfigEntry(configInfo, (inst, readMsg) => - { - AssignValueConditional(readMsg.ReadColorR8G8B8A8(), inst); - - }, (inst, writeMsg) => - { - writeMsg.WriteColorR8G8B8A8(inst.Value); - }); - } - - private Result> CreateConfigVector2(IConfigInfo configInfo) - { - return CreateConfigEntry(configInfo, (inst, readMsg) => - { - AssignValueConditional(new Vector2(readMsg.ReadSingle(), readMsg.ReadSingle()), inst); - - }, (inst, writeMsg) => - { - writeMsg.WriteSingle(inst.Value.X); - writeMsg.WriteSingle(inst.Value.Y); - }); - } - - private Result> CreateConfigVector3(IConfigInfo configInfo) - { - return CreateConfigEntry(configInfo, (inst, readMsg) => - { - AssignValueConditional(new Vector3(readMsg.ReadSingle(), readMsg.ReadSingle(), readMsg.ReadSingle()), inst); - - }, (inst, writeMsg) => - { - writeMsg.WriteSingle(inst.Value.X); - writeMsg.WriteSingle(inst.Value.Y); - writeMsg.WriteSingle(inst.Value.Z); - }); - } - - private Result> CreateConfigVector4(IConfigInfo configInfo) - { - return CreateConfigEntry(configInfo, (inst, readMsg) => - { - AssignValueConditional(new Vector4( - readMsg.ReadSingle(), - readMsg.ReadSingle(), - readMsg.ReadSingle(), - readMsg.ReadSingle()), inst); - - }, (inst, writeMsg) => - { - writeMsg.WriteSingle(inst.Value.X); - writeMsg.WriteSingle(inst.Value.Y); - writeMsg.WriteSingle(inst.Value.Z); - writeMsg.WriteSingle(inst.Value.W); - }); - } - - - #endregion -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs index 463821d96..1c8b6b32d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs @@ -104,6 +104,11 @@ internal partial class NetworkingService : INetworkingService IsDisposed = true; } + public ulong GetNetworkIdForInstance(INetworkSyncEntity entity) + { + throw new NotImplementedException(); + } + public void RegisterNetVar(INetworkSyncEntity netVar) { throw new NotImplementedException(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs index 5d39e5e20..c3676da59 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs @@ -25,6 +25,7 @@ internal partial interface INetworkingService : IReusableService, ILuaCsNetworki public interface IEntityNetworkingService { + public ulong GetNetworkIdForInstance(INetworkSyncEntity entity); public void RegisterNetVar(INetworkSyncEntity netVar); public void SendNetVar(INetworkSyncEntity netVar); } From e75208507d03e176c9fc2d5c0582841beac7cbd1 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 5 Feb 2026 19:47:47 -0500 Subject: [PATCH 111/288] - Config Services almost ready. - Refactored and flattened namespaces. --- .../LuaCs/Configuration/ISettingControl.cs | 2 +- .../ClientSource/LuaCs/Data/IConfigInfo.cs | 2 +- .../ClientSource/LuaCs/LuaCsSetup.cs | 2 +- .../Compatibility/ILuaCsNetworking.cs | 2 +- .../LuaCs/Services/ConfigService.cs | 2 +- .../LuaCs/Services/IClientLoggerService.cs | 2 +- .../LuaCs/Services/IConfigService.cs | 4 +- .../LuaCs/Services/INetworkingService.cs | 2 +- .../LuaCs/Services/LoggerService.cs | 2 +- .../LuaCs/Services/NetworkingService.cs | 4 +- .../LuaCs/Services/INetworkingService.cs | 2 +- .../LuaCs/Services/NetworkingService.cs | 4 +- .../Compatibility/ILuaCsHook.cs | 3 +- .../LuaCs/Compatibility/ILuaCsLogger.cs | 6 + .../Compatibility/ILuaCsNetworking.cs | 2 +- .../LuaCs/Compatibility/ILuaCsShim.cs | 8 + .../Compatibility/ILuaCsTimer.cs | 2 +- .../Compatibility/ILuaCsUtility.cs | 2 +- .../Data/DataInterfaceImplementations.cs | 2 +- .../SharedSource/LuaCs/Data/IConfigInfo.cs | 2 +- .../ISettingTypeDef.cs | 9 +- .../LuaCs/Data/ServicesConfigData.cs | 2 +- .../{Configuration => Data}/SettingBase.cs | 2 +- .../{Configuration => Data}/SettingEntry.cs | 4 +- .../LuaCs/Data/SettingRangeEntry.cs | 76 ++ .../SettingsFactoryRegistrationProvider.cs | 67 + .../SharedSource/LuaCs/IEvents.cs | 4 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 11 +- .../Services/Compatibility/ILuaCsLogger.cs | 6 - .../Services/Compatibility/ILuaCsShim.cs | 6 - .../LuaCs/Services/PluginService.cs | 6 - .../LuaCs/Services/Safe/ILuaService.cs | 6 - .../INetCallback.cs | 2 +- .../INetworkSyncEntity.cs | 5 +- .../SharedSource/LuaCs/_Plugins/ACsMod.cs | 1 + .../LuaCs/_Plugins/ApplicationMode.cs | 6 - .../LuaCs/_Plugins/AssemblyLoader.cs | 2 +- .../_Plugins/AssemblyLoadingSuccessState.cs | 15 - .../LuaCs/_Plugins/CsPackageManager.cs | 1099 ----------------- .../LuaCs/_Plugins/IAssemblyLoaderService.cs | 2 +- .../LuaCs/_Plugins/IAssemblyPlugin.cs | 2 +- .../SharedSource/LuaCs/_Plugins/RunConfig.cs | 1 + .../{Services => _Services}/ConfigService.cs | 13 +- .../{Services => _Services}/EventService.cs | 4 +- .../{Services => _Services}/LoggerService.cs | 2 +- .../LuaCs/_Services/LuaCsInfoProvider.cs | 18 + .../LuaScriptManagementService.cs | 6 +- .../ModConfigFileParserService.cs | 2 +- .../ModConfigService.cs | 2 +- .../NetworkingService.cs | 4 +- .../PackageManagementService.cs | 2 +- .../PluginManagementService.cs | 7 +- .../LuaCs/_Services/PluginService.cs | 6 + .../Safe => _Services}/SafeStorageService.cs | 2 +- .../ServicesProvider.cs | 2 +- .../SettingsFileParserService.cs | 2 +- .../{Services => _Services}/StorageService.cs | 2 +- .../_Interfaces/IAssemblyManagementService.cs | 2 +- .../_Interfaces/IConfigService.cs | 15 +- .../_Interfaces/IEventService.cs | 6 +- .../_Interfaces}/IHelperServiceDefinitions.cs | 2 +- .../_Interfaces/ILoggerService.cs | 2 +- .../_Interfaces/ILuaCsInfoProvider.cs | 42 + .../ILuaScriptManagementService.cs | 2 +- .../_Interfaces/IModConfigService.cs | 4 +- .../_Interfaces/INetworkingService.cs | 6 +- .../_Interfaces/IPackageManagementService.cs | 2 +- .../_Interfaces/IPluginManagementService.cs | 2 +- .../_Interfaces/IPluginService.cs | 2 +- .../_Interfaces}/ISafeStorageService.cs | 2 +- .../_Interfaces/IService.cs | 2 +- .../_Interfaces/IServicesProvider.cs | 2 +- .../_Interfaces/IStorageService.cs | 2 +- .../_Lua}/DefaultLuaRegistrar.cs | 2 +- .../_Lua}/ILuaConfigService.cs | 4 +- .../_Lua}/ILuaDataService.cs | 2 +- .../_Lua}/ILuaEventService.cs | 4 +- .../_Lua}/ILuaNetworkingService.cs | 2 +- .../_Lua}/ILuaPackageManagementService.cs | 2 +- .../_Lua}/ILuaPackageService.cs | 2 +- .../Safe => _Services/_Lua}/ILuaPatcher.cs | 4 +- .../_Lua}/ILuaScriptLoader.cs | 2 +- .../LuaCs/_Services/_Lua/ILuaService.cs | 6 + .../LuaClasses/LuaBarotraumaAdditions.cs | 0 .../_Lua}/LuaClasses/LuaConverters.cs | 2 +- .../_Lua}/LuaClasses/LuaCsLogger.cs | 0 .../_Lua}/LuaClasses/LuaCsNetworking.cs | 0 .../LuaClasses/LuaCsPerformanceCounter.cs | 2 +- .../_Lua}/LuaClasses/LuaCsSteam.cs | 0 .../_Lua}/LuaClasses/LuaCsTimer.cs | 4 +- .../_Lua}/LuaClasses/LuaCsUtility.cs | 0 .../_Lua}/LuaClasses/LuaGame.cs | 4 +- .../_Lua}/LuaClasses/LuaPlatformAccessor.cs | 0 .../_Lua}/LuaClasses/LuaRequire.cs | 0 .../_Lua}/LuaClasses/LuaTypes.cs | 0 .../_Lua}/LuaPatcherCompat.cs | 6 +- .../_Lua}/LuaPatcherService.cs | 4 +- .../_Lua}/LuaScriptLoader.cs | 4 +- .../_Lua}/LuaUserDataService.cs | 2 +- .../_Lua}/SafeLuaUserDataService.cs | 4 +- .../BarotraumaTest/LuaCs/HookPatchHelpers.cs | 4 +- 101 files changed, 350 insertions(+), 1276 deletions(-) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => }/Compatibility/ILuaCsHook.cs (92%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsLogger.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => }/Compatibility/ILuaCsNetworking.cs (55%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsShim.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => }/Compatibility/ILuaCsTimer.cs (88%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => }/Compatibility/ILuaCsUtility.cs (84%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Configuration => Data}/ISettingTypeDef.cs (90%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Configuration => Data}/SettingBase.cs (97%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Configuration => Data}/SettingEntry.cs (99%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingRangeEntry.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsLogger.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsShim.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaService.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Networking => _Networking}/INetCallback.cs (81%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Networking => _Networking}/INetworkSyncEntity.cs (96%) delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ApplicationMode.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoadingSuccessState.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/CsPackageManager.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/ConfigService.cs (96%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/EventService.cs (99%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/LoggerService.cs (99%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/LuaScriptManagementService.cs (98%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Processing => _Services}/ModConfigFileParserService.cs (99%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Processing => _Services}/ModConfigService.cs (99%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/NetworkingService.cs (97%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/PackageManagementService.cs (99%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/PluginManagementService.cs (99%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginService.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services}/SafeStorageService.cs (99%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/ServicesProvider.cs (99%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Processing => _Services}/SettingsFileParserService.cs (99%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/StorageService.cs (99%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/_Interfaces/IAssemblyManagementService.cs (96%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/_Interfaces/IConfigService.cs (67%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/_Interfaces/IEventService.cs (91%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Processing => _Services/_Interfaces}/IHelperServiceDefinitions.cs (93%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/_Interfaces/ILoggerService.cs (95%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaCsInfoProvider.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/_Interfaces/ILuaScriptManagementService.cs (98%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/_Interfaces/IModConfigService.cs (90%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/_Interfaces/INetworkingService.cs (88%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/_Interfaces/IPackageManagementService.cs (97%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/_Interfaces/IPluginManagementService.cs (98%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/_Interfaces/IPluginService.cs (98%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Interfaces}/ISafeStorageService.cs (98%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/_Interfaces/IService.cs (96%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/_Interfaces/IServicesProvider.cs (99%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services => _Services}/_Interfaces/IStorageService.cs (99%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/DefaultLuaRegistrar.cs (99%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/ILuaConfigService.cs (61%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/ILuaDataService.cs (82%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/ILuaEventService.cs (95%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/ILuaNetworkingService.cs (58%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/ILuaPackageManagementService.cs (60%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/ILuaPackageService.cs (57%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/ILuaPatcher.cs (91%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/ILuaScriptLoader.cs (90%) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaService.cs rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/LuaClasses/LuaBarotraumaAdditions.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/LuaClasses/LuaConverters.cs (99%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/LuaClasses/LuaCsLogger.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/LuaClasses/LuaCsNetworking.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/LuaClasses/LuaCsPerformanceCounter.cs (98%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/LuaClasses/LuaCsSteam.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/LuaClasses/LuaCsTimer.cs (97%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/LuaClasses/LuaCsUtility.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/LuaClasses/LuaGame.cs (99%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/LuaClasses/LuaPlatformAccessor.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/LuaClasses/LuaRequire.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/LuaClasses/LuaTypes.cs (100%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/LuaPatcherCompat.cs (98%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/LuaPatcherService.cs (99%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/LuaScriptLoader.cs (98%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/LuaUserDataService.cs (99%) rename Barotrauma/BarotraumaShared/SharedSource/LuaCs/{Services/Safe => _Services/_Lua}/SafeLuaUserDataService.cs (99%) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/ISettingControl.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/ISettingControl.cs index f478dc50b..a560e4fc4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/ISettingControl.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/ISettingControl.cs @@ -1,6 +1,6 @@ using System; -namespace Barotrauma.LuaCs.Configuration; +namespace Barotrauma.LuaCs.Data; public interface ISettingControl : ISettingBase { diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs index 1e26659a1..5a902c753 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IConfigInfo.cs @@ -1,4 +1,4 @@ -using Barotrauma.LuaCs.Configuration; +using Barotrauma.LuaCs.Data; namespace Barotrauma.LuaCs.Data; diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index 6c5be4e53..20839ed92 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -5,7 +5,7 @@ using System.IO; using System.Linq; using System.Text; using Barotrauma.CharacterEditor; -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs; // ReSharper disable ObjectCreationAsStatement diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs index 570debbaf..dbfbb9530 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs @@ -1,6 +1,6 @@ using Barotrauma.Networking; -namespace Barotrauma.LuaCs.Services.Compatibility; +namespace Barotrauma.LuaCs.Compatibility; internal partial interface ILuaCsNetworking : ILuaCsShim { diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs index b9de7b917..f2294c49c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs @@ -5,7 +5,7 @@ using Barotrauma.LuaCs.Data; using Barotrauma.Networking; using FluentResults; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public sealed partial class ConfigService { diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IClientLoggerService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IClientLoggerService.cs index 54b5445cf..5c1ff7e0d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IClientLoggerService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IClientLoggerService.cs @@ -1,4 +1,4 @@ -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public interface IClientLoggerService : IReusableService { diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs index 7218ef2cc..34b44c316 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using System.Collections.Immutable; using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs; using Barotrauma.Networking; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public partial interface IConfigService { diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/INetworkingService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/INetworkingService.cs index d79efba19..e78f8a245 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/INetworkingService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/INetworkingService.cs @@ -1,6 +1,6 @@ using Barotrauma.Networking; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; internal partial interface INetworkingService : IReusableService { diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/LoggerService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/LoggerService.cs index aee2efabb..bb1af88a4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/LoggerService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/LoggerService.cs @@ -1,6 +1,6 @@ using Microsoft.Xna.Framework; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public partial class LoggerService : ILoggerService, IClientLoggerService { diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs index 623bef305..d19a3ae9e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs @@ -1,10 +1,10 @@ -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs; using Barotrauma.Networking; using System; using System.Collections.Concurrent; using System.Collections.Generic; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; partial class NetworkingService : INetworkingService { diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/INetworkingService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/INetworkingService.cs index 43e7e2f95..bc64aa010 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/INetworkingService.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/INetworkingService.cs @@ -1,7 +1,7 @@  using Barotrauma.Networking; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; internal partial interface INetworkingService : IReusableService { diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs index c5789827b..cddd49806 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs @@ -1,11 +1,11 @@ -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs; using Barotrauma.Networking; using System; using System.Collections.Generic; using System.Linq; // ReSharper disable once CheckNamespace -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; partial class NetworkingService : INetworkingService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsHook.cs similarity index 92% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsHook.cs index 6ef5d256e..69e57544d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsHook.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsHook.cs @@ -1,7 +1,8 @@ using System; using System.Reflection; +using Barotrauma.LuaCs; -namespace Barotrauma.LuaCs.Services.Compatibility; +namespace Barotrauma.LuaCs.Compatibility; public interface ILuaCsHook : ILuaPatcher, ILuaCsShim { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsLogger.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsLogger.cs new file mode 100644 index 000000000..bcf5a3ebc --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsLogger.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Compatibility; + +public interface ILuaCsLogger : ILuaCsShim +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs similarity index 55% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs index fe78085bd..0ac2c99d4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs @@ -1,4 +1,4 @@ -namespace Barotrauma.LuaCs.Services.Compatibility; +namespace Barotrauma.LuaCs.Compatibility; internal partial interface ILuaCsNetworking : ILuaCsShim { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsShim.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsShim.cs new file mode 100644 index 000000000..8f119b106 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsShim.cs @@ -0,0 +1,8 @@ +using Barotrauma.LuaCs; + +namespace Barotrauma.LuaCs.Compatibility; + +public interface ILuaCsShim : IService +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsTimer.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsTimer.cs similarity index 88% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsTimer.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsTimer.cs index f0a3f6b46..cc6c9081c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsTimer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsTimer.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Barotrauma.LuaCs.Services.Compatibility; +namespace Barotrauma.LuaCs.Compatibility; internal partial interface ILuaCsTimer : ILuaCsShim { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsUtility.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsUtility.cs similarity index 84% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsUtility.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsUtility.cs index 4e1bde042..3ab6fafed 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsUtility.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsUtility.cs @@ -1,4 +1,4 @@ -namespace Barotrauma.LuaCs.Services.Compatibility; +namespace Barotrauma.LuaCs.Compatibility; public interface ILuaCsUtility : ILuaCsShim { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs index 514188b3f..0c0fad9cd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Xml.Linq; -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs; using Barotrauma.Steam; using OneOf; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs index 164d37791..696683af9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigInfo.cs @@ -1,6 +1,6 @@ using System; using System.Xml.Linq; -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs; using Barotrauma.Networking; namespace Barotrauma.LuaCs.Data; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingTypeDef.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs similarity index 90% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingTypeDef.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs index 0f2534db6..89e5b9662 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/ISettingTypeDef.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Xml.Linq; using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs; using Barotrauma.Networking; -namespace Barotrauma.LuaCs.Configuration; +namespace Barotrauma.LuaCs.Data; public interface ISettingBase : IDataInfo, IEquatable, IDisposable { @@ -68,9 +68,10 @@ public interface ISettingBase : ISettingBase where T : IEquatable, IConver /// /// Creates a setting representing a value of the given with a minimum and maximum value. -/// Must be a type compatible with . +/// Can only be either an or a . /// -/// The value type. See +/// The type selection is limited by the Undertow implementation of the GUI Slider. +/// The value type, either or public interface ISettingRangeBase : ISettingBase where T : IEquatable, IConvertible { T MinValue { get; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs index f24283ac5..87cfdf29c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs @@ -7,7 +7,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Security.AccessControl; -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs; using Barotrauma.Networking; using FluentResults; using OneOf.Types; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingBase.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs similarity index 97% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingBase.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs index ca7fbdcc0..a79c453c5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingBase.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs @@ -3,7 +3,7 @@ using System.Xml.Linq; using Barotrauma.LuaCs.Data; using OneOf; -namespace Barotrauma.LuaCs.Configuration; +namespace Barotrauma.LuaCs.Data; public abstract class SettingBase : ISettingBase { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingEntry.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs index 040a3a065..55788c8f3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Configuration/SettingEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs @@ -2,12 +2,12 @@ using System.Runtime.CompilerServices; using System.Xml.Linq; using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs; using Barotrauma.Networking; using Microsoft.Toolkit.Diagnostics; using OneOf; -namespace Barotrauma.LuaCs.Configuration; +namespace Barotrauma.LuaCs.Data; public class SettingEntry : SettingBase, ISettingBase, INetworkSyncEntity where T : IEquatable, IConvertible { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingRangeEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingRangeEntry.cs new file mode 100644 index 000000000..1b094674f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingRangeEntry.cs @@ -0,0 +1,76 @@ +using System; +using System.Xml.Linq; +using Barotrauma.LuaCs.Data; +using Microsoft.Toolkit.Diagnostics; +using OneOf; + +namespace Barotrauma.LuaCs.Data; + +public abstract class SettingRangeBase : SettingEntry, ISettingRangeBase where T : IEquatable, IConvertible +{ + public SettingRangeBase(IConfigInfo configInfo, Func, bool> valueChangePredicate) : base(configInfo, valueChangePredicate) + { + } + + public T MinValue { get; protected init; } + public T MaxValue { get; protected init; } + public int IncrementalSteps { get; protected init; } +} + +public class SettingRangeFloat : SettingRangeBase +{ + public class RangeFactory : ISettingBase.IFactory + { + public SettingRangeFloat CreateInstance(IConfigInfo configInfo, Func, bool> valueChangePredicate) + { + Guard.IsNotNull(configInfo, nameof(configInfo)); + return new SettingRangeFloat(configInfo, valueChangePredicate); + } + } + + public SettingRangeFloat(IConfigInfo configInfo, Func, bool> valueChangePredicate) : base(configInfo, valueChangePredicate) + { + // funny values in case they forget to set them in the config. + MinValue = configInfo.Element.GetAttributeFloat("Min", float.MinValue); + MaxValue = configInfo.Element.GetAttributeFloat("Max", float.MaxValue); + IncrementalSteps = configInfo.Element.GetAttributeInt("Steps", 3); + } + + public override bool TrySetValue(float value) + { + if (value > MaxValue || value < MinValue) + { + return false; + } + return base.TrySetValue(value); + } +} + +public class SettingRangeInt : SettingRangeBase +{ + public class RangeFactory : ISettingBase.IFactory + { + public SettingRangeInt CreateInstance(IConfigInfo configInfo, Func, bool> valueChangePredicate) + { + Guard.IsNotNull(configInfo, nameof(configInfo)); + return new SettingRangeInt(configInfo, valueChangePredicate); + } + } + + public SettingRangeInt(IConfigInfo configInfo, Func, bool> valueChangePredicate) : base(configInfo, valueChangePredicate) + { + // funny values in case they forget to set them in the config. + MinValue = configInfo.Element.GetAttributeInt("Min", int.MinValue); + MaxValue = configInfo.Element.GetAttributeInt("Max", int.MaxValue); + IncrementalSteps = configInfo.Element.GetAttributeInt("Steps", 3); + } + + public override bool TrySetValue(int value) + { + if (value > MaxValue || value < MinValue) + { + return false; + } + return base.TrySetValue(value); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs new file mode 100644 index 000000000..35bf2b73b --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs @@ -0,0 +1,67 @@ +using System; +using System.Xml.Linq; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs; + +namespace Barotrauma.LuaCs.Data; + +public interface ISettingsRegistrationProvider : IService +{ + void RegisterTypeProviders(IConfigService configService, Func, bool> valueChangePredicate); +} + +public class SettingsEntryRegistrar : ISettingsRegistrationProvider +{ + private ILuaCsInfoProvider _infoProvider; + + public SettingsEntryRegistrar(ILuaCsInfoProvider infoProvider) + { + _infoProvider = infoProvider; + } + + public void RegisterTypeProviders(IConfigService configService, Func, bool> valueChangePredicate) + { + // ISettingBase + RegisterSettingEntry(configService, "bool"); + RegisterSettingEntry(configService, "byte"); + RegisterSettingEntry(configService, "sbyte"); + RegisterSettingEntry(configService, "short"); + RegisterSettingEntry(configService, "ushort"); + RegisterSettingEntry(configService, "int"); + RegisterSettingEntry(configService, "uint"); + RegisterSettingEntry(configService, "long"); + RegisterSettingEntry(configService, "ulong"); + RegisterSettingEntry(configService, "string"); + // ISettingRangeBase + // ISettingList + } + + private void RegisterSettingEntry(IConfigService configService, string typeName) where T : IEquatable, IConvertible + { + configService.RegisterSettingTypeInitializer(typeName, cfgInfo => + { + return new SettingEntry.Factory().CreateInstance(cfgInfo.Info, (val) => + { + return !cfgInfo.Info.Element.GetAttributeBool("ReadOnly", false) + && cfgInfo.Info.EditableStates.HasFlag(_infoProvider?.CurrentRunState ?? RunState.Running); + }); + }); + } + + public void Dispose() + { + if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) + { + return; + } + _infoProvider.Dispose(); + _infoProvider = null; + } + + private int _isDisposed; + public bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index c65f4051b..7eca1b12e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Reflection; -using Barotrauma.LuaCs.Configuration; -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs; using Barotrauma.Networking; using Dynamitey; using ImpromptuInterface; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 0e044d6f9..19ea7031e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -9,20 +9,16 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; using Barotrauma.LuaCs; -using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Events; -using Barotrauma.LuaCs.Services; -using Barotrauma.LuaCs.Services.Compatibility; -using Barotrauma.LuaCs.Services.Processing; -using Barotrauma.LuaCs.Services.Safe; +using Barotrauma.LuaCs.Compatibility; using Barotrauma.Networking; using Barotrauma.Steam; using FluentResults; using ImpromptuInterface; using LightInject; using Microsoft.Toolkit.Diagnostics; -using AssemblyLoader = Barotrauma.LuaCs.Services.AssemblyLoader; +using AssemblyLoader = Barotrauma.LuaCs.AssemblyLoader; namespace Barotrauma { @@ -193,10 +189,11 @@ namespace Barotrauma servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); // TODO: INetworkingService servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsLogger.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsLogger.cs deleted file mode 100644 index 30c82860e..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsLogger.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Barotrauma.LuaCs.Services.Compatibility; - -public interface ILuaCsLogger : ILuaCsShim -{ - -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsShim.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsShim.cs deleted file mode 100644 index 19a9cf663..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Compatibility/ILuaCsShim.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Barotrauma.LuaCs.Services.Compatibility; - -public interface ILuaCsShim : IService -{ - -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginService.cs deleted file mode 100644 index 098fa1954..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Barotrauma.LuaCs.Services; - -public class PluginService -{ - -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaService.cs deleted file mode 100644 index 0c6093319..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Barotrauma.LuaCs.Services.Safe; - -public interface ILuaService -{ - -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetCallback.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/INetCallback.cs similarity index 81% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetCallback.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/INetCallback.cs index 9d3c86853..62ce88f26 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetCallback.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/INetCallback.cs @@ -1,6 +1,6 @@ using System; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public partial interface INetCallback { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetworkSyncEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/INetworkSyncEntity.cs similarity index 96% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetworkSyncEntity.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/INetworkSyncEntity.cs index 1847dc2ef..c9c5e3851 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Networking/INetworkSyncEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/INetworkSyncEntity.cs @@ -1,10 +1,9 @@ using System; -using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs; using Barotrauma.Networking; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public interface INetworkSyncEntity { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs index d453dfbba..9ee8833bc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using Barotrauma.LuaCs; namespace Barotrauma { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ApplicationMode.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ApplicationMode.cs deleted file mode 100644 index 6e60184bb..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ApplicationMode.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Barotrauma; - -public enum ApplicationMode -{ - Client, Server -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs index 87f920d70..6a323c12e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs @@ -21,7 +21,7 @@ using Path = System.IO.Path; [assembly: InternalsVisibleTo(IAssemblyLoaderService.InternalsAwareAssemblyName)] -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService { public class Factory : IAssemblyLoaderService.IFactory diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoadingSuccessState.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoadingSuccessState.cs deleted file mode 100644 index e55821eb3..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoadingSuccessState.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Barotrauma; - -public enum AssemblyLoadingSuccessState -{ - ACLLoadFailure, - AlreadyLoaded, - BadFilePath, - CannotLoadFile, - InvalidAssembly, - NoAssemblyFound, - PluginInstanceFailure, - BadName, - CannotLoadFromStream, - Success -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/CsPackageManager.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/CsPackageManager.cs deleted file mode 100644 index 9e7060756..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/CsPackageManager.cs +++ /dev/null @@ -1,1099 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using Barotrauma.LuaCs; -using Barotrauma.LuaCs.Services; -using Barotrauma.Steam; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using MonoMod.Utils; - -// ReSharper disable InconsistentNaming - -namespace Barotrauma; - -/*public sealed class CsPackageManager : IDisposable -{ - #region PRIVATE_FUNCDATA - - private static readonly CSharpParseOptions ScriptParseOptions = CSharpParseOptions.Default - .WithPreprocessorSymbols(new[] - { -#if SERVER - "SERVER" -#elif CLIENT - "CLIENT" -#else - "UNDEFINED" -#endif -#if DEBUG - ,"DEBUG" -#endif - }); - -#if WINDOWS - private const string PLATFORM_TARGET = "Windows"; -#elif OSX - private const string PLATFORM_TARGET = "OSX"; -#elif LINUX - private const string PLATFORM_TARGET = "Linux"; -#endif - -#if CLIENT - private const string ARCHITECTURE_TARGET = "Client"; -#elif SERVER - private const string ARCHITECTURE_TARGET = "Server"; -#endif - - private static readonly CSharpCompilationOptions CompilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) - .WithMetadataImportOptions(MetadataImportOptions.All) -#if DEBUG - .WithOptimizationLevel(OptimizationLevel.Debug) -#else - .WithOptimizationLevel(OptimizationLevel.Release) -#endif - .WithAllowUnsafe(true); - - private static readonly SyntaxTree BaseAssemblyImports = CSharpSyntaxTree.ParseText( - new StringBuilder() - .AppendLine("using System.Reflection;") - .AppendLine("using Barotrauma;") - .AppendLine("using System.Runtime.CompilerServices;") - .AppendLine("[assembly: IgnoresAccessChecksTo(\"BarotraumaCore\")]") -#if CLIENT - .AppendLine("[assembly: IgnoresAccessChecksTo(\"Barotrauma\")]") -#elif SERVER - .AppendLine("[assembly: IgnoresAccessChecksTo(\"DedicatedServer\")]") -#endif - .ToString(), - ScriptParseOptions); - - private readonly string[] _publicizedAssembliesToLoad = - { - "BarotraumaCore.dll", -#if CLIENT - "Barotrauma.dll" -#elif SERVER - "DedicatedServer.dll" -#endif - }; - - - private const string SCRIPT_FILE_REGEX = "*.cs"; - private const string ASSEMBLY_FILE_REGEX = "*.dll"; - - private readonly float _assemblyUnloadTimeoutSeconds = 6f; - private Guid _publicizedAssemblyLoader; - private readonly List _currentPackagesByLoadOrder = new(); - private readonly Dictionary> _packagesDependencies = new(); - private readonly Dictionary _loadedCompiledPackageAssemblies = new(); - private readonly Dictionary _reverseLookupGuidList = new(); - private readonly Dictionary> _loadedPlugins = new (); - private readonly Dictionary> _pluginTypes = new(); // where Type : IAssemblyPlugin - private readonly Dictionary _packageRunConfigs = new(); - private readonly Dictionary> _luaRegisteredTypes = new(); - private readonly AssemblyManager _assemblyManager; - private readonly LuaCsSetup _luaCsSetup; - private DateTime _assemblyUnloadStartTime; - - - #endregion - - #region PUBLIC_API - - #region LUA_EXTENSIONS - - /// - /// Searches for all types in all loaded assemblies from content packages who's names contain the name string and registers them with the Lua Interpreter. - /// - /// - /// - /// - public bool LuaTryRegisterPackageTypes(string name, bool caseSensitive = false) - { - if (!AssembliesLoaded) - return false; - var matchingPacks = _loadedCompiledPackageAssemblies - .Where(kvp => kvp.Key.Name.ToLowerInvariant().Contains(name.ToLowerInvariant())) - .Select(kvp => kvp.Value) - .ToImmutableList(); - if (!matchingPacks.Any()) - return false; - var types = matchingPacks - .Where(guid => !_luaRegisteredTypes.ContainsKey(guid)) - .Select(guid => new KeyValuePair>( - guid, - _assemblyManager.TryGetSubTypesFromACL(guid, out var types) - ? types.ToImmutableList() - : ImmutableList.Empty)) - .ToImmutableList(); - if (!types.Any()) - return false; - foreach (var kvp in types) - { - _luaRegisteredTypes[kvp.Key] = kvp.Value; - foreach (Type type in kvp.Value) - { - MoonSharp.Interpreter.UserData.RegisterType(type); - } - } - - return true; - } - - #endregion - - /// - /// Whether assemblies have been loaded. - /// - public bool AssembliesLoaded { get; private set; } - - - /// - /// Whether loaded plugins had their preloader run. - /// - public bool PluginsPreInit { get; private set; } - - /// - /// Whether plugins' types have been instantiated. - /// - public bool PluginsInitialized { get; private set; } - - /// - /// Whether plugins are fully loaded. - /// - public bool PluginsLoaded { get; private set; } - - public IEnumerable GetCurrentPackagesByLoadOrder() => _currentPackagesByLoadOrder; - - /// - /// Tries to find the content package that a given plugin belongs to. - /// - /// Package if found, null otherwise. - /// The IAssemblyPlugin type to find. - /// - public bool TryGetPackageForPlugin(out ContentPackage package) where T : IAssemblyPlugin - { - package = null; - - var t = typeof(T); - var guid = _pluginTypes - .Where(kvp => kvp.Value.Contains(t)) - .Select(kvp => kvp.Key) - .FirstOrDefault(Guid.Empty); - - if (guid.Equals(Guid.Empty) || !_reverseLookupGuidList.ContainsKey(guid) || _reverseLookupGuidList[guid] is null) - return false; - package = _reverseLookupGuidList[guid]; - return true; - } - - - /// - /// Tries to get the loaded plugins for a given package. - /// - /// Package to find. - /// The collection of loaded plugins. - /// - public bool TryGetLoadedPluginsForPackage(ContentPackage package, out IEnumerable loadedPlugins) - { - loadedPlugins = null; - if (package is null || !_loadedCompiledPackageAssemblies.ContainsKey(package)) - return false; - var guid = _loadedCompiledPackageAssemblies[package]; - if (guid.Equals(Guid.Empty) || !_loadedPlugins.ContainsKey(guid)) - return false; - loadedPlugins = _loadedPlugins[guid]; - return true; - } - - /// - /// Called when clean up is being performed. Use when relying on or making use of references from this manager. - /// - public event Action OnDispose; - - [MethodImpl(MethodImplOptions.Synchronized)] - public void Dispose() - { - // send events for cleanup - try - { - OnDispose?.Invoke(); - } - catch (Exception e) - { - ModUtils.Logging.PrintError($"Error while executing Dispose event: {e.Message}"); - } - - // cleanup events - if (OnDispose is not null) - { - foreach (Delegate del in OnDispose.GetInvocationList()) - { - OnDispose -= (del as System.Action); - } - } - - // cleanup plugins and assemblies - ReflectionUtils.ResetCache(); - UnloadPlugins(); - // try cleaning up the assemblies - _pluginTypes.Clear(); // remove assembly references - _loadedPlugins.Clear(); - _publicizedAssemblyLoader = Guid.Empty; - _packagesDependencies.Clear(); - _loadedCompiledPackageAssemblies.Clear(); - _reverseLookupGuidList.Clear(); - _packageRunConfigs.Clear(); - _currentPackagesByLoadOrder.Clear(); - - // lua cleanup - foreach (var kvp in _luaRegisteredTypes) - { - foreach (Type type in kvp.Value) - { - MoonSharp.Interpreter.UserData.UnregisterType(type); - } - } - _luaRegisteredTypes.Clear(); - - _assemblyUnloadStartTime = DateTime.Now; - _publicizedAssemblyLoader = Guid.Empty; - - // we can't wait forever or app dies but we can try to be graceful - while (!_assemblyManager.TryBeginDispose()) - { - Thread.Sleep(20); // give the assembly context unloader time to run (async) - if (_assemblyUnloadStartTime.AddSeconds(_assemblyUnloadTimeoutSeconds) > DateTime.Now) - { - break; - } - } - - _assemblyUnloadStartTime = DateTime.Now; - Thread.Sleep(100); // give the garbage collector time to finalize the disposed assemblies. - while (!_assemblyManager.FinalizeDispose()) - { - Thread.Sleep(100); // give the garbage collector time to finalize the disposed assemblies. - if (_assemblyUnloadStartTime.AddSeconds(_assemblyUnloadTimeoutSeconds) > DateTime.Now) - { - break; - } - } - - _assemblyManager.OnAssemblyLoaded -= AssemblyManagerOnAssemblyLoaded; - _assemblyManager.OnAssemblyUnloading -= AssemblyManagerOnAssemblyUnloading; - - AssembliesLoaded = false; - GC.SuppressFinalize(this); - } - - /// - /// Begins the loading process of scanning packages for scripts and binary assemblies, compiling and executing them. - /// - /// - public AssemblyLoadingSuccessState LoadAssemblyPackages() - { - if (AssembliesLoaded) - { - return AssemblyLoadingSuccessState.AlreadyLoaded; - } - - _assemblyManager.OnAssemblyLoaded += AssemblyManagerOnAssemblyLoaded; - _assemblyManager.OnAssemblyUnloading += AssemblyManagerOnAssemblyUnloading; - - // log error if some ACLs are still unloading (some assembly is still in use) - _assemblyManager.FinalizeDispose(); //Update lists - if (_assemblyManager.IsCurrentlyUnloading) - { - ModUtils.Logging.PrintMessage($"The below ACLs are still unloading:"); - foreach (var wkref in _assemblyManager.StillUnloadingACLs) - { - if (wkref.TryGetTarget(out var tgt)) - { - ModUtils.Logging.PrintMessage($"ACL Name: {tgt.FriendlyName}"); - foreach (Assembly assembly in tgt.Assemblies) - { - ModUtils.Logging.PrintMessage($"-- Assembly: {assembly.GetName()}"); - } - } - } - } - - ImmutableList publicizedAssemblies = ImmutableList.Empty; - List publicizedAssembliesLocList = new(); - - foreach (string dllName in _publicizedAssembliesToLoad) - { - GetFiles(publicizedAssembliesLocList, dllName); - } - - void GetFiles(List list, string searchQuery) - { - bool workshopFirst = _luaCsSetup.Config.PreferToUseWorkshopLuaSetup || LuaCsSetup.IsRunningInsideWorkshop; - - var publicizedDir = Path.Combine(Environment.CurrentDirectory, "Publicized"); - - // if using workshop lua setup is checked, try to use the publicized assemblies in the content package there instead. - if (workshopFirst) - { - var pck = LuaCsSetup.GetPackage(LuaCsSetup.LuaForBarotraumaId); - if (pck is not null) - { - publicizedDir = Path.Combine(pck.Dir, "Binary", "Publicized"); - } - } - - try - { - list.AddRange(Directory.GetFiles(publicizedDir, searchQuery)); - } - // no directory found, use the other one - catch (DirectoryNotFoundException) - { - if (workshopFirst) - { - ModUtils.Logging.PrintError($"Unable to find /Binary/Publicized/ . Using Game folder instead."); - publicizedDir = Path.Combine(Environment.CurrentDirectory, "Publicized"); - } - else - { - ModUtils.Logging.PrintError($"Unable to find /Publicized/ . Using LuaCsPackage folder instead."); - var pck = LuaCsSetup.GetPackage(LuaCsSetup.LuaForBarotraumaId); - if (pck is not null) - { - publicizedDir = Path.Combine(pck.Dir, "Binary", "Publicized"); - } - } - - // search for assemblies - list.AddRange(Directory.GetFiles(publicizedDir, searchQuery)); - } - } - - // try load them into an acl - var loadState = _assemblyManager.LoadAssembliesFromLocations(publicizedAssembliesLocList, "luacs_publicized_assemblies", ref _publicizedAssemblyLoader); - - // loaded - if (loadState is AssemblyLoadingSuccessState.Success) - { - if (_assemblyManager.TryGetACL(_publicizedAssemblyLoader, out var acl)) - { - publicizedAssemblies = acl.Acl.Assemblies.ToImmutableList(); - _assemblyManager.SetACLToTemplateMode(_publicizedAssemblyLoader); - } - } - - - // get packages - IEnumerable packages = BuildPackagesList(); - - // check and load config - _packageRunConfigs.AddRange(packages - .Select(p => new KeyValuePair(p, GetRunConfigForPackage(p))) - .ToDictionary(p => p.Key, p=> p.Value)); - - // filter not to be loaded - var cpToRunA = _packageRunConfigs - .Where(kvp => ShouldRunPackage(kvp.Key, kvp.Value)) - .Select(kvp => kvp.Key) - .ToHashSet(); - - //-- filter and remove duplicate mods, prioritize /LocalMods/ - HashSet cpNames = new(); - HashSet duplicateNames = new(); - - // search - foreach (ContentPackage package in cpToRunA) - { - if (cpNames.Contains(package.Name)) - { - if (!duplicateNames.Contains(package.Name)) - { - duplicateNames.Add(package.Name); - } - } - else - { - cpNames.Add(package.Name); - } - } - - // remove - foreach (string name in duplicateNames) - { - var duplCpList = cpToRunA - .Where(p => p.Name.Equals(name)) - .ToHashSet(); - - if (duplCpList.Count < 2) // one or less found - continue; - - ContentPackage toKeep = null; - foreach (ContentPackage package in duplCpList) - { - if (package.Dir.Contains("LocalMods")) - { - toKeep = package; - break; - } - } - - toKeep ??= duplCpList.First(); - - duplCpList.Remove(toKeep); // remove all but this one - cpToRunA.RemoveWhere(p => duplCpList.Contains(p)); - } - - var cpToRun = cpToRunA.ToImmutableList(); - - // build dependencies map - bool reliableMap = TryBuildDependenciesMap(cpToRun, out var packDeps); - if (!reliableMap) - { - ModUtils.Logging.PrintMessage($"{nameof(CsPackageManager)}: Unable to create reliable dependencies map."); - } - - _packagesDependencies.AddRange(packDeps.ToDictionary( - kvp => kvp.Key, - kvp => kvp.Value.ToImmutableList()) - ); - - List packagesToLoadInOrder = new(); - - // build load order - if (reliableMap && OrderAndFilterPackagesByDependencies( - _packagesDependencies, - out var readyToLoad, - out var cannotLoadPackages)) - { - packagesToLoadInOrder.AddRange(readyToLoad); - if (cannotLoadPackages is not null) - { - ModUtils.Logging.PrintError($"{nameof(CsPackageManager)}: Unable to load the following mods due to dependency errors:"); - foreach (var pair in cannotLoadPackages) - { - ModUtils.Logging.PrintError($"Package: {pair.Key.Name} | Reason: {pair.Value}"); - } - } - } - else - { - // use unsorted list on failure and send error message. - packagesToLoadInOrder.AddRange(_packagesDependencies.Select( p=> p.Key)); - ModUtils.Logging.PrintError($"{nameof(CsPackageManager)}: Unable to create a reliable load order. Defaulting to unordered loading!"); - } - - // get assemblies and scripts' filepaths from packages - var toLoad = packagesToLoadInOrder - .Select(cp => new KeyValuePair( - cp, - new LoadableData( - TryScanPackagesForAssemblies(cp, out var list1) ? list1 : null, - TryScanPackageForScripts(cp, out var list2) ? list2 : null, - GetRunConfigForPackage(cp)))) - .ToImmutableDictionary(); - - HashSet badPackages = new(); - foreach (var pair in toLoad) - { - // check if unloadable - if (badPackages.Contains(pair.Key)) - continue; - - // try load binary assemblies - var id = Guid.Empty; // id for the ACL for this package defined by AssemblyManager. - AssemblyLoadingSuccessState successState; - if (pair.Value.AssembliesFilePaths is not null && pair.Value.AssembliesFilePaths.Any()) - { - ModUtils.Logging.PrintMessage($"Loading assemblies for CPackage {pair.Key.Name}"); -#if DEBUG - foreach (string assembliesFilePath in pair.Value.AssembliesFilePaths) - { - ModUtils.Logging.PrintMessage($"Found assemblies located at {Path.GetFullPath(ModUtils.IO.SanitizePath(assembliesFilePath))}"); - } -#endif - - successState = _assemblyManager.LoadAssembliesFromLocations(pair.Value.AssembliesFilePaths, pair.Key.Name, ref id); - - // error handling - if (successState is not AssemblyLoadingSuccessState.Success) - { - ModUtils.Logging.PrintError($"{nameof(CsPackageManager)}: Unable to load the binary assemblies for package {pair.Key.Name}. Error: {successState.ToString()}"); - UpdatePackagesToDisable(ref badPackages, pair.Key, _packagesDependencies); - continue; - } - } - - // try compile scripts to assemblies - if (pair.Value.ScriptsFilePaths is not null && pair.Value.ScriptsFilePaths.Any()) - { - ModUtils.Logging.PrintMessage($"Loading scripts for CPackage {pair.Key.Name}"); - List syntaxTrees = new(); - - syntaxTrees.Add(GetPackageScriptImports()); - bool abortPackage = false; - // load scripts data from files - foreach (string scriptPath in pair.Value.ScriptsFilePaths) - { - var state = ModUtils.IO.GetOrCreateFileText(scriptPath, out string fileText, null, false); - // could not load file data - if (state is not ModUtils.IO.IOActionResultState.Success) - { - ModUtils.Logging.PrintError($"{nameof(CsPackageManager)}: Unable to load the script files for package {pair.Key.Name}. Error: {state.ToString()}"); - UpdatePackagesToDisable(ref badPackages, pair.Key, _packagesDependencies); - abortPackage = true; - break; - } - - try - { - CancellationToken token = new(); - syntaxTrees.Add(SyntaxFactory.ParseSyntaxTree(fileText, ScriptParseOptions, scriptPath, Encoding.Default, token)); - // cancel if parsing failed - if (token.IsCancellationRequested) - { - ModUtils.Logging.PrintError($"{nameof(CsPackageManager)}: Unable to load the script files for package {pair.Key.Name}. Error: Syntax Parse Error."); - UpdatePackagesToDisable(ref badPackages, pair.Key, _packagesDependencies); - abortPackage = true; - break; - } - } - catch (Exception e) - { - // unknown error - ModUtils.Logging.PrintError($"{nameof(CsPackageManager)}: Unable to load the script files for package {pair.Key.Name}. Error: {e.Message}"); - UpdatePackagesToDisable(ref badPackages, pair.Key, _packagesDependencies); - abortPackage = true; - break; - } - - } - - if (abortPackage) - continue; - - // try compile - successState = _assemblyManager.LoadAssemblyFromMemory( - pair.Value.config.UseInternalAssemblyName ? "CompiledAssembly" : pair.Key.Name.Replace(" ",""), - syntaxTrees, - null, - CompilationOptions, - pair.Key.Name, - ref id, - pair.Value.config.UseNonPublicizedAssemblies ? null : publicizedAssemblies); - - if (successState is not AssemblyLoadingSuccessState.Success) - { - ModUtils.Logging.PrintError($"{nameof(CsPackageManager)}: Unable to compile script assembly for package {pair.Key.Name}. Error: {successState.ToString()}"); - UpdatePackagesToDisable(ref badPackages, pair.Key, _packagesDependencies); - continue; - } - } - - // something was loaded, add to index - if (id != Guid.Empty) - { - ModUtils.Logging.PrintMessage($"Assemblies from CPackage {pair.Key.Name} loaded with Guid {id}."); - _loadedCompiledPackageAssemblies.Add(pair.Key, id); - _reverseLookupGuidList.Add(id, pair.Key); - } - } - - // update loaded packages to exclude bad packages - _currentPackagesByLoadOrder.AddRange(toLoad - .Where(p => !badPackages.Contains(p.Key)) - .Select(p => p.Key)); - - // build list of plugins - foreach (var pair in _loadedCompiledPackageAssemblies) - { - if (_assemblyManager.TryGetSubTypesFromACL(pair.Value, out var types)) - { - _pluginTypes[pair.Value] = types.ToImmutableHashSet(); - foreach (var type in _pluginTypes[pair.Value]) - { - ModUtils.Logging.PrintMessage($"Loading type: {type.Name}"); - } - } - } - - this.AssembliesLoaded = true; - return AssemblyLoadingSuccessState.Success; - - - bool ShouldRunPackage(ContentPackage package, RunConfig config) - { - throw new NotImplementedException(); - } - - void UpdatePackagesToDisable(ref HashSet set, - ContentPackage newDisabledPackage, - IEnumerable>> dependenciesMap) - { - set.Add(newDisabledPackage); - foreach (var package in dependenciesMap) - { - if (package.Value.Contains(newDisabledPackage)) - set.Add(newDisabledPackage); - } - } - } - - /// - /// Executes instantiated plugins' Initialize() and OnLoadCompleted() methods. - /// - public void RunPluginsInit() - { - if (!AssembliesLoaded) - { - ModUtils.Logging.PrintError($"{nameof(CsPackageManager)}: Attempted to call plugins' Initialize() without any loaded assemblies!"); - return; - } - - if (!PluginsInitialized) - { - ModUtils.Logging.PrintError($"{nameof(CsPackageManager)}: Attempted to call plugins' Initialize() without type instantiation!"); - return; - } - - if (PluginsLoaded) - return; - - foreach (var contentPlugins in _loadedPlugins) - { - // init - foreach (var plugin in contentPlugins.Value) - { - TryRun(() => plugin.Initialize(), $"{nameof(IAssemblyPlugin.Initialize)}", $"CP: {_reverseLookupGuidList[contentPlugins.Key].Name} Plugin: {plugin.GetType().Name}"); - } - } - - foreach (var contentPlugins in _loadedPlugins) - { - // load complete - foreach (var plugin in contentPlugins.Value) - { - TryRun(() => plugin.OnLoadCompleted(), $"{nameof(IAssemblyPlugin.OnLoadCompleted)}", $"CP: {_reverseLookupGuidList[contentPlugins.Key].Name} Plugin: {plugin.GetType().Name}"); - } - } - - PluginsLoaded = true; - } - - /// - /// Executes instantiated plugins' PreInitPatching() method. - /// - public void RunPluginsPreInit() - { - if (!AssembliesLoaded) - { - ModUtils.Logging.PrintError($"{nameof(CsPackageManager)}: Attempted to call plugins' PreInitPatching() without any loaded assemblies!"); - return; - } - - if (!PluginsInitialized) - { - ModUtils.Logging.PrintError($"{nameof(CsPackageManager)}: Attempted to call plugins' PreInitPatching() without type initialization!"); - return; - } - - if (PluginsPreInit) - { - return; - } - - foreach (var contentPlugins in _loadedPlugins) - { - // init - foreach (var plugin in contentPlugins.Value) - { - TryRun(() => plugin.PreInitPatching(), $"{nameof(IAssemblyPlugin.PreInitPatching)}", $"CP: {_reverseLookupGuidList[contentPlugins.Key].Name} Plugin: {plugin.GetType().Name}"); - } - } - - PluginsPreInit = true; - } - - /// - /// Initializes plugin types that are registered. - /// - /// - public void InstantiatePlugins(bool force = false) - { - if (!AssembliesLoaded) - { - ModUtils.Logging.PrintError($"{nameof(CsPackageManager)}: Attempted to instantiate plugins without any loaded assemblies!"); - return; - } - - if (PluginsInitialized) - { - if (force) - UnloadPlugins(); - else - { - ModUtils.Logging.PrintError($"{nameof(CsPackageManager)}: Attempted to load plugins when they were already loaded!"); - return; - } - } - - foreach (var pair in _pluginTypes) - { - // instantiate - foreach (Type type in pair.Value) - { - if (!_loadedPlugins.ContainsKey(pair.Key)) - _loadedPlugins.Add(pair.Key, new()); - else if (_loadedPlugins[pair.Key] is null) - _loadedPlugins[pair.Key] = new(); - IAssemblyPlugin plugin = null; - try - { - plugin = (IAssemblyPlugin)Activator.CreateInstance(type); - _loadedPlugins[pair.Key].Add(plugin); - } - catch (Exception e) - { - ModUtils.Logging.PrintError($"{nameof(CsPackageManager)}: Error while instantiating plugin of type {type}. Now disposing..."); - ModUtils.Logging.PrintError($"{nameof(CsPackageManager)}: Details: {e.Message} | {e.InnerException}"); - - if (plugin is not null) - { - // ReSharper disable once AccessToModifiedClosure - TryRun(() => plugin?.Dispose(), nameof(IAssemblyPlugin.Dispose), type.FullName ?? type.Name); - plugin = null; - } - } - } - } - - PluginsInitialized = true; - } - - /// - /// Unloads all plugins by calling Dispose() on them. Note: This does not remove their external references nor - /// unregister their types. - /// - public void UnloadPlugins() - { - foreach (var contentPlugins in _loadedPlugins) - { - foreach (var plugin in contentPlugins.Value) - { - TryRun(() => plugin.Dispose(), $"{nameof(IAssemblyPlugin.Dispose)}", $"CP: {_reverseLookupGuidList[contentPlugins.Key].Name} Plugin: {plugin.GetType().Name}"); - } - contentPlugins.Value.Clear(); - } - - _loadedPlugins.Clear(); - - PluginsInitialized = false; - PluginsPreInit = false; - PluginsLoaded = false; - } - - - /// - /// Gets the RunConfig.xml for the given package located at [cp_root]/CSharp/RunConfig.xml. - /// Generates a default config if one is not found. - /// - /// The package to search for. - /// RunConfig data. - /// True if a config is loaded, false if one was created. - public static bool GetOrCreateRunConfig(ContentPackage package, out RunConfig config) - { - var path = System.IO.Path.Combine(Path.GetFullPath(package.Dir), "CSharp", "RunConfig.xml"); - if (!File.Exists(path)) - { - config = new RunConfig(true).Sanitize(); - return false; - } - return ModUtils.IO.LoadOrCreateTypeXml(out config, path, () => new RunConfig(true).Sanitize(), false); - } - - #endregion - - #region INTERNALS - - private void TryRun(Action action, string messageMethodName, string messageTypeName) - { - try - { - action?.Invoke(); - } - catch (Exception e) - { - ModUtils.Logging.PrintError($"{nameof(CsPackageManager)}: Error while running {messageMethodName}() on plugin of type {messageTypeName}"); - ModUtils.Logging.PrintError($"{nameof(CsPackageManager)}: Details: {e.Message} | {e.InnerException}"); - } - } - - private void AssemblyManagerOnAssemblyUnloading(Assembly assembly) - { - ReflectionUtils.RemoveAssemblyFromCache(assembly); - } - - private void AssemblyManagerOnAssemblyLoaded(Assembly assembly) - { - //ReflectionUtils.AddNonAbstractAssemblyTypes(assembly); - // As ReflectionUtils.GetDerivedNonAbstract is only used for Prefabs & Barotrauma-specific implementing types, - // we can safely not register System/Core assemblies. - if (assembly.FullName is not null && assembly.FullName.StartsWith("System.")) - return; - ReflectionUtils.AddNonAbstractAssemblyTypes(assembly, true); - } - - internal CsPackageManager([NotNull] AssemblyManager assemblyManager, [NotNull] LuaCsSetup luaCsSetup) - { - this._assemblyManager = assemblyManager; - this._luaCsSetup = luaCsSetup; - } - - ~CsPackageManager() - { - this.Dispose(); - } - - private static bool TryScanPackageForScripts(ContentPackage package, out ImmutableList scriptFilePaths) - { - string pathShared = Path.Combine(ModUtils.IO.GetContentPackageDir(package), "CSharp", "Shared"); - string pathArch = Path.Combine(ModUtils.IO.GetContentPackageDir(package), "CSharp", ARCHITECTURE_TARGET); - - List files = new(); - - if (Directory.Exists(pathShared)) - files.AddRange(Directory.GetFiles(pathShared, SCRIPT_FILE_REGEX, SearchOption.AllDirectories)); - if (Directory.Exists(pathArch)) - files.AddRange(Directory.GetFiles(pathArch, SCRIPT_FILE_REGEX, SearchOption.AllDirectories)); - - if (files.Count > 0) - { - scriptFilePaths = files.ToImmutableList(); - return true; - } - scriptFilePaths = ImmutableList.Empty; - return false; - } - - private static bool TryScanPackagesForAssemblies(ContentPackage package, out ImmutableList assemblyFilePaths) - { - string path = Path.Combine(ModUtils.IO.GetContentPackageDir(package), "bin", ARCHITECTURE_TARGET, PLATFORM_TARGET); - - if (!Directory.Exists(path)) - { - assemblyFilePaths = ImmutableList.Empty; - return false; - } - - assemblyFilePaths = System.IO.Directory.GetFiles(path, ASSEMBLY_FILE_REGEX, SearchOption.AllDirectories) - .ToImmutableList(); - return assemblyFilePaths.Count > 0; - } - - private static RunConfig GetRunConfigForPackage(ContentPackage package) - { - if (!GetOrCreateRunConfig(package, out var config)) - config.AutoGenerated = true; - return config; - } - - private IEnumerable BuildPackagesList() - { - // get unique list of content packages. - // Note: there is an old issue where the AllPackages group - // would sometimes not contain packages downloaded from the host, so we union enabled. - return ContentPackageManager.AllPackages.Union(ContentPackageManager.EnabledPackages.All).Where(pack => !pack.Name.ToLowerInvariant().Equals("vanilla")); - } - - - private static SyntaxTree GetPackageScriptImports() => BaseAssemblyImports; - - - /// - /// Builds a list of ContentPackage dependencies for each of the packages in the list. Note: All dependencies must be included in the provided list of packages. - /// - /// List of packages to check - /// Dependencies by package - /// True if all dependencies were found. - private static bool TryBuildDependenciesMap(ImmutableList packages, out Dictionary> dependenciesMap) - { - bool reliableMap = true; // remains true if all deps were found. - dependenciesMap = new(); - foreach (var package in packages) - { - dependenciesMap.Add(package, new()); - if (GetOrCreateRunConfig(package, out var config)) - { - if (config.Dependencies is null || !config.Dependencies.Any()) - continue; - - foreach (RunConfig.Dependency dependency in config.Dependencies) - { - ContentPackage dep = packages.FirstOrDefault(p => - (dependency.SteamWorkshopId != 0 && p.TryExtractSteamWorkshopId(out var steamWorkshopId) - && steamWorkshopId.Value == dependency.SteamWorkshopId) - || (!dependency.PackageName.IsNullOrWhiteSpace() && p.Name.ToLowerInvariant().Contains(dependency.PackageName.ToLowerInvariant())), null); - - if (dep is not null) - { - dependenciesMap[package].Add(dep); - } - else - { - ModUtils.Logging.PrintWarning($"Warning: The ContentPackage {package.Name} lists a dependency of (STEAMID: {dependency.SteamWorkshopId}, PackageName: {dependency.PackageName}) but it could not be found in the to-be-loaded CSharp packages list!"); - reliableMap = false; - } - } - } - } - - return reliableMap; - } - - /// - /// Given a table of packages and dependent packages, will sort them by dependency loading order along with packages - /// that cannot be loaded due to errors or failing the predicate checks. - /// - /// A dictionary/map with key as the package and the elements as it's dependencies. - /// List of packages that are ready to load and in the correct order. - /// Packages with errors or cyclic dependencies. Element is error message. Null if empty. - /// Optional: Allows for a custom checks to be performed on each package. - /// Returns a bool indicating if the package is ready to load. - /// Whether the process produces a usable list. - private static bool OrderAndFilterPackagesByDependencies( - Dictionary> packages, - out IEnumerable readyToLoad, - out IEnumerable> cannotLoadPackages, - Func packageChecksPredicate = null) - { - HashSet completedPackages = new(); - List readyPackages = new(); - Dictionary unableToLoad = new(); - HashSet currentNodeChain = new(); - - readyToLoad = readyPackages; - - try - { - foreach (var toProcessPack in packages) - { - ProcessPackage(toProcessPack.Key, toProcessPack.Value); - } - - PackageProcRet ProcessPackage(ContentPackage packageToProcess, IEnumerable dependencies) - { - //cyclic handling - if (unableToLoad.ContainsKey(packageToProcess)) - { - return PackageProcRet.BadPackage; - } - - // already processed - if (completedPackages.Contains(packageToProcess)) - { - return PackageProcRet.AlreadyCompleted; - } - - // cyclic check - if (currentNodeChain.Contains(packageToProcess)) - { - StringBuilder sb = new(); - sb.AppendLine("Error: Cyclic Dependency. ") - .Append( - "The following ContentPackages rely on eachother in a way that makes it impossible to know which to load first! ") - .Append( - "Note: the package listed twice shows where the cycle starts/ends and is not necessarily the problematic package."); - int i = 0; - foreach (var package in currentNodeChain) - { - i++; - sb.AppendLine($"{i}. {package.Name}"); - } - - sb.AppendLine($"{i}. {packageToProcess.Name}"); - unableToLoad.Add(packageToProcess, sb.ToString()); - completedPackages.Add(packageToProcess); - return PackageProcRet.BadPackage; - } - - if (packageChecksPredicate is not null && !packageChecksPredicate.Invoke(packageToProcess)) - { - unableToLoad.Add(packageToProcess, $"Unable to load package {packageToProcess.Name} due to failing checks."); - completedPackages.Add(packageToProcess); - return PackageProcRet.BadPackage; - } - - currentNodeChain.Add(packageToProcess); - - foreach (ContentPackage dependency in dependencies) - { - // The mod lists a dependent that was not found during the discovery phase. - if (!packages.ContainsKey(dependency)) - { - // search to see if it's enabled - if (!ContentPackageManager.EnabledPackages.All.Contains(dependency)) - { - // present warning but allow loading anyways, better to let the user just disable the package if it's really an issue. - ModUtils.Logging.PrintWarning( - $"Warning: the ContentPackage of {packageToProcess.Name} requires the Dependency {dependency.Name} but this package wasn't found in the enabled mods list!"); - } - - continue; - } - - var ret = ProcessPackage(dependency, packages[dependency]); - - if (ret is PackageProcRet.BadPackage) - { - if (!unableToLoad.ContainsKey(packageToProcess)) - { - unableToLoad.Add(packageToProcess, $"Error: Dependency failure. Failed to load {dependency.Name}"); - } - currentNodeChain.Remove(packageToProcess); - if (!completedPackages.Contains(packageToProcess)) - { - completedPackages.Add(packageToProcess); - } - return PackageProcRet.BadPackage; - } - } - - currentNodeChain.Remove(packageToProcess); - completedPackages.Add(packageToProcess); - readyPackages.Add(packageToProcess); - return PackageProcRet.Completed; - } - } - catch (Exception e) - { - ModUtils.Logging.PrintError($"Error while generating dependency loading order! Exception: {e.Message}"); -#if DEBUG - ModUtils.Logging.PrintError($"Stack Trace: {e.StackTrace}"); -#endif - cannotLoadPackages = unableToLoad.Any() ? unableToLoad : null; - return false; - } - cannotLoadPackages = unableToLoad.Any() ? unableToLoad : null; - return true; - } - - private enum PackageProcRet : byte - { - AlreadyCompleted, - Completed, - BadPackage - } - - private record LoadableData(ImmutableList AssembliesFilePaths, ImmutableList ScriptsFilePaths, RunConfig config); - - #endregion -} -*/ diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs index c3dd6983a..8a6115bc1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs @@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyPlugin.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyPlugin.cs index 964ce312f..7667c86cd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyPlugin.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyPlugin.cs @@ -1,6 +1,6 @@ using System; using Barotrauma.LuaCs.Events; -namespace Barotrauma; +namespace Barotrauma.LuaCs; public interface IAssemblyPlugin : IDisposable, IEventPluginPreInitialize, IEventPluginInitialize, IEventPluginLoadCompleted { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/RunConfig.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/RunConfig.cs index 0e46032c1..d5283156a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/RunConfig.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/RunConfig.cs @@ -6,6 +6,7 @@ using Barotrauma.LuaCs.Data; namespace Barotrauma; [Serializable] +[Obsolete($"Use {nameof(IModConfigInfo)} instead. This class exists for legacy compatibility only.")] public sealed class RunConfig : IRunConfig { /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs similarity index 96% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs index 9aea4a8bc..987b81bde 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs @@ -9,14 +9,13 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; -using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Events; -using Barotrauma.LuaCs.Services.Processing; +using Barotrauma.LuaCs; using FluentResults; using Microsoft.Toolkit.Diagnostics; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public sealed partial class ConfigService : IConfigService { @@ -122,7 +121,7 @@ public sealed partial class ConfigService : IConfigService private readonly ConcurrentDictionary<(ContentPackage OwnerPackage, string InternalName), ISettingBase> _settingsInstances = new(); - private readonly ConcurrentDictionary> + private readonly ConcurrentDictionary> _instanceFactory = new(); private readonly ConcurrentDictionary> _settingsInstancesByPackage = new(); @@ -146,7 +145,7 @@ public sealed partial class ConfigService : IConfigService } - public void RegisterSettingTypeInitializer(string typeIdentifier, Func settingFactory) where T : class, ISettingBase + public void RegisterSettingTypeInitializer(string typeIdentifier, Func<(IConfigService ConfigService, IConfigInfo Info), T> settingFactory) where T : class, ISettingBase { Guard.IsNotNullOrWhiteSpace(typeIdentifier, nameof(typeIdentifier)); Guard.IsNotNull(settingFactory, nameof(settingFactory)); @@ -199,7 +198,7 @@ public sealed partial class ConfigService : IConfigService .SelectMany(tr => tr) .ToImmutableArray(); - var instanceQueue = new Queue<(IConfigInfo configInfo, Func factory)>(); + var instanceQueue = new Queue<(IConfigInfo configInfo, Func<(IConfigService ConfigService, IConfigInfo Info), ISettingBase> factory)>(); foreach (var info in toProcessDocs) { @@ -222,7 +221,7 @@ public sealed partial class ConfigService : IConfigService { try { - toProcessInstanceQueue.Enqueue((instanceFactoryInfo.configInfo, instanceFactoryInfo.factory(instanceFactoryInfo.configInfo))); + toProcessInstanceQueue.Enqueue((instanceFactoryInfo.configInfo, instanceFactoryInfo.factory((this, instanceFactoryInfo.configInfo)))); } catch (Exception e) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs index bf7a04271..32e543738 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs @@ -1,6 +1,6 @@ using Barotrauma.Extensions; using Barotrauma.LuaCs.Events; -using Barotrauma.LuaCs.Services.Compatibility; +using Barotrauma.LuaCs.Compatibility; using FluentResults; using FluentResults.LuaCs; using HarmonyLib; @@ -15,7 +15,7 @@ using System.Collections.Immutable; using System.Linq; using System.Reflection; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public partial class EventService : IEventService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs index f0bfc110a..ec455ae0a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs @@ -5,7 +5,7 @@ using FluentResults; using Microsoft.Xna.Framework; using MoonSharp.Interpreter; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public partial class LoggerService : ILoggerService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs new file mode 100644 index 000000000..0637da337 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs @@ -0,0 +1,18 @@ +namespace Barotrauma.LuaCs; + +public sealed class LuaCsInfoProvider : ILuaCsInfoProvider +{ + public void Dispose() + { + // stateless service + } + + public bool IsDisposed => false; + public bool IsCsEnabled => GameMain.LuaCs.IsCsEnabled; + public bool DisableErrorGUIOverlay => GameMain.LuaCs.DisableErrorGUIOverlay; + public bool HideUserNamesInLogs => GameMain.LuaCs.HideUserNamesInLogs; + public ulong LuaForBarotraumaSteamId => GameMain.LuaCs.LuaForBarotraumaSteamId; + public bool RestrictMessageSize => GameMain.LuaCs.RestrictMessageSize; + public string LocalDataSavePath => GameMain.LuaCs.LocalDataSavePath; + public RunState CurrentRunState => GameMain.LuaCs.CurrentRunState; +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs similarity index 98% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 97564b9be..ed5678049 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -1,8 +1,7 @@ #nullable enable using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services.Compatibility; -using Barotrauma.LuaCs.Services.Safe; +using Barotrauma.LuaCs.Compatibility; using Barotrauma.Networking; using FluentResults; using Microsoft.CodeAnalysis; @@ -21,8 +20,9 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Threading.Tasks; +using Barotrauma.LuaCs; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigFileParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigFileParserService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs index b35212eae..1a6f63933 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigFileParserService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs @@ -8,7 +8,7 @@ using Barotrauma.LuaCs.Data; using FluentResults; using Microsoft.Toolkit.Diagnostics; -namespace Barotrauma.LuaCs.Services.Processing; +namespace Barotrauma.LuaCs; public sealed class ModConfigFileParserService : IParserServiceAsync, diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs index 5ce1aff99..cd3ea8085 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs @@ -14,7 +14,7 @@ using FluentResults; using Microsoft.Toolkit.Diagnostics; using MoonSharp.VsCodeDebugger.SDK; -namespace Barotrauma.LuaCs.Services.Processing; +namespace Barotrauma.LuaCs; public sealed class ModConfigService : IModConfigService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs similarity index 97% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs index 1c8b6b32d..455f02138 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs @@ -1,9 +1,9 @@ -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs; using Barotrauma.Networking; using System; using System.Collections.Generic; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; internal partial class NetworkingService : INetworkingService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs index e2878bb58..f07b83e62 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs @@ -9,7 +9,7 @@ using Barotrauma.LuaCs.Data; using FluentResults; using Microsoft.Toolkit.Diagnostics; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public sealed class PackageManagementService : IPackageManagementService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index b6144435c..3164128d1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -25,7 +25,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Toolkit.Diagnostics; using OneOf; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public class PluginManagementService : IAssemblyManagementService { @@ -71,12 +71,11 @@ public class PluginManagementService : IAssemblyManagementService private static readonly SyntaxTree BaseAssemblyImports = CSharpSyntaxTree.ParseText( new StringBuilder() - .AppendLine("global using LuaCsHook = Barotrauma.LuaCs.Services.Compatibility.ILuaCsHook;") + .AppendLine("global using LuaCsHook = Barotrauma.LuaCs.Compatibility.ILuaCsHook;") .AppendLine("using System.Reflection;") .AppendLine("using Barotrauma;") .AppendLine("using Barotrauma.LuaCs;") - .AppendLine("using Barotrauma.LuaCs.Services;") - .AppendLine("using Barotrauma.LuaCs.Services.Compatibility;") + .AppendLine("using Barotrauma.LuaCs.Compatibility;") .AppendLine("using System.Runtime.CompilerServices;") .AppendLine("[assembly: IgnoresAccessChecksTo(\"BarotraumaCore\")]") #if CLIENT diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginService.cs new file mode 100644 index 000000000..1c42ebcd4 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginService.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs; + +public class PluginService +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SafeStorageService.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SafeStorageService.cs index 34c433f21..983a13b32 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SafeStorageService.cs @@ -15,7 +15,7 @@ using System.Threading.Tasks; using System.Xml.Linq; using Path = System.IO.Path; -namespace Barotrauma.LuaCs.Services.Safe; +namespace Barotrauma.LuaCs; public class SafeStorageService : StorageService, ISafeStorageService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ServicesProvider.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ServicesProvider.cs index 71fcb54b2..e9e06fe44 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/ServicesProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ServicesProvider.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; using System.Threading; using LightInject; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public class ServicesProvider : IServicesProvider diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/SettingsFileParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SettingsFileParserService.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/SettingsFileParserService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SettingsFileParserService.cs index a44cb285e..5ab01f02e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/SettingsFileParserService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SettingsFileParserService.cs @@ -9,7 +9,7 @@ using FluentResults; using Microsoft.Toolkit.Diagnostics; using OneOf; -namespace Barotrauma.LuaCs.Services.Processing; +namespace Barotrauma.LuaCs; public sealed class SettingsFileParserService : IParserServiceOneToManyAsync, diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/StorageService.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/StorageService.cs index 5abfdc0a2..0de6b864b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/StorageService.cs @@ -17,7 +17,7 @@ using Microsoft.Toolkit.Diagnostics; using Error = FluentResults.Error; using Path = System.IO.Path; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public class StorageService : IStorageService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IAssemblyManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IAssemblyManagementService.cs similarity index 96% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IAssemblyManagementService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IAssemblyManagementService.cs index d82105455..dc5d8ab19 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IAssemblyManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IAssemblyManagementService.cs @@ -10,7 +10,7 @@ using OneOf; // ReSharper disable InconsistentNaming -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public interface IAssemblyManagementService : IPluginManagementService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConfigService.cs similarity index 67% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConfigService.cs index cddaa17bb..0cf698084 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConfigService.cs @@ -4,26 +4,17 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using System.Xml.Linq; -using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services; -using Barotrauma.LuaCs.Services.Safe; +using Barotrauma.LuaCs; using Barotrauma.Networking; using FluentResults; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public partial interface IConfigService : IReusableService, ILuaConfigService { - void RegisterSettingTypeInitializer(string typeIdentifier, Func settingFactory) + void RegisterSettingTypeInitializer(string typeIdentifier, Func<(IConfigService ConfigService, IConfigInfo Info), T> settingFactory) where T : class, ISettingBase; - /// - /// - /// - /// - /// - /// - /// Task LoadConfigsAsync(ImmutableArray configResources); Task LoadConfigsProfilesAsync(ImmutableArray configProfileResources); FluentResults.Result DisposePackageData(ContentPackage package); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IEventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IEventService.cs similarity index 91% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IEventService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IEventService.cs index a5ef5ce67..eca7648ae 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IEventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IEventService.cs @@ -1,10 +1,10 @@ using System; using System.Reflection; using Barotrauma.LuaCs.Events; -using Barotrauma.LuaCs.Services.Compatibility; -using Barotrauma.LuaCs.Services.Safe; +using Barotrauma.LuaCs.Compatibility; +using Barotrauma.LuaCs; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public interface IEventService : IReusableService, ILuaEventService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IHelperServiceDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IHelperServiceDefinitions.cs similarity index 93% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IHelperServiceDefinitions.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IHelperServiceDefinitions.cs index b9d3146f9..23e623fc2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Processing/IHelperServiceDefinitions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IHelperServiceDefinitions.cs @@ -5,7 +5,7 @@ using System.Xml.Linq; using Barotrauma.LuaCs.Data; using FluentResults; -namespace Barotrauma.LuaCs.Services.Processing; +namespace Barotrauma.LuaCs; public interface IParserService : IService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILoggerService.cs similarity index 95% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILoggerService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILoggerService.cs index e1b0805e5..c4c110379 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILoggerService.cs @@ -3,7 +3,7 @@ using Barotrauma.Networking; using FluentResults; using Microsoft.Xna.Framework; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; /// /// Provides console and debug logging services diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaCsInfoProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaCsInfoProvider.cs new file mode 100644 index 000000000..337294941 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaCsInfoProvider.cs @@ -0,0 +1,42 @@ +namespace Barotrauma.LuaCs; + +/// +/// Provides access to data from the current . +/// +public interface ILuaCsInfoProvider : IService +{ + /// + /// Whether C# plugin code is enabled. + /// + public bool IsCsEnabled { get; } + + /// + /// Whether the popup error GUI should be hidden/suppressed. + /// + public bool DisableErrorGUIOverlay { get; } + + /// + /// Whether usernames are anonymized or show in logs. + /// + public bool HideUserNamesInLogs { get; } + + /// + /// The SteamId of the Workshop LuaCs CPackage in use, if available. + /// + public ulong LuaForBarotraumaSteamId { get; } + + /// + /// Restrict the maximum size of messages sent over the network. + /// + public bool RestrictMessageSize { get; } + + /// + /// The local save path for all local data storage for mods. + /// + public string LocalDataSavePath { get; } + + /// + /// The current state of the Execution State Machine. + /// + public RunState CurrentRunState { get; } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs similarity index 98% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs index d05a8e046..c490a11d1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/ILuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs @@ -10,7 +10,7 @@ using FluentResults; using MoonSharp.Interpreter; using MoonSharp.Interpreter.Interop; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public interface ILuaScriptManagementService : IReusableService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IModConfigService.cs similarity index 90% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IModConfigService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IModConfigService.cs index 78bac94aa..847c61020 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IModConfigService.cs @@ -4,10 +4,10 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services.Processing; +using Barotrauma.LuaCs; using FluentResults; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public interface IModConfigService : IService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs similarity index 88% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs index c3676da59..76543ea07 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/INetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs @@ -1,10 +1,10 @@ using System; using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services; -using Barotrauma.LuaCs.Services.Compatibility; +using Barotrauma.LuaCs; +using Barotrauma.LuaCs.Compatibility; using Barotrauma.Networking; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; internal delegate void NetMessageReceived(IReadMessage netMessage); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPackageManagementService.cs similarity index 97% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPackageManagementService.cs index b36bf93a4..c59da4ecf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPackageManagementService.cs @@ -8,7 +8,7 @@ using Barotrauma.Extensions; using Barotrauma.LuaCs.Data; using FluentResults; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public interface IPackageManagementService : IReusableService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPluginManagementService.cs similarity index 98% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPluginManagementService.cs index 711e69580..51453255f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPluginManagementService.cs @@ -5,7 +5,7 @@ using System.Reflection; using Barotrauma.LuaCs.Data; using Microsoft.CodeAnalysis; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public interface IPluginManagementService : IReusableService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPluginService.cs similarity index 98% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPluginService.cs index cfafe8e56..35cf1dfc4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPluginService.cs @@ -4,7 +4,7 @@ using System.Collections.Immutable; using System.Reflection; using Barotrauma.LuaCs.Data; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public interface IPluginService : IReusableService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ISafeStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ISafeStorageService.cs similarity index 98% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ISafeStorageService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ISafeStorageService.cs index 773bbbc28..e3e4428cc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ISafeStorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ISafeStorageService.cs @@ -1,6 +1,6 @@ using System.Collections.Immutable; -namespace Barotrauma.LuaCs.Services.Safe; +namespace Barotrauma.LuaCs; public interface ISafeStorageService : IStorageService, ISafeStorageValidation { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IService.cs similarity index 96% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IService.cs index a5502fc94..27abce8e2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IService.cs @@ -1,7 +1,7 @@ using System; using Microsoft.Toolkit.Diagnostics; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; /// /// Defines a service that can be reset to it's post-constructor state and reused without needing to be disposed. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IServicesProvider.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IServicesProvider.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IServicesProvider.cs index 901f3de4a..847540737 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IServicesProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IServicesProvider.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using LightInject; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; /// /// Provides instancing and management of IServices. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IStorageService.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IStorageService.cs index 19f112840..3a50e3dc2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IStorageService.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using System.Xml.Linq; using FluentResults; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public interface IStorageService : IService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/DefaultLuaRegistrar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/DefaultLuaRegistrar.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs index c7bc3c518..357121433 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/DefaultLuaRegistrar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs @@ -7,7 +7,7 @@ using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public interface IDefaultLuaRegistrar : IService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaConfigService.cs similarity index 61% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaConfigService.cs index f349e0f6a..ca60c8318 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaConfigService.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; -using Barotrauma.LuaCs.Configuration; +using Barotrauma.LuaCs.Data; using Microsoft.Xna.Framework; -namespace Barotrauma.LuaCs.Services.Safe; +namespace Barotrauma.LuaCs; public interface ILuaConfigService : ILuaService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaDataService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaDataService.cs similarity index 82% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaDataService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaDataService.cs index 050a9329b..4e3b49a83 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaDataService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaDataService.cs @@ -1,6 +1,6 @@ using MoonSharp.Interpreter; -namespace Barotrauma.LuaCs.Services.Safe; +namespace Barotrauma.LuaCs; /// /// Service for providing stateful functions and in-memory storage for lua functions diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaEventService.cs similarity index 95% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaEventService.cs index 50d470965..f075a5d78 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaEventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaEventService.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using Barotrauma.LuaCs.Events; -using Barotrauma.LuaCs.Services.Compatibility; +using Barotrauma.LuaCs.Compatibility; -namespace Barotrauma.LuaCs.Services.Safe; +namespace Barotrauma.LuaCs; public interface ILuaSafeEventService : ILuaService, ILuaCsHook { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaNetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaNetworkingService.cs similarity index 58% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaNetworkingService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaNetworkingService.cs index 5df591472..0a7447fe1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaNetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaNetworkingService.cs @@ -1,4 +1,4 @@ -namespace Barotrauma.LuaCs.Services.Safe; +namespace Barotrauma.LuaCs; public interface ILuaNetworkingService : ILuaService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaPackageManagementService.cs similarity index 60% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageManagementService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaPackageManagementService.cs index 391d680a5..c0b11ad49 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaPackageManagementService.cs @@ -1,4 +1,4 @@ -namespace Barotrauma.LuaCs.Services.Safe; +namespace Barotrauma.LuaCs; public interface ILuaPackageManagementService : ILuaService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaPackageService.cs similarity index 57% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaPackageService.cs index 891224015..f2c1b9162 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPackageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaPackageService.cs @@ -1,4 +1,4 @@ -namespace Barotrauma.LuaCs.Services.Safe; +namespace Barotrauma.LuaCs; public interface ILuaPackageService : ILuaService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPatcher.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaPatcher.cs similarity index 91% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPatcher.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaPatcher.cs index eba27adba..4de514875 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaPatcher.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaPatcher.cs @@ -1,8 +1,8 @@ using System.Reflection; -using static Barotrauma.LuaCs.Services.Compatibility.ILuaCsHook; +using static Barotrauma.LuaCs.Compatibility.ILuaCsHook; using LuaCsCompatPatchFunc = Barotrauma.LuaCsPatch; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public interface ILuaPatcher : IReusableService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaScriptLoader.cs similarity index 90% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaScriptLoader.cs index 5dde8dfbf..9cb26c3d6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/ILuaScriptLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaScriptLoader.cs @@ -4,7 +4,7 @@ using Barotrauma.LuaCs.Data; using FluentResults; using MoonSharp.Interpreter.Loaders; -namespace Barotrauma.LuaCs.Services.Safe; +namespace Barotrauma.LuaCs; public interface ILuaScriptLoader : IService, IScriptLoader, ISafeStorageValidation { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaService.cs new file mode 100644 index 000000000..6db78b287 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaService.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs; + +public interface ILuaService +{ + +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaBarotraumaAdditions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaBarotraumaAdditions.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaBarotraumaAdditions.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaBarotraumaAdditions.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaConverters.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaConverters.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaConverters.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaConverters.cs index b7d49ace8..a056afd54 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaConverters.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaConverters.cs @@ -5,7 +5,7 @@ using FarseerPhysics.Dynamics; using LuaCsCompatPatchFunc = Barotrauma.LuaCsPatch; using Barotrauma.Networking; using System.Collections.Immutable; -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs; namespace Barotrauma { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsLogger.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsLogger.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsNetworking.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsNetworking.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsNetworking.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsNetworking.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsPerformanceCounter.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsPerformanceCounter.cs similarity index 98% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsPerformanceCounter.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsPerformanceCounter.cs index 591ed005b..8d900e460 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsPerformanceCounter.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsPerformanceCounter.cs @@ -1,4 +1,4 @@ -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs; using System; using System.Collections.Generic; using System.Diagnostics; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsSteam.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsSteam.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsSteam.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsSteam.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsTimer.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsTimer.cs similarity index 97% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsTimer.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsTimer.cs index 67e23cb5c..a34986f56 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsTimer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsTimer.cs @@ -1,6 +1,6 @@ using Barotrauma.LuaCs.Events; -using Barotrauma.LuaCs.Services; -using Barotrauma.LuaCs.Services.Compatibility; +using Barotrauma.LuaCs; +using Barotrauma.LuaCs.Compatibility; using System; using System.Collections.Generic; using System.Diagnostics; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsUtility.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsUtility.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaCsUtility.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsUtility.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaGame.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaGame.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs index 6557b2fab..7e2a8c631 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaGame.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs @@ -3,13 +3,13 @@ using System.Collections.Generic; using System.Linq; using System.Xml.Linq; using Barotrauma.Items.Components; -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs; using Barotrauma.Networking; using FarseerPhysics.Dynamics; using Microsoft.Xna.Framework; using MoonSharp.Interpreter; -namespace Barotrauma.LuaCs.Services +namespace Barotrauma.LuaCs { partial class LuaGame : IReusableService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPlatformAccessor.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaPlatformAccessor.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaPlatformAccessor.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaPlatformAccessor.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaRequire.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaRequire.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaRequire.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaRequire.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaTypes.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaTypes.cs similarity index 100% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaClasses/LuaTypes.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaTypes.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaPatcherCompat.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaPatcherCompat.cs similarity index 98% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaPatcherCompat.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaPatcherCompat.cs index bb5a79331..4acf68f33 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaPatcherCompat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaPatcherCompat.cs @@ -1,11 +1,11 @@ -global using LuaCsHook = Barotrauma.LuaCs.Services.Compatibility.ILuaCsHook; +global using LuaCsHook = Barotrauma.LuaCs.Compatibility.ILuaCsHook; using System; using System.Linq; using System.Reflection; using HarmonyLib; using System.Collections.Generic; -using Barotrauma.LuaCs.Services.Compatibility; +using Barotrauma.LuaCs.Compatibility; using MoonSharp.Interpreter; using LuaCsCompatPatchFunc = Barotrauma.LuaCsPatch; @@ -15,7 +15,7 @@ namespace Barotrauma public delegate object LuaCsPatch(object self, Dictionary args); } -namespace Barotrauma.LuaCs.Services +namespace Barotrauma.LuaCs { partial class LuaPatcherService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaPatcherService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaPatcherService.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaPatcherService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaPatcherService.cs index 5d51769f7..55743b52d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaPatcherService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaPatcherService.cs @@ -1,4 +1,4 @@ -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs; using HarmonyLib; using Microsoft.Xna.Framework; using MoonSharp.Interpreter; @@ -23,7 +23,7 @@ namespace Barotrauma public delegate DynValue LuaCsPatchFunc(object instance, LuaPatcherService.ParameterTable ptable); } -namespace Barotrauma.LuaCs.Services +namespace Barotrauma.LuaCs { public partial class LuaPatcherService : ILuaPatcher { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaScriptLoader.cs similarity index 98% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaScriptLoader.cs index 28376f513..7c5b0445e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaScriptLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaScriptLoader.cs @@ -8,10 +8,10 @@ using MoonSharp.Interpreter.Loaders; using System.Linq; using System.Threading.Tasks; using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Services.Safe; +using Barotrauma.LuaCs; using FluentResults; -namespace Barotrauma.LuaCs.Services.Safe +namespace Barotrauma.LuaCs { public class LuaScriptLoader : ScriptLoaderBase, ILuaScriptLoader { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaUserDataService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaUserDataService.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaUserDataService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaUserDataService.cs index 51a20c41d..0584fc056 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/LuaUserDataService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaUserDataService.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public interface ILuaUserDataService : IReusableService { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeLuaUserDataService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/SafeLuaUserDataService.cs similarity index 99% rename from Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeLuaUserDataService.cs rename to Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/SafeLuaUserDataService.cs index 9f2dc0130..fe23103fe 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeLuaUserDataService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/SafeLuaUserDataService.cs @@ -1,12 +1,12 @@ using Barotrauma; -using Barotrauma.LuaCs.Services; +using Barotrauma.LuaCs; using MoonSharp.Interpreter; using MoonSharp.Interpreter.Interop; using System; using System.Collections.Generic; using System.Reflection; -namespace Barotrauma.LuaCs.Services; +namespace Barotrauma.LuaCs; public interface ISafeLuaUserDataService : IService { diff --git a/Barotrauma/BarotraumaTest/LuaCs/HookPatchHelpers.cs b/Barotrauma/BarotraumaTest/LuaCs/HookPatchHelpers.cs index 213744534..854f01140 100644 --- a/Barotrauma/BarotraumaTest/LuaCs/HookPatchHelpers.cs +++ b/Barotrauma/BarotraumaTest/LuaCs/HookPatchHelpers.cs @@ -1,6 +1,6 @@ extern alias Client; extern alias Server; -using Client::Barotrauma.LuaCs.Services; +using Client::Barotrauma.LuaCs; using Client::Barotrauma; using MoonSharp.Interpreter; using System; @@ -8,7 +8,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Text; using System.Threading; -using Server::Barotrauma.LuaCs.Services.Compatibility; +using Server::Barotrauma.LuaCs.Compatibility; using Xunit; namespace TestProject.LuaCs From 771e73a7980f6f291636b31046808a2ebec8378f Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 6 Feb 2026 14:29:25 -0500 Subject: [PATCH 112/288] - Basic Config & Settings working (read-only, changes must be made via debug halt). --- .../LuaCs/Data/ISettingTypeDef.cs | 1 + .../SharedSource/LuaCs/Data/SettingEntry.cs | 9 +- .../SettingsFactoryRegistrationProvider.cs | 55 ++++++--- .../SharedSource/LuaCs/LuaCsSetup.cs | 112 ++++++++++-------- 4 files changed, 102 insertions(+), 75 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs index 89e5b9662..766a4e02f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs @@ -86,6 +86,7 @@ public interface ISettingRangeBase : ISettingBase where T : IEquatable, /// The value type. See public interface ISettingList : ISettingBase where T : IEquatable, IConvertible { + bool TrySetValueByIndex(int index); IReadOnlyList Options { get; } IReadOnlyList StringOptions { get; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs index 55788c8f3..24aec5f62 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs @@ -36,18 +36,11 @@ public class SettingEntry : SettingBase, ISettingBase, INetworkSyncEntity try { Value = (T)Convert.ChangeType(ConfigInfo.Element.GetAttributeString("Value", null), typeof(T)); + DefaultValue = Value; } catch (Exception e) when (e is InvalidCastException or ArgumentNullException) { Value = default(T); - } - - try - { - DefaultValue = (T)Convert.ChangeType(ConfigInfo.Element.GetAttributeString("Value", null), typeof(T)); - } - catch (Exception e) when (e is InvalidCastException or ArgumentNullException) - { DefaultValue = default(T); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs index 35bf2b73b..3ab15e4d6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs @@ -2,6 +2,7 @@ using System.Xml.Linq; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs; +using OneOf; namespace Barotrauma.LuaCs.Data; @@ -21,33 +22,53 @@ public class SettingsEntryRegistrar : ISettingsRegistrationProvider public void RegisterTypeProviders(IConfigService configService, Func, bool> valueChangePredicate) { + // TODO: Replace this with a proper IFactory lookup pattern. // ISettingBase - RegisterSettingEntry(configService, "bool"); - RegisterSettingEntry(configService, "byte"); - RegisterSettingEntry(configService, "sbyte"); - RegisterSettingEntry(configService, "short"); - RegisterSettingEntry(configService, "ushort"); - RegisterSettingEntry(configService, "int"); - RegisterSettingEntry(configService, "uint"); - RegisterSettingEntry(configService, "long"); - RegisterSettingEntry(configService, "ulong"); - RegisterSettingEntry(configService, "string"); + RegisterSettingEntry(configService, "bool", valueChangePredicate); + RegisterSettingEntry(configService, "byte", valueChangePredicate); + RegisterSettingEntry(configService, "sbyte", valueChangePredicate); + RegisterSettingEntry(configService, "short", valueChangePredicate); + RegisterSettingEntry(configService, "ushort", valueChangePredicate); + RegisterSettingEntry(configService, "int", valueChangePredicate); + RegisterSettingEntry(configService, "uint", valueChangePredicate); + RegisterSettingEntry(configService, "long", valueChangePredicate); + RegisterSettingEntry(configService, "ulong", valueChangePredicate); + RegisterSettingEntry(configService, "string", valueChangePredicate); + // ISettingRangeBase - // ISettingList + configService.RegisterSettingTypeInitializer("rangeInt", cfgInfo => + { + return new SettingRangeInt.RangeFactory().CreateInstance(cfgInfo.Info, (val) => + IsValueChangeAllowed(cfgInfo.Info, val, valueChangePredicate)); + }); + + configService.RegisterSettingTypeInitializer("rangeFloat", cfgInfo => + { + return new SettingRangeFloat.RangeFactory().CreateInstance(cfgInfo.Info, (val) => + IsValueChangeAllowed(cfgInfo.Info, val, valueChangePredicate)); + }); + + // ISettingList : Not Implemented yet } - private void RegisterSettingEntry(IConfigService configService, string typeName) where T : IEquatable, IConvertible + private void RegisterSettingEntry(IConfigService configService, string typeName, Func, bool> valueChangePredicate) where T : IEquatable, IConvertible { configService.RegisterSettingTypeInitializer(typeName, cfgInfo => { - return new SettingEntry.Factory().CreateInstance(cfgInfo.Info, (val) => - { - return !cfgInfo.Info.Element.GetAttributeBool("ReadOnly", false) - && cfgInfo.Info.EditableStates.HasFlag(_infoProvider?.CurrentRunState ?? RunState.Running); - }); + return new SettingEntry.Factory().CreateInstance(cfgInfo.Info, (val) => + IsValueChangeAllowed(cfgInfo.Info, val, valueChangePredicate)); }); } + private bool IsValueChangeAllowed(IConfigInfo info, OneOf newValue, + Func, bool> valueChangePredicate) + { + return !info.Element.GetAttributeBool("ReadOnly", false) + || !info.EditableStates.HasFlag(_infoProvider?.CurrentRunState ?? RunState.Running) + || valueChangePredicate is null + || valueChangePredicate.Invoke(newValue); + } + public void Dispose() { if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 19ea7031e..b4379a95b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -33,28 +33,10 @@ namespace Barotrauma { // == startup _servicesProvider = SetupServicesProvider(); - if (!ValidateLuaCsContent()) - { - Logger.LogError($"{nameof(LuaCsSetup)}: ModConfig.xml missing. Unable to continue."); - throw new ApplicationException($"{nameof(LuaCsSetup)}: Lua's ModConfig.xml is missing. Unable to continue."); - } _runStateMachine = SetupStateMachine(); SubscribeToLuaCsEvents(); } - private bool ValidateLuaCsContent() - { -#if DEBUG - // TODO: we just wanna boot for now - return true; -#endif - // check if /Content/ModConfig.xml exists - // if not, try to copy missing files from the Local Mods folder - // if not, try to copy missing files from the Workshop Mods folder - // if that fails, throw an error and exit. - throw new NotImplementedException(); - } - private void SubscribeToLuaCsEvents() { EventService.Subscribe(this); // game state hook in @@ -102,7 +84,7 @@ namespace Barotrauma internal set => _isCsEnabled?.TrySetValue(value); } - private SettingEntry _isCsEnabled; + private ISettingBase _isCsEnabled; /// /// Whether the popup error GUI should be hidden/suppressed. @@ -112,7 +94,7 @@ namespace Barotrauma get => _disableErrorGUIOverlay?.Value ?? false; internal set => _disableErrorGUIOverlay?.TrySetValue(value); } - private SettingEntry _disableErrorGUIOverlay; + private ISettingBase _disableErrorGUIOverlay; /// /// Whether usernames are anonymized or show in logs. @@ -122,7 +104,7 @@ namespace Barotrauma get => _hideUserNamesInLogs?.Value ?? false; internal set => _hideUserNamesInLogs?.TrySetValue(value); } - private SettingEntry _hideUserNamesInLogs; + private ISettingBase _hideUserNamesInLogs; /// /// The SteamId of the Workshop LuaCs CPackage in use, if available. @@ -132,7 +114,7 @@ namespace Barotrauma get => _luaForBarotraumaSteamId?.Value ?? 0; internal set => _luaForBarotraumaSteamId?.TrySetValue(value); } - private SettingEntry _luaForBarotraumaSteamId; + private ISettingBase _luaForBarotraumaSteamId; /// /// TODO: @evilfactory@users.noreply.github.com @@ -142,7 +124,7 @@ namespace Barotrauma get => _restrictMessageSize?.Value ?? false; internal set => _restrictMessageSize?.TrySetValue(value); } - private SettingEntry _restrictMessageSize; + private ISettingBase _restrictMessageSize; /// /// The local save path for all local data storage for mods. @@ -152,28 +134,45 @@ namespace Barotrauma get => _localDataSavePath?.Value ?? Path.Combine(Directory.GetCurrentDirectory(), "/Data/Mods"); internal set => _localDataSavePath?.TrySetValue(value); } - private SettingEntry _localDataSavePath; + private ISettingBase _localDataSavePath; void LoadLuaCsConfig() { - _isCsEnabled = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "IsCsEnabled", out var val1) ? val1 - : throw new NullReferenceException($"{nameof(IsCsEnabled)} cannot be loaded."); - _disableErrorGUIOverlay = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "DisableErrorGUIOverlay", out var val3) ? val3 - : throw new NullReferenceException($"{nameof(DisableErrorGUIOverlay)} cannot be loaded."); - _hideUserNamesInLogs = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "HideUserNamesInLogs", out var val4) ? val4 - : throw new NullReferenceException($"{nameof(HideUserNamesInLogs)} cannot be loaded."); - _luaForBarotraumaSteamId = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "LuaForBarotraumaSteamId", out var val5) ? val5 - : throw new NullReferenceException($"{nameof(LuaForBarotraumaSteamId)} cannot be loaded."); - _restrictMessageSize = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "RestrictMessageSize", out var val7) ? val7 - : throw new NullReferenceException($"{nameof(RestrictMessageSize)} cannot be loaded."); - _localDataSavePath = ConfigService.TryGetConfig>(ContentPackageManager.VanillaCorePackage, "LocalDataSavePath", out var val8) ? val8 - : throw new NullReferenceException($"{nameof(LocalDataSavePath)} cannot be loaded."); + var luaCsPackage = ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches("LuaCsForBarotrauma"), null) + ?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches("LuaCsForBarotrauma")) + ?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches("LuaCsForBarotrauma")); + + _isCsEnabled = + ConfigService.TryGetConfig>(luaCsPackage, "IsCsEnabled", out var val1) + ? val1 + : null; + _disableErrorGUIOverlay = + ConfigService.TryGetConfig>(luaCsPackage, "DisableErrorGUIOverlay", out var val3) + ? val3 + : null; + _hideUserNamesInLogs = + ConfigService.TryGetConfig>(luaCsPackage, "HideUserNamesInLogs", out var val4) + ? val4 + : null; + _luaForBarotraumaSteamId = + ConfigService.TryGetConfig>(luaCsPackage, "LuaForBarotraumaSteamId", out var val5) + ? val5 + : null; + _restrictMessageSize = + ConfigService.TryGetConfig>(luaCsPackage, "RestrictMessageSize", out var val7) + ? val7 + : null; + _localDataSavePath = + ConfigService.TryGetConfig>(luaCsPackage, "LocalDataSavePath", out var val8) + ? val8 + : null; } private IServicesProvider SetupServicesProvider() { var servicesProvider = new ServicesProvider(); + // Base Service servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); @@ -182,9 +181,22 @@ namespace Barotrauma servicesProvider.RegisterServiceResolver(factory => factory.GetInstance() as ILuaCsHook); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceResolver(factory => factory.GetInstance()); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + // TODO: INetworkingService + + // Extension/Sub Services + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType, ModConfigFileParserService>(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType, ModConfigFileParserService>(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType, ModConfigFileParserService>(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType, SettingsFileParserService>(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType, SettingsFileParserService>(ServiceLifetime.Transient); + + // All Lua Extras servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); @@ -193,20 +205,13 @@ namespace Barotrauma servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - servicesProvider.RegisterServiceType(ServiceLifetime.Transient); - // TODO: INetworkingService - servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - servicesProvider.RegisterServiceType(ServiceLifetime.Transient); - servicesProvider.RegisterServiceType, ModConfigFileParserService>(ServiceLifetime.Transient); - servicesProvider.RegisterServiceType, ModConfigFileParserService>(ServiceLifetime.Transient); - servicesProvider.RegisterServiceType, ModConfigFileParserService>(ServiceLifetime.Transient); - servicesProvider.RegisterServiceType, SettingsFileParserService>(ServiceLifetime.Transient); - servicesProvider.RegisterServiceType, SettingsFileParserService>(ServiceLifetime.Transient); + // service config data servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + // gen IL servicesProvider.Compile(); return servicesProvider; @@ -291,6 +296,7 @@ namespace Barotrauma Logger.LogResults(PackageManagementService.UnloadAllPackages()); } + ConfigService.Reset(); LuaScriptManagementService.Reset(); PackageManagementService.Reset(); EventService.Reset(); @@ -309,9 +315,12 @@ namespace Barotrauma if (!PackageManagementService.IsAnyPackageLoaded()) { + foreach (var registrationProvider in _servicesProvider.GetAllServices()) + { + registrationProvider.RegisterTypeProviders(ConfigService, null); + } Logger.LogResults(PackageManagementService.LoadPackagesInfo(ContentPackageManager.EnabledPackages.All.ToImmutableArray())); - // TODO: Implement full xml content necessary for this to work. - //LoadLuaCsConfig(); + LoadLuaCsConfig(); } CurrentRunState = RunState.LoadedNoExec; @@ -321,9 +330,12 @@ namespace Barotrauma { if (!PackageManagementService.IsAnyPackageLoaded()) { + foreach (var registrationProvider in _servicesProvider.GetAllServices()) + { + registrationProvider.RegisterTypeProviders(ConfigService, null); + } Logger.LogResults(PackageManagementService.LoadPackagesInfo(ContentPackageManager.EnabledPackages.All.ToImmutableArray())); - // TODO: Enable later - //LoadLuaCsConfig(); + LoadLuaCsConfig(); } if (!PackageManagementService.IsAnyPackageRunning()) From fa340e91de36f5a84353421f4ff44f65fd61f5ff Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 6 Feb 2026 14:36:33 -0500 Subject: [PATCH 113/288] Fixed an issue affecting parsing the required runstate to edit a setting. --- .../LuaCs/Data/SettingsFactoryRegistrationProvider.cs | 2 +- .../LuaCs/_Services/SettingsFileParserService.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs index 3ab15e4d6..61a561fd2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs @@ -64,7 +64,7 @@ public class SettingsEntryRegistrar : ISettingsRegistrationProvider Func, bool> valueChangePredicate) { return !info.Element.GetAttributeBool("ReadOnly", false) - || !info.EditableStates.HasFlag(_infoProvider?.CurrentRunState ?? RunState.Running) + || info.EditableStates < _infoProvider.CurrentRunState || valueChangePredicate is null || valueChangePredicate.Invoke(newValue); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SettingsFileParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SettingsFileParserService.cs index 5ab01f02e..70699e6c4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SettingsFileParserService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SettingsFileParserService.cs @@ -92,9 +92,9 @@ public sealed class SettingsFileParserService : OwnerPackage = res.path.ContentPackage, DataType = element.GetAttributeString("Type", string.Empty), Element = element, - EditableStates = element.GetAttributeBool("AllowChangesWhileExecuting", true) ? RunState.Running : - element.GetAttributeBool("ReadOnly", false) ? RunState.LoadedNoExec : - RunState.Unloaded, + EditableStates = element.GetAttributeBool("ReadOnly", false) ? RunState.Unloaded : + element.GetAttributeBool("AllowChangesWhileExecuting", true) ? RunState.Running : + RunState.LoadedNoExec, NetSync = element.GetAttributeEnum("NetSync", NetSync.None), #if CLIENT DisplayName = $"{packageIdent}.{name}.DisplayName", From b3d0fbeb5d9f417d07d30e3964122be998ebb8dd Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Fri, 6 Feb 2026 19:17:43 -0300 Subject: [PATCH 114/288] Add cfg_setvalue command --- .../SharedSource/DebugConsole.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index 05444468b..22597e938 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -2311,6 +2311,55 @@ namespace Barotrauma NewMessage($"Start item set changed to \"{AutoItemPlacer.DefaultStartItemSet}\""); }, isCheat: false)); + commands.Add(new Command("cfg_setvalue", "cfg_setvalue [Content Package] [InternalName] [ValueString]: sets a config.", (string[] args) => + { + if (args.Length < 1) + { + ThrowError("Please specify the name of the package to set the config."); + return; + } + + if (args.Length < 2) + { + ThrowError("Please specify the name of the config."); + return; + } + + if (args.Length < 3) + { + ThrowError("Please specify the value to set the config to."); + return; + } + + var package = ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Name == args[0]); + if (package == null) + { + ThrowError($"Could not find the package {args[0]}!"); + return; + } + + string internalName = args[1]; + string valueString = args[2]; + + if (!GameMain.LuaCs.ConfigService.TryGetConfig(package, internalName, out LuaCs.Data.ISettingBase setting)) + { + ThrowError($"Could not get config with name {internalName}"); + return; + } + + if (setting.TrySetValue(valueString)) + { + NewMessage($"Set config {internalName} value to {valueString}", Color.Green); + } + else + { + ThrowError($"Failed to set config value"); + } + }, getValidArgs: () => new[] + { + ContentPackageManager.RegularPackages.Select(p => p.Name).ToArray() + })); + //"dummy commands" that only exist so that the server can give clients permissions to use them //TODO: alphabetical order? commands.Add(new Command("control", "control [character name]: Start controlling the specified character (client-only).", null, () => From dcd7df4860c02e4c6586f3ba245e11006cf72acb Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 6 Feb 2026 17:20:15 -0500 Subject: [PATCH 115/288] Fixed TrySetValue returning an incorrect result. --- .../BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs index 24aec5f62..76d6e4d91 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs @@ -102,7 +102,7 @@ public class SettingEntry : SettingBase, ISettingBase, INetworkSyncEntity } }); - return isFailed || TrySetValue(typeConvertedValue); + return !isFailed && TrySetValue(typeConvertedValue); } public override OneOf GetSerializableValue() => Value.ToString(); From c84d9660e20b56f93e85cfebc453aacb1e0825a2 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Fri, 6 Feb 2026 20:14:57 -0300 Subject: [PATCH 116/288] Add command cfg_getvalue --- .../SharedSource/DebugConsole.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index 22597e938..79fcd8002 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -2311,6 +2311,41 @@ namespace Barotrauma NewMessage($"Start item set changed to \"{AutoItemPlacer.DefaultStartItemSet}\""); }, isCheat: false)); + commands.Add(new Command("cfg_getvalue", "cfg_getvalue [Content Package] [InternalName] [ValueString]: gets a config value.", (string[] args) => + { + if (args.Length < 1) + { + ThrowError("Please specify the name of the package to set the config."); + return; + } + + if (args.Length < 2) + { + ThrowError("Please specify the name of the config."); + return; + } + + var package = ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Name == args[0]); + if (package == null) + { + ThrowError($"Could not find the package {args[0]}!"); + return; + } + + string internalName = args[1]; + + if (!GameMain.LuaCs.ConfigService.TryGetConfig(package, internalName, out LuaCs.Data.ISettingBase setting)) + { + ThrowError($"Could not get config with name {internalName}"); + return; + } + + NewMessage($"config {internalName} value is {setting.GetStringValue()}", Color.Green); + }, getValidArgs: () => new[] + { + ContentPackageManager.RegularPackages.Select(p => p.Name).ToArray() + })); + commands.Add(new Command("cfg_setvalue", "cfg_setvalue [Content Package] [InternalName] [ValueString]: sets a config.", (string[] args) => { if (args.Length < 1) From 2c29969bfbe2e9e3236967425246c22bd49f7f62 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 6 Feb 2026 18:50:27 -0500 Subject: [PATCH 117/288] - Oops. --- .../SharedSource/LuaCs/_Services/PluginManagementService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index 3164128d1..dc757b23e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -223,7 +223,8 @@ public class PluginManagementService : IAssemblyManagementService foreach (var type in ass.GetSafeTypes()) { if ((includeInterfaces || !type.IsInterface) - && (includeAbstractTypes || !type.IsAbstract)) + && (includeAbstractTypes || !type.IsAbstract) + && type.IsAssignableTo(typeof(T))) { builder.Add(type); } From d47b75c7785234fb57d7375bd4f3e339b7935cfe Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Fri, 6 Feb 2026 21:34:25 -0300 Subject: [PATCH 118/288] Give Lua references to IEvent and finally add an alias for think --- .../_Services/LuaScriptManagementService.cs | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index ed5678049..d9b7a0346 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -21,6 +21,8 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using Barotrauma.LuaCs; +using Barotrauma.LuaCs.Events; +using System.Diagnostics; namespace Barotrauma.LuaCs; @@ -41,9 +43,10 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService private readonly ILuaScriptServicesConfig _luaScriptServicesConfig; private readonly ILoggerService _loggerService; private readonly LuaGame _luaGame; - private readonly ILuaCsHook _luaCsHook; + private readonly IEventService _eventService; private readonly ILuaCsTimer _luaCsTimer; private readonly IDefaultLuaRegistrar _defaultLuaRegistrar; + private readonly IPluginManagementService _pluginManagementService; //private readonly ILuaCsNetworking _luaCsNetworking; //private readonly ILuaCsUtility _luaCsUtility; @@ -54,8 +57,9 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService ISafeLuaUserDataService safeUserDataService, IDefaultLuaRegistrar defaultLuaRegistrar, ILuaScriptServicesConfig luaScriptServicesConfig, + IPluginManagementService pluginManagementService, LuaGame luaGame, - ILuaCsHook luaCsHook, + IEventService eventService, //ILuaCsNetworking luaCsNetworking, //ILuaCsUtility luaCsUtility, ILuaCsTimer luaCsTimer @@ -67,12 +71,15 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _defaultLuaRegistrar = defaultLuaRegistrar; _luaScriptServicesConfig = luaScriptServicesConfig; _loggerService = loggerService; + _pluginManagementService = pluginManagementService; _luaGame = luaGame; - _luaCsHook = luaCsHook; + _eventService = eventService; //_luaCsNetworking = luaCsNetworking; //_luaCsUtility = luaCsUtility; _luaCsTimer = luaCsTimer; + + RegisterLuaEvents(); } public bool IsDisposed { get; private set; } @@ -159,6 +166,11 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService return _script.LoadFile(file, globalContext, codeStringFriendly); } + private void RegisterLuaEvents() + { + _eventService.RegisterLuaEventAlias("think", "OnUpdate"); + } + private void SetupEnvironment() { _script = new Script(CoreModules.Preset_SoftSandbox | CoreModules.Debug | CoreModules.IO | CoreModules.OS_System); @@ -196,7 +208,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _script.Globals["dostring"] = (Func)_script.DoString; _script.Globals["load"] = (Func)_script.LoadString; _script.Globals["Game"] = _luaGame; - _script.Globals["Hook"] = _luaCsHook; + _script.Globals["Hook"] = _eventService; _script.Globals["Timer"] = _luaCsTimer; _script.Globals["File"] = UserData.CreateStatic(); //_script.Globals["Networking"] = _luaCsNetworking; @@ -213,8 +225,25 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _script.Globals["LuaUserData"] = _safeUserDataService; } + Table eventsTable = new Table(_script); + + var typesValue = _pluginManagementService.GetImplementingTypes(includeInterfaces: true, includeAbstractTypes: true); + if (typesValue.IsSuccess) + { + foreach (var eventType in typesValue.Value) + { + if (eventType.IsGenericType) { continue; } + if (!eventType.IsInterface) { continue; } + + UserData.RegisterType(eventType); + eventsTable[eventType.Name] = UserData.CreateStatic(eventType); + } + } + + _script.Globals["Events"] = eventsTable; + _script.Globals["ExecutionNumber"] = 0; - _script.Globals["CSActive"] = false; + _script.Globals["CSActive"] = GameMain.LuaCs.IsCsEnabled; _script.Globals["SERVER"] = LuaCsSetup.IsServer; _script.Globals["CLIENT"] = LuaCsSetup.IsClient; @@ -305,6 +334,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService public FluentResults.Result Reset() { _userDataService.Reset(); + RegisterLuaEvents(); return DisposeAllPackageResources(); } From 87dc9be10ea43967d4a58b6bf632d7add4f54408 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Fri, 6 Feb 2026 23:50:43 -0500 Subject: [PATCH 119/288] -Changed NetworkSync interfaces. --- .../SharedSource/LuaCs/Data/SettingEntry.cs | 4 ++-- .../SharedSource/LuaCs/_Networking/INetworkSyncEntity.cs | 4 ++-- .../SharedSource/LuaCs/_Services/NetworkingService.cs | 8 ++++---- .../LuaCs/_Services/_Interfaces/INetworkingService.cs | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs index 76d6e4d91..40709b429 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs @@ -9,7 +9,7 @@ using OneOf; namespace Barotrauma.LuaCs.Data; -public class SettingEntry : SettingBase, ISettingBase, INetworkSyncEntity where T : IEquatable, IConvertible +public class SettingEntry : SettingBase, ISettingBase, INetworkSyncVar where T : IEquatable, IConvertible { public class Factory : ISettingBase.IFactory> { @@ -109,7 +109,7 @@ public class SettingEntry : SettingBase, ISettingBase, INetworkSyncEntity // -- Networking protected IEntityNetworkingService NetworkingService; - public ulong InstanceId => NetworkingService?.GetNetworkIdForInstance(this) ?? 0ul; + public Guid InstanceId => NetworkingService?.GetNetworkIdForInstance(this) ?? Guid.Empty; public void SetNetworkOwner(IEntityNetworkingService networkingService) { NetworkingService = networkingService; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/INetworkSyncEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/INetworkSyncEntity.cs index c9c5e3851..75ec9d865 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/INetworkSyncEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/INetworkSyncEntity.cs @@ -5,12 +5,12 @@ using Barotrauma.Networking; namespace Barotrauma.LuaCs; -public interface INetworkSyncEntity +public interface INetworkSyncVar { /// /// Network-synchronized object ID. Used for networking send/receive message events. /// - ulong InstanceId { get; } + Guid InstanceId { get; } /// /// Sets the that is currently managing this instance. The diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs index 455f02138..baf09ecd1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs @@ -22,7 +22,7 @@ internal partial class NetworkingService : INetworkingService ReceiveIds } - private Dictionary netVars = new Dictionary(); + private Dictionary netVars = new Dictionary(); private Dictionary netReceives = new Dictionary(); private Dictionary packetToId = new Dictionary(); private Dictionary idToPacket = new Dictionary(); @@ -104,17 +104,17 @@ internal partial class NetworkingService : INetworkingService IsDisposed = true; } - public ulong GetNetworkIdForInstance(INetworkSyncEntity entity) + public Guid GetNetworkIdForInstance(INetworkSyncVar var) { throw new NotImplementedException(); } - public void RegisterNetVar(INetworkSyncEntity netVar) + public void RegisterNetVar(INetworkSyncVar netVar) { throw new NotImplementedException(); } - public void SendNetVar(INetworkSyncEntity netVar) + public void SendNetVar(INetworkSyncVar netVar) { throw new NotImplementedException(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs index 76543ea07..bd1957d23 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs @@ -25,7 +25,7 @@ internal partial interface INetworkingService : IReusableService, ILuaCsNetworki public interface IEntityNetworkingService { - public ulong GetNetworkIdForInstance(INetworkSyncEntity entity); - public void RegisterNetVar(INetworkSyncEntity netVar); - public void SendNetVar(INetworkSyncEntity netVar); + public Guid GetNetworkIdForInstance(INetworkSyncVar var); + public void RegisterNetVar(INetworkSyncVar netVar); + public void SendNetVar(INetworkSyncVar netVar); } From ba10d9d031f9c5e1c6ed8d2973c318898fdf834f Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 7 Feb 2026 18:22:19 -0300 Subject: [PATCH 120/288] Working NetworkingService without net vars --- .../Compatibility/ILuaCsNetworking.cs | 8 -- .../LuaCs/Services/INetworkingService.cs | 8 -- .../LuaCs/Services/NetworkingService.cs | 63 ++++----- .../ClientSource/Networking/GameClient.cs | 3 +- .../LuaCs/Services/INetworkingService.cs | 9 -- .../LuaCs/Services/NetworkingService.cs | 60 ++++----- .../ServerSource/Networking/GameServer.cs | 11 +- .../LuaCs/Compatibility/ILuaCsNetworking.cs | 2 +- .../SharedSource/LuaCs/IEvents.cs | 18 +++ .../SharedSource/LuaCs/LuaCsSetup.cs | 17 ++- .../_Services/LuaScriptManagementService.cs | 8 +- .../LuaCs/_Services/NetworkingService.cs | 124 ++++++++++++------ .../_Interfaces/INetworkingService.cs | 26 ++-- .../_Services/_Lua/DefaultLuaRegistrar.cs | 3 - .../_Lua/LuaClasses/LuaConverters.cs | 16 +++ 15 files changed, 226 insertions(+), 150 deletions(-) delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/INetworkingService.cs delete mode 100644 Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/INetworkingService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs deleted file mode 100644 index dbfbb9530..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/Compatibility/ILuaCsNetworking.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Barotrauma.Networking; - -namespace Barotrauma.LuaCs.Compatibility; - -internal partial interface ILuaCsNetworking : ILuaCsShim -{ - -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/INetworkingService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/INetworkingService.cs deleted file mode 100644 index e78f8a245..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/INetworkingService.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Barotrauma.Networking; - -namespace Barotrauma.LuaCs; - -internal partial interface INetworkingService : IReusableService -{ - void NetMessageReceived(IReadMessage message, ServerPacketHeader header); -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs index d19a3ae9e..34c1a978d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs @@ -1,4 +1,5 @@ using Barotrauma.LuaCs; +using Barotrauma.LuaCs.Events; using Barotrauma.Networking; using System; using System.Collections.Concurrent; @@ -6,51 +7,51 @@ using System.Collections.Generic; namespace Barotrauma.LuaCs; -partial class NetworkingService : INetworkingService +public partial class NetworkingService : INetworkingService, IEventConnectedToServer, IEventServerRawNetMessageReceived { private ConcurrentDictionary> receiveQueue = new(); - public void SendSyncMessage() + public void OnConnectedToServer() { - if (GameMain.Client == null) { return; } - - WriteOnlyMessage message = new WriteOnlyMessage(); - message.WriteByte((byte)ClientPacketHeader.LUA_NET_MESSAGE); - message.WriteByte((byte)LuaCsClientToServer.RequestAllIds); - GameMain.Client.ClientPeer.Send(message, DeliveryMethod.Reliable); + SendSyncMessage(); } - public void NetMessageReceived(IReadMessage netMessage, ServerPacketHeader header, Client client = null) + public void OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader) { - if (header != ServerPacketHeader.LUA_NET_MESSAGE) + if (serverPacketHeader != ServerPacketHeader.LUA_NET_MESSAGE) { return; } - LuaCsServerToClient luaCsHeader = (LuaCsServerToClient)netMessage.ReadByte(); + ServerToClient luaCsHeader = (ServerToClient)netMessage.ReadByte(); switch (luaCsHeader) { - case LuaCsServerToClient.NetMessageString: + case ServerToClient.NetMessageNetId: HandleNetMessageString(netMessage); break; - case LuaCsServerToClient.NetMessageId: + case ServerToClient.NetMessageInternalId: HandleNetMessageId(netMessage); break; - case LuaCsServerToClient.ReceiveIds: + case ServerToClient.ReceiveNetIds: ReadIds(netMessage); break; } } - public void NetMessageReceived(IReadMessage message, ServerPacketHeader header) + private void SendSyncMessage() { - throw new NotImplementedException(); + if (GameMain.Client == null) { return; } + + WriteOnlyMessage message = new WriteOnlyMessage(); + message.WriteByte((byte)ClientPacketHeader.LUA_NET_MESSAGE); + message.WriteByte((byte)ClientToServer.RequestAllNetIds); + GameMain.Client.ClientPeer.Send(message, DeliveryMethod.Reliable); } - public IWriteMessage Start(Guid netId) + public IWriteMessage Start(NetId netId) { var message = new WriteOnlyMessage(); @@ -58,19 +59,24 @@ partial class NetworkingService : INetworkingService if (idToPacket.ContainsKey(netId)) { - message.WriteByte((byte)LuaCsClientToServer.NetMessageId); + message.WriteByte((byte)ClientToServer.NetMessageInternalId); message.WriteUInt16(idToPacket[netId]); } else { - message.WriteByte((byte)LuaCsClientToServer.NetMessageString); - message.WriteBytes(netId.ToByteArray(), 0, 16); + message.WriteByte((byte)ClientToServer.NetMessageNetId); + NetId.Write(message, netId); } return message; } - public void RequestId(Guid netId) + public void SendToServer(IWriteMessage netMessage, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable) + { + GameMain.Client.ClientPeer.Send(netMessage, deliveryMethod); + } + + private void RequestId(NetId netId) { if (idToPacket.ContainsKey(netId)) { return; } @@ -78,16 +84,11 @@ partial class NetworkingService : INetworkingService WriteOnlyMessage message = new WriteOnlyMessage(); message.WriteByte((byte)ClientPacketHeader.LUA_NET_MESSAGE); - message.WriteByte((byte)LuaCsClientToServer.RequestSingleId); + message.WriteByte((byte)ClientToServer.RequestSingleNetId); - message.WriteBytes(netId.ToByteArray(), 0, 16); + NetId.Write(message, netId); - Send(message, DeliveryMethod.Reliable); - } - - public void Send(IWriteMessage netMessage, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable) - { - GameMain.Client.ClientPeer.Send(netMessage, deliveryMethod); + SendToServer(message, DeliveryMethod.Reliable); } private void HandleNetMessageId(IReadMessage netMessage, Client client = null) @@ -105,7 +106,7 @@ partial class NetworkingService : INetworkingService if (GameSettings.CurrentConfig.VerboseLogging) { - LuaCsLogger.LogMessage($"Received NetMessage with unknown id {id} from server, storing in queue in case we receive the id later."); + _loggerService.LogMessage($"Received NetMessage with unknown id {id} from server, storing in queue in case we receive the id later."); } } } @@ -117,7 +118,7 @@ partial class NetworkingService : INetworkingService for (int i = 0; i < size; i++) { ushort packetId = netMessage.ReadUInt16(); - Guid netId = new Guid(netMessage.ReadBytes(16)); + NetId netId = NetId.Read(netMessage); packetToId[packetId] = netId; idToPacket[netId] = packetId; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index 5b908beef..d5d195ce8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -1,6 +1,7 @@ using Barotrauma.Extensions; using Barotrauma.IO; using Barotrauma.Items.Components; +using Barotrauma.LuaCs.Events; using Barotrauma.Steam; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; @@ -603,7 +604,7 @@ namespace Barotrauma.Networking { ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte(); - GameMain.LuaCs.NetworkingService.NetMessageReceived(inc, header); + GameMain.LuaCs.EventService.PublishEvent(p => p.OnReceivedServerNetMessage(inc, header)); if (roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize && header is not ( diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/INetworkingService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/INetworkingService.cs deleted file mode 100644 index bc64aa010..000000000 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/INetworkingService.cs +++ /dev/null @@ -1,9 +0,0 @@ - -using Barotrauma.Networking; - -namespace Barotrauma.LuaCs; - -internal partial interface INetworkingService : IReusableService -{ - void NetMessageReceived(IReadMessage message, ClientPacketHeader header, Client client); -} diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs index cddd49806..f2bed92ae 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs @@ -1,4 +1,4 @@ -using Barotrauma.LuaCs; +using Barotrauma.LuaCs.Events; using Barotrauma.Networking; using System; using System.Collections.Generic; @@ -7,7 +7,7 @@ using System.Linq; // ReSharper disable once CheckNamespace namespace Barotrauma.LuaCs; -partial class NetworkingService : INetworkingService +partial class NetworkingService : INetworkingService, IEventClientRawNetMessageReceived { private const int MaxRegisterPerClient = 1000; @@ -15,7 +15,7 @@ partial class NetworkingService : INetworkingService private ushort currentId = 0; - public IWriteMessage Start(Guid netId) + public IWriteMessage Start(NetId netId) { var message = new WriteOnlyMessage(); @@ -23,42 +23,44 @@ partial class NetworkingService : INetworkingService if (idToPacket.ContainsKey(netId)) { - message.WriteByte((byte)LuaCsServerToClient.NetMessageId); + message.WriteByte((byte)ServerToClient.NetMessageInternalId); message.WriteUInt16(idToPacket[netId]); } else { - message.WriteByte((byte)LuaCsServerToClient.NetMessageString); - message.WriteBytes(netId.ToByteArray(), 0, 16); + message.WriteByte((byte)ServerToClient.NetMessageNetId); + NetId.Write(message, netId); } return message; } - public void NetMessageReceived(IReadMessage netMessage, ClientPacketHeader header, Client client = null) + public void OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader serverPacketHeader, NetworkConnection sender) { - if (header != ClientPacketHeader.LUA_NET_MESSAGE) + if (serverPacketHeader != ClientPacketHeader.LUA_NET_MESSAGE) { return; } - LuaCsClientToServer luaCsHeader = (LuaCsClientToServer)netMessage.ReadByte(); + Client client = GameMain.Server.ConnectedClients.First(c => c.Connection == sender); + + ClientToServer luaCsHeader = (ClientToServer)netMessage.ReadByte(); switch (luaCsHeader) { - case LuaCsClientToServer.NetMessageString: + case ClientToServer.NetMessageNetId: HandleNetMessageString(netMessage, client); break; - case LuaCsClientToServer.NetMessageId: + case ClientToServer.NetMessageInternalId: HandleNetMessageId(netMessage, client); break; - case LuaCsClientToServer.RequestAllIds: + case ClientToServer.RequestAllNetIds: WriteAllIds(client); break; - case LuaCsClientToServer.RequestSingleId: + case ClientToServer.RequestSingleNetId: RequestIdSingle(netMessage, client); break; } @@ -70,7 +72,7 @@ partial class NetworkingService : INetworkingService if (packetToId.ContainsKey(id)) { - Guid netId = packetToId[id]; + NetId netId = packetToId[id]; HandleNetMessage(netMessage, netId, client); } @@ -78,12 +80,12 @@ partial class NetworkingService : INetworkingService { if (GameSettings.CurrentConfig.VerboseLogging) { - LuaCsLogger.LogError($"Received NetMessage for unknown id {id} from {GameServer.ClientLogName(client)}."); + _loggerService.LogError($"Received NetMessage for unknown id {id} from {GameServer.ClientLogName(client)}."); } } } - private ushort RegisterId(Guid netId) + private ushort RegisterId(NetId netId) { if (idToPacket.ContainsKey(netId)) { @@ -92,7 +94,7 @@ partial class NetworkingService : INetworkingService if (currentId >= ushort.MaxValue) { - LuaCsLogger.LogError($"Tried to register more than {ushort.MaxValue} network ids!"); + _loggerService.LogError($"Tried to register more than {ushort.MaxValue} network ids!"); return 0; } @@ -108,7 +110,7 @@ partial class NetworkingService : INetworkingService private void RequestIdSingle(IReadMessage netMessage, Client client) { - Guid netId = new Guid(netMessage.ReadBytes(16)); + NetId netId = NetId.Read(netMessage); if (!idToPacket.ContainsKey(netId) && client.AccountId.TryUnwrap(out AccountId id)) { @@ -121,7 +123,7 @@ partial class NetworkingService : INetworkingService if (clientRegisterCount[id.StringRepresentation] > MaxRegisterPerClient) { - LuaCsLogger.Log($"{GameServer.ClientLogName(client)} Tried to register more than {MaxRegisterPerClient} Ids!"); + _loggerService.Log($"{GameServer.ClientLogName(client)} Tried to register more than {MaxRegisterPerClient} Ids!"); return; } } @@ -129,38 +131,36 @@ partial class NetworkingService : INetworkingService RegisterId(netId); } - private void WriteIdToAll(ushort packet, Guid netId) + private void WriteIdToAll(ushort packet, NetId netId) { WriteOnlyMessage message = new WriteOnlyMessage(); message.WriteByte((byte)ServerPacketHeader.LUA_NET_MESSAGE); - message.WriteByte((byte)LuaCsServerToClient.ReceiveIds); + message.WriteByte((byte)ServerToClient.ReceiveNetIds); message.WriteUInt16(1); message.WriteUInt16(packet); - message.WriteBytes(netId.ToByteArray(), 0, 16); + NetId.Write(message, netId); - Send(message, null, DeliveryMethod.Reliable); + SendToClient(message, null, DeliveryMethod.Reliable); } private void WriteAllIds(Client client) { WriteOnlyMessage message = new WriteOnlyMessage(); message.WriteByte((byte)ServerPacketHeader.LUA_NET_MESSAGE); - message.WriteByte((byte)LuaCsServerToClient.ReceiveIds); + message.WriteByte((byte)ServerToClient.ReceiveNetIds); message.WriteUInt16((ushort)packetToId.Count()); - foreach ((ushort packet, Guid netId) in packetToId) + foreach ((ushort packet, NetId netId) in packetToId) { message.WriteUInt16(packet); - message.WriteBytes(netId.ToByteArray(), 0, 16); + NetId.Write(message, netId); } - Send(message, client.Connection, DeliveryMethod.Reliable); + SendToClient(message, client.Connection, DeliveryMethod.Reliable); } - public void ClientWriteLobby(Client client) => GameMain.Server.ClientWriteLobby(client); - - public void Send(IWriteMessage netMessage, NetworkConnection connection = null, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable) + public void SendToClient(IWriteMessage netMessage, NetworkConnection connection = null, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable) { if (connection == null) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 8a734eec6..a7b5dd02b 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -1,19 +1,20 @@ using Barotrauma.Extensions; using Barotrauma.IO; using Barotrauma.Items.Components; +using Barotrauma.LuaCs.Events; +using Barotrauma.PerkBehaviors; using Barotrauma.Steam; using Lidgren.Network; using Microsoft.Xna.Framework; +using MoonSharp.Interpreter; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; +using System.Net; using System.Threading; using System.Xml.Linq; -using MoonSharp.Interpreter; -using System.Net; -using Barotrauma.PerkBehaviors; namespace Barotrauma.Networking { @@ -836,8 +837,8 @@ namespace Barotrauma.Networking using var _ = dosProtection.Start(connectedClient); ClientPacketHeader header = (ClientPacketHeader)inc.ReadByte(); - - GameMain.LuaCs.NetworkingService.NetMessageReceived(inc, header, connectedClient); + + GameMain.LuaCs.EventService.PublishEvent(p => p.OnReceivedClientNetMessage(inc, header, sender)); switch (header) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs index 0ac2c99d4..b5356b084 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs @@ -1,6 +1,6 @@ namespace Barotrauma.LuaCs.Compatibility; -internal partial interface ILuaCsNetworking : ILuaCsShim +public interface ILuaCsNetworking : ILuaCsShim { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index 7eca1b12e..8c311239a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -121,6 +121,24 @@ public interface IEventDrawUpdate : IEvent }.ActLike(); } +#if CLIENT +public interface IEventServerRawNetMessageReceived : IEvent +{ + void OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader); +} + +public interface IEventConnectedToServer : IEvent +{ + void OnConnectedToServer(); +} + +#elif SERVER +public interface IEventClientRawNetMessageReceived : IEvent +{ + void OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader serverPacketHeader, NetworkConnection sender); +} +#endif + #endregion #region Networking diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index b4379a95b..55c48708e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -184,7 +184,7 @@ namespace Barotrauma servicesProvider.RegisterServiceResolver(factory => factory.GetInstance()); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - // TODO: INetworkingService + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); // Extension/Sub Services servicesProvider.RegisterServiceType(ServiceLifetime.Transient); @@ -296,10 +296,11 @@ namespace Barotrauma Logger.LogResults(PackageManagementService.UnloadAllPackages()); } + EventService.Reset(); ConfigService.Reset(); LuaScriptManagementService.Reset(); PackageManagementService.Reset(); - EventService.Reset(); + NetworkingService.Reset(); SubscribeToLuaCsEvents(); @@ -343,10 +344,18 @@ namespace Barotrauma #if DEBUG Logger.LogResults(PackageManagementService.ExecuteLoadedPackages(ContentPackageManager.EnabledPackages.All.ToImmutableArray(), true)); #else - Logger.LogResults(PackageManagementService.ExecuteLoadedPackages(ContentPackageManager.EnabledPackages.All.ToImmutableArray(), IsCsEnabled)); + Logger.LogResults(PackageManagementService.ExecuteLoadedPackages(ContentPackageManager.EnabledPackages.All.ToImmutableArray(), IsCsEnabled)); #endif } - + +#if CLIENT + // Technically not very accurate, but we want to call after we run mods anyway + if (GameMain.Client != null) + { + EventService.PublishEvent(static p => p.OnConnectedToServer()); + } +#endif + CurrentRunState = RunState.Running; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index d9b7a0346..da7e3dcc3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -47,7 +47,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService private readonly ILuaCsTimer _luaCsTimer; private readonly IDefaultLuaRegistrar _defaultLuaRegistrar; private readonly IPluginManagementService _pluginManagementService; - //private readonly ILuaCsNetworking _luaCsNetworking; + private readonly INetworkingService _networkingService; //private readonly ILuaCsUtility _luaCsUtility; public LuaScriptManagementService( @@ -58,9 +58,9 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService IDefaultLuaRegistrar defaultLuaRegistrar, ILuaScriptServicesConfig luaScriptServicesConfig, IPluginManagementService pluginManagementService, + INetworkingService networkingService, LuaGame luaGame, IEventService eventService, - //ILuaCsNetworking luaCsNetworking, //ILuaCsUtility luaCsUtility, ILuaCsTimer luaCsTimer ) @@ -72,6 +72,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _luaScriptServicesConfig = luaScriptServicesConfig; _loggerService = loggerService; _pluginManagementService = pluginManagementService; + _networkingService = networkingService; _luaGame = luaGame; _eventService = eventService; @@ -192,6 +193,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService UserData.RegisterType(typeof(ILuaScriptResourceInfo)); UserData.RegisterType(typeof(IResourceInfo)); UserData.RegisterType(typeof(IUserDataDescriptor)); + UserData.RegisterType(typeof(INetworkingService)); new LuaConverters(_script).RegisterLuaConverters(); @@ -211,7 +213,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _script.Globals["Hook"] = _eventService; _script.Globals["Timer"] = _luaCsTimer; _script.Globals["File"] = UserData.CreateStatic(); - //_script.Globals["Networking"] = _luaCsNetworking; + _script.Globals["Networking"] = _networkingService; //_script.Globals["Steam"] = Steam; if (GameMain.LuaCs.IsCsEnabled) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs index baf09ecd1..3f9e3d0e2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs @@ -1,52 +1,88 @@ using Barotrauma.LuaCs; +using Barotrauma.LuaCs.Compatibility; +using Barotrauma.LuaCs.Events; using Barotrauma.Networking; +using FluentResults; using System; using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; namespace Barotrauma.LuaCs; -internal partial class NetworkingService : INetworkingService +public partial class NetworkingService : INetworkingService { - private enum LuaCsClientToServer + public readonly record struct NetId { - NetMessageId, - NetMessageString, - RequestSingleId, - RequestAllIds, + private readonly string _value; + + public NetId(string netId) + { + _value = netId; + } + + public static void Write(IWriteMessage message, NetId netId) + { + message.WriteString(netId._value); + } + + public static NetId Read(IReadMessage message) + { + return new NetId(message.ReadString()); + } } - private enum LuaCsServerToClient + private enum ClientToServer { - NetMessageId, - NetMessageString, - ReceiveIds + NetMessageInternalId, + NetMessageNetId, + RequestSingleNetId, + RequestAllNetIds, } - private Dictionary netVars = new Dictionary(); - private Dictionary netReceives = new Dictionary(); - private Dictionary packetToId = new Dictionary(); - private Dictionary idToPacket = new Dictionary(); + private enum ServerToClient + { + NetMessageInternalId, + NetMessageNetId, + ReceiveNetIds + } + + private Dictionary netReceives = new Dictionary(); + private Dictionary packetToId = new Dictionary(); + private Dictionary idToPacket = new Dictionary(); public bool IsActive { get { - return GameMain.NetworkMember != null; // ehh? + return GameMain.NetworkMember != null; } } + public bool IsSynchronized { get; private set; } public bool IsDisposed { get; private set; } - public void Initialize() + private readonly IEventService _eventService; + private readonly ILoggerService _loggerService; + + public NetworkingService(IEventService eventService, ILoggerService loggerService) { + _eventService = eventService; + _loggerService = loggerService; + #if SERVER IsSynchronized = true; -#elif CLIENT - SendSyncMessage(); #endif + + SubscribeToEvents(); } - public void Receive(Guid netId, NetMessageReceived callback) + public void Receive(string netIdString, NetMessageReceived callback) => Receive(new NetId(netIdString), callback); + public void Receive(Guid netIdGuid, NetMessageReceived callback) => Receive(new NetId(netIdGuid.ToString()), callback); + public IWriteMessage Start(string netIdString) => Start(new NetId(netIdString)); + public IWriteMessage Start(Guid netIdGuid) => Start(new NetId(netIdGuid.ToString())); + + public void Receive(NetId netId, NetMessageReceived callback) { #if SERVER RegisterId(netId); @@ -56,18 +92,21 @@ internal partial class NetworkingService : INetworkingService netReceives[netId] = callback; } - private void HandleNetMessage(IReadMessage netMessage, Guid netId, Client client = null) + private void HandleNetMessage(IReadMessage netMessage, NetId netId, Client client = null) { if (netReceives.ContainsKey(netId)) { try { +#if CLIENT netReceives[netId](netMessage); +#elif SERVER + netReceives[netId](netMessage, client.Connection); +#endif } catch (Exception e) { - LuaCsLogger.LogError($"Exception thrown inside NetMessageReceive({netId})", LuaCsMessageOrigin.CSharpMod); - LuaCsLogger.HandleException(e, LuaCsMessageOrigin.CSharpMod); + _loggerService.LogResults(new ExceptionalError("Exception thrown inside NetMessageReceive({netId})", e)); } } else @@ -75,9 +114,9 @@ internal partial class NetworkingService : INetworkingService if (GameSettings.CurrentConfig.VerboseLogging) { #if SERVER - LuaCsLogger.LogError($"Received NetMessage for unknown netid {netId} from {GameServer.ClientLogName(client)}."); + _loggerService.LogError($"Received NetMessage for unknown netid {netId} from {GameServer.ClientLogName(client)}."); #else - LuaCsLogger.LogError($"Received NetMessage for unknown netid {netId} from server."); + _loggerService.LogError($"Received NetMessage for unknown netid {netId} from server."); #endif } } @@ -85,23 +124,19 @@ internal partial class NetworkingService : INetworkingService private void HandleNetMessageString(IReadMessage netMessage, Client client = null) { - Guid guid = new Guid(netMessage.ReadBytes(16)); + NetId netId = NetId.Read(netMessage); - HandleNetMessage(netMessage, guid, client); + HandleNetMessage(netMessage, netId, client); } - public FluentResults.Result Reset() + private void SubscribeToEvents() { - IsSynchronized = false; - netReceives = new Dictionary(); - packetToId = new Dictionary(); - idToPacket = new Dictionary(); - return FluentResults.Result.Ok(); - } - - public void Dispose() - { - IsDisposed = true; +#if CLIENT + _eventService.Subscribe(this); + _eventService.Subscribe(this); +#elif SERVER + _eventService.Subscribe(this); +#endif } public Guid GetNetworkIdForInstance(INetworkSyncVar var) @@ -118,4 +153,19 @@ internal partial class NetworkingService : INetworkingService { throw new NotImplementedException(); } + + public FluentResults.Result Reset() + { + IsSynchronized = false; + netReceives = new Dictionary(); + packetToId = new Dictionary(); + idToPacket = new Dictionary(); + SubscribeToEvents(); + return FluentResults.Result.Ok(); + } + + public void Dispose() + { + IsDisposed = true; + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs index bd1957d23..f4abbf6e2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs @@ -6,26 +6,32 @@ using Barotrauma.Networking; namespace Barotrauma.LuaCs; -internal delegate void NetMessageReceived(IReadMessage netMessage); +#if CLIENT +public delegate void NetMessageReceived(IReadMessage netMessage); +#elif SERVER +public delegate void NetMessageReceived(IReadMessage netMessage, NetworkConnection connection); +#endif -internal partial interface INetworkingService : IReusableService, ILuaCsNetworking, IEntityNetworkingService +public interface INetworkingService : IReusableService, ILuaCsNetworking, IEntityNetworkingService { bool IsActive { get; } bool IsSynchronized { get; } - public IWriteMessage Start(Guid netId); - public void Receive(Guid netId, NetMessageReceived action); + IWriteMessage Start(string netId); + IWriteMessage Start(Guid netId); + void Receive(string netId, NetMessageReceived action); + void Receive(Guid netId, NetMessageReceived action); #if SERVER - public void Send(IWriteMessage netMessage, NetworkConnection connection = null, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable); + void SendToClient(IWriteMessage netMessage, NetworkConnection connection = null, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable); #elif CLIENT - public void Send(IWriteMessage netMessage, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable); + void SendToServer(IWriteMessage netMessage, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable); #endif - + } public interface IEntityNetworkingService { - public Guid GetNetworkIdForInstance(INetworkSyncVar var); - public void RegisterNetVar(INetworkSyncVar netVar); - public void SendNetVar(INetworkSyncVar netVar); + Guid GetNetworkIdForInstance(INetworkSyncVar var); + void RegisterNetVar(INetworkSyncVar netVar); + void SendNetVar(INetworkSyncVar netVar); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs index 357121433..6efff3b05 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs @@ -49,12 +49,9 @@ public class DefaultLuaRegistrar : IDefaultLuaRegistrar continue; } - _loggerService.LogMessage($"Registered {type.FullName}"); - _userDataService.RegisterType(type.FullName); } - _userDataService.RegisterType("Barotrauma.LuaSByte"); _userDataService.RegisterType("Barotrauma.LuaByte"); _userDataService.RegisterType("Barotrauma.LuaInt16"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaConverters.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaConverters.cs index a056afd54..23b3c5684 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaConverters.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaConverters.cs @@ -134,6 +134,22 @@ namespace Barotrauma RegisterHandler(f => (GUITextBlock.ClickableArea.OnClickDelegate)( (a1, a2) => Call(f, a1, a2))); } + + Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(NetMessageReceived), v => (NetMessageReceived)((arg1) => + { + if (v.Function.OwnerScript == _script) + { + Call(v.Function, arg1); + } + })); +#elif SERVER + Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(NetMessageReceived), v => (NetMessageReceived)((arg1, arg2) => + { + if (v.Function.OwnerScript == _script) + { + Call(v.Function, arg1, arg2); + } + })); #endif Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Table, typeof(Pair), v => From 422e8a6185610521459eb4b52f2bfbeaec889009 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 7 Feb 2026 21:53:30 -0300 Subject: [PATCH 121/288] Misc Lua fixes --- .../_Services/LuaScriptManagementService.cs | 10 ++++-- .../ILuaScriptManagementService.cs | 10 +++--- .../_Services/_Lua/DefaultLuaRegistrar.cs | 3 +- .../_Lua/LuaClasses/LuaConverters.cs | 20 +++++------ .../LuaCs/_Services/_Lua/LuaPatcherService.cs | 1 - .../_Services/_Lua/LuaUserDataService.cs | 33 +------------------ 6 files changed, 24 insertions(+), 53 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index da7e3dcc3..8eea2cf31 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -28,6 +28,8 @@ namespace Barotrauma.LuaCs; class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService { + public Script? InternalScript => _script; + private Script? _script; private bool _isRunning; [MemberNotNullWhen(true, nameof(_script))] @@ -184,8 +186,10 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService Script.GlobalOptions.ShouldPCallCatchException = (Exception ex) => { return true; }; + UserData.RegisterType(); UserData.RegisterType(typeof(LuaGame)); - UserData.RegisterType(typeof(EventService)); + StandardUserDataDescriptor descriptor = (StandardUserDataDescriptor)UserData.RegisterType(typeof(EventService)); + descriptor.AddDynValue("HookMethodType", UserData.CreateStatic()); UserData.RegisterType(typeof(ILuaCsNetworking)); UserData.RegisterType(typeof(ILuaCsUtility)); UserData.RegisterType(typeof(ILuaCsTimer)); @@ -195,7 +199,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService UserData.RegisterType(typeof(IUserDataDescriptor)); UserData.RegisterType(typeof(INetworkingService)); - new LuaConverters(_script).RegisterLuaConverters(); + new LuaConverters(this).RegisterLuaConverters(); var luaRequire = new LuaRequire(_script); @@ -287,7 +291,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService return result; } - public DynValue? CallFunction(DynValue luaFunction, params object[] args) + public DynValue? CallFunctionSafe(object luaFunction, params object[] args) { if (!IsRunning) { return null; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs index c490a11d1..e62da94a3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs @@ -14,11 +14,12 @@ namespace Barotrauma.LuaCs; public interface ILuaScriptManagementService : IReusableService { - #region Script_Ops + Script? InternalScript { get; } object? GetGlobalTableValue(string tableName); FluentResults.Result DoString(string code); - + DynValue? CallFunctionSafe(object luaFunction, params object[] args); + /// /// Parses and loads script sources (code) into a memory cache without executing it. /// @@ -54,8 +55,5 @@ public interface ILuaScriptManagementService : IReusableService /// /// /// May be functionally equivalent to - FluentResults.Result DisposeAllPackageResources(); - - #endregion - + FluentResults.Result DisposeAllPackageResources(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs index 6efff3b05..a622cd133 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs @@ -39,12 +39,13 @@ public class DefaultLuaRegistrar : IDefaultLuaRegistrar _userDataService.RegisterType("Barotrauma.Success`2"); _userDataService.RegisterType("Barotrauma.Failure`2"); _userDataService.RegisterType("Barotrauma.Range`1"); + _userDataService.RegisterType("Barotrauma.ItemPrefab"); List assembliesToScan = [typeof(DefaultLuaRegistrar).Assembly, typeof(Identifier).Assembly, typeof(Microsoft.Xna.Framework.Vector2).Assembly]; foreach (var type in assembliesToScan.SelectMany(a => a.GetTypes())) { - if (type.IsEnum || type.IsDefined(typeof(CompilerGeneratedAttribute)) || !_safeUserDataService.IsAllowed(type.FullName)) + if (type.IsEnum || type.Name.StartsWith("<") || type.IsDefined(typeof(CompilerGeneratedAttribute)) || !_safeUserDataService.IsAllowed(type.FullName)) { continue; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaConverters.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaConverters.cs index 23b3c5684..e2783dd10 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaConverters.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaConverters.cs @@ -11,14 +11,14 @@ namespace Barotrauma { public class LuaConverters { - private readonly Script _script; + private readonly ILuaScriptManagementService _luaScriptManagementService; - public LuaConverters(Script script) + public LuaConverters(ILuaScriptManagementService luaScriptManagementService) { - _script = script; + _luaScriptManagementService = luaScriptManagementService; } - private DynValue Call(object function, params object[] arguments) => _script.Call(function, arguments); + private DynValue Call(object function, params object[] arguments) => _luaScriptManagementService.CallFunctionSafe(function, arguments); public void RegisterLuaConverters() { @@ -34,7 +34,7 @@ namespace Barotrauma Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(LuaCsAction), v => (LuaCsAction)(args => { - if (v.Function.OwnerScript == _script) + if (v.Function.OwnerScript == _luaScriptManagementService) { Call(v.Function, args); } @@ -42,7 +42,7 @@ namespace Barotrauma Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(LuaCsFunc), v => (LuaCsFunc)(args => { - if (v.Function.OwnerScript == _script) + if (v.Function.OwnerScript == _luaScriptManagementService.InternalScript) { return Call(v.Function, args); } @@ -51,7 +51,7 @@ namespace Barotrauma Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(LuaCsCompatPatchFunc), v => (LuaCsCompatPatchFunc)((self, args) => { - if (v.Function.OwnerScript == _script) + if (v.Function.OwnerScript == _luaScriptManagementService.InternalScript) { return Call(v.Function, self, args); } @@ -60,7 +60,7 @@ namespace Barotrauma Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(LuaCsPatchFunc), v => (LuaCsPatchFunc)((self, args) => { - if (v.Function.OwnerScript == _script) + if (v.Function.OwnerScript == _luaScriptManagementService.InternalScript) { return Call(v.Function, self, args); } @@ -137,7 +137,7 @@ namespace Barotrauma Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(NetMessageReceived), v => (NetMessageReceived)((arg1) => { - if (v.Function.OwnerScript == _script) + if (v.Function.OwnerScript == _luaScriptManagementService.InternalScript) { Call(v.Function, arg1); } @@ -145,7 +145,7 @@ namespace Barotrauma #elif SERVER Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(NetMessageReceived), v => (NetMessageReceived)((arg1, arg2) => { - if (v.Function.OwnerScript == _script) + if (v.Function.OwnerScript == _luaScriptManagementService.InternalScript) { Call(v.Function, arg1, arg2); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaPatcherService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaPatcherService.cs index 55743b52d..181e04df3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaPatcherService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaPatcherService.cs @@ -794,7 +794,6 @@ namespace Barotrauma.LuaCs } registeredPatches.Clear(); - patchModuleBuilder = null; compatHookPrefixMethods.Clear(); compatHookPostfixMethods.Clear(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaUserDataService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaUserDataService.cs index 0584fc056..3efb28799 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaUserDataService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaUserDataService.cs @@ -87,7 +87,7 @@ public class LuaUserDataService : ILuaUserDataService throw new ScriptRuntimeException($"tried to register a type that doesn't exist: {typeName}."); } - return UserData.RegisterType(type, new CallableUserDataDescriptor(type)); + return UserData.RegisterType(type); } public void RegisterExtensionType(string typeName) @@ -407,34 +407,3 @@ public class LuaUserDataService : ILuaUserDataService return FluentResults.Result.Ok(); } } - -sealed class CallableUserDataDescriptor : StandardUserDataDescriptor -{ - public CallableUserDataDescriptor(Type type) - : base(type, InteropAccessMode.Default) - { - } - - public override DynValue MetaIndex(Script script, object obj, string metaname) - { - if (metaname == "__call") - { - return DynValue.NewCallback((ctx, args) => - { - var self = args[0]; - - var ctor = base.Index(script, obj, DynValue.NewString("__new"), true); - - if (ctor == null || ctor.IsNil()) - { - throw new ScriptRuntimeException("Attempted to call userdata without __new."); - } - - var callArgs = args.GetArray().Skip(1).ToArray(); - return script.Call(ctor, callArgs); - }); - } - - return base.MetaIndex(script, obj, metaname); - } -} From e76aaf5a34a1e74afb4b96717897ee4b0a795ea5 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 7 Feb 2026 23:29:00 -0300 Subject: [PATCH 122/288] Fix deadlock when reloading packages --- .../BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 55c48708e..b37b3d49b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -244,10 +244,13 @@ namespace Barotrauma { return; } - - var state = CurrentRunState; - SetRunState(RunState.Unloaded); - SetRunState(state); + + CoroutineManager.Invoke(() => + { + var state = CurrentRunState; + SetRunState(RunState.Unloaded); + SetRunState(state); + }); } private void ProcessEnabledPackageChanges(ImmutableArray packages) From 02b1f524b60b62239c0b9bed049d8773b9630211 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 7 Feb 2026 23:29:22 -0300 Subject: [PATCH 123/288] Re-enable DefaultHook.lua --- .../LocalMods/LuaCsForBarotrauma/Lua/DefaultHook.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultHook.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultHook.lua index d78c7dc48..520e884ff 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultHook.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultHook.lua @@ -1,5 +1,3 @@ -if true then return end - Hook.Patch("Barotrauma.Item", "TryInteract", { "Barotrauma.Character", From 67c195034d91c2459daa9938f75dbdb7e63ec617 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sat, 7 Feb 2026 22:58:56 -0500 Subject: [PATCH 124/288] Network Sync work. Added network unique key. --- .../SharedSource/LuaCs/LuaCsSetup.cs | 1 + .../LuaCs/_Networking/INetworkIdProvider.cs | 37 +++++++++++++++++ .../LuaCs/_Networking/INetworkSyncEntity.cs | 2 +- .../LuaCs/_Networking/NetworkingIdProvider.cs | 41 +++++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/INetworkIdProvider.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/NetworkingIdProvider.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index b37b3d49b..78622d75a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -195,6 +195,7 @@ namespace Barotrauma servicesProvider.RegisterServiceType, ModConfigFileParserService>(ServiceLifetime.Transient); servicesProvider.RegisterServiceType, SettingsFileParserService>(ServiceLifetime.Transient); servicesProvider.RegisterServiceType, SettingsFileParserService>(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); // All Lua Extras servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/INetworkIdProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/INetworkIdProvider.cs new file mode 100644 index 000000000..441f6e8bc --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/INetworkIdProvider.cs @@ -0,0 +1,37 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Barotrauma.Items.Components; +using Barotrauma.LuaCs.Data; + +namespace Barotrauma.LuaCs; + +/// +/// Provides a deterministic ID for a given instance under multiple circumstances, for use with +/// network synchronization. +/// +internal interface INetworkIdProvider : IService +{ + /// + /// Deterministically generates a GUID for the given parameters. + /// + /// The instance. + /// The GUID for the entity. + Guid GetNetworkIdForInstance([NotNull] IDataInfo instance); + + /// + /// Deterministically generates a GUID for the given parameters. + /// + /// The instance. + /// The that this instance is attached to, if any. + /// The entity type, if any. + /// The GUID for the entity. + Guid GetNetworkIdForInstance([NotNull] IDataInfo instance, TEntity attachedEntity) where TEntity : Entity; + + /// + /// Deterministically generates a GUID for the given parameters. + /// + /// The instance. + /// The that this instance is attached to, if any. + /// The GUID for the entity. + Guid GetNetworkIdForInstance([NotNull] IDataInfo instance, [MaybeNull] ItemComponent attachedItemComponent); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/INetworkSyncEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/INetworkSyncEntity.cs index 75ec9d865..a13e0a190 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/INetworkSyncEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/INetworkSyncEntity.cs @@ -5,7 +5,7 @@ using Barotrauma.Networking; namespace Barotrauma.LuaCs; -public interface INetworkSyncVar +public interface INetworkSyncVar : IDataInfo { /// /// Network-synchronized object ID. Used for networking send/receive message events. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/NetworkingIdProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/NetworkingIdProvider.cs new file mode 100644 index 000000000..a3b2157c2 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Networking/NetworkingIdProvider.cs @@ -0,0 +1,41 @@ +using System; +using Barotrauma.Items.Components; +using Barotrauma.LuaCs.Data; +using System.Security.Cryptography; +using System.Text; + +namespace Barotrauma.LuaCs; + +internal class NetworkingIdProvider : INetworkIdProvider +{ + public void Dispose() + { + //stateless service + } + + public bool IsDisposed => false; + + private Guid GetNetworkIdFromStringMd5(string id) + { + return new Guid(MD5.Create().ComputeHash(Encoding.ASCII.GetBytes(id))); + } + + public Guid GetNetworkIdForInstance(IDataInfo instance) + { + var str = $"{instance.OwnerPackage.Name}.{instance.InternalName}"; + return GetNetworkIdFromStringMd5(str); + } + + public Guid GetNetworkIdForInstance(IDataInfo instance, TEntity attachedEntity) where TEntity : Entity + { + var str = $"{nameof(TEntity)}({attachedEntity.ID}).{instance.OwnerPackage.Name}.{instance.InternalName}"; + return GetNetworkIdFromStringMd5(str); + } + + public Guid GetNetworkIdForInstance(IDataInfo instance, ItemComponent attachedItemComponent) + { + var attachedEntity = attachedItemComponent.Item; + var str = $"{attachedEntity.GetType().Name}({attachedEntity.ID}).ComponentId({attachedEntity.Components.IndexOf(attachedItemComponent)}).{instance.OwnerPackage.Name}.{instance.InternalName}"; + return GetNetworkIdFromStringMd5(str); + } +} From c637e34b48c18373ccf7198ad9944fb6ac9e7ed5 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 8 Feb 2026 01:30:44 -0300 Subject: [PATCH 125/288] Fix GUI enums in Lua --- .../LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs index a622cd133..ff92e09ec 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs @@ -148,6 +148,12 @@ public class DefaultLuaRegistrar : IDefaultLuaRegistrar _userDataService.RegisterType("Microsoft.Xna.Framework.Input.Keys"); _userDataService.RegisterType("Microsoft.Xna.Framework.Input.KeyboardState"); + _userDataService.RegisterType("Barotrauma.Anchor"); + _userDataService.RegisterType("Barotrauma.Alignment"); + _userDataService.RegisterType("Barotrauma.Pivot"); + _userDataService.RegisterType("Barotrauma.Key"); + _userDataService.RegisterType("Barotrauma.PlayerInput"); + _userDataService.RegisterType("Barotrauma.Inventory+SlotReference"); } From f01ee61278f240c1ce4c45fac2adf8cdc56d0af5 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 8 Feb 2026 01:37:24 -0300 Subject: [PATCH 126/288] Clear file cache when resetting LuaScriptManagementService --- .../SharedSource/LuaCs/_Services/LuaScriptManagementService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 8eea2cf31..479910bb2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -339,6 +339,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService public FluentResults.Result Reset() { + _luaScriptLoader.ClearCaches(); _userDataService.Reset(); RegisterLuaEvents(); return DisposeAllPackageResources(); From 224e32ccf19b36951dfe37294bcdd8da6ec85fc1 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 8 Feb 2026 06:39:44 -0500 Subject: [PATCH 127/288] Some work on save/load for configs. --- .../SharedSource/LuaCs/LuaCsSetup.cs | 2 +- .../LuaCs/_Services/ConfigService.cs | 103 +++++++++++++++++- .../_Services/_Interfaces/IConfigService.cs | 3 + 3 files changed, 106 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 78622d75a..6cae9a540 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -117,7 +117,7 @@ namespace Barotrauma private ISettingBase _luaForBarotraumaSteamId; /// - /// TODO: @evilfactory@users.noreply.github.com + /// Whether the maximum message size over the network should be restricted. /// public bool RestrictMessageSize { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs index 987b81bde..2cfd7a265 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs @@ -118,6 +118,7 @@ public sealed partial class ConfigService : IConfigService #endregion + private const string SaveDataFileName = "SettingData.xml"; private readonly ConcurrentDictionary<(ContentPackage OwnerPackage, string InternalName), ISettingBase> _settingsInstances = new(); @@ -129,19 +130,25 @@ public sealed partial class ConfigService : IConfigService private IStorageService _storageService; private ILoggerService _logger; private IEventService _eventService; + private ILuaCsInfoProvider _luaCsInfoProvider; private IParserServiceOneToManyAsync _configInfoParserService; private IParserServiceOneToManyAsync _configProfileInfoParserService; public ConfigService(ILoggerService logger, IStorageService storageService, IParserServiceOneToManyAsync configInfoParserService, - IParserServiceOneToManyAsync configProfileInfoParserService, IEventService eventService) + IParserServiceOneToManyAsync configProfileInfoParserService, + IEventService eventService, + ILuaCsInfoProvider luaCsInfoProvider) { _logger = logger; _storageService = storageService; _configInfoParserService = configInfoParserService; _configProfileInfoParserService = configProfileInfoParserService; _eventService = eventService; + _luaCsInfoProvider = luaCsInfoProvider; + + _storageService.UseCaching = true; } @@ -257,10 +264,104 @@ public sealed partial class ConfigService : IConfigService throw new NotImplementedException(); } + public FluentResults.Result LoadSavedValueForConfig(ISettingBase setting) + { + Guard.IsNotNull(setting, nameof(setting)); + using var lck = _operationLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + if (_storageService.LoadLocalXml(setting.OwnerPackage, SaveDataFileName) is not { } saveFileResult + || saveFileResult is { IsFailed: true }) + { + return FluentResults.Result.Fail( + $"{nameof(LoadSavedValueForConfig)}: Could not open save file for setting [{setting.OwnerPackage.Name}.{setting.InternalName}]"); + } + + if (saveFileResult.Value.Root is not {} rootElement + || !string.Equals(rootElement.Name.LocalName, "Configuration", StringComparison.InvariantCultureIgnoreCase)) + { + return FluentResults.Result.Fail($"{nameof(LoadSavedValueForConfig)}: Root invalid for setting [{setting.OwnerPackage.Name}.{setting.InternalName}]"); + } + + if (rootElement.GetChildElement(setting.OwnerPackage.Name, StringComparison.InvariantCulture) + ?.GetChildElement(setting.InternalName, StringComparison.InvariantCulture) is not {} cfgValueElement) + { + return FluentResults.Result.Fail($"{nameof(LoadSavedValueForConfig)}: Could not find saved value for setting:[{setting.OwnerPackage.Name}.{setting.InternalName}]"); + } + + return FluentResults.Result.OkIf(setting.TrySetValue(cfgValueElement), new Error($"Failed to set value for [{setting.OwnerPackage.Name}.{setting.InternalName}]")); + } + + public FluentResults.Result LoadSavedConfigsValues() + { + throw new NotImplementedException(); + } + + public FluentResults.Result SaveConfigValue(ISettingBase setting) + { + XDocument cpCfgValues; + if (_storageService.LoadLocalXml(setting.OwnerPackage, SaveDataFileName) is not {} saveFileResult) + { + return FluentResults.Result.Fail($"{nameof(SaveConfigValue)}: Storage Service Failure while trying to load file for setting [{setting.OwnerPackage.Name}.{setting.InternalName}]"); + } + + // get Configuration + if (saveFileResult.IsFailed) + { + cpCfgValues = new XDocument(new XDeclaration("1.0", "utf-8", "yes"), new XElement("Configuration")); + } + else + { + cpCfgValues = saveFileResult.Value; + } + + if (cpCfgValues.Root is null || cpCfgValues.Root.Name != "Configuration") + { + return FluentResults.Result.Fail($"{nameof(SaveConfigValue)}: Bad save file format for setting: [{setting.OwnerPackage.Name}.{setting.InternalName}]"); + } + + XElement currentTarget = GetOrAddElement(cpCfgValues.Root, setting.OwnerPackage.Name, name => new XElement(name)); + currentTarget = GetOrAddElement(currentTarget, setting.InternalName, name => new XElement(name)); + + var ret = setting.GetSerializableValue().Match(str => + { + var tgt = currentTarget.Attribute("Value"); + if (tgt is null) + { + var attr = new XAttribute("Value", str); + currentTarget.Add(attr); + } + + return FluentResults.Result.Ok(); + }, + elem => + { + currentTarget.ReplaceNodes(new XElement("Value", elem)); + return FluentResults.Result.Ok(); + }); + + ret.WithReasons(_storageService.SaveLocalXml(setting.OwnerPackage, SaveDataFileName, cpCfgValues).Reasons); + return ret; + + XElement GetOrAddElement(XElement containerElement, string elementName, Func factory) + { + var element = containerElement.Element(elementName); + if (element is null) + { + element = factory(elementName); + containerElement.Add(element); + } + return element; + } + } + + public FluentResults.Result DisposePackageData(ContentPackage package) { Guard.IsNotNull(package, nameof(package)); using var lck = _operationLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + ConcurrentBag toDispose; using (var settingsLck = _settingsByPackageLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult()) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConfigService.cs index 0cf698084..7e271c625 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConfigService.cs @@ -17,6 +17,9 @@ public partial interface IConfigService : IReusableService, ILuaConfigService where T : class, ISettingBase; Task LoadConfigsAsync(ImmutableArray configResources); Task LoadConfigsProfilesAsync(ImmutableArray configProfileResources); + FluentResults.Result LoadSavedValueForConfig(ISettingBase setting); + FluentResults.Result LoadSavedConfigsValues(); + FluentResults.Result SaveConfigValue(ISettingBase setting); FluentResults.Result DisposePackageData(ContentPackage package); FluentResults.Result DisposeAllPackageData(); bool TryGetConfig(ContentPackage package, string internalName, out T instance) where T : ISettingBase; From ce4cd1fefd0c8e9d4e10f3be9b59e0a6f0fe70ca Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 8 Feb 2026 11:51:09 -0300 Subject: [PATCH 128/288] Implement most of the net var networking functionality --- .../LuaCs/Services/NetworkingService.cs | 2 +- .../LuaCs/_Services/NetworkingService.cs | 85 ++++++++++++++++--- .../_Interfaces/INetworkingService.cs | 4 +- 3 files changed, 75 insertions(+), 16 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs index 34c1a978d..7381d416d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; namespace Barotrauma.LuaCs; -public partial class NetworkingService : INetworkingService, IEventConnectedToServer, IEventServerRawNetMessageReceived +partial class NetworkingService : INetworkingService, IEventConnectedToServer, IEventServerRawNetMessageReceived { private ConcurrentDictionary> receiveQueue = new(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs index 3f9e3d0e2..261621bbf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs @@ -4,13 +4,14 @@ using Barotrauma.LuaCs.Events; using Barotrauma.Networking; using FluentResults; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; namespace Barotrauma.LuaCs; -public partial class NetworkingService : INetworkingService +internal partial class NetworkingService : INetworkingService { public readonly record struct NetId { @@ -47,9 +48,12 @@ public partial class NetworkingService : INetworkingService ReceiveNetIds } - private Dictionary netReceives = new Dictionary(); - private Dictionary packetToId = new Dictionary(); - private Dictionary idToPacket = new Dictionary(); + + private ConcurrentDictionary netVars = []; + + private ConcurrentDictionary netReceives = []; + private ConcurrentDictionary packetToId = []; + private ConcurrentDictionary idToPacket = []; public bool IsActive { @@ -64,10 +68,12 @@ public partial class NetworkingService : INetworkingService private readonly IEventService _eventService; private readonly ILoggerService _loggerService; + private readonly INetworkIdProvider _networkIdProvider; - public NetworkingService(IEventService eventService, ILoggerService loggerService) + internal NetworkingService(IEventService eventService, INetworkIdProvider networkIdProvider, ILoggerService loggerService) { _eventService = eventService; + _networkIdProvider = networkIdProvider; _loggerService = loggerService; #if SERVER @@ -82,7 +88,7 @@ public partial class NetworkingService : INetworkingService public IWriteMessage Start(string netIdString) => Start(new NetId(netIdString)); public IWriteMessage Start(Guid netIdGuid) => Start(new NetId(netIdGuid.ToString())); - public void Receive(NetId netId, NetMessageReceived callback) + internal void Receive(NetId netId, NetMessageReceived callback) { #if SERVER RegisterId(netId); @@ -101,7 +107,7 @@ public partial class NetworkingService : INetworkingService #if CLIENT netReceives[netId](netMessage); #elif SERVER - netReceives[netId](netMessage, client.Connection); + netReceives[netId](netMessage, client); #endif } catch (Exception e) @@ -141,25 +147,78 @@ public partial class NetworkingService : INetworkingService public Guid GetNetworkIdForInstance(INetworkSyncVar var) { - throw new NotImplementedException(); + return _networkIdProvider.GetNetworkIdForInstance(var); } public void RegisterNetVar(INetworkSyncVar netVar) { - throw new NotImplementedException(); + netVar.SetNetworkOwner(this); + + NetId netId = new NetId(netVar.InstanceId.ToString()); + netVars[netVar] = netId; + +#if CLIENT + Receive(netId, (IReadMessage message) => + { + if (netVar.SyncType == NetSync.None) + { + _loggerService.LogWarning($"Received net var from server but {nameof(NetSync)} is {netVar.SyncType.ToString()}"); + return; + } + + netVar.ReadNetMessage(message); + }); +#elif SERVER + Receive(netId, (IReadMessage message, Client client) => + { + if (netVar.SyncType == NetSync.None || netVar.SyncType == NetSync.ServerAuthority) + { + _loggerService.LogWarning($"Received net var from {GameServer.ClientLogName(client)} but {nameof(NetSync)} is {netVar.SyncType.ToString()}"); + return; + } + + if (!client.HasPermission(netVar.WritePermissions)) + { + _loggerService.LogWarning($"Received net var from {GameServer.ClientLogName(client)} but the client lacks permissions to modify it"); + return; + } + + netVar.ReadNetMessage(message); + }); +#endif } public void SendNetVar(INetworkSyncVar netVar) { - throw new NotImplementedException(); + if (!netVars.TryGetValue(netVar, out NetId netId)) + { + throw new InvalidOperationException("Tried to send net var across network without registering first"); + } + + if (netVar.SyncType == NetSync.None) { return; } +#if CLIENT + if (netVar.SyncType == NetSync.ServerAuthority) { return; } +#elif SERVER + if (netVar.SyncType == NetSync.ClientOneWay) { return; } +#endif + + IWriteMessage message = Start(netId); + netVar.WriteNetMessage(message); +#if CLIENT + SendToServer(message); +#elif SERVER + SendToClient(message); +#endif } public FluentResults.Result Reset() { IsSynchronized = false; - netReceives = new Dictionary(); - packetToId = new Dictionary(); - idToPacket = new Dictionary(); + netReceives = new ConcurrentDictionary(); + packetToId = new ConcurrentDictionary(); + idToPacket = new ConcurrentDictionary(); + netVars = new ConcurrentDictionary(); + SubscribeToEvents(); return FluentResults.Result.Ok(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs index f4abbf6e2..7d6ac2930 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs @@ -9,10 +9,10 @@ namespace Barotrauma.LuaCs; #if CLIENT public delegate void NetMessageReceived(IReadMessage netMessage); #elif SERVER -public delegate void NetMessageReceived(IReadMessage netMessage, NetworkConnection connection); +internal delegate void NetMessageReceived(IReadMessage netMessage, Client connection); #endif -public interface INetworkingService : IReusableService, ILuaCsNetworking, IEntityNetworkingService +internal interface INetworkingService : IReusableService, ILuaCsNetworking, IEntityNetworkingService { bool IsActive { get; } bool IsSynchronized { get; } From 705137e993f3c52e9de06b366768591e6c24edd6 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 8 Feb 2026 15:03:03 -0300 Subject: [PATCH 129/288] Finalize networking service --- .../ClientSource/LuaCs/Services/NetworkingService.cs | 2 +- .../ServerSource/LuaCs/Services/NetworkingService.cs | 12 +++++++++--- .../LuaCs/_Services/NetworkingService.cs | 12 ++++++++++-- .../_Services/_Interfaces/INetworkingService.cs | 1 + 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs index 7381d416d..578e5c50f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs @@ -47,7 +47,7 @@ partial class NetworkingService : INetworkingService, IEventConnectedToServer, I WriteOnlyMessage message = new WriteOnlyMessage(); message.WriteByte((byte)ClientPacketHeader.LUA_NET_MESSAGE); - message.WriteByte((byte)ClientToServer.RequestAllNetIds); + message.WriteByte((byte)ClientToServer.RequestSync); GameMain.Client.ClientPeer.Send(message, DeliveryMethod.Reliable); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs index f2bed92ae..fd5bcd90c 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs @@ -56,8 +56,8 @@ partial class NetworkingService : INetworkingService, IEventClientRawNetMessageR HandleNetMessageId(netMessage, client); break; - case ClientToServer.RequestAllNetIds: - WriteAllIds(client); + case ClientToServer.RequestSync: + WriteSync(client); break; case ClientToServer.RequestSingleNetId: @@ -144,7 +144,7 @@ partial class NetworkingService : INetworkingService, IEventClientRawNetMessageR SendToClient(message, null, DeliveryMethod.Reliable); } - private void WriteAllIds(Client client) + private void WriteSync(Client client) { WriteOnlyMessage message = new WriteOnlyMessage(); message.WriteByte((byte)ServerPacketHeader.LUA_NET_MESSAGE); @@ -158,6 +158,12 @@ partial class NetworkingService : INetworkingService, IEventClientRawNetMessageR } SendToClient(message, client.Connection, DeliveryMethod.Reliable); + + // TODO: when we move to using GUIDs for everything, this should combined into a single message + foreach (INetworkSyncVar netVar in netVars.Keys) + { + SendNetVar(netVar, client.Connection); + } } public void SendToClient(IWriteMessage netMessage, NetworkConnection connection = null, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs index 261621bbf..834fbb6b7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs @@ -38,7 +38,7 @@ internal partial class NetworkingService : INetworkingService NetMessageInternalId, NetMessageNetId, RequestSingleNetId, - RequestAllNetIds, + RequestSync, } private enum ServerToClient @@ -184,11 +184,19 @@ internal partial class NetworkingService : INetworkingService } netVar.ReadNetMessage(message); + + // Sync back to all clients + if (netVar.SyncType != NetSync.ClientOneWay) + { + SendNetVar(netVar); + } }); #endif } - public void SendNetVar(INetworkSyncVar netVar) + public void SendNetVar(INetworkSyncVar netVar) => SendNetVar(netVar); + + public void SendNetVar(INetworkSyncVar netVar, NetworkConnection connection = null) { if (!netVars.TryGetValue(netVar, out NetId netId)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs index 7d6ac2930..d72b2c303 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs @@ -34,4 +34,5 @@ public interface IEntityNetworkingService Guid GetNetworkIdForInstance(INetworkSyncVar var); void RegisterNetVar(INetworkSyncVar netVar); void SendNetVar(INetworkSyncVar netVar); + void SendNetVar(INetworkSyncVar netVar, NetworkConnection connection); } From 36a126774a402044e639ed605280e3517458a84b Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 8 Feb 2026 15:06:22 -0300 Subject: [PATCH 130/288] Register INetworkIdProvider --- Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 6cae9a540..4c159098c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -185,6 +185,7 @@ namespace Barotrauma servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); // Extension/Sub Services servicesProvider.RegisterServiceType(ServiceLifetime.Transient); From 5b10661874561eee858a0c30d9f8c27c49543037 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 8 Feb 2026 18:26:59 -0300 Subject: [PATCH 131/288] Legacy network stuff --- .../LuaCs/Compatibility/ILuaCsNetworking.cs | 2 +- .../BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs | 3 ++- .../SharedSource/LuaCs/_Services/NetworkingService.cs | 11 ++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs index b5356b084..aafcd5712 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs @@ -2,5 +2,5 @@ public interface ILuaCsNetworking : ILuaCsShim { - + void Receive(string netId, LuaCsAction action); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 4c159098c..226f2c6e1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -375,7 +375,8 @@ namespace Barotrauma #region LegacyRedirects - public ILuaCsHook Hook => this.EventService; + public ILuaCsHook Hook => this.EventService; + public INetworkingService Networking => this.NetworkingService; #endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs index 834fbb6b7..8c8cac4ef 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs @@ -70,7 +70,7 @@ internal partial class NetworkingService : INetworkingService private readonly ILoggerService _loggerService; private readonly INetworkIdProvider _networkIdProvider; - internal NetworkingService(IEventService eventService, INetworkIdProvider networkIdProvider, ILoggerService loggerService) + public NetworkingService(IEventService eventService, INetworkIdProvider networkIdProvider, ILoggerService loggerService) { _eventService = eventService; _networkIdProvider = networkIdProvider; @@ -83,6 +83,15 @@ internal partial class NetworkingService : INetworkingService SubscribeToEvents(); } + public void Receive(string netIdString, LuaCsAction callback) + { +#if SERVER + Receive(new NetId(netIdString), (IReadMessage message, Client client) => callback(message, client)); +#elif CLIENT + Receive(new NetId(netIdString), (IReadMessage message) => callback(message, null)); +#endif + } + public void Receive(string netIdString, NetMessageReceived callback) => Receive(new NetId(netIdString), callback); public void Receive(Guid netIdGuid, NetMessageReceived callback) => Receive(new NetId(netIdGuid.ToString()), callback); public IWriteMessage Start(string netIdString) => Start(new NetId(netIdString)); From 0a91b896943cde7cd2e3c63fc6ae9993787b28c5 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 8 Feb 2026 21:29:31 -0300 Subject: [PATCH 132/288] Add publicized assemblies to metadata references --- .../_Services/PluginManagementService.cs | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index dc757b23e..6f44d9ed4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -72,10 +72,10 @@ public class PluginManagementService : IAssemblyManagementService private static readonly SyntaxTree BaseAssemblyImports = CSharpSyntaxTree.ParseText( new StringBuilder() .AppendLine("global using LuaCsHook = Barotrauma.LuaCs.Compatibility.ILuaCsHook;") - .AppendLine("using System.Reflection;") - .AppendLine("using Barotrauma;") - .AppendLine("using Barotrauma.LuaCs;") - .AppendLine("using Barotrauma.LuaCs.Compatibility;") + .AppendLine("global using System.Reflection;") + .AppendLine("global using Barotrauma;") + .AppendLine("global using Barotrauma.LuaCs;") + .AppendLine("global using Barotrauma.LuaCs.Compatibility;") .AppendLine("using System.Runtime.CompilerServices;") .AppendLine("[assembly: IgnoresAccessChecksTo(\"BarotraumaCore\")]") #if CLIENT @@ -559,10 +559,32 @@ public class PluginManagementService : IAssemblyManagementService #if !DEBUG throw new NotImplementedException($"Needs to use publicized barotrauma assemblies and cache metadata."); #endif + var publicizedDir = Path.Combine(Directory.GetCurrentDirectory(), "Publicized"); + + string[] publicizedAssemblies = + { +#if CLIENT + "Barotrauma", +#elif SERVER + "DedicatedServer", +#endif + "BarotraumaCore" + }; + + var publicizedRefs = publicizedAssemblies + .Select(name => Path.Combine(publicizedDir, $"{name}.dll")) + .Where(File.Exists) + .Select(path => MetadataReference.CreateFromFile(path)); + + var runtimeRefs = AppDomain.CurrentDomain.GetAssemblies() + .Where(ass => + !string.IsNullOrWhiteSpace(ass.Location) && + !publicizedAssemblies.Contains(ass.GetName().Name)) + .Select(ass => MetadataReference.CreateFromFile(ass.Location)); + return Basic.Reference.Assemblies.Net80.References.All - .Union(AppDomain.CurrentDomain.GetAssemblies() - .Where(ass => !ass.Location.IsNullOrWhiteSpace()) - .Select(ass => MetadataReference.CreateFromFile(ass.Location))); + .Union(runtimeRefs) + .Union(publicizedRefs); } } From 668197ad6f0070c220185fdc2a0e15337c53ddb0 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 8 Feb 2026 21:45:17 -0300 Subject: [PATCH 133/288] Fix Lua/Cs execute order --- .../_Services/PackageManagementService.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs index f07b83e62..0edc75b45 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs @@ -223,10 +223,24 @@ public sealed class PackageManagementService : IPackageManagementService var toLoadPackagesIndents = loadingOrderedPackages .SelectMany(p => p.Key.AltNames.Union(new []{ p.Key.Name }).ToIdentifiers()) .ToImmutableHashSet(); - - + + // NOTE: Config/Settings are instanced in LoadPackages() - + + if (executeCsAssemblies) + { + var plugins = SelectCompatible(loadingOrderedPackages + .SelectMany(pkg => pkg.Value.Assemblies) + .ToImmutableArray(), toLoadPackagesIndents, loadOrderByPackage); + + if (!plugins.IsDefaultOrEmpty) + { + result.WithReasons(_pluginManagementService.LoadAssemblyResources(plugins).Reasons); + result.WithReasons(_pluginManagementService.ActivatePluginInstances( + plugins.Select(p => p.OwnerPackage).ToImmutableArray(), false).Reasons); + } + } + //lua scripts var luaScripts = SelectCompatible(loadingOrderedPackages .SelectMany(pkg => pkg.Value.LuaScripts) @@ -234,21 +248,7 @@ public sealed class PackageManagementService : IPackageManagementService if (!luaScripts.IsDefaultOrEmpty) { - result.WithReasons(_luaScriptManagementService.ExecuteLoadedScripts(luaScripts).Reasons); - } - - if (executeCsAssemblies) - { - var plugins = SelectCompatible(loadingOrderedPackages - .SelectMany(pkg => pkg.Value.Assemblies) - .ToImmutableArray(), toLoadPackagesIndents, loadOrderByPackage); - - if (!plugins.IsDefaultOrEmpty) - { - result.WithReasons(_pluginManagementService.LoadAssemblyResources(plugins).Reasons); - result.WithReasons(_pluginManagementService.ActivatePluginInstances( - plugins.Select(p => p.OwnerPackage).ToImmutableArray(), false).Reasons); - } + result.WithReasons(_luaScriptManagementService.ExecuteLoadedScripts(luaScripts); } foreach (var package in loadingOrderedPackages) From bcc4357a1680c549e6c81dcc06bbd91b75aa8cf7 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 8 Feb 2026 21:46:00 -0300 Subject: [PATCH 134/288] Pass in cs enabled check in ExecuteLoadedScripts --- .../_Services/LuaScriptManagementService.cs | 20 +++++++++---------- .../_Services/PackageManagementService.cs | 2 +- .../ILuaScriptManagementService.cs | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 479910bb2..cf2f53eb1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -174,7 +174,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _eventService.RegisterLuaEventAlias("think", "OnUpdate"); } - private void SetupEnvironment() + private void SetupEnvironment(bool enableSandbox) { _script = new Script(CoreModules.Preset_SoftSandbox | CoreModules.Debug | CoreModules.IO | CoreModules.OS_System); _script.Options.DebugPrint = (string msg) => @@ -220,16 +220,16 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _script.Globals["Networking"] = _networkingService; //_script.Globals["Steam"] = Steam; - if (GameMain.LuaCs.IsCsEnabled) - { - UserData.RegisterType(typeof(LuaUserDataService)); - _script.Globals["LuaUserData"] = _userDataService; - } - else + if (enableSandbox) { UserData.RegisterType(typeof(SafeLuaUserDataService)); _script.Globals["LuaUserData"] = _safeUserDataService; } + else + { + UserData.RegisterType(typeof(LuaUserDataService)); + _script.Globals["LuaUserData"] = _userDataService; + } Table eventsTable = new Table(_script); @@ -249,7 +249,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _script.Globals["Events"] = eventsTable; _script.Globals["ExecutionNumber"] = 0; - _script.Globals["CSActive"] = GameMain.LuaCs.IsCsEnabled; + _script.Globals["CSActive"] = !enableSandbox; _script.Globals["SERVER"] = LuaCsSetup.IsServer; _script.Globals["CLIENT"] = LuaCsSetup.IsClient; @@ -257,7 +257,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _defaultLuaRegistrar.RegisterAll(); } - public FluentResults.Result ExecuteLoadedScripts(ImmutableArray executionOrder) + public FluentResults.Result ExecuteLoadedScripts(ImmutableArray executionOrder, bool enableSandbox) { if (_isRunning) { @@ -266,7 +266,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _loggerService.LogMessage("Executing Lua scripts"); - SetupEnvironment(); + SetupEnvironment(enableSandbox); var result = FluentResults.Result.Ok(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs index 0edc75b45..a51ee79b6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs @@ -248,7 +248,7 @@ public sealed class PackageManagementService : IPackageManagementService if (!luaScripts.IsDefaultOrEmpty) { - result.WithReasons(_luaScriptManagementService.ExecuteLoadedScripts(luaScripts); + result.WithReasons(_luaScriptManagementService.ExecuteLoadedScripts(luaScripts, enableSandbox: !executeCsAssemblies).Reasons); } foreach (var package in loadingOrderedPackages) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs index e62da94a3..2117046da 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs @@ -34,7 +34,7 @@ public interface ILuaScriptManagementService : IReusableService /// /// // [Required] - FluentResults.Result ExecuteLoadedScripts(ImmutableArray executionOrder); + FluentResults.Result ExecuteLoadedScripts(ImmutableArray executionOrder, bool enableSandbox); /// /// From a505d48a4c7807d8d590b1e3f483342ad05e5657 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 8 Feb 2026 20:23:30 -0500 Subject: [PATCH 135/288] - Loading/Saving for Settings via console. --- .../SharedSource/DebugConsole.cs | 4 + .../LuaCs/Data/ServicesConfigData.cs | 139 +----------------- .../SharedSource/LuaCs/LuaCsSetup.cs | 2 + .../LuaCs/_Services/ConfigService.cs | 23 ++- .../LuaCs/_Services/LoggerService.cs | 7 +- .../LuaCs/_Services/StorageService.cs | 11 +- 6 files changed, 40 insertions(+), 146 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index 79fcd8002..51cf0444f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -2385,6 +2385,10 @@ namespace Barotrauma if (setting.TrySetValue(valueString)) { NewMessage($"Set config {internalName} value to {valueString}", Color.Green); + if (GameMain.LuaCs.ConfigService.SaveConfigValue(setting) is { IsFailed: true } res) + { + NewMessage($"Failed to save new config data to disk. Reasons: {res.ToString()}"); + } } else { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs index 87cfdf29c..2601e1826 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ServicesConfigData.cs @@ -25,31 +25,14 @@ public interface IStorageServiceConfig : IService #if CLIENT string TempDownloadsDirectory { get; } #endif - - //ReadOnlyCollection SafeIOReadDirectories { get; } - //ReadOnlyCollection SafeIOWriteDirectories { get; } - IEnumerable GlobalIOReadWhitelist(); - IEnumerable GlobalIOWriteWhitelist(); - - bool IOReadWhiteListContains(string filePath); - bool IOWriteWhiteListContains(string filePath); - string LocalDataSavePath { get; } string LocalDataPathRegex { get; } string LocalPackageDataPath { get; } - public string RunLocation { get; } - bool GlobalSafeIOEnabled { get; } } -internal interface IStorageServiceConfigUpdate +public record StorageServiceConfig : IStorageServiceConfig { - public FluentResults.Result SetSafeReadFilePaths(string[] filePaths); - public FluentResults.Result SetSafeWriteFilePaths(string[] filePaths); -} - -public record StorageServiceConfig : IStorageServiceConfig, IStorageServiceConfigUpdate -{ - private static readonly string ExecutionLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location.CleanUpPath()); + private static readonly string ExecutionLocation = Directory.GetCurrentDirectory().CleanUpPathCrossPlatform(); public string LocalModsDirectory { get; init; } = System.IO.Path.GetFullPath(ContentPackage.LocalModsDir).CleanUpPath(); public string WorkshopModsDirectory { get; init; } = System.IO.Path.GetFullPath(ContentPackage.WorkshopModsDir).CleanUpPath(); @@ -60,125 +43,11 @@ public record StorageServiceConfig : IStorageServiceConfig, IStorageServiceConfi #if CLIENT public string TempDownloadsDirectory { get; init; } = System.IO.Path.GetFullPath(ModReceiver.DownloadFolder).CleanUpPath(); #endif - - private readonly AsyncReaderWriterLock _safeIOReadLock = new(); - private readonly AsyncReaderWriterLock _safeIOWriteLock = new(); - private readonly ConcurrentDictionary _safeIOReadFilePaths = new(); - - private readonly ConcurrentDictionary _safeIOWriteFilePaths = new(); - - public IEnumerable GlobalIOReadWhitelist() - { - using var lck = _safeIOReadLock.AcquireReaderLock().GetAwaiter().GetResult(); - - if (_safeIOReadFilePaths.Count == 0) - { - yield break; - } - - foreach (var path in _safeIOReadFilePaths) - { - yield return path.Key; - } - } - - public IEnumerable GlobalIOWriteWhitelist() - { - using var lck = _safeIOWriteLock.AcquireReaderLock().GetAwaiter().GetResult(); - - if (_safeIOWriteFilePaths.Count == 0) - { - yield break; - } - - foreach (var path in _safeIOWriteFilePaths) - { - yield return path.Key; - } - } - - public bool IOReadWhiteListContains(string filePath) - { - if (filePath.IsNullOrWhiteSpace()) - return false; - return _safeIOReadFilePaths.ContainsKey(filePath); - } - - public bool IOWriteWhiteListContains(string filePath) - { - if (filePath.IsNullOrWhiteSpace()) - return false; - return _safeIOWriteFilePaths.ContainsKey(filePath); - } - - public string LocalDataSavePath => Path.Combine(ExecutionLocation, "/Data/Mods/"); - + public string LocalDataSavePath => Path.Combine(ExecutionLocation, "Data/Mods").CleanUpPathCrossPlatform(); public string LocalDataPathRegex => "%ModDir%"; - public string RunLocation => ExecutionLocation; - public bool GlobalSafeIOEnabled => false; - public string LocalPackageDataPath - { - get - { - return ContainsIllegalPaths(LocalDataSavePath) ? $"/Data/Mods/{LocalDataPathRegex}" - : Path.Combine(LocalDataSavePath, LocalDataPathRegex); - - bool ContainsIllegalPaths(string path) - { - throw new NotImplementedException(); - } - } - } - - - public FluentResults.Result SetSafeReadFilePaths(string[] filePaths) - { - using var lck = _safeIOReadLock.AcquireWriterLock().GetAwaiter().GetResult(); - return SetSafeDirectory(_safeIOReadFilePaths, filePaths); - } - - public FluentResults.Result SetSafeWriteFilePaths(string[] filePaths) - { - using var lck = _safeIOWriteLock.AcquireWriterLock().GetAwaiter().GetResult(); - return SetSafeDirectory(_safeIOWriteFilePaths, filePaths); - } - - private FluentResults.Result SetSafeDirectory(ConcurrentDictionary target, string[] filePaths) - { - if (filePaths is null || filePaths.Length < 1) - { - target.Clear(); - return FluentResults.Result.Ok(); - } - - FluentResults.Result result = new(); - - target.Clear(); - foreach (string path in filePaths) - { - if (path.IsNullOrWhiteSpace()) - { - result = result.WithError($"ServicesConfigData: A supplied whitelist path was null."); - continue; - } - - try - { - var path2 = Path.GetFullPath(path); - target.TryAdd(path2, 0); - } - catch (Exception e) - { - result = result.WithError( - new ExceptionalError(e).WithMetadata(FluentResults.LuaCs.MetadataType.ExceptionObject, this)); - continue; - } - } - - return result.WithSuccess($"Whitelist updated."); - } + public string LocalPackageDataPath => Path.Combine(LocalDataSavePath, LocalDataPathRegex); public void Dispose() { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 226f2c6e1..5a0f6a09b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -326,7 +326,9 @@ namespace Barotrauma registrationProvider.RegisterTypeProviders(ConfigService, null); } Logger.LogResults(PackageManagementService.LoadPackagesInfo(ContentPackageManager.EnabledPackages.All.ToImmutableArray())); + Logger.LogResults(ConfigService.LoadSavedConfigsValues()); LoadLuaCsConfig(); + } CurrentRunState = RunState.LoadedNoExec; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs index 2cfd7a265..445da3fc0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs @@ -112,13 +112,14 @@ public sealed partial class ConfigService : IConfigService _settingsInstances.Clear(); _instanceFactory.Clear(); _settingsInstancesByPackage.Clear(); + _storageService.PurgeCache(); return result; } #endregion - private const string SaveDataFileName = "SettingData.xml"; + private const string SaveDataFileName = "SettingsData.xml"; private readonly ConcurrentDictionary<(ContentPackage OwnerPackage, string InternalName), ISettingBase> _settingsInstances = new(); @@ -294,7 +295,21 @@ public sealed partial class ConfigService : IConfigService public FluentResults.Result LoadSavedConfigsValues() { - throw new NotImplementedException(); + ImmutableArray cfgValues; + using (var lck = _operationLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult()) + { + IService.CheckDisposed(this); + cfgValues = _settingsInstances.Select(kvp => kvp.Value).ToImmutableArray(); + } + + var ret = new FluentResults.Result(); + + foreach (var settingBase in cfgValues) + { + ret.WithReasons(LoadSavedValueForConfig(settingBase).Reasons); + } + + return ret; } public FluentResults.Result SaveConfigValue(ISettingBase setting) @@ -331,6 +346,10 @@ public sealed partial class ConfigService : IConfigService var attr = new XAttribute("Value", str); currentTarget.Add(attr); } + else + { + tgt.Value = str; + } return FluentResults.Result.Ok(); }, diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs index ec455ae0a..f7488e925 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs @@ -195,7 +195,6 @@ public partial class LoggerService : ILoggerService LogError($"FluentResults::IError: {error.Message}"); } - if (error.Reasons != null) { foreach (var reason in error.Reasons) @@ -209,17 +208,17 @@ public partial class LoggerService : ILoggerService public void LogDebug(string message, Color? color = null) { - throw new NotImplementedException(); + throw new NotImplementedException($"@EvilFactory will implement this at the end of development."); } public void LogDebugWarning(string message) { - throw new NotImplementedException(); + throw new NotImplementedException($"@EvilFactory will implement this at the end of development."); } public void LogDebugError(string message) { - throw new NotImplementedException(); + throw new NotImplementedException($"@EvilFactory will implement this at the end of development."); } public void Dispose() { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/StorageService.cs index 0de6b864b..1d3e43ce5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/StorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/StorageService.cs @@ -128,10 +128,9 @@ public class StorageService : IStorageService try { var path = System.IO.Path.GetFullPath(Path.Combine( - ConfigData.LocalPackageDataPath.Replace(ConfigData.LocalDataPathRegex, package.ToIdentifier().Value) - .CleanUpPathCrossPlatform(), - localFilePath)); - if (!path.StartsWith(ConfigData.LocalDataSavePath)) + ConfigData.LocalPackageDataPath.Replace(ConfigData.LocalDataPathRegex, package.Name).CleanUpPathCrossPlatform(), + localFilePath.CleanUpPathCrossPlatform())); + if (!path.StartsWith(Path.GetFullPath(ConfigData.LocalDataSavePath))) ThrowHelper.ThrowUnauthorizedAccessException($"{nameof(GetAbsolutePathForLocal)}: The local path of '{path}' is not a local path!"); return path; } @@ -433,7 +432,8 @@ public class StorageService : IStorageService { var fp = filePath.CleanUpPath(); fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); - System.IO.File.WriteAllText(fp, t, encoding); + Directory.CreateDirectory(Path.GetDirectoryName(fp)!); + System.IO.File.WriteAllText(fp, t, encoding ?? Encoding.UTF8); if (UseCaching) _fsCache[filePath] = t; return new FluentResults.Result().WithSuccess($"Saved to file successfully"); @@ -460,6 +460,7 @@ public class StorageService : IStorageService { var fp = filePath.CleanUpPath(); fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); + Directory.CreateDirectory(Path.GetDirectoryName(fp)!); System.IO.File.WriteAllBytes(fp, b); if (UseCaching) _fsCache[filePath] = b; From e8bec96970107fe4039aca5818287ce84632c3c3 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 8 Feb 2026 23:38:52 -0300 Subject: [PATCH 136/288] THE REQUIRE PATHS WORK!@!!!!!!11! --- .../LuaCsForBarotrauma/Lua/LuaSetup.lua | 22 ++++++++----------- .../_Services/LuaScriptManagementService.cs | 13 ++++++++++- .../LuaCs/_Services/_Lua/LuaScriptLoader.cs | 2 +- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua index d8c531649..8b83b4fd5 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua @@ -2,10 +2,6 @@ LuaSetup = {} local path = ... -package.path = {path .. "/Lua/?.lua"} - -setmodulepaths(package.path) - local function AddTableToGlobal(tbl) for k, v in pairs(tbl) do _G[k] = v @@ -13,22 +9,22 @@ local function AddTableToGlobal(tbl) end if SERVER then - AddTableToGlobal(require("DefaultLib/LibServer")) + AddTableToGlobal(dofile(path .. "/Lua/DefaultLib/LibServer.lua")) else - AddTableToGlobal(require("DefaultLib/LibClient")) + AddTableToGlobal(dofile(path .. "/Lua/DefaultLib/LibClient.lua")) end -AddTableToGlobal(require("DefaultLib/LibShared")) +AddTableToGlobal(dofile(path .. "/Lua/DefaultLib/LibShared.lua")) -AddTableToGlobal(require("CompatibilityLib")) +AddTableToGlobal(dofile(path .. "/Lua/CompatibilityLib.lua")) -require("DefaultHook") +dofile(path .. "/Lua/DefaultHook.lua") Descriptors = LuaSetup.LuaUserData -require("DefaultLib/Utils/Math") -require("DefaultLib/Utils/String") -require("DefaultLib/Utils/Util") -require("DefaultLib/Utils/SteamApi") +dofile(path .. "/Lua/DefaultLib/Utils/Math.lua") +dofile(path .. "/Lua/DefaultLib/Utils/String.lua") +dofile(path .. "/Lua/DefaultLib/Utils/Util.lua") +dofile(path .. "/Lua/DefaultLib/Utils/SteamApi.lua") LuaSetup = nil \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index cf2f53eb1..ed67f6525 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -268,10 +268,21 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService SetupEnvironment(enableSandbox); + if (_script == null) { return FluentResults.Result.Ok(); } // never happens + var result = FluentResults.Result.Ok(); _isRunning = true; + var packages = executionOrder.Select(r => r.OwnerPackage) + .Distinct() + .Select(p => $"{p.Dir}/Lua/?.lua") + .ToArray(); + + ((LuaScriptLoader)_luaScriptLoader).ModulePaths = packages; + Table package = (Table)_script.Globals["package"]; + package.Set("path", DynValue.FromObject(_script, packages)); + foreach (ILuaScriptResourceInfo resource in executionOrder.Where(l => l.IsAutorun)) { foreach (ContentPath filePath in resource.FilePaths) @@ -279,7 +290,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService try { _loggerService.LogMessage($"Run {filePath.Value}"); - _script?.Call(_script.LoadFile(filePath.FullPath), Path.GetDirectoryName(resource.OwnerPackage.Path)); + _script.Call(_script.LoadFile(filePath.FullPath), resource.OwnerPackage.Dir); } catch(Exception e) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaScriptLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaScriptLoader.cs index 7c5b0445e..e8a3ffc75 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaScriptLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaScriptLoader.cs @@ -77,7 +77,7 @@ namespace Barotrauma.LuaCs return false; } - return true; + return result.Value; } private void UnsafeLogErrors(string message, FluentResults.Result result = null) From 70e98467e8ce6e3020e01eaf2dcbe0c10272f573 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 8 Feb 2026 23:47:02 -0300 Subject: [PATCH 137/288] nuh uh --- .../SharedSource/LuaCs/_Services/LoggerService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs index f7488e925..0a590aac6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs @@ -208,17 +208,17 @@ public partial class LoggerService : ILoggerService public void LogDebug(string message, Color? color = null) { - throw new NotImplementedException($"@EvilFactory will implement this at the end of development."); + Log(message, color ?? Color.Purple); } public void LogDebugWarning(string message) { - throw new NotImplementedException($"@EvilFactory will implement this at the end of development."); + Log(message, Color.Yellow); } public void LogDebugError(string message) { - throw new NotImplementedException($"@EvilFactory will implement this at the end of development."); + Log(message, Color.Red); } public void Dispose() { } From 511f98ec184f066fb44f8053665221e8d6c3bccd Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 9 Feb 2026 15:52:37 -0500 Subject: [PATCH 138/288] - Added pre-touch to the ContentPath.FullPath to make them thread-safe. --- .../ContentManagement/ContentPath.cs | 7 +++-- .../_Services/PackageManagementService.cs | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPath.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPath.cs index 0d9a4a112..2661aef1d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPath.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPath.cs @@ -99,12 +99,13 @@ namespace Barotrauma public static ContentPath FromRaw(ContentPackage? contentPackage, string? rawValue) { var newRaw = new ContentPath(contentPackage, rawValue); - if (prevCreatedRaw is not null && prevCreatedRaw.ContentPackage == contentPackage && + // Removed as this almost never happens but makes the constructor not thread-safe. + /*if (prevCreatedRaw is not null && prevCreatedRaw.ContentPackage == contentPackage && prevCreatedRaw.RawValue == rawValue) { newRaw.cachedValue = prevCreatedRaw.Value; } - prevCreatedRaw = newRaw; + prevCreatedRaw = newRaw;*/ return newRaw; } @@ -158,4 +159,4 @@ namespace Barotrauma public override string? ToString() => Value; } -} \ No newline at end of file +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs index a51ee79b6..5483fab56 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using Barotrauma.Extensions; using Barotrauma.LuaCs.Data; @@ -138,7 +139,9 @@ public sealed class PackageManagementService : IPackageManagementService { result.WithReasons(pkgConfig.Config.Reasons); if (pkgConfig.Config.IsSuccess) + { result.WithReasons(UnsafeAddPackageInternal(pkgConfig.Source, pkgConfig.Config.Value).Reasons); + } } return result; @@ -152,6 +155,32 @@ public sealed class PackageManagementService : IPackageManagementService return FluentResults.Result.Ok(); } + // We need to touch ContentPath.Fullpath once in a single-threaded context to make it thread-safe. + foreach (var info in config.Assemblies) + { + TouchMeFullPaths(info); + } + + foreach (var info in config.Configs) + { + TouchMeFullPaths(info); + } + + foreach (var info in config.LuaScripts) + { + TouchMeFullPaths(info); + } + + // We need to touch ContentPath.Fullpath once in a single-threaded context to make it thread-safe. + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.PreserveSig)] + void TouchMeFullPaths(IBaseResourceInfo info) + { + foreach (var contentPath in info.FilePaths) + { + var s = contentPath.FullPath; + } + } + _loadedPackages[package] = config; try { From 95376622faa6fd4880dc84e7ff6a924905792c1a Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 9 Feb 2026 16:58:53 -0500 Subject: [PATCH 139/288] - Added LuaCsForBarotrauma check to enabled packages list. --- .../SharedSource/LuaCs/LuaCsSetup.cs | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 5a0f6a09b..db4fbbfd0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -280,6 +280,30 @@ namespace Barotrauma } _runStateMachine.GotoState(targetRunState); } + + private ImmutableArray GetEnabledPackagesList() + { + var enabledRegular = ContentPackageManager.EnabledPackages.Regular.ToImmutableArray(); + if (!enabledRegular.Any( + p => p.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase) + || p.Name.Equals("Lua for Barotrauma", StringComparison.InvariantCultureIgnoreCase))) + { + var luaCs = ContentPackageManager.AllPackages.FirstOrDefault( + p => p.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase) + || p.Name.Equals("Lua For Barotrauma", 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!"), + createMessageBox: true); + return enabledRegular; + } + + enabledRegular = new[] { luaCs }.Concat(enabledRegular).ToImmutableArray(); + } + + return enabledRegular; + } private StateMachine SetupStateMachine() { @@ -325,7 +349,7 @@ namespace Barotrauma { registrationProvider.RegisterTypeProviders(ConfigService, null); } - Logger.LogResults(PackageManagementService.LoadPackagesInfo(ContentPackageManager.EnabledPackages.All.ToImmutableArray())); + Logger.LogResults(PackageManagementService.LoadPackagesInfo(GetEnabledPackagesList())); Logger.LogResults(ConfigService.LoadSavedConfigsValues()); LoadLuaCsConfig(); @@ -342,17 +366,13 @@ namespace Barotrauma { registrationProvider.RegisterTypeProviders(ConfigService, null); } - Logger.LogResults(PackageManagementService.LoadPackagesInfo(ContentPackageManager.EnabledPackages.All.ToImmutableArray())); + Logger.LogResults(PackageManagementService.LoadPackagesInfo(GetEnabledPackagesList())); LoadLuaCsConfig(); } if (!PackageManagementService.IsAnyPackageRunning()) { -#if DEBUG - Logger.LogResults(PackageManagementService.ExecuteLoadedPackages(ContentPackageManager.EnabledPackages.All.ToImmutableArray(), true)); -#else - Logger.LogResults(PackageManagementService.ExecuteLoadedPackages(ContentPackageManager.EnabledPackages.All.ToImmutableArray(), IsCsEnabled)); -#endif + Logger.LogResults(PackageManagementService.ExecuteLoadedPackages(GetEnabledPackagesList(), IsCsEnabled)); } #if CLIENT @@ -362,7 +382,6 @@ namespace Barotrauma EventService.PublishEvent(static p => p.OnConnectedToServer()); } #endif - CurrentRunState = RunState.Running; } From dc1093eeed7ca9fcb0bce2013983780d1a225a75 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 9 Feb 2026 17:18:50 -0500 Subject: [PATCH 140/288] Added LuaCs package lookup to the info provider. --- .../LuaCs/_Services/LuaCsInfoProvider.cs | 31 ++++++++++++++++++- .../_Interfaces/ILuaCsInfoProvider.cs | 5 +++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs index 0637da337..81ff151b0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs @@ -1,4 +1,8 @@ -namespace Barotrauma.LuaCs; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Barotrauma.LuaCs; public sealed class LuaCsInfoProvider : ILuaCsInfoProvider { @@ -15,4 +19,29 @@ public sealed class LuaCsInfoProvider : ILuaCsInfoProvider public bool RestrictMessageSize => GameMain.LuaCs.RestrictMessageSize; public string LocalDataSavePath => GameMain.LuaCs.LocalDataSavePath; public RunState CurrentRunState => GameMain.LuaCs.CurrentRunState; + public ContentPackage LuaCsForBarotraumaPackage + { + get + { + var luaCs = FirstOrDefaultLua(ContentPackageManager.EnabledPackages.All); + if (luaCs == null) + { + luaCs = FirstOrDefaultLua(ContentPackageManager.LocalPackages.Regular); + } + + if (luaCs == null) + { + luaCs = FirstOrDefaultLua(ContentPackageManager.WorkshopPackages.Regular); + } + + return luaCs; + + ContentPackage FirstOrDefaultLua(IEnumerable packages) + { + return packages.FirstOrDefault(p => + p.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase) + || p.Name.Equals("Lua for Barotrauma", StringComparison.InvariantCultureIgnoreCase)); + } + } + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaCsInfoProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaCsInfoProvider.cs index 337294941..2cf93ed94 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaCsInfoProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaCsInfoProvider.cs @@ -39,4 +39,9 @@ public interface ILuaCsInfoProvider : IService /// The current state of the Execution State Machine. /// public RunState CurrentRunState { get; } + + /// + /// Returns the best-matching LuaCsForBarotrauma package (enabled list > localMods > WorkshopMods). + /// + public ContentPackage LuaCsForBarotraumaPackage { get; } } From 64831bd580c6127f0be9f8755c8c0a6131e5c519 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 9 Feb 2026 17:43:59 -0500 Subject: [PATCH 141/288] - Made the assembly compilation logs output in production builds. - Removed double logging in debug builds. --- .../SharedSource/LuaCs/_Services/PluginManagementService.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index 6f44d9ed4..c020949da 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -442,13 +442,7 @@ public class PluginManagementService : IAssemblyManagementService { var res = assemblyLoader.LoadAssemblyFromFile(binResource.FullPath, dependencyPaths); result.WithReasons(res.Reasons); -#if DEBUG _logger.LogResults(res.ToResult()); -#endif - if (res.IsFailed) - { - _logger.LogResults(res.ToResult()); - } } } From a61e705dbf157e14af8ef6d1aec9f8d20fa3b4b6 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Mon, 9 Feb 2026 19:51:31 -0300 Subject: [PATCH 142/288] Better LoggerService --- .../BarotraumaClient/ClientSource/GameMain.cs | 1 + .../BarotraumaServer/ServerSource/GameMain.cs | 2 + .../LuaCs/_Services/LoggerService.cs | 238 +++++++++--------- .../_Services/_Interfaces/ILoggerService.cs | 10 + 4 files changed, 128 insertions(+), 123 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index 874de92fc..2dc46b3d7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -1052,6 +1052,7 @@ namespace Barotrauma SoundManager?.Update(); LuaCs.EventService.PublishEvent(sub => sub.OnUpdate(Timing.Step)); + LuaCs.Logger.ProcessLogs(); Timing.Accumulator -= Timing.Step; diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs index 66d8ab350..b438e9af6 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs @@ -365,6 +365,8 @@ namespace Barotrauma CoroutineManager.Update(paused: false, (float)Timing.Step); LuaCs.EventService.PublishEvent(sub => sub.OnUpdate(Timing.Step)); + LuaCs.Logger.ProcessLogs(); + performanceCounterTimer.Stop(); if (GameMain.LuaCs.PerformanceCounter.EnablePerformanceCounter) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs index 0a590aac6..cbd773c6d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs @@ -1,9 +1,11 @@ -using System; -using System.Linq; -using Barotrauma.Networking; +using Barotrauma.Networking; using FluentResults; +using HarmonyLib; using Microsoft.Xna.Framework; using MoonSharp.Interpreter; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; namespace Barotrauma.LuaCs; @@ -11,18 +13,118 @@ public partial class LoggerService : ILoggerService { public bool HideUserNames = true; -#if SERVER - private const string LogPrefix = "SV"; - private const int NetMaxLength = 1024; // character limit of vanilla Barotrauma's chat system. - private const int NetMaxMessages = 60; + private List logSubscribers = []; + private ConcurrentQueue logQueue = []; - // This is used so it's possible to call logging functions inside the serverLog - // hook without creating an infinite loop - private bool _lockLog = false; +#if SERVER + private const string TargetPrefix = "[SV]"; + private const int NetMaxLength = 1024; // character limit of vanilla Barotrauma's chat system. + private const int NetMaxMessages = 60; + + // This is used so it's possible to call logging functions inside the serverLog + // hook without creating an infinite loop + private bool _isInsideLogCall = false; #else - private const string LogPrefix = "CL"; + private const string TargetPrefix = "[CL]"; #endif - + + public LoggerService() { } + + public void Subscribe(ILoggerSubscriber subscriber) + { + logSubscribers.Add(subscriber); + } + + public void Unsubscribe(ILoggerSubscriber subscriber) + { + logSubscribers.Remove(subscriber); + } + + public void ProcessLogs() + { + while (logQueue.TryDequeue(out PendingLog log)) + { + logSubscribers.ForEach(s => s.OnLog(log)); + + DebugConsole.NewMessage(log.Message, log.Color); + +#if SERVER + if (GameMain.Server != null) + { + if (GameMain.Server.ServerSettings.SaveServerLogs) + { + string logMessage = "[LuaCs] " + log.Message; + GameMain.Server.ServerSettings.ServerLog.WriteLine(logMessage, log.MessageType, false); + + if (!_isInsideLogCall) + { + _isInsideLogCall = true; + GameMain.LuaCs?.Hook?.Call("serverLog", logMessage, log.MessageType); + _isInsideLogCall = false; + } + } + + for (int i = 0; i < log.Message.Length; i += NetMaxLength) + { + string subStr = log.Message.Substring(i, Math.Min(1024, log.Message.Length - i)); + BroadcastMessage(subStr); + } + } + + void BroadcastMessage(string m) + { + foreach (var client in GameMain.Server.ConnectedClients) + { + ChatMessage consoleMessage = ChatMessage.Create("", m, ChatMessageType.Console, null, textColor: log.Color); + GameMain.Server.SendDirectChatMessage(consoleMessage, client); + + if (!GameMain.Server.ServerSettings.SaveServerLogs || !client.HasPermission(ClientPermissions.ServerLog)) + { + continue; + } + + ChatMessage logMessage = ChatMessage.Create(log.MessageType.ToString(), "[LuaCs] " + m, ChatMessageType.ServerLog, null); + GameMain.Server.SendDirectChatMessage(logMessage, client); + } + } +#endif + } + } + + public void Log(string message, Color? color = null, ServerLog.MessageType messageType = ServerLog.MessageType.ServerMessage) + { + if (HideUserNames && !Environment.UserName.IsNullOrEmpty()) + { + message = message.Replace(Environment.UserName, "USERNAME"); + } + + message = $"{TargetPrefix} {message}"; + + logQueue.Enqueue(new PendingLog(message, color, messageType)); + } + + public void LogError(string message) + { + Log($"{message}", Color.Red, ServerLog.MessageType.Error); + } + + public void LogWarning(string message) + { + Log($"{message}", Color.Yellow, ServerLog.MessageType.ServerMessage); + } + + public void LogMessage(string message, Color? serverColor = null, Color? clientColor = null) + { + serverColor ??= Color.MediumPurple; + clientColor ??= Color.Purple; + +#if SERVER + Log(message, serverColor); +#else + Log(message, clientColor); +#endif + } + public void HandleException(Exception exception, string prefix = null) { string errorString = ""; @@ -54,120 +156,10 @@ public partial class LoggerService : ILoggerService errorString = $"{prefix ?? ""}{s}"; break; } - + LogError(prefix + Environment.UserName + " " + errorString); } - public void LogError(string message) - { - Log($"{message}", Color.Red, ServerLog.MessageType.Error); - } - - public void LogWarning(string message) - { - Log($"{message}", Color.Yellow, ServerLog.MessageType.ServerMessage); - } - - public void LogMessage(string message, Color? serverColor = null, Color? clientColor = null) - { - serverColor ??= Color.MediumPurple; - clientColor ??= Color.Purple; - -#if SERVER - Log(message, serverColor); -#else - Log(message, clientColor); -#endif - } - - public void Log(string message, Color? color = null, ServerLog.MessageType messageType = ServerLog.MessageType.ServerMessage) - { - // TODO: Make this thread Async compatible. - - if (HideUserNames && !Environment.UserName.IsNullOrEmpty()) - { - message = message.Replace(Environment.UserName, "USERNAME"); - } - - DebugConsole.NewMessage(message, color); - -#if SERVER - void BroadcastMessage(string m) - { - foreach (var client in GameMain.Server.ConnectedClients) - { - ChatMessage consoleMessage = ChatMessage.Create("", m, ChatMessageType.Console, null, textColor: color); - GameMain.Server.SendDirectChatMessage(consoleMessage, client); - - if (!GameMain.Server.ServerSettings.SaveServerLogs || !client.HasPermission(ClientPermissions.ServerLog)) - { - continue; - } - - ChatMessage logMessage = ChatMessage.Create(messageType.ToString(), "[LuaCs] " + m, ChatMessageType.ServerLog, null); - GameMain.Server.SendDirectChatMessage(logMessage, client); - } - } - - if (GameMain.Server != null) - { - if (GameMain.Server.ServerSettings.SaveServerLogs) - { - string logMessage = "[LuaCs] " + message; - GameMain.Server.ServerSettings.ServerLog.WriteLine(logMessage, messageType, false); - - if (!_lockLog) - { - _lockLog = true; - GameMain.LuaCs?.Hook?.Call("serverLog", logMessage, messageType); - _lockLog = false; - } - } - - for (int i = 0; i < message.Length; i += NetMaxLength) - { - string subStr = message.Substring(i, Math.Min(1024, message.Length - i)); - BroadcastMessage(subStr); - } - } -#endif - } - - public void HandleException(Exception ex) - { - string errorString = ""; - switch (ex) - { - case NetRuntimeException netRuntimeException: - if (netRuntimeException.DecoratedMessage == null) - { - errorString = netRuntimeException.ToString(); - } - else - { - // FIXME: netRuntimeException.ToString() doesn't print the InnerException's stack trace... - errorString = $"{netRuntimeException.DecoratedMessage}: {netRuntimeException}"; - } - break; - case InterpreterException interpreterException: - if (interpreterException.DecoratedMessage == null) - { - errorString = interpreterException.ToString(); - } - else - { - errorString = interpreterException.DecoratedMessage; - } - break; - default: - errorString = ex.StackTrace != null - ? ex.ToString() - : $"{ex}\n{Environment.StackTrace}"; - break; - } - - LogError(errorString); - } public void LogResults(FluentResults.Result result) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILoggerService.cs index c4c110379..c5ac48768 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILoggerService.cs @@ -5,11 +5,21 @@ using Microsoft.Xna.Framework; namespace Barotrauma.LuaCs; +public readonly record struct PendingLog(string Message, Color? Color, ServerLog.MessageType MessageType); + +public interface ILoggerSubscriber +{ + void OnLog(PendingLog pendingLog); +} + /// /// Provides console and debug logging services /// public interface ILoggerService : IReusableService { + void Subscribe(ILoggerSubscriber subscriber); + void Unsubscribe(ILoggerSubscriber subscriber); + void ProcessLogs(); void HandleException(Exception exception, string prefix = null); void LogError(string message); void LogWarning(string message); From f1bae4c1ca5fd611758e37474f3767601c705154 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 9 Feb 2026 18:37:27 -0500 Subject: [PATCH 143/288] - Added Copy-On-Build for publicized assemblies to the luatrauma localmods folder. --- Barotrauma/BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 1 + Barotrauma/BarotraumaClient/WindowsClient.csproj | 2 +- Barotrauma/BarotraumaServer/LinuxServer.csproj | 2 ++ Barotrauma/BarotraumaServer/MacServer.csproj | 2 ++ Barotrauma/BarotraumaServer/WindowsServer.csproj | 1 + Barotrauma/BarotraumaShared/LuatraumaBuild.props | 11 +++++++++++ 7 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/LuatraumaBuild.props diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 371d9d527..3526da62c 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -216,5 +216,5 @@ - + diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 39d3507cb..3bc04570d 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -221,5 +221,6 @@ + diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 37458baff..ada79dc72 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -248,5 +248,5 @@ - + diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 029e0ccc1..9035c5baf 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -164,4 +164,6 @@ + + diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 566b5fc54..8e35877bc 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -167,4 +167,6 @@ + + diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index fa071c43c..e1dcacd9e 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -169,4 +169,5 @@ + diff --git a/Barotrauma/BarotraumaShared/LuatraumaBuild.props b/Barotrauma/BarotraumaShared/LuatraumaBuild.props new file mode 100644 index 000000000..79440aadb --- /dev/null +++ b/Barotrauma/BarotraumaShared/LuatraumaBuild.props @@ -0,0 +1,11 @@ + + + + + + + + From d14c353115c4ef2da99fa36c615f8c4155857028 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Mon, 9 Feb 2026 21:01:07 -0300 Subject: [PATCH 144/288] Remove old LuaCsNetworking --- .../ClientSource/LuaCs/LuaCsNetworking.cs | 143 ------------- .../ServerSource/LuaCs/LuaCsNetworking.cs | 194 ------------------ .../_Lua/LuaClasses/LuaCsNetworking.cs | 177 ---------------- 3 files changed, 514 deletions(-) delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsNetworking.cs delete mode 100644 Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsNetworking.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsNetworking.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsNetworking.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsNetworking.cs deleted file mode 100644 index 32c830bb4..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsNetworking.cs +++ /dev/null @@ -1,143 +0,0 @@ -using Barotrauma.Networking; -using System.Collections.Generic; - -namespace Barotrauma -{ - partial class LuaCsNetworking - { - private Dictionary> receiveQueue = new Dictionary>(); - - public void SendSyncMessage() - { - if (GameMain.Client == null) { return; } - - WriteOnlyMessage message = new WriteOnlyMessage(); - message.WriteByte((byte)ClientPacketHeader.LUA_NET_MESSAGE); - message.WriteByte((byte)LuaCsClientToServer.RequestAllIds); - GameMain.Client.ClientPeer.Send(message, DeliveryMethod.Reliable); - } - - public void NetMessageReceived(IReadMessage netMessage, ServerPacketHeader header, Client client = null) - { - if (header != ServerPacketHeader.LUA_NET_MESSAGE) - { - GameMain.LuaCs.Hook.Call("netMessageReceived", netMessage, header, client); - return; - } - - LuaCsServerToClient luaCsHeader = (LuaCsServerToClient)netMessage.ReadByte(); - - switch (luaCsHeader) - { - case LuaCsServerToClient.NetMessageString: - HandleNetMessageString(netMessage); - break; - - case LuaCsServerToClient.NetMessageId: - HandleNetMessageId(netMessage); - break; - - case LuaCsServerToClient.ReceiveIds: - ReadIds(netMessage); - break; - } - } - - public IWriteMessage Start(string netMessageName) - { - var message = new WriteOnlyMessage(); - - message.WriteByte((byte)ClientPacketHeader.LUA_NET_MESSAGE); - - if (stringToId.ContainsKey(netMessageName)) - { - message.WriteByte((byte)LuaCsClientToServer.NetMessageId); - message.WriteUInt16(stringToId[netMessageName]); - } - else - { - message.WriteByte((byte)LuaCsClientToServer.NetMessageString); - message.WriteString(netMessageName); - } - - return message; - } - - public void Receive(string netMessageName, LuaCsAction callback) - { - RequestId(netMessageName); - - netReceives[netMessageName] = callback; - } - - public void RequestId(string netMessageName) - { - if (stringToId.ContainsKey(netMessageName)) { return; } - - if (GameMain.Client == null) { return; } - - WriteOnlyMessage message = new WriteOnlyMessage(); - message.WriteByte((byte)ClientPacketHeader.LUA_NET_MESSAGE); - message.WriteByte((byte)LuaCsClientToServer.RequestSingleId); - - message.WriteString(netMessageName); - - Send(message, DeliveryMethod.Reliable); - } - - public void Send(IWriteMessage netMessage, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable) - { - GameMain.Client.ClientPeer.Send(netMessage, deliveryMethod); - } - - private void HandleNetMessageId(IReadMessage netMessage, Client client = null) - { - ushort id = netMessage.ReadUInt16(); - - if (idToString.ContainsKey(id)) - { - string name = idToString[id]; - - HandleNetMessage(netMessage, name, client); - } - else - { - if (!receiveQueue.ContainsKey(id)) { receiveQueue[id] = new Queue(); } - receiveQueue[id].Enqueue(netMessage); - - if (GameSettings.CurrentConfig.VerboseLogging) - { - LuaCsLogger.LogMessage($"Received NetMessage with unknown id {id} from server, storing in queue in case we receive the id later."); - } - } - } - - private void ReadIds(IReadMessage netMessage) - { - ushort size = netMessage.ReadUInt16(); - - for (int i = 0; i < size; i++) - { - ushort id = netMessage.ReadUInt16(); - string name = netMessage.ReadString(); - - idToString[id] = name; - stringToId[name] = id; - - if (!receiveQueue.ContainsKey(id)) - { - continue; - } - - while (receiveQueue[id].TryDequeue(out var queueMessage)) - { - if (netReceives.ContainsKey(name)) - { - netReceives[name](queueMessage, null); - } - } - } - } - } - -} diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsNetworking.cs deleted file mode 100644 index 62810ca56..000000000 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsNetworking.cs +++ /dev/null @@ -1,194 +0,0 @@ -using Barotrauma.Networking; -using System.Collections.Generic; -using System.Linq; - -namespace Barotrauma -{ - partial class LuaCsNetworking - { - private const int MaxRegisterPerClient = 1000; - - private Dictionary clientRegisterCount = new Dictionary(); - - private ushort currentId = 0; - - public void NetMessageReceived(IReadMessage netMessage, ClientPacketHeader header, Client client = null) - { - if (header != ClientPacketHeader.LUA_NET_MESSAGE) - { - GameMain.LuaCs.Hook.Call("netMessageReceived", netMessage, header, client); - return; - } - - LuaCsClientToServer luaCsHeader = (LuaCsClientToServer)netMessage.ReadByte(); - - switch (luaCsHeader) - { - case LuaCsClientToServer.NetMessageString: - HandleNetMessageString(netMessage, client); - break; - - case LuaCsClientToServer.NetMessageId: - HandleNetMessageId(netMessage, client); - break; - - case LuaCsClientToServer.RequestAllIds: - WriteAllIds(client); - break; - - case LuaCsClientToServer.RequestSingleId: - RequestIdSingle(netMessage, client); - break; - } - } - - private void HandleNetMessageId(IReadMessage netMessage, Client client = null) - { - ushort id = netMessage.ReadUInt16(); - - if (idToString.ContainsKey(id)) - { - string name = idToString[id]; - - HandleNetMessage(netMessage, name, client); - } - else - { - if (GameSettings.CurrentConfig.VerboseLogging) - { - LuaCsLogger.LogError($"Received NetMessage for unknown id {id} from {GameServer.ClientLogName(client)}."); - } - } - } - - public IWriteMessage Start(string netMessageName) - { - var message = new WriteOnlyMessage(); - - message.WriteByte((byte)ServerPacketHeader.LUA_NET_MESSAGE); - - if (stringToId.ContainsKey(netMessageName)) - { - message.WriteByte((byte)LuaCsServerToClient.NetMessageId); - message.WriteUInt16(stringToId[netMessageName]); - } - else - { - message.WriteByte((byte)LuaCsServerToClient.NetMessageString); - message.WriteString(netMessageName); - } - - return message; - } - - public void Receive(string netMessageName, LuaCsAction callback) - { - RegisterId(netMessageName); - - netReceives[netMessageName] = callback; - } - - public ushort RegisterId(string name) - { - if (stringToId.ContainsKey(name)) - { - return stringToId[name]; - } - - if (currentId >= ushort.MaxValue) - { - LuaCsLogger.LogError($"Tried to register more than {ushort.MaxValue} network ids!"); - return 0; - } - - currentId++; - - idToString[currentId] = name; - stringToId[name] = currentId; - - WriteIdToAll(currentId, name); - - return currentId; - } - - private void RequestIdSingle(IReadMessage netMessage, Client client) - { - string name = netMessage.ReadString(); - - if (!stringToId.ContainsKey(name) && client.AccountId.TryUnwrap(out AccountId id)) - { - if (!clientRegisterCount.ContainsKey(id.StringRepresentation)) - { - clientRegisterCount[id.StringRepresentation] = 0; - } - - clientRegisterCount[id.StringRepresentation]++; - - if (clientRegisterCount[id.StringRepresentation] > MaxRegisterPerClient) - { - LuaCsLogger.Log($"{GameServer.ClientLogName(client)} Tried to register more than {MaxRegisterPerClient} Ids!"); - return; - } - } - - RegisterId(name); - } - - private void WriteIdToAll(ushort id, string name) - { - WriteOnlyMessage message = new WriteOnlyMessage(); - message.WriteByte((byte)ServerPacketHeader.LUA_NET_MESSAGE); - message.WriteByte((byte)LuaCsServerToClient.ReceiveIds); - - message.WriteUInt16(1); - message.WriteUInt16(id); - message.WriteString(name); - - Send(message, null, DeliveryMethod.Reliable); - } - - private void WriteAllIds(Client client) - { - WriteOnlyMessage message = new WriteOnlyMessage(); - message.WriteByte((byte)ServerPacketHeader.LUA_NET_MESSAGE); - message.WriteByte((byte)LuaCsServerToClient.ReceiveIds); - - message.WriteUInt16((ushort)idToString.Count()); - foreach ((ushort id, string name) in idToString) - { - message.WriteUInt16(id); - message.WriteString(name); - } - - Send(message, client.Connection, DeliveryMethod.Reliable); - } - - public void ClientWriteLobby(Client client) => GameMain.Server.ClientWriteLobby(client); - - public void Send(IWriteMessage netMessage, NetworkConnection connection = null, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable) - { - if (connection == null) - { - foreach (NetworkConnection conn in Client.ClientList.Select(c => c.Connection)) - { - GameMain.Server.ServerPeer.Send(netMessage, conn, deliveryMethod); - } - } - else - { - GameMain.Server.ServerPeer.Send(netMessage, connection, deliveryMethod); - } - } - - public void UpdateClientPermissions(Client client) - { - GameMain.Server.UpdateClientPermissions(client); - } - - public int FileSenderMaxPacketsPerUpdate - { - get { return FileSender.FileTransferOut.MaxPacketsPerUpdate; } - set { FileSender.FileTransferOut.MaxPacketsPerUpdate = value; } - } - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsNetworking.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsNetworking.cs deleted file mode 100644 index 38042d1c1..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsNetworking.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System; -using System.IO; -using System.Collections.Generic; -using System.Net.Http; -using System.Text; -using Barotrauma.Networking; - -namespace Barotrauma -{ - partial class LuaCsNetworking - { - private static readonly HttpClient client = new HttpClient(); - - private enum LuaCsClientToServer - { - NetMessageId, - NetMessageString, - RequestSingleId, - RequestAllIds, - } - - private enum LuaCsServerToClient - { - NetMessageId, - NetMessageString, - ReceiveIds - } - - public bool RestrictMessageSize = true; - - private Dictionary netReceives = new Dictionary(); - private Dictionary idToString = new Dictionary(); - private Dictionary stringToId = new Dictionary(); - - public void Initialize() - { -#if CLIENT - SendSyncMessage(); -#endif - } - - public void Remove(string netMessageName) - { - netReceives.Remove(netMessageName); - } - - public IWriteMessage Start() - { - return new WriteOnlyMessage(); - } - - public string IdToString(ushort id) - { - if (idToString.ContainsKey(id)) { return idToString[id]; } - - return null; - } - - public ushort StringToId(string name) - { - if (stringToId.ContainsKey(name)) { return stringToId[name]; } - - return 0; - } - - private void HandleNetMessage(IReadMessage netMessage, string name, Client client = null) - { - if (netReceives.ContainsKey(name)) - { - try - { - netReceives[name](netMessage, client); - } - catch (Exception e) - { - LuaCsLogger.LogError($"Exception thrown inside NetMessageReceive({name})", LuaCsMessageOrigin.CSharpMod); - LuaCsLogger.HandleException(e, LuaCsMessageOrigin.CSharpMod); - } - } - else - { - if (GameSettings.CurrentConfig.VerboseLogging) - { -#if SERVER - LuaCsLogger.LogError($"Received NetMessage for unknown name {name} from {GameServer.ClientLogName(client)}."); -#else - LuaCsLogger.LogError($"Received NetMessage for unknown name {name} from server."); -#endif - } - } - } - - private void HandleNetMessageString(IReadMessage netMessage, Client client = null) - { - string name = netMessage.ReadString(); - - HandleNetMessage(netMessage, name, client); - } - - public async void HttpRequest(string url, LuaCsAction callback, string data = null, string method = "POST", string contentType = "application/json", Dictionary headers = null, string savePath = null) - { - try - { - HttpRequestMessage request = new HttpRequestMessage(new HttpMethod(method), url); - - if (headers != null) - { - foreach (var header in headers) - { - request.Headers.Add(header.Key, header.Value); - } - } - - if (data != null) - { - request.Content = new StringContent(data, Encoding.UTF8, contentType); - } - - HttpResponseMessage response = await client.SendAsync(request); - - if (savePath != null) - { - if (LuaCsFile.IsPathAllowedException(savePath)) - { - byte[] responseData = await response.Content.ReadAsByteArrayAsync(); - - using (var fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write)) - { - fileStream.Write(responseData, 0, responseData.Length); - } - } - } - - throw new NotImplementedException(); - - string responseBody = await response.Content.ReadAsStringAsync(); - - /*GameMain.LuaCs.Timer.Wait((object[] par) => - { - callback(responseBody, (int)response.StatusCode, response.Headers); - }, 0);*/ - } - catch (HttpRequestException e) - { - throw new NotImplementedException(); - //GameMain.LuaCs.Timer.Wait((object[] par) => { callback(e.Message, e.StatusCode, null); }, 0); - } - catch (Exception e) - { - throw new NotImplementedException(); - //GameMain.LuaCs.Timer.Wait((object[] par) => { callback(e.Message, null, null); }, 0); - } - } - - public void HttpPost(string url, LuaCsAction callback, string data, string contentType = "application/json", Dictionary headers = null, string savePath = null) - { - HttpRequest(url, callback, data, "POST", contentType, headers, savePath); - } - - - public void HttpGet(string url, LuaCsAction callback, Dictionary headers = null, string savePath = null) - { - HttpRequest(url, callback, null, "GET", null, headers, savePath); - } - - public void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData) - { - GameMain.NetworkMember.CreateEntityEvent(entity, extraData); - } - - public ushort LastClientListUpdateID - { - get { return GameMain.NetworkMember.LastClientListUpdateID; } - set { GameMain.NetworkMember.LastClientListUpdateID = value; } - } - } -} From fb4648d759f6d3c54478770edce62a91e1c03971 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Mon, 9 Feb 2026 21:54:44 -0300 Subject: [PATCH 145/288] Converting everything into Harmony patches part 1 --- .../BarotraumaClient/ClientSource/GameMain.cs | 5 +- .../LuaCs/Services/NetworkingService.cs | 4 +- .../ClientSource/PlayerInput.cs | 2 - .../ClientSource/Screens/SubEditorScreen.cs | 2 - .../BarotraumaServer/ServerSource/GameMain.cs | 5 +- .../SharedSource/Characters/Character.cs | 3 - .../Health/Afflictions/Affliction.cs | 1 - .../ContentPackageManager.cs | 13 -- .../SharedSource/LuaCs/IEvents.cs | 97 ++++++++++--- .../SharedSource/LuaCs/LuaCsSetup.cs | 34 ++--- .../_Services/HarmonyEventPatchesService.cs | 130 ++++++++++++++++++ .../_Services/LuaScriptManagementService.cs | 5 + .../LuaCs/_Services/NetworkingService.cs | 2 +- .../SharedSource/Screens/Screen.cs | 1 - 14 files changed, 236 insertions(+), 68 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index 2dc46b3d7..b37ca5668 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -297,6 +297,8 @@ namespace Barotrauma MainThread = Thread.CurrentThread; Window.FileDropped += OnFileDropped; + + LuaCs.GetType(); } public static void ExecuteAfterContentFinishedLoading(Action action) @@ -1051,9 +1053,6 @@ namespace Barotrauma SoundManager?.Update(); - LuaCs.EventService.PublishEvent(sub => sub.OnUpdate(Timing.Step)); - LuaCs.Logger.ProcessLogs(); - Timing.Accumulator -= Timing.Step; updateCount++; diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs index 578e5c50f..4cde8a52b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs @@ -7,11 +7,11 @@ using System.Collections.Generic; namespace Barotrauma.LuaCs; -partial class NetworkingService : INetworkingService, IEventConnectedToServer, IEventServerRawNetMessageReceived +partial class NetworkingService : INetworkingService, IEventServerConnected, IEventServerRawNetMessageReceived { private ConcurrentDictionary> receiveQueue = new(); - public void OnConnectedToServer() + public void OnServerConnected() { SendSyncMessage(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/PlayerInput.cs b/Barotrauma/BarotraumaClient/ClientSource/PlayerInput.cs index d3aa62499..593627a43 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/PlayerInput.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/PlayerInput.cs @@ -495,8 +495,6 @@ namespace Barotrauma allowInput = true; } - GameMain.LuaCs.Hook.Call("keyUpdate", deltaTime); - oldMouseState = mouseState; mouseState = latestMouseState; UpdateVariable(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index d7b368980..8fb3e79a9 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -1532,8 +1532,6 @@ namespace Barotrauma public override void Select() { Select(enableAutoSave: true); - - GameMain.LuaCs.EventService.PublishEvent(sub => sub.OnScreenSelected(this)); } public void Select(bool enableAutoSave = true) diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs index b438e9af6..c875eed2f 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs @@ -114,6 +114,8 @@ namespace Barotrauma GameScreen = new GameScreen(); MainThread = Thread.CurrentThread; + + LuaCs.GetType(); } public void Init() @@ -364,9 +366,6 @@ namespace Barotrauma TaskPool.Update(); CoroutineManager.Update(paused: false, (float)Timing.Step); - LuaCs.EventService.PublishEvent(sub => sub.OnUpdate(Timing.Step)); - LuaCs.Logger.ProcessLogs(); - performanceCounterTimer.Stop(); if (GameMain.LuaCs.PerformanceCounter.EnablePerformanceCounter) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index af94426d0..46ee0b105 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -1433,8 +1433,6 @@ namespace Barotrauma } #endif - GameMain.LuaCs.Hook.Call("character.created", new object[] { newCharacter }); - return newCharacter; } @@ -1889,7 +1887,6 @@ namespace Barotrauma } } info.Job?.GiveJobItems(this, isPvPMode, spawnPoint); - GameMain.LuaCs.Hook.Call("character.giveJobItems", this, spawnPoint, isPvPMode); } public void GiveIdCardTags(WayPoint spawnPoint, bool createNetworkEvent = false) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs index 2dbe048f5..1202d80f4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs @@ -482,7 +482,6 @@ namespace Barotrauma { GrainEffectStrength -= amount; } - GameMain.LuaCs.Hook.Call("afflictionUpdate", new object[] { this, characterHealth, targetLimb, deltaTime }); } public void ApplyStatusEffects(ActionType type, float deltaTime, CharacterHealth characterHealth, Limb targetLimb) diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs index 3a1f8d0f0..dba9f064b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs @@ -53,8 +53,6 @@ namespace Barotrauma public static void SetCore(CorePackage newCore) { SetCoreEnumerable(newCore).Consume(); - GameMain.LuaCs.EventService.PublishEvent( - sub => sub.OnEnabledPackageListChanged(Core, Regular)); } public static IEnumerable SetCoreEnumerable(CorePackage newCore) @@ -94,8 +92,6 @@ namespace Barotrauma public static void SetRegular(IReadOnlyList newRegular) { SetRegularEnumerable(newRegular).Consume(); - GameMain.LuaCs.EventService.PublishEvent( - sub => sub.OnEnabledPackageListChanged(Core, Regular)); } public static IEnumerable SetRegularEnumerable(IReadOnlyList inNewRegular) @@ -338,9 +334,6 @@ namespace Barotrauma Debug.WriteLine($"Loaded \"{newPackage.Name}\""); } - - GameMain.LuaCs.EventService.PublishEvent(sub => - sub.OnAllPackageListChanged(ContentPackageManager.CorePackages, ContentPackageManager.RegularPackages)); } private readonly string directory; @@ -580,12 +573,6 @@ namespace Barotrauma yield return p.Transform(loadingRange); } - GameMain.LuaCs.EventService.PublishEvent( - sub => sub.OnAllPackageListChanged(CorePackages, RegularPackages)); - - GameMain.LuaCs.EventService.PublishEvent( - sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); - yield return LoadProgress.Progress(1.0f); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index 8c311239a..673ac328f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -1,4 +1,5 @@ -using System; +using System.Runtime.CompilerServices; +using System; using System.Collections.Generic; using System.Reflection; using Barotrauma.LuaCs.Data; @@ -69,6 +70,69 @@ internal interface IEventSettingInstanceLifetime : IEvent +{ + void OnAfflictionUpdate(Affliction affliction, CharacterHealth characterHealth, Limb targetLimb, float deltaTime); + static IEventAfflictionUpdate IEvent.GetLuaRunner(IDictionary luaFunc) => new + { + IsLuaRunner = Return.Arguments(() => true), + OnKeyUpdate = ReturnVoid.Arguments((Affliction affliction, CharacterHealth characterHealth, Limb targetLimb, float deltaTime) => luaFunc[nameof(OnAfflictionUpdate)](affliction, characterHealth, targetLimb, deltaTime)) + }.ActLike(); +} + +internal interface IEventGiveCharacterJobItems : IEvent +{ + void OnGiveCharacterJobItems(Character character, WayPoint spawnPoint, bool isPvPMode); + static IEventGiveCharacterJobItems IEvent.GetLuaRunner(IDictionary luaFunc) => new + { + IsLuaRunner = Return.Arguments(() => true), + OnKeyUpdate = ReturnVoid.Arguments((Character character, WayPoint spawnPoint, bool isPvPMode) => luaFunc[nameof(OnGiveCharacterJobItems)](character, spawnPoint, isPvPMode)) + }.ActLike(); +} + +internal interface IEventCharacterCreated : IEvent +{ + void OnCharacterCreated(Character character); + static IEventCharacterCreated IEvent.GetLuaRunner(IDictionary luaFunc) => new + { + IsLuaRunner = Return.Arguments(() => true), + OnKeyUpdate = ReturnVoid.Arguments((Character character) => luaFunc[nameof(OnCharacterCreated)](character)) + }.ActLike(); +} + +/* +internal interface IEventHumanCPRFailed : IEvent +{ + void OnHumanCPRFailed(Character character); + static IEventHumanCPRFailed IEvent.GetLuaRunner(IDictionary luaFunc) => new + { + IsLuaRunner = Return.Arguments(() => true), + OnKeyUpdate = ReturnVoid.Arguments((Character character) => luaFunc[nameof(OnHumanCPRFailed)](character)) + }.ActLike(); +} + + +internal interface IEventHumanCPRSuccess : IEvent +{ + void OnHumanCPRSuccess(Character character); + static IEventHumanCPRSuccess IEvent.GetLuaRunner(IDictionary luaFunc) => new + { + IsLuaRunner = Return.Arguments(() => true), + OnKeyUpdate = ReturnVoid.Arguments((Character character) => luaFunc[nameof(OnHumanCPRSuccess)](character)) + }.ActLike(); +} +*/ + +public interface IEventKeyUpdate : IEvent +{ + void OnKeyUpdate(double deltaTime); + static IEventKeyUpdate IEvent.GetLuaRunner(IDictionary luaFunc) => new + { + IsLuaRunner = Return.Arguments(() => true), + OnKeyUpdate = ReturnVoid.Arguments((double deltaTime) => luaFunc[nameof(OnKeyUpdate)](deltaTime)) + }.ActLike(); +} + /// /// Called as soon as round begins to load before any loading takes place. /// @@ -121,31 +185,17 @@ public interface IEventDrawUpdate : IEvent }.ActLike(); } -#if CLIENT -public interface IEventServerRawNetMessageReceived : IEvent -{ - void OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader); -} - -public interface IEventConnectedToServer : IEvent -{ - void OnConnectedToServer(); -} - -#elif SERVER -public interface IEventClientRawNetMessageReceived : IEvent -{ - void OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader serverPacketHeader, NetworkConnection sender); -} -#endif - #endregion #region Networking - #region Networking-Server #if SERVER +public interface IEventClientRawNetMessageReceived : IEvent +{ + void OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader serverPacketHeader, NetworkConnection sender); +} + /// /// Called when a client connects to the server and has loaded into the lobby. /// @@ -163,10 +213,17 @@ interface IEventClientConnected : IEvent }.ActLike(); } #endif + #endregion #region Networking-Client #if CLIENT + +public interface IEventServerRawNetMessageReceived : IEvent +{ + void OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader); +} + /// /// Called when the client has connected to the server and loaded to the lobby. /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index db4fbbfd0..5ffb73d69 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -1,25 +1,21 @@ -using System; -using System.Collections.Concurrent; +using Barotrauma.LuaCs; +using Barotrauma.LuaCs.Compatibility; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Events; +using Barotrauma.Networking; +using FluentResults; +using ImpromptuInterface; +using LightInject; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; -using System.Net.Mime; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Barotrauma.LuaCs; -using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs.Events; -using Barotrauma.LuaCs.Compatibility; -using Barotrauma.Networking; -using Barotrauma.Steam; -using FluentResults; -using ImpromptuInterface; -using LightInject; -using Microsoft.Toolkit.Diagnostics; +using System.Runtime.CompilerServices; using AssemblyLoader = Barotrauma.LuaCs.AssemblyLoader; +[assembly: InternalsVisibleTo("ImpromptuInterfaceDynamicAssembly")] +[assembly: InternalsVisibleTo("Dynamitey")] namespace Barotrauma { internal delegate void LuaCsMessageLogger(string message); @@ -34,6 +30,9 @@ namespace Barotrauma // == startup _servicesProvider = SetupServicesProvider(); _runStateMachine = SetupStateMachine(); + + _servicesProvider.GetService(); + SubscribeToLuaCsEvents(); } @@ -186,6 +185,7 @@ namespace Barotrauma servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); // Extension/Sub Services servicesProvider.RegisterServiceType(ServiceLifetime.Transient); @@ -379,7 +379,7 @@ namespace Barotrauma // Technically not very accurate, but we want to call after we run mods anyway if (GameMain.Client != null) { - EventService.PublishEvent(static p => p.OnConnectedToServer()); + EventService.PublishEvent(static p => p.OnServerConnected()); } #endif CurrentRunState = RunState.Running; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs new file mode 100644 index 000000000..c31ac49db --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs @@ -0,0 +1,130 @@ +using Barotrauma.LuaCs; +using Barotrauma.LuaCs.Events; +using Barotrauma.Networking; +using HarmonyLib; +using Microsoft.Xna.Framework; +using System; +using static Barotrauma.ContentPackageManager; + +namespace Barotrauma.LuaCs; + +[HarmonyPatch] +internal class HarmonyEventPatchesService : IService +{ + public bool IsDisposed { get; private set; } + + private static IEventService _eventService; + private static ILoggerService _loggerService; + private readonly Harmony Harmony; + + public HarmonyEventPatchesService(IEventService eventService, ILoggerService loggerService) + { + _eventService = eventService; + _loggerService = loggerService; + Harmony = new Harmony("LuaCsForBarotrauma.Events"); + Harmony.PatchAll(typeof(HarmonyEventPatchesService)); + } + + [HarmonyPatch(typeof(CoroutineManager), nameof(CoroutineManager.Update)), HarmonyPostfix] + public static void CoroutineManager_Update_Post() + { + _eventService.PublishEvent(x => x.OnUpdate(Timing.TotalTime)); + _loggerService.ProcessLogs(); + } + + [HarmonyPatch(typeof(Screen), nameof(Screen.Select)), HarmonyPostfix] + public static void Screen_Selected_Post(Screen __instance) + { + _eventService.PublishEvent(x => x.OnScreenSelected(__instance)); + } + + [HarmonyPatch(typeof(ContentPackageManager.PackageSource), nameof(ContentPackageManager.PackageSource.Refresh)), HarmonyPostfix] + public static void PackageSource_Refresh_Post() + { + _eventService.PublishEvent(x => x.OnAllPackageListChanged(ContentPackageManager.CorePackages, ContentPackageManager.RegularPackages)); + } + + [HarmonyPatch(typeof(ContentPackageManager), nameof(ContentPackageManager.Init)), HarmonyPostfix] + public static void ContentPackageManager_Init_Post() + { + _eventService.PublishEvent(x => x.OnAllPackageListChanged(ContentPackageManager.CorePackages, ContentPackageManager.RegularPackages)); + _eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); + } + + [HarmonyPatch(typeof(ContentPackageManager.EnabledPackages), nameof(ContentPackageManager.EnabledPackages.SetCore)), HarmonyPostfix] + public static void EnabledPackages_SetCore_Post() + { + _eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); + } + + [HarmonyPatch(typeof(ContentPackageManager.EnabledPackages), nameof(ContentPackageManager.EnabledPackages.SetRegular)), HarmonyPostfix] + public static void EnabledPackages_SetRegular_Post() + { + _eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); + } + + + +#if CLIENT + [HarmonyPatch(typeof(GameClient), "ReadDataMessage"), HarmonyPrefix] + public static void GameClient_ReadDataMessage_Pre(IReadMessage inc) + { + ServerPacketHeader header = (ServerPacketHeader)inc.PeekByte(); // Read but don't advance the read pointer + _eventService.PublishEvent(x => x.OnReceivedServerNetMessage(inc, header)); + } + + [HarmonyPatch(typeof(SubEditorScreen), nameof(SubEditorScreen.Select), new Type[] { }), HarmonyPostfix] + public static void SubEditorScreen_Selected_Post(Screen __instance) + { + _eventService.PublishEvent(x => x.OnScreenSelected(__instance)); + } + + [HarmonyPatch(typeof(PlayerInput), nameof(PlayerInput.Update)), HarmonyPrefix] + public static void PlayerInput_Update_Pre(double deltaTime) + { + _eventService.PublishEvent(x => x.OnKeyUpdate(deltaTime)); + } +#elif SERVER + [HarmonyPatch(typeof(GameServer), "ReadDataMessage"), HarmonyPrefix] + public static void GameServer_ReadDataMessage_Pre(NetworkConnection sender, IReadMessage inc) + { + ClientPacketHeader header = (ClientPacketHeader)inc.PeekByte(); // Read but don't advance the read pointer + _eventService.PublishEvent(x => x.OnReceivedClientNetMessage(inc, header, sender)); + } +#endif + + [HarmonyPatch(typeof(Character), nameof(Character.Create), new[] { + typeof(CharacterPrefab), + typeof(Vector2), + typeof(string), + typeof(CharacterInfo), + typeof(ushort), + typeof(bool), + typeof(bool), + typeof(bool), + typeof(RagdollParams), + typeof(bool) + }), HarmonyPostfix] + public static void Character_Create_Post(Character __result) + { + _eventService.PublishEvent(x => x.OnCharacterCreated(__result)); + } + + [HarmonyPatch(typeof(Character), nameof(Character.GiveJobItems)), HarmonyPostfix] + public static void Character_GiveJobItems_Post(Character __instance, WayPoint spawnPoint, bool isPvPMode) + { + _eventService.PublishEvent(x => x.OnGiveCharacterJobItems(__instance, spawnPoint, isPvPMode)); + } + + [HarmonyPatch(typeof(Affliction), nameof(Affliction.Update)), HarmonyPostfix] + public static void Affliction_Update_Post(Affliction __instance, CharacterHealth characterHealth, Limb targetLimb, float deltaTime) + { + _eventService.PublishEvent(x => x.OnAfflictionUpdate(__instance, characterHealth, targetLimb, deltaTime)); + } + + public void Dispose() + { + Harmony.UnpatchSelf(); + IsDisposed = true; + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index ed67f6525..04b21c67a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -172,6 +172,11 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService private void RegisterLuaEvents() { _eventService.RegisterLuaEventAlias("think", "OnUpdate"); + _eventService.RegisterLuaEventAlias("keyUpdate", "OnKeyUpdate"); + _eventService.RegisterLuaEventAlias("character.created", "OnCharacterCreated"); + _eventService.RegisterLuaEventAlias("character.giveJobItems", "OnGiveCharacterJobItems"); + _eventService.RegisterLuaEventAlias("afflictionUpdate", "OnAfflictionUpdate"); + } private void SetupEnvironment(bool enableSandbox) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs index 8c8cac4ef..909e1f2c7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs @@ -147,7 +147,7 @@ internal partial class NetworkingService : INetworkingService private void SubscribeToEvents() { #if CLIENT - _eventService.Subscribe(this); + _eventService.Subscribe(this); _eventService.Subscribe(this); #elif SERVER _eventService.Subscribe(this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs b/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs index abe2c0f3d..622a47591 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Screens/Screen.cs @@ -43,7 +43,6 @@ GUI.SettingsMenuOpen = false; #endif Selected = this; - GameMain.LuaCs.EventService.PublishEvent(sub => sub.OnScreenSelected(this)); } public virtual Camera Cam => null; From 30149b504d327a37cebec1db30573f130af3d9b3 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 9 Feb 2026 21:32:57 -0500 Subject: [PATCH 146/288] - Added publicized assemblies to LuaCsForBarotrauma package via ModConfig.xml - Added XmlAttribute tags for ModConfig.xml defined properties. - GetType and GetImplementingTypes rework. --- .../LuaCsForBarotrauma/ModConfig.xml | 3 + .../Data/DataInterfaceImplementations.cs | 7 +- .../LuaCs/Data/IBaseInfoDefinitions.cs | 5 + .../LuaCs/Data/IConfigProfileInfo.cs | 2 +- .../SharedSource/LuaCs/Data/IDataInfo.cs | 2 + .../LuaCs/Data/IResourceInfoDeclarations.cs | 12 ++ .../LuaCs/_Plugins/AssemblyLoader.cs | 10 +- .../_Services/ModConfigFileParserService.cs | 7 +- .../LuaCs/_Services/ModConfigService.cs | 6 +- .../_Services/PluginManagementService.cs | 108 ++++++++++++------ 10 files changed, 114 insertions(+), 48 deletions(-) diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml index 363f7695b..cd06c211b 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml @@ -2,4 +2,7 @@ + + + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs index 0c0fad9cd..4a3ec7268 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Xml.Linq; +using System.Xml.Serialization; using Barotrauma.LuaCs; using Barotrauma.Steam; using OneOf; @@ -44,6 +45,7 @@ public record AssemblyResourceInfo : BaseResourceInfo, IAssemblyResourceInfo public string FriendlyName { get; init; } public bool IsScript { get; init; } public bool UseInternalAccessName { get; init; } + public bool IsReferenceModeOnly { get; init; } } /// @@ -81,9 +83,12 @@ public record ConfigInfo : IConfigInfo public record ConfigProfileInfo : IConfigProfileInfo { + /// + /// Profile name. + /// public string InternalName { get; init; } public ContentPackage OwnerPackage { get; init; } - public IReadOnlyList<(string ConfigName, XElement Element)> ProfileValues { get; init; } + public IReadOnlyList<(string SettingName, XElement Element)> ProfileValues { get; init; } } #endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs index 4f5cb032b..2326ecce2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IBaseInfoDefinitions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.Globalization; +using System.Xml.Serialization; namespace Barotrauma.LuaCs.Data; @@ -24,12 +25,14 @@ public interface IPlatformInfo /// Platforms that these localization files should be loaded for. /// [Required] + [XmlAttribute("Platform")] Platform SupportedPlatforms { get; } /// /// Targets that these localization files should be loaded for. /// [Required] + [XmlAttribute("Target")] Target SupportedTargets { get; } } @@ -44,6 +47,7 @@ public interface IResourceInfo : IPlatformInfo /// Specifies the loading order for all assets of the same type (ie. styles, assemblies, etc.) from /// the same . Lower number is higher priority, see /// + [XmlAttribute("LoadPriority")] int LoadPriority { get; } /// @@ -56,5 +60,6 @@ public interface IResourceInfo : IPlatformInfo /// Marks this resource as optional (ie. Cross-CP content). Setting this to true will allow the dependency system to /// try and order the loading but not fail if it runs into circular dependency issues. /// + [XmlAttribute("Optional")] bool Optional { get; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs index 5d5f9065d..28e9a6e1a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IConfigProfileInfo.cs @@ -5,5 +5,5 @@ namespace Barotrauma.LuaCs.Data; public interface IConfigProfileInfo : IDataInfo { - IReadOnlyList<(string ConfigName, XElement Element)> ProfileValues { get; } + IReadOnlyList<(string SettingName, XElement Element)> ProfileValues { get; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IDataInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IDataInfo.cs index d49be249b..652ddd967 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IDataInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IDataInfo.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Xml.Serialization; namespace Barotrauma.LuaCs.Data; @@ -11,6 +12,7 @@ public interface IDataInfo : IEqualityComparer, IEquatable /// /// Internal name unique within the resources inside a package. /// + [XmlAttribute("Name")] string InternalName { get; } /// /// The package this information belongs to. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs index f40850cfa..32d17a16e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IResourceInfoDeclarations.cs @@ -2,6 +2,7 @@ using System.Collections.Immutable; using System.Globalization; using System.Runtime.CompilerServices; +using System.Xml.Serialization; namespace Barotrauma.LuaCs.Data; @@ -18,6 +19,7 @@ public interface ILuaScriptResourceInfo : IBaseResourceInfo /// /// Should this script be run automatically. /// + [XmlAttribute("IsAutorun")] public bool IsAutorun { get; } } @@ -27,17 +29,27 @@ public interface IAssemblyResourceInfo : IBaseResourceInfo /// The friendly name of the assembly. Script files belonging to the same assembly should all have the same name. /// Legacy scripts will all be given the sanitized name of the Content Package they belong to. /// + [XmlAttribute("FriendlyName")] public string FriendlyName { get; } /// /// Is this entry referring to a script file collection. /// + [XmlAttribute("IsScript")] public bool IsScript { get; } /// /// [Required(IsScript: true)] Whether the internal compiled assembly name should be named to enabled use of the /// attribute. /// + [XmlAttribute("UseInternalAccessName")] public bool UseInternalAccessName { get; } + + /// + /// Should the following resources only be used for Compilation MetadataReference. + /// NOTE: Affects the entire package's assembly resources, meant for internal use only. + /// + [XmlAttribute("IsReferenceModeOnly")] + public bool IsReferenceModeOnly { get; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs index 6a323c12e..bd82efe1f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs @@ -208,6 +208,10 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService AreOperationRunning = true; foreach (var data in _loadedAssemblyData.Values) { + if (data.AssemblyReference is null) + { + continue; + } yield return data.AssemblyReference; } AreOperationRunning = false; @@ -356,10 +360,10 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService if (additionalDependencyPaths.Any()) { var r = AddDependencyPaths(additionalDependencyPaths); - if (!r.IsFailed) + if (r.IsFailed) { // we have errors, loading may not work. - return FluentResults.Result.Fail(new Error($"Failed to load dependency paths.") + return FluentResults.Result.Fail(new Error($"Failed to load dependency paths for '{assemblyFilePath}' with paths: {additionalDependencyPaths.Aggregate((s, ac) => $"{ac}| P={s}")}.") .WithMetadata(MetadataType.ExceptionObject, this) .WithMetadata(MetadataType.RootObject, assemblyFilePath)) .WithErrors(r.Errors); @@ -379,7 +383,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService try { var assembly = LoadFromAssemblyPath(sanitizedFilePath); - _loadedAssemblyData[assembly] = new AssemblyData(assembly, sanitizedFilePath); + _loadedAssemblyData[assembly] = new AssemblyData(assembly, assembly.Location); return new Result().WithSuccess($"Loaded assembly '{assembly.GetName()}'").WithValue(assembly); } catch (FileNotFoundException fnfe) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs index 1a6f63933..33569f85b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs @@ -79,7 +79,8 @@ public sealed class ModConfigFileParserService : // Type Specific FriendlyName = src.Element.GetAttributeString("FriendlyName", string.Empty), IsScript = src.Element.GetAttributeBool("IsScript", false), - UseInternalAccessName = src.Element.GetAttributeBool("UseInternalAccessName", false) + UseInternalAccessName = src.Element.GetAttributeBool("UseInternalAccessName", false), + IsReferenceModeOnly = src.Element.GetAttributeBool("IsReferenceModeOnly", false) }; } @@ -211,8 +212,8 @@ public sealed class ModConfigFileParserService : private (Platform Platform, Target Target) GetRuntimeEnvironment(XElement element) { return ( - Platform: element.GetAttributeEnum("Platform", Platform.Windows | Platform.Linux | Platform.OSX), - Target: element.GetAttributeEnum("Target", Target.Client | Target.Server)); + Platform: element.GetAttributeEnum("Platform", Platform.Any), + Target: element.GetAttributeEnum("Target", Target.Any)); } private async Task>> TryParseGenericResourcesAsync(IEnumerable sources) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs index cd3ea8085..e4bc8897a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs @@ -264,7 +264,8 @@ public sealed class ModConfigService : IModConfigService FriendlyName = $"{src.Name}.{searchPathways.SubFolder.Replace('/','.')}", IncompatiblePackages = ImmutableArray.Empty, RequiredPackages = ImmutableArray.Empty, - IsScript = false + IsScript = false, + IsReferenceModeOnly = false }); } } @@ -304,7 +305,8 @@ public sealed class ModConfigService : IModConfigService IncompatiblePackages = ImmutableArray.Empty, RequiredPackages = ImmutableArray.Empty, UseInternalAccessName = true, - IsScript = true + IsScript = true, + IsReferenceModeOnly = false }); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index c020949da..ecac7daaf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -86,6 +86,29 @@ public class PluginManagementService : IAssemblyManagementService .ToString(), ScriptParseOptions); + private ImmutableArray _baseMetadataReferences = ImmutableArray.Empty; + private IEnumerable BaseMetadataReferences + { + get + { + if (_baseMetadataReferences.IsDefaultOrEmpty) + { + _baseMetadataReferences = Basic.Reference.Assemblies.Net80.References.All + .Union(AssemblyLoadContext.Default.Assemblies + .Where(ass => + !ass.IsDynamic && + !ass.GetName().FullName.EndsWith("Barotrauma.Core") && + !ass.GetName().FullName.EndsWith("Barotrauma") && + !ass.GetName().FullName.EndsWith("DedicatedServer")) + .Select(MetadataReference (ass) => MetadataReference.CreateFromFile(ass.Location))) + .Where(ar => ar is not null) + .ToImmutableArray(); + } + + return _baseMetadataReferences; + } + } + #endregion #region Disposal @@ -213,14 +236,35 @@ public class PluginManagementService : IAssemblyManagementService public Result> GetImplementingTypes(bool includeInterfaces = false, bool includeAbstractTypes = false, bool includeDefaultContext = true) { -#if !DEBUG - throw new NotImplementedException(); -#endif + if (includeInterfaces) + { + includeAbstractTypes = true; + } + + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + var builder = ImmutableArray.CreateBuilder(); - foreach (var ass in AppDomain.CurrentDomain.GetAssemblies()) + if (includeDefaultContext) { - foreach (var type in ass.GetSafeTypes()) + foreach (var ass in AssemblyLoadContext.Default.Assemblies) + { + AddTypesFromAssembly(ass); + } + } + + foreach (var ass in _assemblyLoaders.Values.Where(al => !al.IsReferenceOnlyMode).SelectMany(al => al.Assemblies)) + { + AddTypesFromAssembly(ass); + } + + return builder.ToImmutable(); + + + void AddTypesFromAssembly(Assembly assembly) + { + foreach (var type in assembly.GetSafeTypes()) { if ((includeInterfaces || !type.IsInterface) && (includeAbstractTypes || !type.IsAbstract) @@ -230,8 +274,6 @@ public class PluginManagementService : IAssemblyManagementService } } } - - return builder.ToImmutable(); } public Type GetType(string typeName, bool isByRefType = false, bool includeInterfaces = false, @@ -255,9 +297,21 @@ public class PluginManagementService : IAssemblyManagementService return type; } + + foreach (var ass in AssemblyLoadContext.Default.Assemblies) + { + if (ass.GetType(typeName, false, false) is not {} type2 || (!includeInterfaces && type2.IsInterface)) + { + continue; + } + + return isByRefType ? type2.MakeByRefType() : type2; + } } - foreach (var ass in AssemblyLoadContext.All.SelectMany(alc => alc.Assemblies)) + foreach (var ass in AssemblyLoadContext.All + .Where(alc => alc != AssemblyLoadContext.Default) + .SelectMany(alc => alc.Assemblies)) { if (ass.GetType(typeName, false, false) is not {} type || (!includeInterfaces && type.IsInterface)) { @@ -426,7 +480,7 @@ public class PluginManagementService : IAssemblyManagementService new IAssemblyLoaderService.LoaderInitData( InstanceId: Guid.NewGuid(), contentPackRes.Key.Name, - IsReferenceMode: false, + IsReferenceMode: contentPackRes.Any(r => r.IsReferenceModeOnly), OwnerPackage: contentPackRes.Key, OnUnload: OnAssemblyLoaderUnloading, OnResolvingManaged: OnAssemblyLoaderResolvingManaged, @@ -465,7 +519,7 @@ public class PluginManagementService : IAssemblyManagementService new IAssemblyLoaderService.LoaderInitData( InstanceId: Guid.NewGuid(), contentPackRes.Key.Name, - IsReferenceMode: false, + IsReferenceMode: contentPackRes.Any(r => r.IsReferenceModeOnly), OwnerPackage: contentPackRes.Key, OnUnload: OnAssemblyLoaderUnloading, OnResolvingManaged: OnAssemblyLoaderResolvingManaged, @@ -550,35 +604,13 @@ public class PluginManagementService : IAssemblyManagementService IEnumerable GetMetadataReferences() { -#if !DEBUG - throw new NotImplementedException($"Needs to use publicized barotrauma assemblies and cache metadata."); -#endif - var publicizedDir = Path.Combine(Directory.GetCurrentDirectory(), "Publicized"); - - string[] publicizedAssemblies = + var builder = ImmutableArray.CreateBuilder(); + builder.AddRange(BaseMetadataReferences); + foreach (var loaderService in _assemblyLoaders) { -#if CLIENT - "Barotrauma", -#elif SERVER - "DedicatedServer", -#endif - "BarotraumaCore" - }; - - var publicizedRefs = publicizedAssemblies - .Select(name => Path.Combine(publicizedDir, $"{name}.dll")) - .Where(File.Exists) - .Select(path => MetadataReference.CreateFromFile(path)); - - var runtimeRefs = AppDomain.CurrentDomain.GetAssemblies() - .Where(ass => - !string.IsNullOrWhiteSpace(ass.Location) && - !publicizedAssemblies.Contains(ass.GetName().Name)) - .Select(ass => MetadataReference.CreateFromFile(ass.Location)); - - return Basic.Reference.Assemblies.Net80.References.All - .Union(runtimeRefs) - .Union(publicizedRefs); + builder.AddRange(loaderService.Value.AssemblyReferences.Where(ar => ar is not null)); + } + return builder.ToImmutable(); } } From 6b9e48f96ac015d5919e2625d7755024ca2bb52c Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 10 Feb 2026 14:28:22 -0500 Subject: [PATCH 147/288] - Added ISystem (automatically run services) service type. --- .../SharedSource/LuaCs/LuaCsSetup.cs | 2 +- .../LuaCs/_Services/ServicesProvider.cs | 61 ++++++++++++++++--- .../LuaCs/_Services/_Interfaces/IService.cs | 6 ++ .../_Interfaces/IServicesProvider.cs | 36 +++++------ 4 files changed, 74 insertions(+), 31 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 5ffb73d69..7aefff43f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -215,7 +215,7 @@ namespace Barotrauma servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); // gen IL - servicesProvider.Compile(); + servicesProvider.CompileAndRun(); return servicesProvider; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ServicesProvider.cs index e9e06fe44..9452de187 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ServicesProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ServicesProvider.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; @@ -12,7 +15,14 @@ public class ServicesProvider : IServicesProvider { private ServiceContainer _serviceContainerInst; private ServiceContainer ServiceContainer => _serviceContainerInst; - + /// + /// Definition: [Key: InterfaceType, Value: ConcreteTypes] + /// + private readonly ConcurrentDictionary> _systemTypeDefs = new(); + /// + /// Definition: [Key: ConcreteType, Value: TypeInstance] + /// + private readonly ConcurrentDictionary _systemInstances = new(); private readonly ReaderWriterLockSlim _serviceLock = new(); public ServicesProvider() @@ -27,6 +37,13 @@ public class ServicesProvider : IServicesProvider public void RegisterServiceType(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface { + // ISystem services must run as a lifetime singleton + if (typeof(TSvcInterface).IsAssignableTo(typeof(ISystem))) + { + lifetimeInstance = new PerContainerLifetime(); + _systemTypeDefs.GetOrAdd(typeof(TSvcInterface), (type) => new ConcurrentBag()); + } + if (lifetimeInstance is null) { switch (lifetime) @@ -54,7 +71,6 @@ public class ServicesProvider : IServicesProvider ServiceContainer.Register(lifetimeInstance); else ServiceContainer.Register(); - OnServiceRegistered?.Invoke(typeof(TSvcInterface), typeof(TService)); } finally { @@ -70,6 +86,13 @@ public class ServicesProvider : IServicesProvider throw new ArgumentNullException($"Tried to register a service of type {typeof(TService).Name} but the name provided is null or empty." ); } + // ISystem services must run as a lifetime singleton + if (typeof(TSvcInterface).IsAssignableTo(typeof(ISystem))) + { + lifetimeInstance = new PerContainerLifetime(); + _systemTypeDefs.GetOrAdd(typeof(TSvcInterface), (type) => new ConcurrentBag()); + } + if (lifetimeInstance is null) { switch (lifetime) @@ -94,7 +117,6 @@ public class ServicesProvider : IServicesProvider { _serviceLock.EnterReadLock(); ServiceContainer.Register(name, lifetimeInstance); - OnServiceRegistered?.Invoke(typeof(TSvcInterface), typeof(TService)); } finally { @@ -115,20 +137,26 @@ public class ServicesProvider : IServicesProvider } } - public void Compile() + public void CompileAndRun() { try { - _serviceLock.EnterReadLock(); + _serviceLock.EnterWriteLock(); ServiceContainer?.Compile(); + foreach (var typeDef in _systemTypeDefs.Values.SelectMany(type => type)) + { + if (_systemInstances.ContainsKey(typeDef)) + { + continue; + } + _systemInstances[typeDef] = (ISystem)ServiceContainer?.TryGetInstance(typeDef); + } } finally { - _serviceLock.ExitReadLock(); + _serviceLock.ExitWriteLock(); } } - - public event Action OnServiceRegistered; public void InjectServices(T inst) where T : class { @@ -209,19 +237,32 @@ public class ServicesProvider : IServicesProvider } } - [MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.NoInlining)] + [MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.Synchronized)] public void DisposeAndReset() { // Plugins should never be allowed to execute this. if (Assembly.GetCallingAssembly() != Assembly.GetExecutingAssembly()) { throw new MethodAccessException( - $"Assembly {Assembly.GetCallingAssembly().FullName} attempted to call DisposeAllServices()."); + $"Assembly {Assembly.GetCallingAssembly().FullName} attempted to call {nameof(DisposeAndReset)}()."); } try { _serviceLock.EnterWriteLock(); + foreach (var system in _systemInstances.Values) + { + try + { + system.Dispose(); + } + catch (Exception e) + { + // ignored, no logging services available. + } + } + _systemInstances.Clear(); + _systemTypeDefs.Clear(); _serviceContainerInst?.Dispose(); _serviceContainerInst = new ServiceContainer(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IService.cs index 27abce8e2..78fd7c8ba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IService.cs @@ -3,6 +3,12 @@ using Microsoft.Toolkit.Diagnostics; namespace Barotrauma.LuaCs; +/// +/// Represents a that is automatically instantiated at startup for the lifetime of the +/// instance. +/// +public interface ISystem : IReusableService { } + /// /// Defines a service that can be reset to it's post-constructor state and reused without needing to be disposed. /// Intended for persistent services. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IServicesProvider.cs index 847540737..29ad12045 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IServicesProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IServicesProvider.cs @@ -6,7 +6,8 @@ using LightInject; namespace Barotrauma.LuaCs; /// -/// Provides instancing and management of IServices. +/// Provides instancing and management of , , and +/// instances. /// public interface IServicesProvider { @@ -15,28 +16,23 @@ public interface IServicesProvider /// /// Registers a type as a service for a given interface. /// - /// - /// - /// - /// + /// NOTE: services are forced to + /// The of the service when requested. + /// Custom lifetime instance. + /// Service interface. + /// Implementing service type. void RegisterServiceType(ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface; /// /// Registers a type as a service for a given interface that can be requested by name. /// - /// - /// - /// - /// - /// + /// NOTE: services are forced to + /// Name of the service for lookup. + /// The of the service when requested. + /// Custom lifetime instance. + /// Service interface. + /// Implementing service type. void RegisterServiceType(string name, ServiceLifetime lifetime, ILifetime lifetimeInstance = null) where TSvcInterface : class, IService where TService : class, IService, TSvcInterface; - - /// - /// Called whenever a new service type for a given interface is implemented. - /// Args[0]: Interface type - /// Args[1]: Implementing type - /// - event System.Action OnServiceRegistered; /// /// Registers a factory for resolving the service type. @@ -46,14 +42,14 @@ public interface IServicesProvider void RegisterServiceResolver(Func factory) where TSvcInterface : class, IService; /// - /// Runs compilation of registered services. + /// Compiles/Generates IL for registered services and instantiates all registered types. /// - public void Compile(); + public void CompileAndRun(); #endregion #region Services_Instancing_Injection - + /// /// Injects services into the properties of already instanced objects. /// From 9dde5cac7d88442cb7b583ae8c88113abcfcac21 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 10 Feb 2026 15:11:46 -0500 Subject: [PATCH 148/288] Fixed Evil's obsession with OnKeyUpdate. --- .../BarotraumaShared/SharedSource/LuaCs/IEvents.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index 673ac328f..85d670054 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -76,7 +76,7 @@ internal interface IEventAfflictionUpdate : IEvent static IEventAfflictionUpdate IEvent.GetLuaRunner(IDictionary luaFunc) => new { IsLuaRunner = Return.Arguments(() => true), - OnKeyUpdate = ReturnVoid.Arguments((Affliction affliction, CharacterHealth characterHealth, Limb targetLimb, float deltaTime) => luaFunc[nameof(OnAfflictionUpdate)](affliction, characterHealth, targetLimb, deltaTime)) + OnAfflictionUpdate = ReturnVoid.Arguments((Affliction affliction, CharacterHealth characterHealth, Limb targetLimb, float deltaTime) => luaFunc[nameof(OnAfflictionUpdate)](affliction, characterHealth, targetLimb, deltaTime)) }.ActLike(); } @@ -86,7 +86,7 @@ internal interface IEventGiveCharacterJobItems : IEvent.GetLuaRunner(IDictionary luaFunc) => new { IsLuaRunner = Return.Arguments(() => true), - OnKeyUpdate = ReturnVoid.Arguments((Character character, WayPoint spawnPoint, bool isPvPMode) => luaFunc[nameof(OnGiveCharacterJobItems)](character, spawnPoint, isPvPMode)) + OnGiveCharacterJobItems = ReturnVoid.Arguments((Character character, WayPoint spawnPoint, bool isPvPMode) => luaFunc[nameof(OnGiveCharacterJobItems)](character, spawnPoint, isPvPMode)) }.ActLike(); } @@ -96,7 +96,7 @@ internal interface IEventCharacterCreated : IEvent static IEventCharacterCreated IEvent.GetLuaRunner(IDictionary luaFunc) => new { IsLuaRunner = Return.Arguments(() => true), - OnKeyUpdate = ReturnVoid.Arguments((Character character) => luaFunc[nameof(OnCharacterCreated)](character)) + OnCharacterCreated = ReturnVoid.Arguments((Character character) => luaFunc[nameof(OnCharacterCreated)](character)) }.ActLike(); } @@ -107,7 +107,7 @@ internal interface IEventHumanCPRFailed : IEvent static IEventHumanCPRFailed IEvent.GetLuaRunner(IDictionary luaFunc) => new { IsLuaRunner = Return.Arguments(() => true), - OnKeyUpdate = ReturnVoid.Arguments((Character character) => luaFunc[nameof(OnHumanCPRFailed)](character)) + OnHumanCPRFailed = ReturnVoid.Arguments((Character character) => luaFunc[nameof(OnHumanCPRFailed)](character)) }.ActLike(); } @@ -118,7 +118,7 @@ internal interface IEventHumanCPRSuccess : IEvent static IEventHumanCPRSuccess IEvent.GetLuaRunner(IDictionary luaFunc) => new { IsLuaRunner = Return.Arguments(() => true), - OnKeyUpdate = ReturnVoid.Arguments((Character character) => luaFunc[nameof(OnHumanCPRSuccess)](character)) + OnHumanCPRSuccess = ReturnVoid.Arguments((Character character) => luaFunc[nameof(OnHumanCPRSuccess)](character)) }.ActLike(); } */ From 6bbe5be5e6d613b82526b36037ba637a5e3b272b Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:26:46 -0300 Subject: [PATCH 149/288] Fix networking being completely bamboozled --- .../BarotraumaClient/ClientSource/Networking/GameClient.cs | 2 -- .../BarotraumaServer/ServerSource/Networking/GameServer.cs | 2 -- .../LuaCs/_Services/HarmonyEventPatchesService.cs | 6 ++++-- .../SharedSource/LuaCs/_Services/NetworkingService.cs | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index d5d195ce8..98d00c624 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -604,8 +604,6 @@ namespace Barotrauma.Networking { ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte(); - GameMain.LuaCs.EventService.PublishEvent(p => p.OnReceivedServerNetMessage(inc, header)); - if (roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize && header is not ( ServerPacketHeader.STARTGAMEFINALIZE diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index a7b5dd02b..eb971f54b 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -838,8 +838,6 @@ namespace Barotrauma.Networking ClientPacketHeader header = (ClientPacketHeader)inc.ReadByte(); - GameMain.LuaCs.EventService.PublishEvent(p => p.OnReceivedClientNetMessage(inc, header, sender)); - switch (header) { case ClientPacketHeader.PING_RESPONSE: diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs index c31ac49db..22186a47e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs @@ -69,8 +69,9 @@ internal class HarmonyEventPatchesService : IService [HarmonyPatch(typeof(GameClient), "ReadDataMessage"), HarmonyPrefix] public static void GameClient_ReadDataMessage_Pre(IReadMessage inc) { - ServerPacketHeader header = (ServerPacketHeader)inc.PeekByte(); // Read but don't advance the read pointer + ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte(); _eventService.PublishEvent(x => x.OnReceivedServerNetMessage(inc, header)); + inc.BitPosition -= 8; // rewind so the game can read the message } [HarmonyPatch(typeof(SubEditorScreen), nameof(SubEditorScreen.Select), new Type[] { }), HarmonyPostfix] @@ -88,8 +89,9 @@ internal class HarmonyEventPatchesService : IService [HarmonyPatch(typeof(GameServer), "ReadDataMessage"), HarmonyPrefix] public static void GameServer_ReadDataMessage_Pre(NetworkConnection sender, IReadMessage inc) { - ClientPacketHeader header = (ClientPacketHeader)inc.PeekByte(); // Read but don't advance the read pointer + ClientPacketHeader header = (ClientPacketHeader)inc.ReadByte(); _eventService.PublishEvent(x => x.OnReceivedClientNetMessage(inc, header, sender)); + inc.BitPosition -= 8; // rewind so the game can read the message } #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs index 909e1f2c7..afa9c7e7c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs @@ -224,7 +224,7 @@ internal partial class NetworkingService : INetworkingService #if CLIENT SendToServer(message); #elif SERVER - SendToClient(message); + SendToClient(message, connection); #endif } From 9e2fc13460f8e18e0da54c89e65921c82d46a341 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 10 Feb 2026 18:48:31 -0500 Subject: [PATCH 150/288] - Fixed the server not loading saved convars and not having absolute authority. --- .../LuaCs/Data/SettingsFactoryRegistrationProvider.cs | 5 +++++ Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs | 1 + 2 files changed, 6 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs index 61a561fd2..2c79a6b78 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs @@ -63,10 +63,15 @@ public class SettingsEntryRegistrar : ISettingsRegistrationProvider private bool IsValueChangeAllowed(IConfigInfo info, OneOf newValue, Func, bool> valueChangePredicate) { +#if CLIENT return !info.Element.GetAttributeBool("ReadOnly", false) || info.EditableStates < _infoProvider.CurrentRunState || valueChangePredicate is null || valueChangePredicate.Invoke(newValue); +#else + // Server has absolute authority. + return true; +#endif } public void Dispose() diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 7aefff43f..3ebfdd402 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -367,6 +367,7 @@ namespace Barotrauma registrationProvider.RegisterTypeProviders(ConfigService, null); } Logger.LogResults(PackageManagementService.LoadPackagesInfo(GetEnabledPackagesList())); + Logger.LogResults(ConfigService.LoadSavedConfigsValues()); LoadLuaCsConfig(); } From 5e33a908d2e54a83d8ab69014e8be499b1577ccf Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 10 Feb 2026 19:15:23 -0500 Subject: [PATCH 151/288] Removed NIException. --- .../SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs index 7e2a8c631..3c566aa3f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs @@ -272,11 +272,6 @@ namespace Barotrauma.LuaCs public LuaGame() { -#if DEBUG - return; // startup testing -#endif - - throw new NotImplementedException(); /*LuaUserData.MakeFieldAccessible(UserData.RegisterType(typeof(GameSettings)), "currentConfig"); Settings = UserData.CreateStatic(typeof(GameSettings));*/ } From 948b7c48c5733460d4537458e280de8b2a0080e7 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 11 Feb 2026 12:57:49 -0500 Subject: [PATCH 152/288] -- MonoMod.Hook replacement. --- .../ContentPackageManager.cs | 1 - .../SharedSource/LuaCs/LuaCsSetup.cs | 19 +- .../LuaCs/_Services/EventPatchingService.cs | 191 ++++++++++++++++++ .../_Services/HarmonyEventPatchesService.cs | 132 ------------ 4 files changed, 206 insertions(+), 137 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventPatchingService.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs index dba9f064b..03f2214d8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs @@ -9,7 +9,6 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Xml.Linq; using Barotrauma.IO; -using Barotrauma.LuaCs.Events; using Barotrauma.Steam; using Microsoft.Xna.Framework; using OneOf.Types; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 3ebfdd402..a6a9ecb07 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -30,10 +30,21 @@ namespace Barotrauma // == startup _servicesProvider = SetupServicesProvider(); _runStateMachine = SetupStateMachine(); - - _servicesProvider.GetService(); - + _servicesProvider.GetService(); SubscribeToLuaCsEvents(); + PatchEventMethods(); + } + + private void PatchEventMethods() + { + if (_servicesProvider.TryGetService(out var svc)) + { + svc.GenerateMethodHooks(); + Logger.LogDebug($"Patched methods."); + return; + } + + Logger.LogError($"Failed to find {nameof(EventPatchingService)}"); } private void SubscribeToLuaCsEvents() @@ -185,7 +196,7 @@ namespace Barotrauma servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); - servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); // Extension/Sub Services servicesProvider.RegisterServiceType(ServiceLifetime.Transient); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventPatchingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventPatchingService.cs new file mode 100644 index 000000000..e8bd20593 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventPatchingService.cs @@ -0,0 +1,191 @@ +using Barotrauma.LuaCs; +using Barotrauma.LuaCs.Events; +using Barotrauma.Networking; +using HarmonyLib; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Reflection; +using MonoMod.RuntimeDetour; +using static Barotrauma.ContentPackageManager; + +namespace Barotrauma.LuaCs; + + +internal class EventPatchingService : IService +{ + public bool IsDisposed { get; private set; } + private static EventPatchingService _instance; + private IEventService _eventService; + private ILoggerService _loggerService; + /// + /// Key: Original Method, Value: Active Hook. + /// + private readonly ConcurrentDictionary _runtimeHooks = new(); + + #region METHODINFO + + private static readonly MethodInfo Screen_Select_Orig = + typeof(Screen).GetMethod(nameof(Screen.Select), BindingFlags.Instance | BindingFlags.Public); + + private static readonly MethodInfo EnabledPackages_SetCore_Orig = + typeof(ContentPackageManager.EnabledPackages).GetMethod(nameof(ContentPackageManager.EnabledPackages.SetCore), + BindingFlags.Static | BindingFlags.Public); + + private static readonly MethodInfo EnabledPackages_SetRegular_Orig = + typeof(ContentPackageManager.EnabledPackages).GetMethod(nameof(ContentPackageManager.EnabledPackages.SetRegular), + BindingFlags.Static | BindingFlags.Public); + + private static readonly MethodInfo Character_Create_Orig = + AccessTools.DeclaredMethod(typeof(Character), + nameof(Character.Create), new [] + { + typeof(CharacterPrefab), + typeof(Vector2), + typeof(string), + typeof(CharacterInfo), + typeof(ushort), + typeof(bool), + typeof(bool), + typeof(bool), + typeof(RagdollParams), + typeof(bool) + }); + + + #endregion + + public EventPatchingService(IEventService eventService, ILoggerService loggerService) + { + _eventService = eventService; + _loggerService = loggerService; + _instance = this; + } + + public void GenerateMethodHooks() + { + IService.CheckDisposed(this); + + if (!_runtimeHooks.IsEmpty) + { + _loggerService.LogError($"{nameof(GenerateMethodHooks)}: Hooks are already active!"); + return; + } + + _runtimeHooks.TryAdd(Screen_Select_Orig, new Hook( + Screen_Select_Orig, + this.Screen_Select_Post + )); + + _runtimeHooks.TryAdd(EnabledPackages_SetCore_Orig, new Hook( + EnabledPackages_SetCore_Orig, + typeof(EventPatchingService).GetMethod(nameof(EnabledPackages_SetCore_Post)) + )); + + _runtimeHooks.TryAdd(EnabledPackages_SetRegular_Orig, new Hook( + EnabledPackages_SetRegular_Orig, + typeof(EventPatchingService).GetMethod(nameof(EnabledPackages_SetRegular_Post)) + )); + + _runtimeHooks.TryAdd(Character_Create_Orig, new Hook( + Character_Create_Orig, + this.Character_Create_Post + )); + } + + public void CoroutineManager_Update_Post() + { + _eventService.PublishEvent(x => x.OnUpdate(Timing.TotalTime)); + _loggerService.ProcessLogs(); + } + + public void Screen_Select_Post(Action orig, Screen src) + { + orig(src); + _eventService.PublishEvent(x => x.OnScreenSelected(Screen.Selected)); + } + + public void PackageSource_Refresh_Post() + { + _eventService.PublishEvent(x => x.OnAllPackageListChanged(ContentPackageManager.CorePackages, ContentPackageManager.RegularPackages)); + } + + public void ContentPackageManager_Init_Post() + { + _eventService.PublishEvent(x => x.OnAllPackageListChanged(ContentPackageManager.CorePackages, ContentPackageManager.RegularPackages)); + _eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); + } + + public static void EnabledPackages_SetCore_Post(Action orig, CorePackage newCore) + { + orig(newCore); + _instance?._eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); + } + + public static void EnabledPackages_SetRegular_Post(Action> orig, IReadOnlyList newRegular) + { + orig(newRegular); + _instance?._eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); + } + +#if CLIENT + public void GameClient_ReadDataMessage_Pre(IReadMessage inc) + { + ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte(); + _eventService.PublishEvent(x => x.OnReceivedServerNetMessage(inc, header)); + inc.BitPosition -= 8; // rewind so the game can read the message + } + + public void SubEditorScreen_Selected_Post(Screen __instance) + { + _eventService.PublishEvent(x => x.OnScreenSelected(__instance)); + } + + public void PlayerInput_Update_Pre(double deltaTime) + { + _eventService.PublishEvent(x => x.OnKeyUpdate(deltaTime)); + } +#elif SERVER + public void GameServer_ReadDataMessage_Pre(NetworkConnection sender, IReadMessage inc) + { + ClientPacketHeader header = (ClientPacketHeader)inc.ReadByte(); + _eventService.PublishEvent(x => x.OnReceivedClientNetMessage(inc, header, sender)); + inc.BitPosition -= 8; // rewind so the game can read the message + } +#endif + + // Character.Create(), Line 1411. + public Character Character_Create_Post(Func orig, + CharacterPrefab prefab, Vector2 position, string seed, + CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, + bool isRemotePlayer = false, bool hasAi = true, bool createNetworkEvent = true, + RagdollParams ragdoll = null, bool spawnInitialItems = true) + { + Character result = orig(prefab, position, seed, characterInfo, id, isRemotePlayer, hasAi, createNetworkEvent, + ragdoll, spawnInitialItems); + _eventService.PublishEvent(x => x.OnCharacterCreated(result)); + return result; + } + + public void Character_GiveJobItems_Post(Character __instance, WayPoint spawnPoint, bool isPvPMode) + { + _eventService.PublishEvent(x => x.OnGiveCharacterJobItems(__instance, spawnPoint, isPvPMode)); + } + + public void Affliction_Update_Post(Affliction __instance, CharacterHealth characterHealth, Limb targetLimb, float deltaTime) + { + _eventService.PublishEvent(x => x.OnAfflictionUpdate(__instance, characterHealth, targetLimb, deltaTime)); + } + + public void Dispose() + { + IsDisposed = true; + foreach (var runtimeHook in _runtimeHooks) + { + runtimeHook.Value.Dispose(); + } + _runtimeHooks.Clear(); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs deleted file mode 100644 index 22186a47e..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs +++ /dev/null @@ -1,132 +0,0 @@ -using Barotrauma.LuaCs; -using Barotrauma.LuaCs.Events; -using Barotrauma.Networking; -using HarmonyLib; -using Microsoft.Xna.Framework; -using System; -using static Barotrauma.ContentPackageManager; - -namespace Barotrauma.LuaCs; - -[HarmonyPatch] -internal class HarmonyEventPatchesService : IService -{ - public bool IsDisposed { get; private set; } - - private static IEventService _eventService; - private static ILoggerService _loggerService; - private readonly Harmony Harmony; - - public HarmonyEventPatchesService(IEventService eventService, ILoggerService loggerService) - { - _eventService = eventService; - _loggerService = loggerService; - Harmony = new Harmony("LuaCsForBarotrauma.Events"); - Harmony.PatchAll(typeof(HarmonyEventPatchesService)); - } - - [HarmonyPatch(typeof(CoroutineManager), nameof(CoroutineManager.Update)), HarmonyPostfix] - public static void CoroutineManager_Update_Post() - { - _eventService.PublishEvent(x => x.OnUpdate(Timing.TotalTime)); - _loggerService.ProcessLogs(); - } - - [HarmonyPatch(typeof(Screen), nameof(Screen.Select)), HarmonyPostfix] - public static void Screen_Selected_Post(Screen __instance) - { - _eventService.PublishEvent(x => x.OnScreenSelected(__instance)); - } - - [HarmonyPatch(typeof(ContentPackageManager.PackageSource), nameof(ContentPackageManager.PackageSource.Refresh)), HarmonyPostfix] - public static void PackageSource_Refresh_Post() - { - _eventService.PublishEvent(x => x.OnAllPackageListChanged(ContentPackageManager.CorePackages, ContentPackageManager.RegularPackages)); - } - - [HarmonyPatch(typeof(ContentPackageManager), nameof(ContentPackageManager.Init)), HarmonyPostfix] - public static void ContentPackageManager_Init_Post() - { - _eventService.PublishEvent(x => x.OnAllPackageListChanged(ContentPackageManager.CorePackages, ContentPackageManager.RegularPackages)); - _eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); - } - - [HarmonyPatch(typeof(ContentPackageManager.EnabledPackages), nameof(ContentPackageManager.EnabledPackages.SetCore)), HarmonyPostfix] - public static void EnabledPackages_SetCore_Post() - { - _eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); - } - - [HarmonyPatch(typeof(ContentPackageManager.EnabledPackages), nameof(ContentPackageManager.EnabledPackages.SetRegular)), HarmonyPostfix] - public static void EnabledPackages_SetRegular_Post() - { - _eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); - } - - - -#if CLIENT - [HarmonyPatch(typeof(GameClient), "ReadDataMessage"), HarmonyPrefix] - public static void GameClient_ReadDataMessage_Pre(IReadMessage inc) - { - ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte(); - _eventService.PublishEvent(x => x.OnReceivedServerNetMessage(inc, header)); - inc.BitPosition -= 8; // rewind so the game can read the message - } - - [HarmonyPatch(typeof(SubEditorScreen), nameof(SubEditorScreen.Select), new Type[] { }), HarmonyPostfix] - public static void SubEditorScreen_Selected_Post(Screen __instance) - { - _eventService.PublishEvent(x => x.OnScreenSelected(__instance)); - } - - [HarmonyPatch(typeof(PlayerInput), nameof(PlayerInput.Update)), HarmonyPrefix] - public static void PlayerInput_Update_Pre(double deltaTime) - { - _eventService.PublishEvent(x => x.OnKeyUpdate(deltaTime)); - } -#elif SERVER - [HarmonyPatch(typeof(GameServer), "ReadDataMessage"), HarmonyPrefix] - public static void GameServer_ReadDataMessage_Pre(NetworkConnection sender, IReadMessage inc) - { - ClientPacketHeader header = (ClientPacketHeader)inc.ReadByte(); - _eventService.PublishEvent(x => x.OnReceivedClientNetMessage(inc, header, sender)); - inc.BitPosition -= 8; // rewind so the game can read the message - } -#endif - - [HarmonyPatch(typeof(Character), nameof(Character.Create), new[] { - typeof(CharacterPrefab), - typeof(Vector2), - typeof(string), - typeof(CharacterInfo), - typeof(ushort), - typeof(bool), - typeof(bool), - typeof(bool), - typeof(RagdollParams), - typeof(bool) - }), HarmonyPostfix] - public static void Character_Create_Post(Character __result) - { - _eventService.PublishEvent(x => x.OnCharacterCreated(__result)); - } - - [HarmonyPatch(typeof(Character), nameof(Character.GiveJobItems)), HarmonyPostfix] - public static void Character_GiveJobItems_Post(Character __instance, WayPoint spawnPoint, bool isPvPMode) - { - _eventService.PublishEvent(x => x.OnGiveCharacterJobItems(__instance, spawnPoint, isPvPMode)); - } - - [HarmonyPatch(typeof(Affliction), nameof(Affliction.Update)), HarmonyPostfix] - public static void Affliction_Update_Post(Affliction __instance, CharacterHealth characterHealth, Limb targetLimb, float deltaTime) - { - _eventService.PublishEvent(x => x.OnAfflictionUpdate(__instance, characterHealth, targetLimb, deltaTime)); - } - - public void Dispose() - { - Harmony.UnpatchSelf(); - IsDisposed = true; - } -} From 59584fefc107e18733a72d372eaba1386fcb560f Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 11 Feb 2026 15:16:04 -0500 Subject: [PATCH 153/288] - Updated dependencies versions. - Made EventService handle errors again. --- Barotrauma/BarotraumaShared/Luatrauma.props | 4 ++-- .../SharedSource/LuaCs/_Services/EventService.cs | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Luatrauma.props b/Barotrauma/BarotraumaShared/Luatrauma.props index a17acc078..a93e33094 100644 --- a/Barotrauma/BarotraumaShared/Luatrauma.props +++ b/Barotrauma/BarotraumaShared/Luatrauma.props @@ -2,13 +2,13 @@ - + - + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs index 32e543738..25716cf3d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs @@ -318,9 +318,6 @@ public partial class EventService : IEventService { results.WithError(new ExceptionalError(e)); _loggerService.LogError(e.Message); -#if DEBUG - throw; -#endif continue; } } From 471e256353fadb1800ff6096bd63548ba39773cc Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 11 Feb 2026 15:17:32 -0500 Subject: [PATCH 154/288] Revert "-- MonoMod.Hook replacement." This reverts commit 948b7c48c5733460d4537458e280de8b2a0080e7. --- .../ContentPackageManager.cs | 1 + .../SharedSource/LuaCs/LuaCsSetup.cs | 19 +- .../LuaCs/_Services/EventPatchingService.cs | 191 ------------------ .../_Services/HarmonyEventPatchesService.cs | 132 ++++++++++++ 4 files changed, 137 insertions(+), 206 deletions(-) delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventPatchingService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs index 03f2214d8..dba9f064b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs @@ -9,6 +9,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Xml.Linq; using Barotrauma.IO; +using Barotrauma.LuaCs.Events; using Barotrauma.Steam; using Microsoft.Xna.Framework; using OneOf.Types; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index a6a9ecb07..3ebfdd402 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -30,21 +30,10 @@ namespace Barotrauma // == startup _servicesProvider = SetupServicesProvider(); _runStateMachine = SetupStateMachine(); - _servicesProvider.GetService(); + + _servicesProvider.GetService(); + SubscribeToLuaCsEvents(); - PatchEventMethods(); - } - - private void PatchEventMethods() - { - if (_servicesProvider.TryGetService(out var svc)) - { - svc.GenerateMethodHooks(); - Logger.LogDebug($"Patched methods."); - return; - } - - Logger.LogError($"Failed to find {nameof(EventPatchingService)}"); } private void SubscribeToLuaCsEvents() @@ -196,7 +185,7 @@ namespace Barotrauma servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); - servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); // Extension/Sub Services servicesProvider.RegisterServiceType(ServiceLifetime.Transient); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventPatchingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventPatchingService.cs deleted file mode 100644 index e8bd20593..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventPatchingService.cs +++ /dev/null @@ -1,191 +0,0 @@ -using Barotrauma.LuaCs; -using Barotrauma.LuaCs.Events; -using Barotrauma.Networking; -using HarmonyLib; -using Microsoft.Xna.Framework; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Reflection; -using MonoMod.RuntimeDetour; -using static Barotrauma.ContentPackageManager; - -namespace Barotrauma.LuaCs; - - -internal class EventPatchingService : IService -{ - public bool IsDisposed { get; private set; } - private static EventPatchingService _instance; - private IEventService _eventService; - private ILoggerService _loggerService; - /// - /// Key: Original Method, Value: Active Hook. - /// - private readonly ConcurrentDictionary _runtimeHooks = new(); - - #region METHODINFO - - private static readonly MethodInfo Screen_Select_Orig = - typeof(Screen).GetMethod(nameof(Screen.Select), BindingFlags.Instance | BindingFlags.Public); - - private static readonly MethodInfo EnabledPackages_SetCore_Orig = - typeof(ContentPackageManager.EnabledPackages).GetMethod(nameof(ContentPackageManager.EnabledPackages.SetCore), - BindingFlags.Static | BindingFlags.Public); - - private static readonly MethodInfo EnabledPackages_SetRegular_Orig = - typeof(ContentPackageManager.EnabledPackages).GetMethod(nameof(ContentPackageManager.EnabledPackages.SetRegular), - BindingFlags.Static | BindingFlags.Public); - - private static readonly MethodInfo Character_Create_Orig = - AccessTools.DeclaredMethod(typeof(Character), - nameof(Character.Create), new [] - { - typeof(CharacterPrefab), - typeof(Vector2), - typeof(string), - typeof(CharacterInfo), - typeof(ushort), - typeof(bool), - typeof(bool), - typeof(bool), - typeof(RagdollParams), - typeof(bool) - }); - - - #endregion - - public EventPatchingService(IEventService eventService, ILoggerService loggerService) - { - _eventService = eventService; - _loggerService = loggerService; - _instance = this; - } - - public void GenerateMethodHooks() - { - IService.CheckDisposed(this); - - if (!_runtimeHooks.IsEmpty) - { - _loggerService.LogError($"{nameof(GenerateMethodHooks)}: Hooks are already active!"); - return; - } - - _runtimeHooks.TryAdd(Screen_Select_Orig, new Hook( - Screen_Select_Orig, - this.Screen_Select_Post - )); - - _runtimeHooks.TryAdd(EnabledPackages_SetCore_Orig, new Hook( - EnabledPackages_SetCore_Orig, - typeof(EventPatchingService).GetMethod(nameof(EnabledPackages_SetCore_Post)) - )); - - _runtimeHooks.TryAdd(EnabledPackages_SetRegular_Orig, new Hook( - EnabledPackages_SetRegular_Orig, - typeof(EventPatchingService).GetMethod(nameof(EnabledPackages_SetRegular_Post)) - )); - - _runtimeHooks.TryAdd(Character_Create_Orig, new Hook( - Character_Create_Orig, - this.Character_Create_Post - )); - } - - public void CoroutineManager_Update_Post() - { - _eventService.PublishEvent(x => x.OnUpdate(Timing.TotalTime)); - _loggerService.ProcessLogs(); - } - - public void Screen_Select_Post(Action orig, Screen src) - { - orig(src); - _eventService.PublishEvent(x => x.OnScreenSelected(Screen.Selected)); - } - - public void PackageSource_Refresh_Post() - { - _eventService.PublishEvent(x => x.OnAllPackageListChanged(ContentPackageManager.CorePackages, ContentPackageManager.RegularPackages)); - } - - public void ContentPackageManager_Init_Post() - { - _eventService.PublishEvent(x => x.OnAllPackageListChanged(ContentPackageManager.CorePackages, ContentPackageManager.RegularPackages)); - _eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); - } - - public static void EnabledPackages_SetCore_Post(Action orig, CorePackage newCore) - { - orig(newCore); - _instance?._eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); - } - - public static void EnabledPackages_SetRegular_Post(Action> orig, IReadOnlyList newRegular) - { - orig(newRegular); - _instance?._eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); - } - -#if CLIENT - public void GameClient_ReadDataMessage_Pre(IReadMessage inc) - { - ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte(); - _eventService.PublishEvent(x => x.OnReceivedServerNetMessage(inc, header)); - inc.BitPosition -= 8; // rewind so the game can read the message - } - - public void SubEditorScreen_Selected_Post(Screen __instance) - { - _eventService.PublishEvent(x => x.OnScreenSelected(__instance)); - } - - public void PlayerInput_Update_Pre(double deltaTime) - { - _eventService.PublishEvent(x => x.OnKeyUpdate(deltaTime)); - } -#elif SERVER - public void GameServer_ReadDataMessage_Pre(NetworkConnection sender, IReadMessage inc) - { - ClientPacketHeader header = (ClientPacketHeader)inc.ReadByte(); - _eventService.PublishEvent(x => x.OnReceivedClientNetMessage(inc, header, sender)); - inc.BitPosition -= 8; // rewind so the game can read the message - } -#endif - - // Character.Create(), Line 1411. - public Character Character_Create_Post(Func orig, - CharacterPrefab prefab, Vector2 position, string seed, - CharacterInfo characterInfo = null, ushort id = Entity.NullEntityID, - bool isRemotePlayer = false, bool hasAi = true, bool createNetworkEvent = true, - RagdollParams ragdoll = null, bool spawnInitialItems = true) - { - Character result = orig(prefab, position, seed, characterInfo, id, isRemotePlayer, hasAi, createNetworkEvent, - ragdoll, spawnInitialItems); - _eventService.PublishEvent(x => x.OnCharacterCreated(result)); - return result; - } - - public void Character_GiveJobItems_Post(Character __instance, WayPoint spawnPoint, bool isPvPMode) - { - _eventService.PublishEvent(x => x.OnGiveCharacterJobItems(__instance, spawnPoint, isPvPMode)); - } - - public void Affliction_Update_Post(Affliction __instance, CharacterHealth characterHealth, Limb targetLimb, float deltaTime) - { - _eventService.PublishEvent(x => x.OnAfflictionUpdate(__instance, characterHealth, targetLimb, deltaTime)); - } - - public void Dispose() - { - IsDisposed = true; - foreach (var runtimeHook in _runtimeHooks) - { - runtimeHook.Value.Dispose(); - } - _runtimeHooks.Clear(); - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs new file mode 100644 index 000000000..22186a47e --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs @@ -0,0 +1,132 @@ +using Barotrauma.LuaCs; +using Barotrauma.LuaCs.Events; +using Barotrauma.Networking; +using HarmonyLib; +using Microsoft.Xna.Framework; +using System; +using static Barotrauma.ContentPackageManager; + +namespace Barotrauma.LuaCs; + +[HarmonyPatch] +internal class HarmonyEventPatchesService : IService +{ + public bool IsDisposed { get; private set; } + + private static IEventService _eventService; + private static ILoggerService _loggerService; + private readonly Harmony Harmony; + + public HarmonyEventPatchesService(IEventService eventService, ILoggerService loggerService) + { + _eventService = eventService; + _loggerService = loggerService; + Harmony = new Harmony("LuaCsForBarotrauma.Events"); + Harmony.PatchAll(typeof(HarmonyEventPatchesService)); + } + + [HarmonyPatch(typeof(CoroutineManager), nameof(CoroutineManager.Update)), HarmonyPostfix] + public static void CoroutineManager_Update_Post() + { + _eventService.PublishEvent(x => x.OnUpdate(Timing.TotalTime)); + _loggerService.ProcessLogs(); + } + + [HarmonyPatch(typeof(Screen), nameof(Screen.Select)), HarmonyPostfix] + public static void Screen_Selected_Post(Screen __instance) + { + _eventService.PublishEvent(x => x.OnScreenSelected(__instance)); + } + + [HarmonyPatch(typeof(ContentPackageManager.PackageSource), nameof(ContentPackageManager.PackageSource.Refresh)), HarmonyPostfix] + public static void PackageSource_Refresh_Post() + { + _eventService.PublishEvent(x => x.OnAllPackageListChanged(ContentPackageManager.CorePackages, ContentPackageManager.RegularPackages)); + } + + [HarmonyPatch(typeof(ContentPackageManager), nameof(ContentPackageManager.Init)), HarmonyPostfix] + public static void ContentPackageManager_Init_Post() + { + _eventService.PublishEvent(x => x.OnAllPackageListChanged(ContentPackageManager.CorePackages, ContentPackageManager.RegularPackages)); + _eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); + } + + [HarmonyPatch(typeof(ContentPackageManager.EnabledPackages), nameof(ContentPackageManager.EnabledPackages.SetCore)), HarmonyPostfix] + public static void EnabledPackages_SetCore_Post() + { + _eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); + } + + [HarmonyPatch(typeof(ContentPackageManager.EnabledPackages), nameof(ContentPackageManager.EnabledPackages.SetRegular)), HarmonyPostfix] + public static void EnabledPackages_SetRegular_Post() + { + _eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); + } + + + +#if CLIENT + [HarmonyPatch(typeof(GameClient), "ReadDataMessage"), HarmonyPrefix] + public static void GameClient_ReadDataMessage_Pre(IReadMessage inc) + { + ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte(); + _eventService.PublishEvent(x => x.OnReceivedServerNetMessage(inc, header)); + inc.BitPosition -= 8; // rewind so the game can read the message + } + + [HarmonyPatch(typeof(SubEditorScreen), nameof(SubEditorScreen.Select), new Type[] { }), HarmonyPostfix] + public static void SubEditorScreen_Selected_Post(Screen __instance) + { + _eventService.PublishEvent(x => x.OnScreenSelected(__instance)); + } + + [HarmonyPatch(typeof(PlayerInput), nameof(PlayerInput.Update)), HarmonyPrefix] + public static void PlayerInput_Update_Pre(double deltaTime) + { + _eventService.PublishEvent(x => x.OnKeyUpdate(deltaTime)); + } +#elif SERVER + [HarmonyPatch(typeof(GameServer), "ReadDataMessage"), HarmonyPrefix] + public static void GameServer_ReadDataMessage_Pre(NetworkConnection sender, IReadMessage inc) + { + ClientPacketHeader header = (ClientPacketHeader)inc.ReadByte(); + _eventService.PublishEvent(x => x.OnReceivedClientNetMessage(inc, header, sender)); + inc.BitPosition -= 8; // rewind so the game can read the message + } +#endif + + [HarmonyPatch(typeof(Character), nameof(Character.Create), new[] { + typeof(CharacterPrefab), + typeof(Vector2), + typeof(string), + typeof(CharacterInfo), + typeof(ushort), + typeof(bool), + typeof(bool), + typeof(bool), + typeof(RagdollParams), + typeof(bool) + }), HarmonyPostfix] + public static void Character_Create_Post(Character __result) + { + _eventService.PublishEvent(x => x.OnCharacterCreated(__result)); + } + + [HarmonyPatch(typeof(Character), nameof(Character.GiveJobItems)), HarmonyPostfix] + public static void Character_GiveJobItems_Post(Character __instance, WayPoint spawnPoint, bool isPvPMode) + { + _eventService.PublishEvent(x => x.OnGiveCharacterJobItems(__instance, spawnPoint, isPvPMode)); + } + + [HarmonyPatch(typeof(Affliction), nameof(Affliction.Update)), HarmonyPostfix] + public static void Affliction_Update_Post(Affliction __instance, CharacterHealth characterHealth, Limb targetLimb, float deltaTime) + { + _eventService.PublishEvent(x => x.OnAfflictionUpdate(__instance, characterHealth, targetLimb, deltaTime)); + } + + public void Dispose() + { + Harmony.UnpatchSelf(); + IsDisposed = true; + } +} From ad152ee74715d44c2b4f6ecbc2bcac61abf1ef54 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 11 Feb 2026 15:19:40 -0500 Subject: [PATCH 155/288] - pOoosh --- .../BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs | 2 -- .../LuaCs/_Services/HarmonyEventPatchesService.cs | 6 ++---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 3ebfdd402..9964cf269 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -30,9 +30,7 @@ namespace Barotrauma // == startup _servicesProvider = SetupServicesProvider(); _runStateMachine = SetupStateMachine(); - _servicesProvider.GetService(); - SubscribeToLuaCsEvents(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs index 22186a47e..916858880 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs @@ -62,9 +62,7 @@ internal class HarmonyEventPatchesService : IService { _eventService.PublishEvent(sub => sub.OnEnabledPackageListChanged(EnabledPackages.Core, EnabledPackages.Regular)); } - - - + #if CLIENT [HarmonyPatch(typeof(GameClient), "ReadDataMessage"), HarmonyPrefix] public static void GameClient_ReadDataMessage_Pre(IReadMessage inc) @@ -126,7 +124,7 @@ internal class HarmonyEventPatchesService : IService public void Dispose() { - Harmony.UnpatchSelf(); IsDisposed = true; + Harmony.UnpatchSelf(); } } From 5747d896ebc5b0fb451ffd4239b64109ff97cf5b Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 12 Feb 2026 14:53:33 -0500 Subject: [PATCH 156/288] - Removed ImpromptuInterfaces --- Barotrauma/BarotraumaShared/Luatrauma.props | 1 - .../SharedSource/LuaCs/IEvents.cs | 210 +++++++++++++----- .../LuaCs/_Services/EventService.cs | 22 +- .../_Services/HarmonyEventPatchesService.cs | 3 + .../_Services/PluginManagementService.cs | 1 - .../_Services/_Interfaces/IEventService.cs | 4 +- .../LuaCs/_Services/_Lua/ILuaEventService.cs | 6 +- 7 files changed, 165 insertions(+), 82 deletions(-) diff --git a/Barotrauma/BarotraumaShared/Luatrauma.props b/Barotrauma/BarotraumaShared/Luatrauma.props index a93e33094..cbcdb4bcc 100644 --- a/Barotrauma/BarotraumaShared/Luatrauma.props +++ b/Barotrauma/BarotraumaShared/Luatrauma.props @@ -8,7 +8,6 @@ - diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index 85d670054..afffcaa7b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -1,12 +1,8 @@ -using System.Runtime.CompilerServices; -using System; +using System; using System.Collections.Generic; using System.Reflection; using Barotrauma.LuaCs.Data; -using Barotrauma.LuaCs; using Barotrauma.Networking; -using Dynamitey; -using ImpromptuInterface; namespace Barotrauma.LuaCs.Events; @@ -19,9 +15,16 @@ namespace Barotrauma.LuaCs.Events; public interface IEvent { bool IsLuaRunner() => false; + + public abstract class LuaWrapperBase : IEvent + { + protected readonly IDictionary LuaFuncs; + protected LuaWrapperBase(IDictionary luaFuncs) => LuaFuncs = luaFuncs; + public bool IsLuaRunner() => true; + } } -public interface IEvent : IEvent where T : IEvent +public interface IEvent : IEvent where T : class, IEvent { static virtual T GetLuaRunner(IDictionary luaFunc) { @@ -73,33 +76,64 @@ internal interface IEventSettingInstanceLifetime : IEvent { void OnAfflictionUpdate(Affliction affliction, CharacterHealth characterHealth, Limb targetLimb, float deltaTime); - static IEventAfflictionUpdate IEvent.GetLuaRunner(IDictionary luaFunc) => new + + static IEventAfflictionUpdate IEvent.GetLuaRunner(IDictionary luaFunc) => + new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventAfflictionUpdate { - IsLuaRunner = Return.Arguments(() => true), - OnAfflictionUpdate = ReturnVoid.Arguments((Affliction affliction, CharacterHealth characterHealth, Limb targetLimb, float deltaTime) => luaFunc[nameof(OnAfflictionUpdate)](affliction, characterHealth, targetLimb, deltaTime)) - }.ActLike(); + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnAfflictionUpdate(Affliction affliction, CharacterHealth characterHealth, Limb targetLimb, float deltaTime) + { + LuaFuncs[nameof(OnAfflictionUpdate)](affliction, characterHealth, targetLimb, deltaTime); + } + } } internal interface IEventGiveCharacterJobItems : IEvent { void OnGiveCharacterJobItems(Character character, WayPoint spawnPoint, bool isPvPMode); - static IEventGiveCharacterJobItems IEvent.GetLuaRunner(IDictionary luaFunc) => new + + static IEventGiveCharacterJobItems IEvent.GetLuaRunner( + IDictionary luaFunc) => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventGiveCharacterJobItems { - IsLuaRunner = Return.Arguments(() => true), - OnGiveCharacterJobItems = ReturnVoid.Arguments((Character character, WayPoint spawnPoint, bool isPvPMode) => luaFunc[nameof(OnGiveCharacterJobItems)](character, spawnPoint, isPvPMode)) - }.ActLike(); + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnGiveCharacterJobItems(Character character, WayPoint spawnPoint, bool isPvPMode) + { + LuaFuncs[nameof(OnGiveCharacterJobItems)](character, spawnPoint, isPvPMode); + } + } } internal interface IEventCharacterCreated : IEvent { void OnCharacterCreated(Character character); - static IEventCharacterCreated IEvent.GetLuaRunner(IDictionary luaFunc) => new + + static IEventCharacterCreated IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventCharacterCreated { - IsLuaRunner = Return.Arguments(() => true), - OnCharacterCreated = ReturnVoid.Arguments((Character character) => luaFunc[nameof(OnCharacterCreated)](character)) - }.ActLike(); + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnCharacterCreated(Character character) + { + LuaFuncs[nameof(OnCharacterCreated)](character); + } + } } + /* internal interface IEventHumanCPRFailed : IEvent { @@ -126,11 +160,21 @@ internal interface IEventHumanCPRSuccess : IEvent public interface IEventKeyUpdate : IEvent { void OnKeyUpdate(double deltaTime); - static IEventKeyUpdate IEvent.GetLuaRunner(IDictionary luaFunc) => new + + static IEventKeyUpdate IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventKeyUpdate { - IsLuaRunner = Return.Arguments(() => true), - OnKeyUpdate = ReturnVoid.Arguments((double deltaTime) => luaFunc[nameof(OnKeyUpdate)](deltaTime)) - }.ActLike(); + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnKeyUpdate(double deltaTime) + { + LuaFuncs[nameof(OnKeyUpdate)](deltaTime); + } + } } /// @@ -139,11 +183,21 @@ public interface IEventKeyUpdate : IEvent public interface IEventRoundStarting : IEvent { void OnRoundStarting(); - static IEventRoundStarting IEvent.GetLuaRunner(IDictionary luaFunc) => new + + static IEventRoundStarting IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventRoundStarting { - IsLuaRunner = Return.Arguments(() => true), - OnRoundStarting = ReturnVoid.Arguments(() => luaFunc[nameof(OnRoundStarting)]()) - }.ActLike(); + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnRoundStarting() + { + LuaFuncs[nameof(OnRoundStarting)](); + } + } } /// @@ -152,11 +206,21 @@ public interface IEventRoundStarting : IEvent public interface IEventRoundStarted : IEvent { void OnRoundStart(); - static IEventRoundStarted IEvent.GetLuaRunner(IDictionary luaFunc) => new + + static IEventRoundStarted IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventRoundStarted { - IsLuaRunner = Return.Arguments(() => true), - OnRoundStart = ReturnVoid.Arguments(() => luaFunc[nameof(OnRoundStart)]()) - }.ActLike(); + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnRoundStart() + { + LuaFuncs[nameof(OnRoundStart)](); + } + } } /// @@ -165,11 +229,20 @@ public interface IEventRoundStarted : IEvent public interface IEventUpdate : IEvent { void OnUpdate(double fixedDeltaTime); - static IEventUpdate IEvent.GetLuaRunner(IDictionary luaFunc) => new + static IEventUpdate IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventUpdate { - IsLuaRunner = Return.Arguments(() => true), - OnUpdate = ReturnVoid.Arguments((fixedDeltaTime) => luaFunc[nameof(OnUpdate)](fixedDeltaTime)) - }.ActLike(); + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnUpdate(double deltaTime) + { + LuaFuncs[nameof(OnUpdate)](deltaTime); + } + } } /// @@ -178,11 +251,21 @@ public interface IEventUpdate : IEvent public interface IEventDrawUpdate : IEvent { void OnDrawUpdate(double deltaTime); - static IEventDrawUpdate IEvent.GetLuaRunner(IDictionary luaFunc) => new + + static IEventDrawUpdate IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventDrawUpdate { - IsLuaRunner = Return.Arguments(() => true), - OnDrawUpdate = ReturnVoid.Arguments((deltaTime) => luaFunc[nameof(OnDrawUpdate)](deltaTime)) - }.ActLike(); + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnDrawUpdate(double deltaTime) + { + LuaFuncs[nameof(OnDrawUpdate)](deltaTime); + } + } } #endregion @@ -206,11 +289,21 @@ interface IEventClientConnected : IEvent /// /// The connecting client. void OnClientConnected(Client client); - static IEventClientConnected IEvent.GetLuaRunner(IDictionary luaFunc) => new + + static IEventClientConnected IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventClientConnected { - IsLuaRunner = Return.Arguments(() => true), - OnClientConnected = ReturnVoid.Arguments((client) => luaFunc[nameof(OnClientConnected)](client)) - }.ActLike(); + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnClientConnected(Client client) + { + LuaFuncs[nameof(OnClientConnected)](client); + } + } } #endif @@ -230,11 +323,21 @@ public interface IEventServerRawNetMessageReceived : IEvent { void OnServerConnected(); - static IEventServerConnected IEvent.GetLuaRunner(IDictionary luaFunc) => new + + static IEventServerConnected IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventServerConnected { - IsLuaRunner = Return.Arguments(() => true), - OnServerConnected = ReturnVoid.Arguments(() => luaFunc[nameof(OnServerConnected)]()) - }.ActLike(); + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnServerConnected() + { + LuaFuncs[nameof(OnServerConnected)](); + } + } } #endif #endregion @@ -249,11 +352,6 @@ public interface IEventServerConnected : IEvent public interface IEventPluginInitialize : IEvent { void Initialize(); - static IEventPluginInitialize IEvent.GetLuaRunner(IDictionary luaFunc) => new - { - IsLuaRunner = Return.Arguments(() => true), - Initialize = ReturnVoid.Arguments(() => luaFunc[nameof(Initialize)]()) - }.ActLike(); } /// @@ -262,11 +360,6 @@ public interface IEventPluginInitialize : IEvent public interface IEventPluginLoadCompleted : IEvent { void OnLoadCompleted(); - static IEventPluginLoadCompleted IEvent.GetLuaRunner(IDictionary luaFunc) => new - { - IsLuaRunner = Return.Arguments(() => true), - OnLoadCompleted = ReturnVoid.Arguments(() => luaFunc[nameof(OnLoadCompleted)]()) - }.ActLike(); } /// @@ -276,11 +369,6 @@ public interface IEventPluginLoadCompleted : IEvent public interface IEventPluginPreInitialize : IEvent { void PreInitPatching(); - static IEventPluginPreInitialize IEvent.GetLuaRunner(IDictionary luaFunc) => new - { - IsLuaRunner = Return.Arguments(() => true), - OnPreInitialize = ReturnVoid.Arguments(() => luaFunc[nameof(PreInitPatching)]()) - }.ActLike(); } /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs index 25716cf3d..224cc8128 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs @@ -1,19 +1,13 @@ -using Barotrauma.Extensions; -using Barotrauma.LuaCs.Events; -using Barotrauma.LuaCs.Compatibility; +using Barotrauma.LuaCs.Events; using FluentResults; -using FluentResults.LuaCs; -using HarmonyLib; using Microsoft.Toolkit.Diagnostics; using MoonSharp.Interpreter; using OneOf; -using RestSharp; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; namespace Barotrauma.LuaCs; @@ -189,7 +183,7 @@ public partial class EventService : IEventService return returnValue; } - public void Subscribe(string identifier, IDictionary callbacks) where T : IEvent + public void Subscribe(string identifier, IDictionary callbacks) where T : class, IEvent { Guard.IsNotNullOrWhiteSpace(identifier, nameof(identifier)); Guard.IsNotNull(callbacks, nameof(callbacks)); @@ -215,12 +209,12 @@ public partial class EventService : IEventService evtSubscribers.TryRemove(identifier, out _); } - public void PublishLuaEvent(LuaCsFunc subscriberRunner) where T : IEvent + public void PublishLuaEvent(LuaCsFunc subscriberRunner) where T : class, IEvent { this.PublishEvent(sub => subscriberRunner(sub)); } - public FluentResults.Result RegisterLuaEventAlias(string luaEventName, string targetMethod) where T : IEvent + public FluentResults.Result RegisterLuaEventAlias(string luaEventName, string targetMethod) where T : class, IEvent { Guard.IsNotNullOrWhiteSpace(luaEventName, nameof(luaEventName)); Guard.IsNotNullOrWhiteSpace(targetMethod, nameof(targetMethod)); @@ -281,7 +275,7 @@ public partial class EventService : IEventService evtSubscribers.TryRemove(subscriber, out _); } - public void ClearAllEventSubscribers() where T : IEvent + public void ClearAllEventSubscribers() where T : class, IEvent { using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); @@ -295,7 +289,7 @@ public partial class EventService : IEventService _subscribers.Clear(); } - public FluentResults.Result PublishEvent(Action action) where T : IEvent + public FluentResults.Result PublishEvent(Action action) where T : class, IEvent { Guard.IsNotNull(action, nameof(action)); using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); @@ -312,7 +306,7 @@ public partial class EventService : IEventService { try { - action.Invoke((T)sub.Value); + action.Invoke(Unsafe.As(sub.Value)); } catch (Exception e) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs index 916858880..e3715af09 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs @@ -25,7 +25,10 @@ internal class HarmonyEventPatchesService : IService Harmony.PatchAll(typeof(HarmonyEventPatchesService)); } + // TODO: This causes like hell in Debug. +#if !DEBUG [HarmonyPatch(typeof(CoroutineManager), nameof(CoroutineManager.Update)), HarmonyPostfix] +#endif public static void CoroutineManager_Update_Post() { _eventService.PublishEvent(x => x.OnUpdate(Timing.TotalTime)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index ecac7daaf..ad32a8d6f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -16,7 +16,6 @@ using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Events; using FluentResults; using FluentResults.LuaCs; -using ImpromptuInterface.Build; using LightInject; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IEventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IEventService.cs index eca7648ae..39c7ad347 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IEventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IEventService.cs @@ -25,7 +25,7 @@ public interface IEventService : IReusableService, ILuaEventService /// Clears all subscribers for a given event type and removes any registration to the type. /// /// The event type. - void ClearAllEventSubscribers() where T : IEvent; + void ClearAllEventSubscribers() where T : class, IEvent; /// /// Clears all subscribers lists. /// @@ -36,5 +36,5 @@ public interface IEventService : IReusableService, ILuaEventService /// /// /// - FluentResults.Result PublishEvent(Action action) where T : IEvent; + FluentResults.Result PublishEvent(Action action) where T : class, IEvent; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaEventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaEventService.cs index f075a5d78..ae789afe5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaEventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaEventService.cs @@ -13,7 +13,7 @@ public interface ILuaSafeEventService : ILuaService, ILuaCsHook /// /// /// A 'method name'=='signature action' dictionary matching the interface method list. - void Subscribe(string identifier, IDictionary callbacks) where T : IEvent; + void Subscribe(string identifier, IDictionary callbacks) where T : class, IEvent; /// /// Removes a subscriber from an event that subscribed under the given identifier. /// @@ -26,7 +26,7 @@ public interface ILuaSafeEventService : ILuaService, ILuaCsHook /// Interface type. /// Execution runner, the subscriber is provided as the first argument in the lua runner. /// - void PublishLuaEvent(LuaCsFunc subscriberRunner) where T : IEvent; + void PublishLuaEvent(LuaCsFunc subscriberRunner) where T : class, IEvent; /// /// Defines the target method name for legacy to target on new @@ -37,7 +37,7 @@ public interface ILuaSafeEventService : ILuaService, ILuaCsHook /// The event interface type. /// Operation success. /// The is null or empty. - public FluentResults.Result RegisterLuaEventAlias(string luaEventName, string targetMethod) where T : IEvent; + public FluentResults.Result RegisterLuaEventAlias(string luaEventName, string targetMethod) where T : class, IEvent; } public interface ILuaEventService : ILuaSafeEventService From 07d838eee0be68a30c5f05ad24807fdc573169ce Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 12 Feb 2026 14:59:24 -0500 Subject: [PATCH 157/288] Finished removing Impromptu Interfaces package. --- Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 9964cf269..1946fba81 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -2,9 +2,6 @@ using Barotrauma.LuaCs.Compatibility; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Events; -using Barotrauma.Networking; -using FluentResults; -using ImpromptuInterface; using LightInject; using System; using System.Collections.Generic; From 5f38b4a43a11449456c64a89e1782fe4fe7b6645 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Thu, 12 Feb 2026 21:50:13 -0300 Subject: [PATCH 158/288] Fix bullshit Lua issues --- .../LuaCs/Compatibility/ILuaCsTimer.cs | 2 +- .../_Services/HarmonyEventPatchesService.cs | 3 --- .../_Services/LuaScriptManagementService.cs | 6 ++--- .../_Lua/LuaClasses/LuaConverters.cs | 2 +- .../_Services/_Lua/LuaClasses/LuaCsTimer.cs | 23 ++++++++++++++----- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsTimer.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsTimer.cs index cc6c9081c..9fe824d0e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsTimer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsTimer.cs @@ -2,7 +2,7 @@ namespace Barotrauma.LuaCs.Compatibility; -internal partial interface ILuaCsTimer : ILuaCsShim +internal partial interface ILuaCsTimer : IReusableService, ILuaCsShim { public static double Time => Timing.TotalTime; public static double GetTime() => Time; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs index e3715af09..916858880 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs @@ -25,10 +25,7 @@ internal class HarmonyEventPatchesService : IService Harmony.PatchAll(typeof(HarmonyEventPatchesService)); } - // TODO: This causes like hell in Debug. -#if !DEBUG [HarmonyPatch(typeof(CoroutineManager), nameof(CoroutineManager.Update)), HarmonyPostfix] -#endif public static void CoroutineManager_Update_Post() { _eventService.PublishEvent(x => x.OnUpdate(Timing.TotalTime)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 04b21c67a..3e92804fe 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -174,9 +174,8 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _eventService.RegisterLuaEventAlias("think", "OnUpdate"); _eventService.RegisterLuaEventAlias("keyUpdate", "OnKeyUpdate"); _eventService.RegisterLuaEventAlias("character.created", "OnCharacterCreated"); - _eventService.RegisterLuaEventAlias("character.giveJobItems", "OnGiveCharacterJobItems"); - _eventService.RegisterLuaEventAlias("afflictionUpdate", "OnAfflictionUpdate"); - + _eventService.RegisterLuaEventAlias("character.giveJobItems", "OnGiveCharacterJobItems"); + _eventService.RegisterLuaEventAlias("afflictionUpdate", "OnAfflictionUpdate"); } private void SetupEnvironment(bool enableSandbox) @@ -357,6 +356,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService { _luaScriptLoader.ClearCaches(); _userDataService.Reset(); + _luaCsTimer.Reset(); RegisterLuaEvents(); return DisposeAllPackageResources(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaConverters.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaConverters.cs index e2783dd10..ab53a5538 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaConverters.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaConverters.cs @@ -34,7 +34,7 @@ namespace Barotrauma Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(LuaCsAction), v => (LuaCsAction)(args => { - if (v.Function.OwnerScript == _luaScriptManagementService) + if (v.Function.OwnerScript == _luaScriptManagementService.InternalScript) { Call(v.Function, args); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsTimer.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsTimer.cs index a34986f56..6d12bd068 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsTimer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsTimer.cs @@ -61,7 +61,7 @@ namespace Barotrauma public LuaCsTimer(IEventService eventService) { _eventService = eventService; - _eventService.Subscribe(this); + SubscribeToEvents(); } private void AddTimer(TimedAction timedAction) @@ -101,11 +101,6 @@ namespace Barotrauma AddTimer(timedAction); } - public void Dispose() - { - _eventService.Unsubscribe(this); - } - public void OnUpdate(double fixedDeltaTime) { lock (timedActions) @@ -135,6 +130,22 @@ namespace Barotrauma } } + private void SubscribeToEvents() + { + _eventService.Subscribe(this); + } + + public FluentResults.Result Reset() + { + SubscribeToEvents(); + return FluentResults.Result.Ok(); + } + + public void Dispose() + { + _eventService.Unsubscribe(this); + } + public bool IsDisposed => false; } } From dff38f7609e09fa80c4d31bba7a5b19302b9ffbc Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 12 Feb 2026 20:21:46 -0500 Subject: [PATCH 159/288] - Added XmlDoc for InternalScript instance. --- .../_Interfaces/ILuaScriptManagementService.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs index 2117046da..0c8a938a2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs @@ -1,19 +1,20 @@ #nullable enable -using System; -using System.Collections.Generic; using System.Collections.Immutable; -using System.Reflection; using System.Threading.Tasks; using Barotrauma.LuaCs.Data; -using FluentResults; using MoonSharp.Interpreter; -using MoonSharp.Interpreter.Interop; namespace Barotrauma.LuaCs; public interface ILuaScriptManagementService : IReusableService { + /// + /// The running instance, if available. + /// + /// + /// It is recommended to avoid using this directly if another API is available for the intended purposes. + /// Script? InternalScript { get; } object? GetGlobalTableValue(string tableName); From 36471f2239c8d7b40b3270976d9b24a5602afbe0 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Fri, 13 Feb 2026 18:44:27 -0300 Subject: [PATCH 160/288] Move more events to be Harmony patches --- .../ServerSource/Networking/GameServer.cs | 2 - .../[DebugOnlyTest]TestLuaMod/Lua/init.lua | 26 ++++++- .../SharedSource/Characters/Character.cs | 1 - .../SharedSource/GameSession/GameSession.cs | 7 -- .../SharedSource/LuaCs/IEvents.cs | 62 ++++++++++++++++ .../_Services/HarmonyEventPatchesService.cs | 71 +++++++++++++++++++ .../_Services/LuaScriptManagementService.cs | 18 +++-- 7 files changed, 171 insertions(+), 16 deletions(-) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index eb971f54b..ddff2d5a1 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -3217,8 +3217,6 @@ namespace Barotrauma.Networking roundStartTime = DateTime.Now; - GameMain.LuaCs.Hook.Call("roundStart"); - startGameCoroutine = null; yield return CoroutineStatus.Success; } diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua index f8f41c492..a3252fc85 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua @@ -1 +1,25 @@ -print("Hello!") \ No newline at end of file +print("Hello!") + +Hook.Add("character.created", "test", function(character) + print("character.created: ", character) +end) + +Hook.Add("character.death", "test", function(character) + print("character.death: ", character) +end) + +Hook.Add("character.giveJobItems", "test", function(character) + print("character.giveJobItems: ", character) +end) + +Hook.Add("roundStart", "test", function() + print("roundStart") +end) + +Hook.Add("roundEnd", "test", function() + print("roundEnd") +end) + +Hook.Add("missionsEnded", "test", function() + print("missionsEnded") +end) \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 46ee0b105..3436ec8a6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -5126,7 +5126,6 @@ namespace Barotrauma AchievementManager.OnCharacterKilled(this, CauseOfDeath); } - GameMain.LuaCs.Hook.Call("character.death", this, causeOfDeathAffliction); KillProjSpecific(causeOfDeath, causeOfDeathAffliction, log); if (info != null) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index f3782f134..b29059ecb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -408,7 +408,6 @@ namespace Barotrauma public void LoadPreviousSave() { - GameMain.LuaCs.Hook.Call("roundEnd"); AchievementManager.OnRoundEnded(this, roundInterrupted: true); Submarine.Unload(); SaveUtil.LoadGame(DataPath); @@ -761,7 +760,6 @@ namespace Barotrauma GUI.PreventPauseMenuToggle = false; HintManager.OnRoundStarted(); - GameMain.LuaCs.Hook.Call("roundStart"); EnableEventLogNotificationIcon(enabled: false); LogStartRoundStats(); @@ -1089,9 +1087,6 @@ namespace Barotrauma { RoundEnding = true; -#if CLIENT - GameMain.LuaCs.Hook.Call("roundEnd"); -#endif //Clear the grids to allow for garbage collection Powered.Grids.Clear(); Powered.ChangedConnections.Clear(); @@ -1110,8 +1105,6 @@ namespace Barotrauma character.CheckTalents(AbilityEffectType.OnRoundEnd); } - GameMain.LuaCs.Hook.Call("missionsEnded", missions); - #if CLIENT if (GUI.PauseMenuOpen) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index afffcaa7b..a392928f7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -133,6 +133,25 @@ internal interface IEventCharacterCreated : IEvent } } +internal interface IEventCharacterDeath : IEvent +{ + void OnCharacterDeath(Character character, Affliction causeOfDeathAffliction, CauseOfDeathType causeOfDeathType); + + static IEventCharacterDeath IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventCharacterDeath + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnCharacterDeath(Character character, Affliction causeOfDeathAffliction, CauseOfDeathType causeOfDeathType) + { + LuaFuncs[nameof(OnCharacterDeath)](character, causeOfDeathAffliction, causeOfDeathType); + } + } +} /* internal interface IEventHumanCPRFailed : IEvent @@ -223,6 +242,49 @@ public interface IEventRoundStarted : IEvent } } +/// +/// Called when a round has ended. +/// +public interface IEventRoundEnded : IEvent +{ + void OnRoundEnd(); + + static IEventRoundEnded IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventRoundEnded + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnRoundEnd() + { + LuaFuncs[nameof(OnRoundEnd)](); + } + } +} + +internal interface IEventMissionsEnded : IEvent +{ + void OnMissionsEnded(IReadOnlyList missions); + + static IEventMissionsEnded IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventMissionsEnded + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnMissionsEnded(IReadOnlyList missions) + { + LuaFuncs[nameof(OnMissionsEnded)](missions); + } + } +} + /// /// Called on game loop normal update. /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs index 916858880..5054bc0ba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs @@ -4,6 +4,9 @@ using Barotrauma.Networking; using HarmonyLib; using Microsoft.Xna.Framework; using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using static Barotrauma.ContentPackageManager; namespace Barotrauma.LuaCs; @@ -23,6 +26,9 @@ internal class HarmonyEventPatchesService : IService _loggerService = loggerService; Harmony = new Harmony("LuaCsForBarotrauma.Events"); Harmony.PatchAll(typeof(HarmonyEventPatchesService)); +#if SERVER + Harmony.PatchAll(typeof(HarmonyEventPatchesService.Patch_StartGame_End)); +#endif } [HarmonyPatch(typeof(CoroutineManager), nameof(CoroutineManager.Update)), HarmonyPostfix] @@ -32,6 +38,35 @@ internal class HarmonyEventPatchesService : IService _loggerService.ProcessLogs(); } +#if CLIENT + [HarmonyPatch(typeof(GameSession), nameof(GameSession.StartRound), new Type[] + { + typeof(LevelData), typeof(bool), typeof(SubmarineInfo), typeof(SubmarineInfo) + }), HarmonyPostfix] + public static void GameSession_StartRound_Post() + { + _eventService.PublishEvent(x => x.OnRoundStart()); + } +#endif + + [HarmonyPatch(typeof(GameSession), nameof(GameSession.EndRound)), HarmonyPrefix] + public static void GameSession_EndRound_Pre() + { + _eventService.PublishEvent(x => x.OnRoundEnd()); + } + + [HarmonyPatch(typeof(GameSession), nameof(GameSession.LoadPreviousSave)), HarmonyPrefix] + public static void GameSession_LoadPreviousSave_Pre() + { + _eventService.PublishEvent(x => x.OnRoundEnd()); + } + + [HarmonyPatch(typeof(GameSession), nameof(GameSession.EndMissions)), HarmonyPostfix] + public static void GameSession_EndMission_Post(GameSession __instance) + { + _eventService.PublishEvent(x => x.OnMissionsEnded(__instance.Missions.ToList())); + } + [HarmonyPatch(typeof(Screen), nameof(Screen.Select)), HarmonyPostfix] public static void Screen_Selected_Post(Screen __instance) { @@ -110,6 +145,12 @@ internal class HarmonyEventPatchesService : IService _eventService.PublishEvent(x => x.OnCharacterCreated(__result)); } + [HarmonyPatch(typeof(Character), nameof(Character.Kill)), HarmonyPostfix] + public static void Character_Kill_Post(Character __instance, Affliction causeOfDeathAffliction, CauseOfDeathType causeOfDeath) + { + _eventService.PublishEvent(x => x.OnCharacterDeath(__instance, causeOfDeathAffliction, causeOfDeath)); + } + [HarmonyPatch(typeof(Character), nameof(Character.GiveJobItems)), HarmonyPostfix] public static void Character_GiveJobItems_Post(Character __instance, WayPoint spawnPoint, bool isPvPMode) { @@ -127,4 +168,34 @@ internal class HarmonyEventPatchesService : IService IsDisposed = true; Harmony.UnpatchSelf(); } + +#if SERVER + [HarmonyPatch] + class Patch_StartGame_End + { + static MethodBase TargetMethod() + { + var original = AccessTools.Method( + typeof(GameServer), + "StartGame" + ); + + return AccessTools.EnumeratorMoveNext(original); + } + + [HarmonyPostfix] + static void Postfix(object __instance, bool __result) + { + if (!__result) { return; } + + var enumerator = __instance as IEnumerator; + if (enumerator == null) { return; } + + if (enumerator.Current == CoroutineStatus.Success) + { + _eventService.PublishEvent(x => x.OnRoundStart()); + } + } + } +#endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 3e92804fe..5a3b2dbfb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -171,11 +171,19 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService private void RegisterLuaEvents() { - _eventService.RegisterLuaEventAlias("think", "OnUpdate"); - _eventService.RegisterLuaEventAlias("keyUpdate", "OnKeyUpdate"); - _eventService.RegisterLuaEventAlias("character.created", "OnCharacterCreated"); - _eventService.RegisterLuaEventAlias("character.giveJobItems", "OnGiveCharacterJobItems"); - _eventService.RegisterLuaEventAlias("afflictionUpdate", "OnAfflictionUpdate"); + _eventService.RegisterLuaEventAlias("think", nameof(IEventUpdate.OnUpdate)); + _eventService.RegisterLuaEventAlias("keyUpdate", nameof(IEventKeyUpdate.OnKeyUpdate)); + _eventService.RegisterLuaEventAlias("afflictionUpdate", nameof(IEventAfflictionUpdate.OnAfflictionUpdate)); + + _eventService.RegisterLuaEventAlias("character.created", nameof(IEventCharacterCreated.OnCharacterCreated)); + _eventService.RegisterLuaEventAlias("character.death", nameof(IEventCharacterDeath.OnCharacterDeath)); + _eventService.RegisterLuaEventAlias("character.giveJobItems", nameof(IEventGiveCharacterJobItems.OnGiveCharacterJobItems)); + + _eventService.RegisterLuaEventAlias("roundStart", nameof(IEventRoundStarted.OnRoundStart)); + _eventService.RegisterLuaEventAlias("roundEnd", nameof(IEventRoundEnded.OnRoundEnd)); + _eventService.RegisterLuaEventAlias("missionsEnded", nameof(IEventMissionsEnded.OnMissionsEnded)); + + } private void SetupEnvironment(bool enableSandbox) From a36bd705059b05e3b051f386bfc1af4f0289e09b Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Fri, 13 Feb 2026 18:58:35 -0300 Subject: [PATCH 161/288] Strip down the original logger class --- .../BarotraumaClient/ClientSource/GameMain.cs | 2 - .../ClientSource/LuaCs/LuaCsSetup.cs | 8 - .../SharedSource/LuaCs/ModUtils.cs | 12 +- .../SharedSource/LuaCs/_Plugins/ACsMod.cs | 2 +- .../_Services/LuaScriptManagementService.cs | 4 +- .../_Services/_Lua/LuaClasses/LuaCsLogger.cs | 146 ++---------------- .../_Services/_Lua/LuaClasses/LuaCsTimer.cs | 6 +- 7 files changed, 24 insertions(+), 156 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index b37ca5668..10086bdb0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -985,8 +985,6 @@ namespace Barotrauma Screen.Selected.AddToGUIUpdateList(); - LuaCsLogger.AddToGUIUpdateList(); - Client?.AddToGUIUpdateList(); SubmarinePreview.AddToGUIUpdateList(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index 20839ed92..b24a584eb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -13,14 +13,6 @@ namespace Barotrauma { partial class LuaCsSetup { - public void AddToGUIUpdateList() - { - if (!DisableErrorGUIOverlay) - { - LuaCsLogger.AddToGUIUpdateList(); - } - } - /// /// /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs index fc30ce5db..a6588444e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs @@ -74,27 +74,27 @@ namespace Barotrauma.LuaCs public static void PrintMessage(string s) { #if SERVER - LuaCsLogger.LogMessage($"[Server] {s}"); + GameMain.LuaCs.Logger.LogMessage($"{s}"); #else - LuaCsLogger.LogMessage($"[Client] {s}"); + GameMain.LuaCs.Logger.LogMessage($"{s}"); #endif } public static void PrintWarning(string s) { #if SERVER - LuaCsLogger.Log($"[Server] {s}", Color.Yellow); + GameMain.LuaCs.Logger.Log($"{s}", Color.Yellow); #else - LuaCsLogger.Log($"[Client] {s}", Color.Yellow); + GameMain.LuaCs.Logger.Log($"{s}", Color.Yellow); #endif } public static void PrintError(string s) { #if SERVER - LuaCsLogger.LogError($"[Server] {s}"); + GameMain.LuaCs.Logger.LogError($"{s}"); #else - LuaCsLogger.LogError($"[Client] {s}"); + GameMain.LuaCs.Logger.LogError($"{s}"); #endif } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs index 9ee8833bc..8065d4dcc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs @@ -48,7 +48,7 @@ namespace Barotrauma } catch (Exception e) { - LuaCsLogger.HandleException(e, LuaCsMessageOrigin.CSharpMod); + GameMain.LuaCs.Logger.HandleException(e); } IsDisposed = true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 5a3b2dbfb..13d339b1e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -191,7 +191,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _script = new Script(CoreModules.Preset_SoftSandbox | CoreModules.Debug | CoreModules.IO | CoreModules.OS_System); _script.Options.DebugPrint = (string msg) => { - _loggerService.Log(msg); + _loggerService.LogMessage($"[Lua] {msg}"); }; _script.Options.ScriptLoader = _luaScriptLoader; _script.Options.CheckThreadAccess = false; @@ -221,7 +221,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _script.Globals["loadfile"] = (Func)LoadFile; _script.Globals["require"] = (Func)luaRequire.Require; - _script.Globals["printerror"] = (DynValue o) => { LuaCsLogger.LogError(o.ToString()); }; + _script.Globals["printerror"] = (DynValue o) => { _loggerService.LogError($"[Lua] {o.ToString()}"); }; _script.Globals["dostring"] = (Func)_script.DoString; _script.Globals["load"] = (Func)_script.LoadString; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs index e854a8e04..7179ece13 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs @@ -15,167 +15,43 @@ namespace Barotrauma partial class LuaCsLogger { - public static bool HideUserNames = true; - -#if SERVER - private const string LogPrefix = "SV"; - private const int NetMaxLength = 1024; // character limit of vanilla Barotrauma's chat system. - private const int NetMaxMessages = 60; - - // This is used so its possible to call logging functions inside the serverLog - // hook without creating an infinite loop - private static bool lockLog = false; -#else - private const string LogPrefix = "CL"; -#endif - - public static LuaCsMessageLogger MessageLogger; - public static LuaCsExceptionHandler ExceptionHandler; - public static void HandleException(Exception ex, LuaCsMessageOrigin origin) { - string errorString = ""; - switch (ex) - { - case NetRuntimeException netRuntimeException: - if (netRuntimeException.DecoratedMessage == null) - { - errorString = netRuntimeException.ToString(); - } - else - { - // FIXME: netRuntimeException.ToString() doesn't print the InnerException's stack trace... - errorString = $"{netRuntimeException.DecoratedMessage}: {netRuntimeException}"; - } - break; - case InterpreterException interpreterException: - if (interpreterException.DecoratedMessage == null) - { - errorString = interpreterException.ToString(); - } - else - { - errorString = interpreterException.DecoratedMessage; - } - break; - default: - errorString = ex.StackTrace != null - ? ex.ToString() - : $"{ex}\n{Environment.StackTrace}"; - break; - } - - LogError(Environment.UserName + " " + errorString, origin); + GameMain.LuaCs.Logger.HandleException(ex); } public static void LogError(string message, LuaCsMessageOrigin origin) { - if (HideUserNames && !Environment.UserName.IsNullOrEmpty()) - { - message = message.Replace(Environment.UserName, "USERNAME"); - } - - switch (origin) - { - case LuaCsMessageOrigin.LuaCs: - case LuaCsMessageOrigin.Unknown: - LogError($"[{LogPrefix} ERROR] {message}"); - break; - case LuaCsMessageOrigin.LuaMod: - LogError($"[{LogPrefix} LUA ERROR] {message}"); - break; - case LuaCsMessageOrigin.CSharpMod: - LogError($"[{LogPrefix} CS ERROR] {message}"); - break; - } + GameMain.LuaCs.Logger.LogError(message); } public static void LogError(string message) { - Log($"{message}", Color.Red, ServerLog.MessageType.Error); + GameMain.LuaCs.Logger.LogError(message); } public static void LogMessage(string message, Color? serverColor = null, Color? clientColor = null) { - if (serverColor == null) { serverColor = Color.MediumPurple; } - if (clientColor == null) { clientColor = Color.Purple; } - -#if SERVER - Log(message, serverColor); -#else - Log(message, clientColor); -#endif + GameMain.LuaCs.Logger.LogMessage(message, serverColor, clientColor); } public static void Log(string message, Color? color = null, ServerLog.MessageType messageType = ServerLog.MessageType.ServerMessage) { - MessageLogger?.Invoke(message); - - DebugConsole.NewMessage(message, color); - -#if SERVER - void broadcastMessage(string m) - { - foreach (var client in GameMain.Server.ConnectedClients) - { - //if (client.ChatMsgQueue.Count > NetMaxMessages) - //{ - // If there's an error or message happening many times per second (inside Update loop for example) - // we will need to discart some messages so the client doesn't get overloaded by all - // those net messages. - // continue; - //} - - ChatMessage consoleMessage = ChatMessage.Create("", m, ChatMessageType.Console, null, textColor: color); - GameMain.Server.SendDirectChatMessage(consoleMessage, client); - - if (!GameMain.Server.ServerSettings.SaveServerLogs || !client.HasPermission(ClientPermissions.ServerLog)) - { - continue; - } - - ChatMessage logMessage = ChatMessage.Create(messageType.ToString(), "[LuaCs] " + m, ChatMessageType.ServerLog, null); - GameMain.Server.SendDirectChatMessage(logMessage, client); - } - } - - if (GameMain.Server != null) - { - if (GameMain.Server.ServerSettings.SaveServerLogs) - { - string logMessage = "[LuaCs] " + message; - GameMain.Server.ServerSettings.ServerLog.WriteLine(logMessage, messageType, false); - - if (!lockLog) - { - lockLog = true; - GameMain.LuaCs?.Hook?.Call("serverLog", logMessage, messageType); - lockLog = false; - } - } - - for (int i = 0; i < message.Length; i += NetMaxLength) - { - string subStr = message.Substring(i, Math.Min(1024, message.Length - i)); - - broadcastMessage(subStr); - } - } -#endif + GameMain.LuaCs.Logger.Log(message, color, messageType); } } partial class LuaCsSetup { // Compatibility with cs mods that use this method. - public static void PrintLuaError(object message) => LuaCsLogger.LogError($"{message}", LuaCsMessageOrigin.LuaMod); - public static void PrintCsError(object message) => LuaCsLogger.LogError($"{message}", LuaCsMessageOrigin.CSharpMod); - public static void PrintGenericError(object message) => LuaCsLogger.LogError($"{message}", LuaCsMessageOrigin.LuaCs); + public static void PrintLuaError(object message) => GameMain.LuaCs.Logger.LogError($"{message}"); + public static void PrintCsError(object message) => GameMain.LuaCs.Logger.LogError($"{message}"); + public static void PrintGenericError(object message) => GameMain.LuaCs.Logger.LogError($"{message}"); - internal void PrintMessage(object message) => LuaCsLogger.LogMessage($"{message}"); + internal void PrintMessage(object message) => GameMain.LuaCs.Logger.LogMessage($"{message}"); - public static void PrintCsMessage(object message) => LuaCsLogger.LogMessage($"{message}"); + public static void PrintCsMessage(object message) => GameMain.LuaCs.Logger.LogMessage($"{message}"); - internal void HandleException(Exception ex, LuaCsMessageOrigin origin) => LuaCsLogger.HandleException(ex, origin); + internal void HandleException(Exception ex, LuaCsMessageOrigin origin) => GameMain.LuaCs.Logger.HandleException(ex); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsTimer.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsTimer.cs index 6d12bd068..d888c4597 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsTimer.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsTimer.cs @@ -57,10 +57,12 @@ namespace Barotrauma private List timedActions = new List(); private readonly IEventService _eventService; + private readonly ILoggerService _loggerService; - public LuaCsTimer(IEventService eventService) + public LuaCsTimer(IEventService eventService, ILoggerService loggerService) { _eventService = eventService; + _loggerService = loggerService; SubscribeToEvents(); } @@ -117,7 +119,7 @@ namespace Barotrauma } catch (Exception e) { - LuaCsLogger.HandleException(e, LuaCsMessageOrigin.CSharpMod); + _loggerService.HandleException(e); } timedActions.Remove(timedAction); From a50dce8fc28a81a733089d0c01741a3b35f8520a Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Fri, 13 Feb 2026 19:56:33 -0300 Subject: [PATCH 162/288] Forgot to remove old Hook Call --- .../ServerSource/Networking/GameServer.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index ddff2d5a1..4365a0527 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -3389,15 +3389,6 @@ namespace Barotrauma.Networking GameMain.GameSession.EndRound(endMessage); } TraitorManager.TraitorResults? traitorResults = traitorManager?.GetEndResults() ?? null; - var result = GameMain.LuaCs.Hook.Call>("roundEnd"); - if (result != null) - { - foreach (var data in result) - { - if (data is TraitorManager.TraitorResults traitorResultData) { traitorResults = traitorResultData; } - if (data is string endMessageData) { endMessage = endMessageData; } - } - } EndRoundTimer = 0.0f; From 13bfffa777598172f82b7fbe3878fdc29ae4cf76 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 15 Feb 2026 16:57:16 -0300 Subject: [PATCH 163/288] More events moved --- .../ServerSource/Networking/GameServer.cs | 14 ---- .../Items/Components/Signal/Connection.cs | 4 - .../SharedSource/LuaCs/IEvents.cs | 74 ++++++++++++++++++- .../_Services/HarmonyEventPatchesService.cs | 45 ++++++++++- .../_Services/LuaScriptManagementService.cs | 6 ++ 5 files changed, 123 insertions(+), 20 deletions(-) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 4365a0527..90e224ccc 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -339,8 +339,6 @@ namespace Barotrauma.Networking SendConsoleMessage("Granted all permissions to " + newClient.Name + ".", newClient); } - GameMain.LuaCs.Hook.Call("client.connected", newClient); - SendChatMessage($"ServerMessage.JoinedServer~[client]={ClientLogName(newClient)}", ChatMessageType.Server, changeType: PlayerConnectionChangeType.Joined); ServerSettings.ServerDetailsChanged = true; @@ -2303,7 +2301,6 @@ namespace Barotrauma.Networking segmentTable.StartNewSegment(ServerNetSegment.ClientList); outmsg.WriteUInt16(LastClientListUpdateID); - GameMain.LuaCs.Hook.Call("writeClientList", c, outmsg); outmsg.WriteByte((byte)Team1Count); outmsg.WriteByte((byte)Team2Count); @@ -2329,13 +2326,6 @@ namespace Barotrauma.Networking IsOwner = client.Connection == OwnerConnection, IsDownloading = FileSender.ActiveTransfers.Any(t => t.Connection == client.Connection) }; - - var result = GameMain.LuaCs.Hook.Call("writeClientList.modifyTempClientData", c, client, tempClientData, outmsg); - - if (result != null) - { - tempClientData = result.Value; - } outmsg.WriteNetSerializableStruct(tempClientData); outmsg.WritePadBits(); @@ -3725,8 +3715,6 @@ namespace Barotrauma.Networking { if (client == null) return; - GameMain.LuaCs.Hook.Call("client.disconnected", client); - if (client.Character != null) { client.Character.ClientDisconnected = true; @@ -4660,8 +4648,6 @@ namespace Barotrauma.Networking $"No suitable jobs available for {c.Name} (karma {c.Karma}). Assigning a random job: {c.AssignedJob.Prefab.Name}."); } } - - GameMain.LuaCs.Hook.Call("jobsAssigned", unassigned); } public void AssignBotJobs(List bots, CharacterTeamType teamID, bool isPvP) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs index e14929804..deabc428b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs @@ -350,15 +350,11 @@ namespace Barotrauma.Items.Components wire.RegisterSignal(signal, source: this); #endif SendSignalIntoConnection(signal, recipient); - GameMain.LuaCs.Hook.Call("signalReceived", signal, recipient); - GameMain.LuaCs.Hook.Call("signalReceived." + recipient.item.Prefab.Identifier, signal, recipient); } foreach (CircuitBoxConnection connection in CircuitBoxConnections) { connection.ReceiveSignal(signal); - GameMain.LuaCs.Hook.Call("signalReceived", signal, connection.Connection); - GameMain.LuaCs.Hook.Call("signalReceived." + connection.Connection.Item.Prefab.Identifier, signal, connection); } enumeratingWires = false; foreach (var removedWire in removedWires) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index a392928f7..391ac742f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using Barotrauma.Items.Components; using Barotrauma.LuaCs.Data; using Barotrauma.Networking; @@ -330,6 +331,26 @@ public interface IEventDrawUpdate : IEvent } } +interface IEventSignalReceived : IEvent +{ + void OnSignalReceived(Signal signal, Connection connection); + + static IEventSignalReceived IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventSignalReceived + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnSignalReceived(Signal signal, Connection connection) + { + LuaFuncs[nameof(OnSignalReceived)](signal, connection); + } + } +} + #endregion #region Networking @@ -342,7 +363,7 @@ public interface IEventClientRawNetMessageReceived : IEvent -/// Called when a client connects to the server and has loaded into the lobby. +/// Called when a client connects to the server. /// interface IEventClientConnected : IEvent { @@ -367,6 +388,57 @@ interface IEventClientConnected : IEvent } } } + +/// +/// Called when a client disconnects from the server. +/// +interface IEventClientDisconnected : IEvent +{ + /// + /// Called when a client connects to the server. + /// + /// The connecting client. + void OnClientDisconnected(Client client); + + static IEventClientDisconnected IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventClientDisconnected + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnClientDisconnected(Client client) + { + LuaFuncs[nameof(OnClientDisconnected)](client); + } + } +} + +interface IEventJobsAssigned : IEvent +{ + /// + /// Called when a client connects to the server. + /// + /// The connecting client. + void OnJobsAssigned(IReadOnlyList unassignedClients); + + static IEventJobsAssigned IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventJobsAssigned + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnJobsAssigned(IReadOnlyList unassignedClients) + { + LuaFuncs[nameof(OnJobsAssigned)](unassignedClients); + } + } +} #endif #endregion diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs index 5054bc0ba..e3907ca72 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs @@ -1,4 +1,5 @@ -using Barotrauma.LuaCs; +using Barotrauma.Items.Components; +using Barotrauma.LuaCs; using Barotrauma.LuaCs.Events; using Barotrauma.Networking; using HarmonyLib; @@ -126,6 +127,28 @@ internal class HarmonyEventPatchesService : IService _eventService.PublishEvent(x => x.OnReceivedClientNetMessage(inc, header, sender)); inc.BitPosition -= 8; // rewind so the game can read the message } + + [HarmonyPatch(typeof(GameServer), "OnInitializationComplete"), HarmonyPostfix] + public static void GameServer_OnInitializationComplete_Post(GameServer __instance) + { + Client client = __instance.ConnectedClients.LastOrDefault(); + if (client == null) { return; } + _eventService.PublishEvent(x => x.OnClientConnected(client)); + } + + [HarmonyPatch(typeof(GameServer), nameof(GameServer.DisconnectClient), new Type[] { typeof(Client), typeof(PeerDisconnectPacket) }), HarmonyPrefix] + public static void GameServer_DisconnectClient_Pre(Client client, PeerDisconnectPacket peerDisconnectPacket) + { + if (client == null) { return; } + + _eventService.PublishEvent(x => x.OnClientDisconnected(client)); + } + + [HarmonyPatch(typeof(GameServer), nameof(GameServer.AssignJobs)), HarmonyPostfix] + public static void GameServer_AssignJobs_Post(List unassigned) + { + _eventService.PublishEvent(x => x.OnJobsAssigned(unassigned)); + } #endif [HarmonyPatch(typeof(Character), nameof(Character.Create), new[] { @@ -163,6 +186,26 @@ internal class HarmonyEventPatchesService : IService _eventService.PublishEvent(x => x.OnAfflictionUpdate(__instance, characterHealth, targetLimb, deltaTime)); } + [HarmonyPatch(typeof(Connection), nameof(Connection.SendSignal)), HarmonyPostfix] + public static void Connection_SendSignal_Post(Connection __instance, Signal signal) + { + foreach (var wire in __instance.Wires) + { + Connection recipient = wire.OtherConnection(__instance); + if (recipient == null) { continue; } + if (recipient.Item == __instance.Item || signal.source?.LastSentSignalRecipients.LastOrDefault() == recipient) { continue; } + + _eventService.PublishEvent(x => x.OnSignalReceived(signal, recipient)); + _eventService.Call("signalReceived." + recipient.Item.Prefab.Identifier, signal, recipient); + } + + foreach (CircuitBoxConnection connection in __instance.CircuitBoxConnections) + { + _eventService.PublishEvent(x => x.OnSignalReceived(signal, connection.Connection)); + _eventService.Call("signalReceived." + connection.Connection.Item.Prefab.Identifier, signal, connection.Connection); + } + } + public void Dispose() { IsDisposed = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 13d339b1e..27ce50979 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -183,7 +183,13 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _eventService.RegisterLuaEventAlias("roundEnd", nameof(IEventRoundEnded.OnRoundEnd)); _eventService.RegisterLuaEventAlias("missionsEnded", nameof(IEventMissionsEnded.OnMissionsEnded)); + _eventService.RegisterLuaEventAlias("signalReceived", nameof(IEventSignalReceived.OnSignalReceived)); +#if SERVER + _eventService.RegisterLuaEventAlias("client.connected", nameof(IEventClientConnected.OnClientConnected)); + _eventService.RegisterLuaEventAlias("client.disconnected", nameof(IEventClientDisconnected.OnClientDisconnected)); + _eventService.RegisterLuaEventAlias("jobsAssigned", nameof(IEventJobsAssigned.OnJobsAssigned)); +#endif } private void SetupEnvironment(bool enableSandbox) From f70251fa3b34c494058a879a2a5f7e598d03aa03 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 15 Feb 2026 17:58:25 -0300 Subject: [PATCH 164/288] Event pain --- .../AI/Objectives/AIObjectiveManager.cs | 4 - .../SharedSource/Characters/Character.cs | 6 - .../SharedSource/Items/Inventory.cs | 10 - .../SharedSource/Items/Item.cs | 15 -- .../SharedSource/LuaCs/IEvents.cs | 190 +++++++++++++++++- .../_Services/HarmonyEventPatchesService.cs | 99 +++++++++ .../_Services/LuaScriptManagementService.cs | 9 + 7 files changed, 294 insertions(+), 39 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs index 83e475c37..23aea4234 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveManager.cs @@ -103,10 +103,6 @@ namespace Barotrauma public void AddObjective(T objective) where T : AIObjective { - var result = GameMain.LuaCs.Hook.Call("AI.addObjective", this, objective); - - if (result != null && result.Value) return; - if (objective == null) { #if DEBUG diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 3436ec8a6..198ff8d93 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -4605,12 +4605,6 @@ namespace Barotrauma public AttackResult DamageLimb(Vector2 worldPosition, Limb hitLimb, IEnumerable afflictions, float stun, bool playSound, Vector2 attackImpulse, Character attacker = null, float damageMultiplier = 1, bool allowStacking = true, float penetration = 0f, bool shouldImplode = false, bool ignoreDamageOverlay = false, bool recalculateVitality = true) { if (Removed) { return new AttackResult(); } - - AttackResult? retAttackResult = GameMain.LuaCs.Hook.Call("character.damageLimb", this, worldPosition, hitLimb, afflictions, stun, playSound, attackImpulse, attacker, damageMultiplier, allowStacking, penetration, shouldImplode); - if (retAttackResult != null) - { - return retAttackResult.Value; - } SetStun(stun); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index d1be5145e..ce4da3bda 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -651,11 +651,6 @@ namespace Barotrauma return; } - var should = GameMain.LuaCs.Hook.Call("inventoryPutItem", this, item, user, i, removeItem); - - if (should != null && should.Value) - return; - if (Owner == null) { return; } Inventory prevInventory = item.ParentInventory; @@ -765,11 +760,6 @@ namespace Barotrauma if (slots[index].Items.Any(it => !it.IsInteractable(user))) { return false; } if (!AllowSwappingContainedItems) { return false; } - var should = GameMain.LuaCs.Hook.Call("inventoryItemSwap", this, item, user, index, swapWholeStack); - - if (should != null) - return should.Value; - //swap to InvSlotType.Any if possible Inventory otherInventory = item.ParentInventory; bool otherIsEquipped = false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index e61097a03..f74a46541 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -1411,8 +1411,6 @@ namespace Barotrauma if (Components.Any(ic => ic is Wire) && Components.All(ic => ic is Wire || ic is Holdable)) { isWire = true; } if (HasTag(Barotrauma.Tags.LogicItem)) { isLogic = true; } - GameMain.LuaCs.Hook.Call("item.created", this); - ApplyStatusEffects(ActionType.OnSpawn, 1.0f); // Set max condition multipliers from campaign settings for RecalculateConditionValues() @@ -3364,10 +3362,6 @@ namespace Barotrauma } if (condition <= 0.0f) { return; } - - var should = GameMain.LuaCs.Hook.Call("item.use", new object[] { this, user, targetLimb, useTarget }); - - if (should != null && should.Value) { return; } bool remove = false; @@ -3400,11 +3394,6 @@ namespace Barotrauma { if (condition <= 0.0f) { return; } - var should = GameMain.LuaCs.Hook.Call("item.secondaryUse", this, character); - - if (should != null && should.Value) - return; - bool remove = false; foreach (ItemComponent ic in components) @@ -4621,8 +4610,6 @@ namespace Barotrauma body.Remove(); body = null; } - - GameMain.LuaCs.Hook.Call("item.removed", this); } public override void Remove() @@ -4707,8 +4694,6 @@ namespace Barotrauma } RemoveProjSpecific(); - - GameMain.LuaCs.Hook.Call("item.removed", this); } private void RemoveFromLists() diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index 391ac742f..662d7742f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -1,9 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using Barotrauma.Items.Components; +using Barotrauma.Items.Components; using Barotrauma.LuaCs.Data; using Barotrauma.Networking; +using Microsoft.Xna.Framework; +using Steamworks.Ugc; +using System; +using System.Collections.Generic; +using System.Reflection; namespace Barotrauma.LuaCs.Events; @@ -351,6 +353,186 @@ interface IEventSignalReceived : IEvent } } +interface IEventItemCreated : IEvent +{ + void OnItemCreated(Item item); + + static IEventItemCreated IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventItemCreated + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnItemCreated(Item item) + { + LuaFuncs[nameof(OnItemCreated)](item); + } + } +} + +interface IEventItemRemoved : IEvent +{ + void OnItemRemoved(Item item); + + static IEventItemRemoved IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventItemRemoved + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnItemRemoved(Item item) + { + LuaFuncs[nameof(OnItemRemoved)](item); + } + } +} + +interface IEventItemUse : IEvent +{ + bool? OnItemUsed(Item item, Character user, Limb targetLimb, Entity useTarget); + + static IEventItemUse IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventItemUse + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public bool? OnItemUsed(Item item, Character user, Limb targetLimb, Entity useTarget) + { + var result = LuaFuncs[nameof(OnItemUsed)](item, user, targetLimb, useTarget); + if (result is bool b) + { + return b; + } + else + { + return null; + } + } + } +} + +interface IEventItemSecondaryUse : IEvent +{ + bool? OnItemSecondaryUsed(Item item, Character user); + + static IEventItemSecondaryUse IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventItemSecondaryUse + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public bool? OnItemSecondaryUsed(Item item, Character user) + { + var result = LuaFuncs[nameof(OnItemSecondaryUsed)](item, user); + if (result is bool b) + { + return b; + } + else + { + return null; + } + } + } +} + +interface IEventCharacterDamageLimb : IEvent +{ + AttackResult? OnCharacterDamageLimb(Character character, Vector2 worldPosition, Limb hitLimb, IEnumerable afflictions, float stun, bool playSound, Vector2 attackImpulse, Character attacker = null, float damageMultiplier = 1, bool allowStacking = true, float penetration = 0f, bool shouldImplode = false); + + static IEventCharacterDamageLimb IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventCharacterDamageLimb + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public AttackResult? OnCharacterDamageLimb(Character character, Vector2 worldPosition, Limb hitLimb, IEnumerable afflictions, float stun, bool playSound, Vector2 attackImpulse, Character attacker = null, float damageMultiplier = 1, bool allowStacking = true, float penetration = 0f, bool shouldImplode = false) + { + var result = LuaFuncs[nameof(OnCharacterDamageLimb)](character, worldPosition, hitLimb, afflictions, stun, playSound, attackImpulse, attacker, damageMultiplier, allowStacking, penetration, shouldImplode); + if (result is AttackResult attackResult) + { + return attackResult; + } + else + { + return null; + } + } + } +} + +interface IEventInventoryPutItem : IEvent +{ + bool? OnInventoryPutItem(Inventory inventory, Item item, Character user, int i, bool removeItem); + + static IEventInventoryPutItem IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventInventoryPutItem + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public bool? OnInventoryPutItem(Inventory inventory, Item item, Character user, int i, bool removeItem) + { + var result = LuaFuncs[nameof(OnInventoryPutItem)](inventory, item, user, i, removeItem); + if (result is bool b) + { + return b; + } + else + { + return null; + } + } + } +} + +interface IEventInventoryItemSwap : IEvent +{ + bool? OnInventoryItemSwap(Inventory inventory, Item item, Character user, int i, bool swapWholeStack); + + static IEventInventoryItemSwap IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventInventoryItemSwap + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public bool? OnInventoryItemSwap(Inventory inventory, Item item, Character user, int i, bool swapWholeStack) + { + var result = LuaFuncs[nameof(OnInventoryItemSwap)](inventory, item, user, i, swapWholeStack); + if (result is bool b) + { + return b; + } + else + { + return null; + } + } + } +} + #endregion #region Networking diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs index e3907ca72..75b6a4e04 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs @@ -180,6 +180,20 @@ internal class HarmonyEventPatchesService : IService _eventService.PublishEvent(x => x.OnGiveCharacterJobItems(__instance, spawnPoint, isPvPMode)); } + [HarmonyPatch(typeof(Character), nameof(Character.DamageLimb)), HarmonyPrefix] + public static bool Character_DamageLimb_Pre(AttackResult __result, Character __instance, Vector2 worldPosition, Limb hitLimb, IEnumerable afflictions, float stun, bool playSound, Vector2 attackImpulse, Character attacker, float damageMultiplier, bool allowStacking, float penetration, bool shouldImplode, bool ignoreDamageOverlay, bool recalculateVitality) + { + AttackResult? result = null; + _eventService.PublishEvent(x => result = x.OnCharacterDamageLimb(__instance, worldPosition, hitLimb, afflictions, stun, playSound, attackImpulse, attacker, damageMultiplier, allowStacking, penetration, shouldImplode)); + if (result != null) + { + __result = (AttackResult)result; + return false; // skip + } + + return true; + } + [HarmonyPatch(typeof(Affliction), nameof(Affliction.Update)), HarmonyPostfix] public static void Affliction_Update_Post(Affliction __instance, CharacterHealth characterHealth, Limb targetLimb, float deltaTime) { @@ -206,6 +220,91 @@ internal class HarmonyEventPatchesService : IService } } + [HarmonyPatch(typeof(Item), MethodType.Constructor, new Type[] { typeof(Rectangle), typeof(ItemPrefab), typeof(Submarine), typeof(bool), typeof(ushort) }), HarmonyPostfix] + public static void Item_Ctor_Post(Item __instance) + { + _eventService.PublishEvent(x => x.OnItemCreated(__instance)); + } + + [HarmonyPatch(typeof(Item), nameof(Item.Remove)), HarmonyPostfix] + public static void Item_Remove_Post(Item __instance) + { + _eventService.PublishEvent(x => x.OnItemRemoved(__instance)); + } + + [HarmonyPatch(typeof(Item), nameof(Item.Remove)), HarmonyPostfix] + public static void Item_ShallowRemove_Post(Item __instance) + { + _eventService.PublishEvent(x => x.OnItemRemoved(__instance)); + } + + [HarmonyPatch(typeof(Item), nameof(Item.Use)), HarmonyPrefix] + public static bool Item_Use_Pre(Item __instance, Character user, Limb targetLimb, Entity useTarget) + { + if (__instance.RequireAimToUse && (user == null || !user.IsKeyDown(InputType.Aim))) + { + return true; + } + + if (__instance.Condition <= 0.0f) { return true; } + + bool? result = null; + _eventService.PublishEvent(x => result = x.OnItemUsed(__instance, user, targetLimb, useTarget)); + if (result == true) + { + return false; // skip + } + + return true; + } + + [HarmonyPatch(typeof(Item), nameof(Item.SecondaryUse)), HarmonyPrefix] + public static bool Item_SecondaryUse_Pre(Item __instance, Character character) + { + if (__instance.Condition <= 0.0f) { return true; } + + bool? result = null; + _eventService.PublishEvent(x => result = x.OnItemSecondaryUsed(__instance, character)); + if (result == true) + { + return false; // skip + } + + return true; + } + + [HarmonyPatch(typeof(Inventory), "PutItem"), HarmonyPrefix] + public static bool Inventory_PutItem_Prefix(Inventory __instance, Item item, int i, Character user, bool removeItem) + { + bool? result = null; + _eventService.PublishEvent(x => result = x.OnInventoryPutItem(__instance, item, user, i, removeItem)); + if (result == true) + { + return false; // skip + } + + return true; + } + + [HarmonyPatch(typeof(Inventory), "TrySwapping"), HarmonyPrefix] + public static bool Inventory_TrySwapping_Prefix(Inventory __instance, Item item, int index, Character user, bool swapWholeStack, ref bool __result) + { + // uncomment when we are plugin + // if (item?.ParentInventory == null || !__instance.slots[index].Any()) { return false; } + // if (__instance.slots[index].Items.Any(it => !it.IsInteractable(user))) { return false; } + if (!__instance.AllowSwappingContainedItems) { return false; } + + bool? result = null; + _eventService.PublishEvent(x => result = x.OnInventoryItemSwap(__instance, item, user, index, swapWholeStack)); + if (result != null) + { + __result = (bool)result; + return false; // skip + } + + return true; + } + public void Dispose() { IsDisposed = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 27ce50979..d0267f7d3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -177,6 +177,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _eventService.RegisterLuaEventAlias("character.created", nameof(IEventCharacterCreated.OnCharacterCreated)); _eventService.RegisterLuaEventAlias("character.death", nameof(IEventCharacterDeath.OnCharacterDeath)); + _eventService.RegisterLuaEventAlias("character.damageLimb", nameof(IEventCharacterDamageLimb.OnCharacterDamageLimb)); _eventService.RegisterLuaEventAlias("character.giveJobItems", nameof(IEventGiveCharacterJobItems.OnGiveCharacterJobItems)); _eventService.RegisterLuaEventAlias("roundStart", nameof(IEventRoundStarted.OnRoundStart)); @@ -185,6 +186,14 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _eventService.RegisterLuaEventAlias("signalReceived", nameof(IEventSignalReceived.OnSignalReceived)); + _eventService.RegisterLuaEventAlias("item.created", nameof(IEventItemCreated.OnItemCreated)); + _eventService.RegisterLuaEventAlias("item.removed", nameof(IEventItemRemoved.OnItemRemoved)); + _eventService.RegisterLuaEventAlias("item.use", nameof(IEventItemUse.OnItemUsed)); + _eventService.RegisterLuaEventAlias("item.secondaryUse", nameof(IEventItemSecondaryUse.OnItemSecondaryUsed)); + + _eventService.RegisterLuaEventAlias("inventoryPutItem", nameof(IEventInventoryPutItem.OnInventoryPutItem)); + _eventService.RegisterLuaEventAlias("inventoryItemSwap", nameof(IEventInventoryItemSwap.OnInventoryItemSwap)); + #if SERVER _eventService.RegisterLuaEventAlias("client.connected", nameof(IEventClientConnected.OnClientConnected)); _eventService.RegisterLuaEventAlias("client.disconnected", nameof(IEventClientDisconnected.OnClientDisconnected)); From 6ac49a10f48c2d151ce06d5da1342afbba43393a Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 15 Feb 2026 06:01:59 -0500 Subject: [PATCH 165/288] - XML GUI Asset service implemented (alpha, requires testing). --- .../LuaCs/Data/StylesResources.cs | 17 + .../ClientSource/LuaCs/LuaCsSetup.cs | 10 + .../LuaCs/Services/IUIStylesCollection.cs | 75 ++++ .../LuaCs/Services/IUIStylesService.cs | 57 +++ .../ModConfigStylesFileParserService.cs | 44 +++ .../LuaCs/Services/UIStylesCollection.cs | 239 ++++++++++++ .../LuaCs/Services/UIStylesService.cs | 350 ++++++++++++++++++ .../WindowsClient.csproj.DotSettings | 2 + .../Data/DataInterfaceImplementations.cs | 2 +- .../SharedSource/LuaCs/Data/IModConfigInfo.cs | 2 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 4 + .../_Services/ModConfigFileParserService.cs | 2 +- .../LuaCs/_Services/ModConfigService.cs | 39 +- .../_Services/PackageManagementService.cs | 45 ++- 14 files changed, 879 insertions(+), 9 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/StylesResources.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesCollection.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesService.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModConfigStylesFileParserService.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesCollection.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesService.cs create mode 100644 Barotrauma/BarotraumaClient/WindowsClient.csproj.DotSettings diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/StylesResources.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/StylesResources.cs new file mode 100644 index 000000000..a56d11112 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/StylesResources.cs @@ -0,0 +1,17 @@ +using System.Collections.Immutable; + +namespace Barotrauma.LuaCs.Data; + +public interface IStylesResourceInfo : IBaseResourceInfo { } + +public record StylesResourceInfo : BaseResourceInfo, IStylesResourceInfo { } + +public partial interface IModConfigInfo +{ + public ImmutableArray Styles { get; } +} + +public partial record ModConfigInfo +{ + public ImmutableArray Styles { get; init; } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index b24a584eb..d1fb680a0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using Barotrauma.CharacterEditor; using Barotrauma.LuaCs; +using Barotrauma.LuaCs.Data; // ReSharper disable ObjectCreationAsStatement @@ -66,6 +67,15 @@ namespace Barotrauma return isCsValueChanged; } + private void SetupServicesProviderClient(IServicesProvider serviceProvider) + { + serviceProvider.RegisterServiceType(ServiceLifetime.Singleton); + // supplied via factory + //serviceProvider.RegisterServiceType(ServiceLifetime.Transient); + serviceProvider.RegisterServiceType, ModConfigFileParserService>(ServiceLifetime.Transient); + serviceProvider.RegisterServiceType(ServiceLifetime.Transient); + } + /// /// Handles changes in game states tracked by screen changes. /// diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesCollection.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesCollection.cs new file mode 100644 index 000000000..3a8ec99d8 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesCollection.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using Barotrauma.LuaCs.Data; +using FluentResults; + +namespace Barotrauma.LuaCs; + +public interface IUIStylesCollection : IService +{ + public interface IFactory : IService + { + /// + /// Returns a new for-each in the given + /// or empty is none. + /// + /// + /// + /// + IEnumerable CreateInstance(IStylesResourceInfo info, IStorageService storageService); + } + + /// + /// The assigned/target for this collection. + /// + public ContentPath Path { get; } + + /// + /// Gets the with the given name. + /// + /// + /// + public Result GetFont(string name); + /// + /// Gets the with the given name. + /// + /// + /// + public Result GetSprite(string name); + /// + /// Gets the with the given name. + /// + /// + /// + public Result GetSpriteSheet(string name); + /// + /// Gets the with the given name. + /// + /// + /// + public Result GetCursor(string name); + /// + /// Gets the with the given name. + /// + /// + /// + public Result GetColor(string name); + + #region BAROTRAUMA.UISTYLEFILE + + /// + /// Definition of + /// + internal void LoadFile(); + /// + /// Definition of + /// + internal void UnloadFile(); + /// + /// Definition of + /// + internal void Sort(); + + #endregion + +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesService.cs new file mode 100644 index 000000000..4d09f1284 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesService.cs @@ -0,0 +1,57 @@ +using System.Collections.Immutable; +using Barotrauma.LuaCs.Data; +using FluentResults; + +namespace Barotrauma.LuaCs; + +public interface IUIStylesService : IReusableService +{ + /// + /// Gets the first loaded . + /// + /// The target + /// The targets as specified in the ModConfig.xml. + /// The asset's name as specified in the styles XML file. + /// A indicating success, and the target if succeeded. + public Result GetColor(ContentPackage package, string internalName, string assetName); + /// + /// Gets the loaded . + /// + /// The target + /// The targets as specified in the ModConfig.xml. + /// The asset's name as specified in the styles XML file. + /// A indicating success, and the target if succeeded. + public Result GetCursor(ContentPackage package, string internalName, string assetName); + /// + /// Gets the loaded . + /// + /// The target + /// The targets as specified in the ModConfig.xml. + /// The asset's name as specified in the styles XML file. + /// A indicating success, and the target if succeeded. + public Result GetFont(ContentPackage package, string internalName, string assetName); + /// + /// Gets the loaded . + /// + /// The target + /// The targets as specified in the ModConfig.xml. + /// The asset's name as specified in the styles XML file. + /// A indicating success, and the target if succeeded. + public Result GetSprite(ContentPackage package, string internalName, string assetName); + /// + /// Gets the loaded . + /// + /// The target + /// The targets as specified in the ModConfig.xml. + /// The asset's name as specified in the styles XML file. + /// A indicating success, and the target if succeeded. + public Result GetSpriteSheet(ContentPackage package, string internalName, string assetName); + + public FluentResults.Result LoadAssets(ImmutableArray resources); + + public FluentResults.Result UnloadPackages(ImmutableArray packages); + + public FluentResults.Result UnloadPackage(ContentPackage package); + + public FluentResults.Result UnloadAllPackages(); +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModConfigStylesFileParserService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModConfigStylesFileParserService.cs new file mode 100644 index 000000000..ade7f4305 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModConfigStylesFileParserService.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Barotrauma.LuaCs.Data; +using FluentResults; + +namespace Barotrauma.LuaCs; + +public sealed partial class ModConfigFileParserService : + IParserServiceAsync +{ + async Task> IParserServiceAsync.TryParseResourceAsync(ResourceParserInfo src) + { + using var lck = await _operationsLock.AcquireReaderLock(); + IService.CheckDisposed(this); + + if (CheckThrowNullRefs(src, "Style") is { IsFailed: true } fail) + return fail; + + var runtimeEnv = GetRuntimeEnvironment(src.Element); + var fileResults = await UnsafeGetCheckedFiles(src.Element, src.Owner, ".xml"); + + if (fileResults.IsFailed) + return FluentResults.Result.Fail(fileResults.Errors); + + return new StylesResourceInfo() + { + SupportedPlatforms = runtimeEnv.Platform, + SupportedTargets = Target.Client, // clientside only + LoadPriority = src.Element.GetAttributeInt("LoadPriority", 0), + FilePaths = fileResults.Value, + Optional = src.Element.GetAttributeBool("Optional", false), + InternalName = src.Element.GetAttributeString("Name", string.Empty), + OwnerPackage = src.Owner, + RequiredPackages = src.Required, + IncompatiblePackages = src.Incompatible + }; + } + + public async Task>> TryParseResourcesAsync(IEnumerable sources) + { + return await this.TryParseGenericResourcesAsync(sources); + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesCollection.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesCollection.cs new file mode 100644 index 000000000..6bf134d8c --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesCollection.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using Barotrauma.Extensions; +using Barotrauma.LuaCs.Data; +using FluentResults; +using Microsoft.Toolkit.Diagnostics; + +namespace Barotrauma.LuaCs; + +public class UIStylesCollection : HashlessFile, IUIStylesCollection +{ + public class Factory : IUIStylesCollection.IFactory + { + public IEnumerable CreateInstance(IStylesResourceInfo info, IStorageService storageService) + { + Guard.IsNotNull(info, nameof(info)); + Guard.IsNotNull(info.OwnerPackage, nameof(info.OwnerPackage)); + if (info.FilePaths.IsDefaultOrEmpty) + { + return ImmutableArray.Empty; + } + + var builder = ImmutableArray.CreateBuilder(); + foreach (var contentPath in info.FilePaths) + { + builder.Add(new UIStylesCollection(contentPath, storageService)); + } + return builder.ToImmutable(); + } + + public void Dispose() + { + //ignore, stateless service + } + + public bool IsDisposed => false; + } + + private readonly ConcurrentDictionary _fonts = new(); + private readonly ConcurrentDictionary _sprites = new(); + private readonly ConcurrentDictionary _spriteSheets = new(); + private readonly ConcurrentDictionary _cursors = new(); + private readonly ConcurrentDictionary _colors = new(); + + /// + /// Only for internal reference. + /// + private UIStyleFile _fakeFile; + + private IStorageService _storageService; + + public UIStylesCollection(ContentPath path, IStorageService storageService) : base(path.ContentPackage, path) + { + Guard.IsNotNull(path, nameof(path)); + Guard.IsNotNull(path.ContentPackage, nameof(path.ContentPackage)); + _storageService = storageService; + _fakeFile = new UIStyleFile(path.ContentPackage, path); + } + + public new ContentPath Path => base.Path; + + public Result GetFont(string name) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + if (_fonts.TryGetValue(name, out var asset)) + { + return asset; + } + + return FluentResults.Result.Fail($"{nameof(GetFont)}: Failed to find the font with the name '{name}'"); + } + + public Result GetSprite(string name) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + if (_sprites.TryGetValue(name, out var asset)) + { + return asset; + } + + return FluentResults.Result.Fail($"{nameof(GetSprite)}: Failed to find the sprite with the name '{name}'"); + } + + public Result GetSpriteSheet(string name) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + if (_spriteSheets.TryGetValue(name, out var asset)) + { + return asset; + } + + return FluentResults.Result.Fail($"{nameof(GetSpriteSheet)}: Failed to find the spritesheet with the name '{name}'"); + } + + public Result GetCursor(string name) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + if (_cursors.TryGetValue(name, out var asset)) + { + return asset; + } + + return FluentResults.Result.Fail($"{nameof(GetCursor)}: Failed to find the cursor with the name '{name}'"); + } + + public Result GetColor(string name) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + if (_colors.TryGetValue(name, out var asset)) + { + return asset; + } + + return FluentResults.Result.Fail($"{nameof(GetColor)}: Failed to find the color with the name '{name}'"); + } + + public override void LoadFile() + { + using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + if (_storageService.LoadPackageXml(Path) is not { IsSuccess: true } result) + { + DebugConsole.LogError($"Failed to load xml from {Path.FullPath}."); + ThrowHelper.ThrowArgumentException($"Failed to load xml from {Path.FullPath}."); + return; + } + + var root = result.Value.Root?.FromPackage(Path.ContentPackage); + if (root is null) + { + return; + } + + var styleElement = root.Name.LocalName.ToLowerInvariant() == "style" ? root : root.GetChildElement("style"); + if (styleElement is null) + return; + + var childElements = styleElement.GetChildElements("Font"); + if (childElements is not null) + AddToList(_fonts, childElements, _fakeFile); + + childElements = styleElement.GetChildElements("Sprite"); + if (childElements is not null) + AddToList(_sprites, childElements, _fakeFile); + + childElements = styleElement.GetChildElements("Spritesheet"); + if (childElements is not null) + AddToList(_spriteSheets, childElements, _fakeFile); + + childElements = styleElement.GetChildElements("Cursor"); + if (childElements is not null) + AddToList(_cursors, childElements, _fakeFile); + + childElements = styleElement.GetChildElements("Color"); + if (childElements is not null) + AddToList(_colors, childElements, _fakeFile); + + void AddToList(ConcurrentDictionary dict, IEnumerable elem, UIStyleFile file) where T1 : GUISelector where T2 : GUIPrefab + { + foreach (ContentXElement prefabElement in elem) + { + string name = prefabElement.GetAttributeString("name", string.Empty); + if (name != string.Empty) + { + var prefab = (T2)Activator.CreateInstance(typeof(T2), new object[]{ prefabElement, file })!; + if (!dict.ContainsKey(name)) + dict[name] = (T1)Activator.CreateInstance(typeof(T1), new object[] { name })!; + dict[name].Prefabs.Add(prefab, false); + } + } + } + } + + public override void UnloadFile() + { + using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + _fonts.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + _sprites.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + _spriteSheets.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + _cursors.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + _colors.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + } + + public override void Sort() + { + using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + _fonts.Values.ForEach(p => p.Prefabs.Sort()); + _sprites.Values.ForEach(p => p.Prefabs.Sort()); + _spriteSheets.Values.ForEach(p => p.Prefabs.Sort()); + _cursors.Values.ForEach(p => p.Prefabs.Sort()); + _colors.Values.ForEach(p => p.Prefabs.Sort()); + } + + #region INTERNAL_DISPOSE + + private readonly AsyncReaderWriterLock _lock = new(); + + public void Dispose() + { + using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) + { + return; + } + + _fonts.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + _sprites.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + _spriteSheets.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + _cursors.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + _colors.Values.ForEach(p => p.Prefabs.RemoveByFile(_fakeFile)); + + _fonts.Clear(); + _sprites.Clear(); + _spriteSheets.Clear(); + _cursors.Clear(); + _colors.Clear(); + } + + private int _isDisposed; + public bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } + + #endregion +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesService.cs new file mode 100644 index 000000000..2508d7376 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesService.cs @@ -0,0 +1,350 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Immutable; +using System.Linq; +using Barotrauma.LuaCs.Data; +using FluentResults; +using Microsoft.Toolkit.Diagnostics; + +namespace Barotrauma.LuaCs; + +public class UIStylesService : IUIStylesService +{ + #region DISPOSAL + + public void Dispose() + { + using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) + { + return; + } + + foreach (var collection in _stylesCollections.Values.SelectMany(c => c)) + { + try + { + collection.Dispose(); + } + catch + { + //ignored + } + } + + _stylesCollections.Clear(); + _storageService.Dispose(); + _stylesCollectionFactory.Dispose(); + + _storageService = null; + _stylesCollectionFactory = null; + } + + private int _isDisposed = 0; + public bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } + public FluentResults.Result Reset() + { + using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + var result = FluentResults.Result.Ok(); + + foreach (var collection in _stylesCollections.Values.SelectMany(c => c)) + { + try + { + collection.Dispose(); + } + catch (Exception e) + { + result.WithError(new ExceptionalError(e)); + } + } + + _stylesCollections.Clear(); + + return result; + } + + private readonly AsyncReaderWriterLock _lock = new(); + + #endregion + + private IStorageService _storageService; + private IUIStylesCollection.IFactory _stylesCollectionFactory; + + private ConcurrentDictionary<(ContentPackage Package, string InternalName), ImmutableArray> + _stylesCollections = new(); + + public UIStylesService(IUIStylesCollection.IFactory stylesCollectionFactory, IStorageService storageService) + { + _stylesCollectionFactory = stylesCollectionFactory; + _storageService = storageService; + } + + public Result GetColor(ContentPackage package, string internalName, string assetName) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + Guard.IsNotNull(package, nameof(package)); + Guard.IsNotNullOrWhiteSpace(internalName, nameof(internalName)); + Guard.IsNotNullOrWhiteSpace(assetName, nameof(assetName)); + + if (!_stylesCollections.TryGetValue((package, internalName), out var collection) + || collection.IsDefaultOrEmpty) + { + return FluentResults.Result.Fail( + $"{nameof(UIStylesService)}: No styles loaded for [ContentPackage].[InternalName] of: [{package.Name}].[{internalName}]"); + } + + var failedResult = new FluentResults.Result(); + + foreach (var stylesCollection in collection) + { + var res = stylesCollection.GetColor(assetName); + if (res.IsSuccess) + { + return res; + } + + failedResult.WithErrors(res.Errors); + } + + return failedResult; + } + + public Result GetCursor(ContentPackage package, string internalName, string assetName) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + Guard.IsNotNull(package, nameof(package)); + Guard.IsNotNullOrWhiteSpace(internalName, nameof(internalName)); + Guard.IsNotNullOrWhiteSpace(assetName, nameof(assetName)); + + if (!_stylesCollections.TryGetValue((package, internalName), out var collection) + || collection.IsDefaultOrEmpty) + { + return FluentResults.Result.Fail( + $"{nameof(UIStylesService)}: No styles loaded for [ContentPackage].[InternalName] of: [{package.Name}].[{internalName}]"); + } + + var failedResult = new FluentResults.Result(); + + foreach (var stylesCollection in collection) + { + var res = stylesCollection.GetCursor(assetName); + if (res.IsSuccess) + { + return res; + } + + failedResult.WithErrors(res.Errors); + } + + return failedResult; + } + + public Result GetFont(ContentPackage package, string internalName, string assetName) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + Guard.IsNotNull(package, nameof(package)); + Guard.IsNotNullOrWhiteSpace(internalName, nameof(internalName)); + Guard.IsNotNullOrWhiteSpace(assetName, nameof(assetName)); + + if (!_stylesCollections.TryGetValue((package, internalName), out var collection) + || collection.IsDefaultOrEmpty) + { + return FluentResults.Result.Fail( + $"{nameof(UIStylesService)}: No styles loaded for [ContentPackage].[InternalName] of: [{package.Name}].[{internalName}]"); + } + + var failedResult = new FluentResults.Result(); + + foreach (var stylesCollection in collection) + { + var res = stylesCollection.GetFont(assetName); + if (res.IsSuccess) + { + return res; + } + + failedResult.WithErrors(res.Errors); + } + + return failedResult; + } + + public Result GetSprite(ContentPackage package, string internalName, string assetName) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + Guard.IsNotNull(package, nameof(package)); + Guard.IsNotNullOrWhiteSpace(internalName, nameof(internalName)); + Guard.IsNotNullOrWhiteSpace(assetName, nameof(assetName)); + + if (!_stylesCollections.TryGetValue((package, internalName), out var collection) + || collection.IsDefaultOrEmpty) + { + return FluentResults.Result.Fail( + $"{nameof(UIStylesService)}: No styles loaded for [ContentPackage].[InternalName] of: [{package.Name}].[{internalName}]"); + } + + var failedResult = new FluentResults.Result(); + + foreach (var stylesCollection in collection) + { + var res = stylesCollection.GetSprite(assetName); + if (res.IsSuccess) + { + return res; + } + + failedResult.WithErrors(res.Errors); + } + + return failedResult; + } + + public Result GetSpriteSheet(ContentPackage package, string internalName, string assetName) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + Guard.IsNotNull(package, nameof(package)); + Guard.IsNotNullOrWhiteSpace(internalName, nameof(internalName)); + Guard.IsNotNullOrWhiteSpace(assetName, nameof(assetName)); + + if (!_stylesCollections.TryGetValue((package, internalName), out var collection) + || collection.IsDefaultOrEmpty) + { + return FluentResults.Result.Fail( + $"{nameof(UIStylesService)}: No styles loaded for [ContentPackage].[InternalName] of: [{package.Name}].[{internalName}]"); + } + + var failedResult = new FluentResults.Result(); + + foreach (var stylesCollection in collection) + { + var res = stylesCollection.GetSpriteSheet(assetName); + if (res.IsSuccess) + { + return res; + } + + failedResult.WithErrors(res.Errors); + } + + return failedResult; + } + + public FluentResults.Result LoadAssets(ImmutableArray resources) + { + using var lck = _lock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + if (resources.IsDefaultOrEmpty) + { + ThrowHelper.ThrowArgumentNullException(nameof(resources)); + } + + var operationSuccess = FluentResults.Result.Ok(); + + foreach (var resource in resources) + { + var builder = ImmutableArray.CreateBuilder(); + if (_stylesCollections.TryGetValue((resource.OwnerPackage, resource.InternalName), out var collection)) + { + builder.AddRange(collection); + } + + try + { + var newCollections = _stylesCollectionFactory.CreateInstance(resource, _storageService).ToImmutableArray(); + foreach (var stylesCollection in newCollections) + { + stylesCollection.LoadFile(); + } + builder.AddRange(newCollections); + } + catch (Exception e) + { + operationSuccess.WithError(new ExceptionalError(e)); + continue; + } + + _stylesCollections[(resource.OwnerPackage, resource.InternalName)] = builder.ToImmutable(); + } + + return operationSuccess; + } + + public FluentResults.Result UnloadPackages(ImmutableArray packages) + { + using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + var toRemove = _stylesCollections + .Select(c => c.Key) + .Where(c => packages.Contains(c.Package)) + .ToImmutableArray(); + + var result = FluentResults.Result.Ok(); + + foreach (var key in toRemove) + { + if (_stylesCollections.TryRemove(key, out var collection) && !collection.IsDefaultOrEmpty) + { + foreach (var stylesCollection in collection) + { + try + { + stylesCollection.UnloadFile(); + } + catch (Exception e) + { + result.WithError(new ExceptionalError(e)); + } + } + } + } + + return result; + } + + public FluentResults.Result UnloadPackage(ContentPackage package) + { + // Yes, this is very cursed/inefficient. We don't care. + return UnloadPackages(new [] { package }.ToImmutableArray()); + } + + public FluentResults.Result UnloadAllPackages() + { + using var lck = _lock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + var result = FluentResults.Result.Ok(); + + foreach (var key in _stylesCollections.Keys.ToImmutableArray()) + { + if (_stylesCollections.TryRemove(key, out var collection) && !collection.IsDefaultOrEmpty) + { + foreach (var stylesCollection in collection) + { + try + { + stylesCollection.UnloadFile(); + } + catch (Exception e) + { + result.WithError(new ExceptionalError(e)); + } + } + } + } + + return result; + } +} diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj.DotSettings b/Barotrauma/BarotraumaClient/WindowsClient.csproj.DotSettings new file mode 100644 index 000000000..051269cc3 --- /dev/null +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs index 4a3ec7268..79c30579b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/DataInterfaceImplementations.cs @@ -15,7 +15,7 @@ namespace Barotrauma.LuaCs.Data; #region ModConfigurationInfo -public record ModConfigInfo : IModConfigInfo +public partial record ModConfigInfo : IModConfigInfo { public ContentPackage Package { get; init; } public ImmutableArray Assemblies { get; init; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs index 70cb2d6d7..c8740e51d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/IModConfigInfo.cs @@ -4,7 +4,7 @@ using System.Xml.Linq; namespace Barotrauma.LuaCs.Data; -public interface IModConfigInfo : IAssembliesResourcesInfo, +public partial interface IModConfigInfo : IAssembliesResourcesInfo, ILuaScriptsResourcesInfo, IConfigsResourcesInfo { // package info diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 1946fba81..b7e5b1f3b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -209,6 +209,10 @@ namespace Barotrauma servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); +#if CLIENT + SetupServicesProviderClient(servicesProvider); +#endif + // gen IL servicesProvider.CompileAndRun(); return servicesProvider; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs index 33569f85b..530632e22 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs @@ -10,7 +10,7 @@ using Microsoft.Toolkit.Diagnostics; namespace Barotrauma.LuaCs; -public sealed class ModConfigFileParserService : +public sealed partial class ModConfigFileParserService : IParserServiceAsync, IParserServiceAsync, IParserServiceAsync diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs index e4bc8897a..6150ec52b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs @@ -23,12 +23,18 @@ public sealed class ModConfigService : IModConfigService private IParserServiceAsync _assemblyParserService; private IParserServiceAsync _luaScriptParserService; private IParserServiceAsync _configParserService; +#if CLIENT + private IParserServiceAsync _stylesParserService; +#endif private readonly AsyncReaderWriterLock _operationsLock = new(); public ModConfigService(IStorageService storageService, IParserServiceAsync assemblyParserService, IParserServiceAsync luaScriptParserService, - IParserServiceAsync configParserService, + IParserServiceAsync configParserService, +#if CLIENT + IParserServiceAsync stylesParserService, +#endif ILoggerService logger) { _storageService = storageService; @@ -36,6 +42,9 @@ public sealed class ModConfigService : IModConfigService _luaScriptParserService = luaScriptParserService; _configParserService = configParserService; _logger = logger; +#if CLIENT + _stylesParserService = stylesParserService; +#endif } #region Dispose @@ -59,6 +68,11 @@ public sealed class ModConfigService : IModConfigService _assemblyParserService = null; _luaScriptParserService = null; _configParserService = null; + +#if CLIENT + _stylesParserService.Dispose(); + _stylesParserService = null; +#endif } catch { @@ -130,14 +144,26 @@ public sealed class ModConfigService : IModConfigService var asmTask = Task.Factory.StartNew(async () => await GetAssembliesFromXml(owner, src)); var cfgTask = Task.Factory.StartNew(async () => await GetConfigsFromXml(owner, src)); var luaTask = Task.Factory.StartNew(async () => await GetLuaScriptsFromXml(owner, src)); +#if CLIENT + var styleTask = Task.Factory.StartNew(async () => await GetStylesFromXml(owner, src)); +#endif - await Task.WhenAll(asmTask, cfgTask, luaTask); + await Task.WhenAll( + asmTask, + cfgTask, +#if CLIENT + styleTask, +#endif + luaTask); return FluentResults.Result.Ok(new ModConfigInfo() { Package = owner, Assemblies = await await asmTask, Configs = await await cfgTask, +#if CLIENT + Styles = await await styleTask, +#endif LuaScripts = await await luaTask }); @@ -151,7 +177,6 @@ public sealed class ModConfigService : IModConfigService XElement cfgElement) { return await GetResourceFromXml(contentPackage, cfgElement, "Config", "FileGroup", _configParserService); - } async Task> GetAssembliesFromXml(ContentPackage contentPackage, @@ -159,6 +184,14 @@ public sealed class ModConfigService : IModConfigService { return await GetResourceFromXml(contentPackage, cfgElement, "Assembly", "FileGroup", _assemblyParserService); } + +#if CLIENT + async Task> GetStylesFromXml(ContentPackage contentPackage, + XElement cfgElement) + { + return await GetResourceFromXml(contentPackage, cfgElement, "Style", "FileGroup", _stylesParserService); + } +#endif async Task> GetResourceFromXml(ContentPackage contentPackage, XElement cfgElement, string elemName, string fileGroupName, IParserServiceAsync resourceService) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs index 5483fab56..a5e8218be 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs @@ -20,6 +20,9 @@ public sealed class PackageManagementService : IPackageManagementService private IConfigService _configService; private ILuaScriptManagementService _luaScriptManagementService; private IPluginManagementService _pluginManagementService; +#if CLIENT + private IUIStylesService _uiStylesService; +#endif private IPackageManagementServiceConfig _runConfig; // state private readonly ConcurrentDictionary _loadedPackages = new(); @@ -40,7 +43,11 @@ public sealed class PackageManagementService : IPackageManagementService IModConfigService modConfigService, ILuaScriptManagementService luaScriptManagementService, IPluginManagementService pluginManagementService, - IConfigService configService, IPackageManagementServiceConfig runConfig) + IConfigService configService, +#if CLIENT + IUIStylesService uiStylesService, +#endif + IPackageManagementServiceConfig runConfig) { _logger = logger; _modConfigService = modConfigService; @@ -48,6 +55,9 @@ public sealed class PackageManagementService : IPackageManagementService _pluginManagementService = pluginManagementService; _configService = configService; _runConfig = runConfig; +#if CLIENT + _uiStylesService = uiStylesService; +#endif } public void Dispose() @@ -61,11 +71,19 @@ public sealed class PackageManagementService : IPackageManagementService _pluginManagementService.Dispose(); _modConfigService.Dispose(); _logger.Dispose(); +#if CLIENT + _uiStylesService.Dispose(); +#endif _logger = null; _luaScriptManagementService = null; _pluginManagementService = null; _modConfigService = null; +#if CLIENT + _uiStylesService = null; +#endif + + _loadedPackages.Clear(); _runningPackages.Clear(); } @@ -86,9 +104,13 @@ public sealed class PackageManagementService : IPackageManagementService try { var operationResult = new FluentResults.Result(); - operationResult.WithReasons(_configService.Reset().Reasons); + operationResult.WithReasons(_luaScriptManagementService.Reset().Reasons); operationResult.WithReasons(_pluginManagementService.Reset().Reasons); + operationResult.WithReasons(_configService.Reset().Reasons); +#if CLIENT + operationResult.WithReasons(_uiStylesService.Reset().Reasons); +#endif _runningPackages.Clear(); _loadedPackages.Clear(); return operationResult; @@ -205,11 +227,19 @@ public sealed class PackageManagementService : IPackageManagementService { return FluentResults.Result.Ok(); } - + +#if CLIENT + if (!config.Styles.IsDefaultOrEmpty) + { + res.WithReasons(_uiStylesService.LoadAssets(config.Styles).Reasons); + } +#endif var r = Task.WhenAll(tasks.ToArray()).ConfigureAwait(false).GetAwaiter().GetResult(); foreach (var task in r) + { res.WithReasons(task.ConfigureAwait(false).GetAwaiter().GetResult().Reasons); + } return res; } catch (Exception e) @@ -363,12 +393,19 @@ public sealed class PackageManagementService : IPackageManagementService IService.CheckDisposed(this); if (!_loadedPackages.ContainsKey(package)) + { return FluentResults.Result.Fail($"{nameof(UnloadPackage)}: The package is not loaded."); + } if (!_runningPackages.IsEmpty) + { return FluentResults.Result.Fail($"{nameof(UnloadPackage)}: Packages are currently executing."); + } var result = new FluentResults.Result(); result.WithReasons(_luaScriptManagementService.DisposePackageResources(package).Reasons); result.WithReasons(_configService.DisposePackageData(package).Reasons); +#if CLIENT + result.WithReasons(_uiStylesService.UnloadPackage(package).Reasons); +#endif _loadedPackages.TryRemove(package, out _); return result; } @@ -384,7 +421,9 @@ public sealed class PackageManagementService : IPackageManagementService var result = new FluentResults.Result(); foreach (var package in packages) + { result.WithReasons(UnloadPackage(package).Reasons); + } return result; } From a0287b8561813da6830e14d9132179474a713396 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 15 Feb 2026 06:01:59 -0500 Subject: [PATCH 166/288] Oops --- .../BarotraumaLibs/BarotraumaCore/Utils/TaskExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Libraries/BarotraumaLibs/BarotraumaCore/Utils/TaskExtensions.cs b/Libraries/BarotraumaLibs/BarotraumaCore/Utils/TaskExtensions.cs index 367a77023..df671ffc2 100644 --- a/Libraries/BarotraumaLibs/BarotraumaCore/Utils/TaskExtensions.cs +++ b/Libraries/BarotraumaLibs/BarotraumaCore/Utils/TaskExtensions.cs @@ -26,7 +26,8 @@ namespace Barotrauma if (task.Exception != null) { var ex = task.Exception.GetInnermost(); - throw new InvalidOperationException($"Failed to get result from task: task failed with exception {ex.Message} ({ex.GetType()}) {ex.StackTrace}"); + // Commented out until Neurotrauma is fixed. Do not Commit. + // throw new InvalidOperationException($"Failed to get result from task: task failed with exception {ex.Message} ({ex.GetType()}) {ex.StackTrace}"); } if (task is not Task) { From c28a08a713ad1e2bd196cef0cdce6299b81308a6 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Mon, 16 Feb 2026 02:22:19 -0500 Subject: [PATCH 167/288] Revert "Oops" This reverts commit a0287b8561813da6830e14d9132179474a713396. --- .../BarotraumaLibs/BarotraumaCore/Utils/TaskExtensions.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Libraries/BarotraumaLibs/BarotraumaCore/Utils/TaskExtensions.cs b/Libraries/BarotraumaLibs/BarotraumaCore/Utils/TaskExtensions.cs index df671ffc2..367a77023 100644 --- a/Libraries/BarotraumaLibs/BarotraumaCore/Utils/TaskExtensions.cs +++ b/Libraries/BarotraumaLibs/BarotraumaCore/Utils/TaskExtensions.cs @@ -26,8 +26,7 @@ namespace Barotrauma if (task.Exception != null) { var ex = task.Exception.GetInnermost(); - // Commented out until Neurotrauma is fixed. Do not Commit. - // throw new InvalidOperationException($"Failed to get result from task: task failed with exception {ex.Message} ({ex.GetType()}) {ex.StackTrace}"); + throw new InvalidOperationException($"Failed to get result from task: task failed with exception {ex.Message} ({ex.GetType()}) {ex.StackTrace}"); } if (task is not Task) { From c8657caefa5c6928013a5152ac0ad301d99b405c Mon Sep 17 00:00:00 2001 From: Joonas Rikkonen Date: Fri, 20 Feb 2026 19:56:19 +0200 Subject: [PATCH 168/288] Update .NET SDK version from 6 to 8 in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5725f01d9..b1aa385e2 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,6 @@ If you're interested in working on the code, either to develop mods or to contri ### Windows - [Visual Studio](https://www.visualstudio.com/vs/community/) with C# 10 support (VS 2022 or later recommended) ### Linux -- [.NET 6 SDK](https://docs.microsoft.com/en-us/dotnet/core/install/linux) +- [.NET 8 SDK](https://docs.microsoft.com/en-us/dotnet/core/install/linux) ### macOS - [Visual Studio 2022 for Mac](https://visualstudio.microsoft.com/vs/mac/) From f52617deab89fa1dd26f2a51efe72d26f3b4a8f0 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:45:41 -0300 Subject: [PATCH 169/288] The great event move --- .../ClientSource/GUI/ChatBox.cs | 4 +- .../ClientSource/Networking/GameClient.cs | 3 +- .../Characters/CharacterNetworking.cs | 6 +- .../ServerSource/Networking/ChatMessage.cs | 15 +- .../ServerSource/Networking/GameServer.cs | 15 +- .../Peers/Server/LidgrenServerPeer.cs | 11 +- .../Primitives/Peers/Server/ServerPeer.cs | 7 +- .../Networking/Voip/VoipServer.cs | 9 +- .../Animation/HumanoidAnimController.cs | 5 +- .../Characters/Animation/Ragdoll.cs | 12 +- .../Health/Afflictions/AfflictionHusk.cs | 3 +- .../Characters/Health/CharacterHealth.cs | 22 +- .../SharedSource/GameSession/GameSession.cs | 3 - .../Items/Components/Holdable/MeleeWeapon.cs | 5 +- .../Components/Machines/Deconstructor.cs | 7 +- .../Items/Components/Signal/WifiComponent.cs | 9 +- .../SharedSource/Items/Item.cs | 21 +- .../SharedSource/LuaCs/IEvents.cs | 426 +++++++++++++++++- .../LuaCs/_Services/LoggerService.cs | 5 +- .../_Services/LuaScriptManagementService.cs | 21 + .../BarotraumaShared/SharedSource/Map/Gap.cs | 8 +- .../SharedSource/Networking/RespawnManager.cs | 3 - 22 files changed, 508 insertions(+), 112 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs index deb4201bf..a8e5e33bf 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs @@ -1,5 +1,6 @@ using Barotrauma.Extensions; using Barotrauma.Items.Components; +using Barotrauma.LuaCs.Events; using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; @@ -413,7 +414,8 @@ namespace Barotrauma { if (GameMain.IsSingleplayer) { - var should = GameMain.LuaCs.Hook.Call("chatMessage", message.Text, message.SenderClient, message.Type, message); + bool? should = null; + GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnChatMessage(message.Text, message.SenderClient, message.Type, message) ?? should); if (should != null && should.Value) { return; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index 98d00c624..3111861d4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -3003,7 +3003,8 @@ namespace Barotrauma.Networking public override void AddChatMessage(ChatMessage message) { - var should = GameMain.LuaCs.Hook.Call("chatMessage", message.Text, message.SenderClient, message.Type, message); + bool? should = null; + GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnChatMessage(message.Text, message.SenderClient, message.Type, message) ?? should); if (should != null && should.Value) { return; } if (string.IsNullOrEmpty(message.Text)) { return; } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs index 560dd223e..8aa2c1809 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs @@ -320,11 +320,7 @@ namespace Barotrauma if (TalentTree.IsViableTalentForCharacter(this, prefab.Identifier, talentSelection)) { - bool? should = GameMain.LuaCs.Hook.Call("character.updateTalent", this, prefab, c); - if (should == null) - { - GiveTalent(prefab.Identifier); - } + GiveTalent(prefab.Identifier); talentSelection.Add(prefab.Identifier); } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs index c76385bf7..83898e882 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs @@ -1,6 +1,8 @@ -using System; -using System.Text; +using Barotrauma.LuaCs.Events; using MoonSharp.Interpreter; +using MoonSharp.VsCodeDebugger.SDK; +using System; +using System.Text; namespace Barotrauma.Networking { @@ -86,12 +88,9 @@ namespace Barotrauma.Networking HandleSpamFilter(c, txt, out bool flaggedAsSpam, similarityMultiplier); if (flaggedAsSpam) { return; } - var should = GameMain.LuaCs.Hook.Call("chatMessage", txt, c, type); - - if (should != null && should.Value) - { - return; - } + bool? should = null; + GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnChatMessage(txt, c, type, ChatMessage.Create(c.Name, txt, type, null, c)) ?? should); + if (should != null && should.Value) { return; } if (type == ChatMessageType.Order) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 90e224ccc..89a92c077 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -3511,7 +3511,8 @@ namespace Barotrauma.Networking return false; } - var result = GameMain.LuaCs.Hook.Call("tryChangeClientName", c, newName, newJob, newTeam); + bool? result = null; + GameMain.LuaCs.EventService.PublishEvent(x => result = x.OnTryClienChangeName(c, newName, newJob, newTeam) ?? result); if (result != null) { @@ -3968,15 +3969,7 @@ namespace Barotrauma.Networking //send to chat-linked wifi components Signal s = new Signal(message, sender: senderCharacter, source: senderRadio.Item); senderRadio.TransmitSignal(s, sentFromChat: true); - } - - var hookChatMsg = ChatMessage.Create(senderName, message, (ChatMessageType)type, senderCharacter, senderClient, changeType); - - var should = GameMain.LuaCs.Hook.Call("modifyChatMessage", hookChatMsg, senderRadio); - - if (should != null && should.Value) - return; - + } //check which clients can receive the message and apply distance effects foreach (Client client in ConnectedClients) @@ -4778,7 +4771,7 @@ namespace Barotrauma.Networking { if (GameMain.Server == null || !GameMain.Server.ServerSettings.SaveServerLogs) { return; } - GameMain.LuaCs?.Hook?.Call("serverLog", line, messageType); + GameMain.LuaCs?.EventService.PublishEvent(x => x.OnServerLog(line, messageType)); GameMain.Server.ServerSettings.ServerLog.WriteLine(line, messageType); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs index e5cf2c975..b9ea8b6eb 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs @@ -187,16 +187,7 @@ namespace Barotrauma.Networking { if (netServer == null) { return; } - var skipDeny = false; - { - var result = GameMain.LuaCs.Hook.Call("lidgren.handleConnection", inc); - if (result != null) { - if (result.Value) skipDeny = true; - else return; - } - } - - if (!skipDeny && connectedClients.Count >= serverSettings.MaxPlayers) + if (connectedClients.Count >= serverSettings.MaxPlayers) { inc.SenderConnection.Deny(PeerDisconnectPacket.WithReason(DisconnectReason.ServerFull).ToLidgrenStringRepresentation()); return; diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs index 3e1a11c33..0003f1ef8 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/ServerPeer.cs @@ -257,12 +257,7 @@ namespace Barotrauma.Networking protected void UpdatePendingClient(PendingClient pendingClient) { - var skipRemove = false; - var result = GameMain.LuaCs.Hook.Call("handlePendingClient", pendingClient); - - if (result != null) skipRemove = result.Value; - - if (!skipRemove && connectedClients.Count >= serverSettings.MaxPlayers) + if (connectedClients.Count >= serverSettings.MaxPlayers) { RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.ServerFull)); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voip/VoipServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voip/VoipServer.cs index 5349d2ea4..a28f2701b 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voip/VoipServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voip/VoipServer.cs @@ -1,7 +1,10 @@ using Barotrauma.Items.Components; +using Barotrauma.LuaCs.Events; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using static Barotrauma.CharacterHealth; +using static Barotrauma.MedicalClinic; namespace Barotrauma.Networking { @@ -96,7 +99,8 @@ namespace Barotrauma.Networking ChatMessage.CanUseRadio(sender.Character, out WifiComponent senderRadio) && (recipientSpectating || ChatMessage.CanUseRadio(recipient.Character, out recipientRadio))) { - var canUse = GameMain.LuaCs.Hook.Call("canUseVoiceRadio", new object[] { sender, recipient }); + bool? canUse = null; + GameMain.LuaCs.EventService.PublishEvent(x => canUse = x.OnCanUseVoiceRadio(sender, recipient) ?? canUse); if (canUse != null) { @@ -116,7 +120,8 @@ namespace Barotrauma.Networking } } - float range = GameMain.LuaCs.Hook.Call("changeLocalVoiceRange", sender, recipient) ?? 1.0f; + float range = 1.0f; + GameMain.LuaCs.EventService.PublishEvent(x => range = x.OnChangeLocalVoiceRange(sender, recipient) ?? range); if (recipientSpectating) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index fb8754e55..3c64fb44c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -1,5 +1,6 @@ using Barotrauma.Extensions; using Barotrauma.Items.Components; +using Barotrauma.LuaCs.Events; using Barotrauma.Networking; using FarseerPhysics; using Microsoft.Xna.Framework; @@ -1305,11 +1306,11 @@ namespace Barotrauma //increase oxygen and clamp it above zero // -> the character should be revived if there are no major afflictions in addition to lack of oxygen target.Oxygen = Math.Max(target.Oxygen + 10.0f, 10.0f); - GameMain.LuaCs.Hook.Call("human.CPRSuccess", this); + GameMain.LuaCs.EventService.PublishEvent(x => x.OnCharacterCPRSuccess(this)); } else { - GameMain.LuaCs.Hook.Call("human.CPRFailed", this); + GameMain.LuaCs.EventService.PublishEvent(x => x.OnCharacterCPRFailed(this)); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index 5b7d36724..7517e00f4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -1,17 +1,18 @@ -using Barotrauma.Networking; +using Barotrauma.Extensions; +using Barotrauma.LuaCs.Events; +using Barotrauma.Networking; using FarseerPhysics; using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics.Contacts; using FarseerPhysics.Dynamics.Joints; using Microsoft.Xna.Framework; +using MoonSharp.Interpreter; using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; -using Barotrauma.Extensions; -using LimbParams = Barotrauma.RagdollParams.LimbParams; using JointParams = Barotrauma.RagdollParams.JointParams; -using MoonSharp.Interpreter; +using LimbParams = Barotrauma.RagdollParams.LimbParams; namespace Barotrauma { @@ -857,7 +858,8 @@ namespace Barotrauma float impactDamage = GetImpactDamage(impact, impactTolerance); - var should = GameMain.LuaCs.Hook.Call("changeFallDamage", impactDamage, character, impactPos, velocity); + float? should = null; + GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnChangeFallDamage(impactDamage, character, impactPos, velocity) ?? should); if (should != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs index 31abf6789..7974edd57 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs @@ -4,6 +4,7 @@ using System.Xml.Linq; using System; using Barotrauma.Extensions; using Microsoft.Xna.Framework; +using Barotrauma.LuaCs.Events; namespace Barotrauma { @@ -343,7 +344,7 @@ namespace Barotrauma if (client != null) { GameMain.Server.SetClientCharacter(client, husk); - GameMain.LuaCs.Hook.Call("husk.clientControlHusk", new object[] { client, husk }); + GameMain.LuaCs.EventService.PublishEvent(x => x.OnClientControlHusk(client, husk)); } #else if (!character.IsRemotelyControlled && character == Character.Controlled) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index 2c2fcad91..2ddc7d8f9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -1,17 +1,19 @@ using Barotrauma.Abilities; +using Barotrauma.Abilities; using Barotrauma.Extensions; +using Barotrauma.Extensions; +using Barotrauma.LuaCs.Events; +using Barotrauma.Networking; using Barotrauma.Networking; using Microsoft.Xna.Framework; +using MoonSharp.Interpreter; using System; using System.Collections.Generic; using System.Globalization; +using System.Globalization; using System.Linq; using System.Xml.Linq; -using Barotrauma.Networking; -using Barotrauma.Extensions; -using System.Globalization; -using MoonSharp.Interpreter; -using Barotrauma.Abilities; +using static OneOf.Types.TrueFalseOrNull; namespace Barotrauma { @@ -655,7 +657,8 @@ namespace Barotrauma return; } - var should = GameMain.LuaCs.Hook.Call("character.applyDamage", this, attackResult, hitLimb, allowStacking); + bool? should = null; + GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnCharacterApplyDamage(this, attackResult, hitLimb, allowStacking) ?? should); if (should != null && should.Value) { return; } foreach (Affliction newAffliction in attackResult.Afflictions) @@ -826,10 +829,9 @@ namespace Barotrauma if (newAffliction.Prefab.TargetSpecies.Any() && newAffliction.Prefab.TargetSpecies.None(s => s == Character.SpeciesName)) { return; } if (Character.Params.Health.ImmunityIdentifiers.Contains(newAffliction.Identifier)) { return; } - var should = GameMain.LuaCs.Hook.Call("character.applyAffliction", this, limbHealth, newAffliction, allowStacking); - - if (should != null && should.Value) - return; + bool? should = null; + GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnCharacterApplyAffliction(this, limbHealth, newAffliction, allowStacking) ?? should); + if (should != null && should.Value) { return; } Affliction existingAffliction = null; foreach ((Affliction affliction, LimbHealth value) in afflictions) diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index b29059ecb..9e625b50a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -1046,9 +1046,6 @@ namespace Barotrauma /// public static ImmutableHashSet GetSessionCrewCharacters(CharacterType type) { - var result = GameMain.LuaCs.Hook.Call("getSessionCrewCharacters", type); - if (result != null) return ImmutableHashSet.Create(result); - if (GameMain.GameSession?.CrewManager is not { } crewManager) { return ImmutableHashSet.Empty; } IEnumerable players; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs index d4d45f120..fe27ceb15 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs @@ -1,4 +1,5 @@ -using FarseerPhysics; +using Barotrauma.LuaCs.Events; +using FarseerPhysics; using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics.Contacts; using Microsoft.Xna.Framework; @@ -435,7 +436,7 @@ namespace Barotrauma.Items.Components Structure targetStructure = target.UserData as Structure ?? targetFixture.UserData as Structure; Item targetItem = target.UserData is Holdable h ? h.Item : target.UserData as Item ?? targetFixture.UserData as Item; Entity targetEntity = targetCharacter ?? targetStructure ?? targetItem ?? target.UserData as Entity; - GameMain.LuaCs.Hook.Call("meleeWeapon.handleImpact", this, target); + GameMain.LuaCs.EventService.PublishEvent(x => x.OnMeleeWeaponHandleImpact(this, target)); if (Attack != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs index cd08a541a..be847f047 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs @@ -1,11 +1,13 @@ using Barotrauma.Abilities; using Barotrauma.Extensions; +using Barotrauma.LuaCs.Events; using Barotrauma.Networking; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Xml.Linq; +using static OneOf.Types.TrueFalseOrNull; namespace Barotrauma.Items.Components { @@ -332,8 +334,9 @@ namespace Barotrauma.Items.Components GameAnalyticsManager.AddDesignEvent("ItemDeconstructed:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "none") + ":" + targetItem.Prefab.Identifier); } - bool? result = GameMain.LuaCs.Hook.Call("item.deconstructed", targetItem, this, user, allowRemove); - if (result == true) { return; } + bool? should = null; + GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnItemDeconstructed(targetItem, this, user, allowRemove) ?? should); + if (should == true) { return; } if (targetItem.AllowDeconstruct && allowRemove) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs index e59ab6599..7f8b4c75b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs @@ -1,10 +1,13 @@ -using Barotrauma.Networking; +using Barotrauma.LuaCs.Events; +using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Xml.Linq; +using static Barotrauma.CharacterHealth; +using static Barotrauma.MedicalClinic; namespace Barotrauma.Items.Components { @@ -228,8 +231,8 @@ namespace Barotrauma.Items.Components public void TransmitSignal(Signal signal, bool sentFromChat) { - var should = GameMain.LuaCs.Hook.Call("wifiSignalTransmitted", this, signal, sentFromChat); - + bool? should = null; + GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnWifiSignalTransmitted(this, signal, sentFromChat) ?? should); if (should != null && should.Value) { return; } bool chatMsgSent = false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index f74a46541..8c700265a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -1,20 +1,23 @@ -using Barotrauma.Items.Components; +using Barotrauma.Abilities; +using Barotrauma.Extensions; +using Barotrauma.Items.Components; +using Barotrauma.LuaCs.Events; +using Barotrauma.MapCreatures.Behavior; using Barotrauma.Networking; using FarseerPhysics; using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics.Contacts; using Microsoft.Xna.Framework; +using MoonSharp.Interpreter; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.Globalization; using System.Linq; using System.Xml.Linq; -using Barotrauma.Extensions; -using Barotrauma.MapCreatures.Behavior; -using MoonSharp.Interpreter; -using System.Collections.Immutable; -using Barotrauma.Abilities; +using static Barotrauma.CharacterHealth; +using static Barotrauma.MedicalClinic; #if CLIENT using Microsoft.Xna.Framework.Graphics; @@ -3891,9 +3894,9 @@ namespace Barotrauma } } - var result = GameMain.LuaCs.Hook.Call("item.readPropertyChange", this, property, parentObject, allowEditing, sender); - if (result != null && result.Value) - return; + bool? should = null; + GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnItemReadPropertyChange(this, property, parentObject, allowEditing, sender) ?? should); + if (should != null && should.Value) { return; } Type type = property.PropertyType; string logValue = ""; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index 662d7742f..12fb85f04 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -1,6 +1,7 @@ using Barotrauma.Items.Components; using Barotrauma.LuaCs.Data; using Barotrauma.Networking; +using FarseerPhysics.Dynamics; using Microsoft.Xna.Framework; using Steamworks.Ugc; using System; @@ -136,6 +137,408 @@ internal interface IEventCharacterCreated : IEvent } } +// TODO: harmony-fy +internal interface IEventHumanCPRSuccess : IEvent +{ + void OnCharacterCPRSuccess(HumanoidAnimController animController); + + static IEventHumanCPRSuccess IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventHumanCPRSuccess + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnCharacterCPRSuccess(HumanoidAnimController animController) + { + LuaFuncs[nameof(OnCharacterCPRSuccess)](animController); + } + } +} + +// TODO: harmony-fy +internal interface IEventHumanCPRFailed : IEvent +{ + void OnCharacterCPRFailed(HumanoidAnimController animController); + + static IEventHumanCPRFailed IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventHumanCPRFailed + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnCharacterCPRFailed(HumanoidAnimController animController) + { + LuaFuncs[nameof(OnCharacterCPRFailed)](animController); + } + } +} + +// TODO: harmony-fy +internal interface IEventClientControlHusk : IEvent +{ + void OnClientControlHusk(Client client, Character husk); + + static IEventClientControlHusk IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventClientControlHusk + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnClientControlHusk(Client client, Character husk) + { + LuaFuncs[nameof(OnClientControlHusk)](client, husk); + } + } +} + +// TODO: harmony-fy +internal interface IEventMeleeWeaponHandleImpact : IEvent +{ + void OnMeleeWeaponHandleImpact(MeleeWeapon meleeWeapon, Body target); + + static IEventMeleeWeaponHandleImpact IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventMeleeWeaponHandleImpact + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnMeleeWeaponHandleImpact(MeleeWeapon meleeWeapon, Body target) + { + LuaFuncs[nameof(OnMeleeWeaponHandleImpact)](meleeWeapon, target); + } + } +} + +// TODO: harmony-fy +internal interface IEventServerLog : IEvent +{ + void OnServerLog(string line, ServerLog.MessageType messageType); + + static IEventServerLog IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventServerLog + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnServerLog(string line, ServerLog.MessageType messageType) + { + LuaFuncs[nameof(OnServerLog)](line, messageType); + } + } +} + +// TODO: harmony-fy +internal interface IEventChatMessage : IEvent +{ + bool? OnChatMessage(string messageText, Client sender, ChatMessageType type, ChatMessage message); + + static IEventChatMessage IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventChatMessage + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public bool? OnChatMessage(string messageText, Client sender, ChatMessageType type, ChatMessage message) + { + var result = LuaFuncs[nameof(OnChatMessage)](messageText, sender, type, message); + if (result is bool b && b) + { + return true; + } + + return null; + } + } +} + +// TODO: harmony-fy +internal interface IEventTryClientChangeName : IEvent +{ + bool? OnTryClienChangeName(Client client, string newName, Identifier newJob, CharacterTeamType newTeam); + + static IEventTryClientChangeName IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventTryClientChangeName + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public bool? OnTryClienChangeName(Client client, string newName, Identifier newJob, CharacterTeamType newTeam) + { + var result = LuaFuncs[nameof(OnTryClienChangeName)](client, newName, newJob, newTeam); + if (result is bool b) + { + return b; + } + + return null; + } + } +} + +// TODO: harmony-fy +internal interface IEventChangeFallDamage : IEvent +{ + float? OnChangeFallDamage(float impactDamage, Character character, Vector2 impactPos, Vector2 velocity); + + static IEventChangeFallDamage IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventChangeFallDamage + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public float? OnChangeFallDamage(float impactDamage, Character character, Vector2 impactPos, Vector2 velocity) + { + var result = LuaFuncs[nameof(OnChangeFallDamage)](impactDamage, character, impactPos, velocity); + if (result is float f) + { + return f; + } + + return null; + } + } +} + +// TODO: harmony-fy +internal interface IEventGapOxygenUpdate : IEvent +{ + bool? OnGapOxygenUpdate(Gap gap, Hull hull1, Hull hull2); + + static IEventGapOxygenUpdate IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventGapOxygenUpdate + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public bool? OnGapOxygenUpdate(Gap gap, Hull hull1, Hull hull2) + { + var result = LuaFuncs[nameof(OnGapOxygenUpdate)](gap, hull1, hull2); + if (result is bool b) + { + return b; + } + + return null; + } + } +} + +// TODO: harmony-fy +internal interface IEventCharacterApplyDamage : IEvent +{ + bool? OnCharacterApplyDamage(CharacterHealth characterHealth, AttackResult attackResult, Limb hitLimb, bool allowStacking); + + static IEventCharacterApplyDamage IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventCharacterApplyDamage + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public bool? OnCharacterApplyDamage(CharacterHealth characterHealth, AttackResult attackResult, Limb hitLimb, bool allowStacking) + { + var result = LuaFuncs[nameof(OnCharacterApplyDamage)](characterHealth, attackResult, hitLimb, allowStacking); + if (result is bool b) + { + return b; + } + + return null; + } + } +} + +// TODO: harmony-fy +internal interface IEventCharacterApplyAffliction : IEvent +{ + bool? OnCharacterApplyAffliction(CharacterHealth characterHealth, CharacterHealth.LimbHealth limbHealth, Affliction newAffliction, bool allowStacking); + + static IEventCharacterApplyAffliction IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventCharacterApplyAffliction + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public bool? OnCharacterApplyAffliction(CharacterHealth characterHealth, CharacterHealth.LimbHealth limbHealth, Affliction newAffliction, bool allowStacking) + { + var result = LuaFuncs[nameof(OnCharacterApplyAffliction)](characterHealth, limbHealth, newAffliction, allowStacking); + if (result is bool b) + { + return b; + } + + return null; + } + } +} + +// TODO: harmony-fy +internal interface IEventItemReadPropertyChange : IEvent +{ + bool? OnItemReadPropertyChange(Item item, SerializableProperty property, object parentObject, bool allowEditing, Client sender); + + static IEventItemReadPropertyChange IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventItemReadPropertyChange + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public bool? OnItemReadPropertyChange(Item item, SerializableProperty property, object parentObject, bool allowEditing, Client sender) + { + var result = LuaFuncs[nameof(OnItemReadPropertyChange)](item, property, parentObject, allowEditing, sender); + if (result is bool b) + { + return b; + } + + return null; + } + } +} + +// TODO: harmony-fy +internal interface IEventCanUseVoiceRadio : IEvent +{ + bool? OnCanUseVoiceRadio(Client sender, Client recipient); + + static IEventCanUseVoiceRadio IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventCanUseVoiceRadio + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public bool? OnCanUseVoiceRadio(Client sender, Client recipient) + { + var result = LuaFuncs[nameof(OnCanUseVoiceRadio)](sender, recipient); + if (result is bool b) + { + return b; + } + + return null; + } + } +} + +// TODO: harmony-fy +internal interface IEventChangeLocalVoiceRange : IEvent +{ + float? OnChangeLocalVoiceRange(Client sender, Client recipient); + + static IEventChangeLocalVoiceRange IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventChangeLocalVoiceRange + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public float? OnChangeLocalVoiceRange(Client sender, Client recipient) + { + var result = LuaFuncs[nameof(OnChangeLocalVoiceRange)](sender, recipient); + if (result is float f) + { + return f; + } + + return null; + } + } +} + +// TODO: harmony-fy +internal interface IEventItemDeconstructed : IEvent +{ + bool? OnItemDeconstructed(Item item, Deconstructor deconstructor, Character user, bool allowRemove); + + static IEventItemDeconstructed IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventItemDeconstructed + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public bool? OnItemDeconstructed(Item item, Deconstructor deconstructor, Character user, bool allowRemove) + { + var result = LuaFuncs[nameof(OnItemDeconstructed)](item, deconstructor, user, allowRemove); + if (result is bool b) + { + return b; + } + + return null; + } + } +} + +// TODO: harmony-fy +internal interface IEventWifiSignalTransmitted : IEvent +{ + bool? OnWifiSignalTransmitted(WifiComponent wifiComponent, Signal signal, bool sentFromChat); + + static IEventWifiSignalTransmitted IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventWifiSignalTransmitted + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public bool? OnWifiSignalTransmitted(WifiComponent wifiComponent, Signal signal, bool sentFromChat) + { + var result = LuaFuncs[nameof(OnWifiSignalTransmitted)](wifiComponent, signal, sentFromChat); + if (result is bool b) + { + return b; + } + + return null; + } + } +} + internal interface IEventCharacterDeath : IEvent { void OnCharacterDeath(Character character, Affliction causeOfDeathAffliction, CauseOfDeathType causeOfDeathType); @@ -156,29 +559,6 @@ internal interface IEventCharacterDeath : IEvent } } -/* -internal interface IEventHumanCPRFailed : IEvent -{ - void OnHumanCPRFailed(Character character); - static IEventHumanCPRFailed IEvent.GetLuaRunner(IDictionary luaFunc) => new - { - IsLuaRunner = Return.Arguments(() => true), - OnHumanCPRFailed = ReturnVoid.Arguments((Character character) => luaFunc[nameof(OnHumanCPRFailed)](character)) - }.ActLike(); -} - - -internal interface IEventHumanCPRSuccess : IEvent -{ - void OnHumanCPRSuccess(Character character); - static IEventHumanCPRSuccess IEvent.GetLuaRunner(IDictionary luaFunc) => new - { - IsLuaRunner = Return.Arguments(() => true), - OnHumanCPRSuccess = ReturnVoid.Arguments((Character character) => luaFunc[nameof(OnHumanCPRSuccess)](character)) - }.ActLike(); -} -*/ - public interface IEventKeyUpdate : IEvent { void OnKeyUpdate(double deltaTime); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs index cbd773c6d..305950efe 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs @@ -1,4 +1,5 @@ -using Barotrauma.Networking; +using Barotrauma.LuaCs.Events; +using Barotrauma.Networking; using FluentResults; using HarmonyLib; using Microsoft.Xna.Framework; @@ -59,7 +60,7 @@ public partial class LoggerService : ILoggerService if (!_isInsideLogCall) { _isInsideLogCall = true; - GameMain.LuaCs?.Hook?.Call("serverLog", logMessage, log.MessageType); + GameMain.LuaCs?.EventService.PublishEvent(x => x.OnServerLog(logMessage, log.MessageType)); _isInsideLogCall = false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index d0267f7d3..6fbb41a35 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -179,6 +179,25 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _eventService.RegisterLuaEventAlias("character.death", nameof(IEventCharacterDeath.OnCharacterDeath)); _eventService.RegisterLuaEventAlias("character.damageLimb", nameof(IEventCharacterDamageLimb.OnCharacterDamageLimb)); _eventService.RegisterLuaEventAlias("character.giveJobItems", nameof(IEventGiveCharacterJobItems.OnGiveCharacterJobItems)); + _eventService.RegisterLuaEventAlias("character.CPRSuccess", nameof(IEventHumanCPRSuccess.OnCharacterCPRSuccess)); + _eventService.RegisterLuaEventAlias("character.CPRFailed", nameof(IEventHumanCPRFailed.OnCharacterCPRFailed)); + _eventService.RegisterLuaEventAlias("character.applyDamage", nameof(IEventCharacterApplyDamage.OnCharacterApplyDamage)); + _eventService.RegisterLuaEventAlias("character.applyAffliction", nameof(IEventCharacterApplyAffliction.OnCharacterApplyAffliction)); + + _eventService.RegisterLuaEventAlias("gapOxygenUpdate", nameof(IEventGapOxygenUpdate.OnGapOxygenUpdate)); + + _eventService.RegisterLuaEventAlias("husk.clientControlHusk", nameof(IEventClientControlHusk.OnClientControlHusk)); + + _eventService.RegisterLuaEventAlias("meleeWeapon.handleImpact", nameof(IEventMeleeWeaponHandleImpact.OnMeleeWeaponHandleImpact)); + + _eventService.RegisterLuaEventAlias("serverLog", nameof(IEventServerLog.OnServerLog)); + + _eventService.RegisterLuaEventAlias("tryChangeClientName", nameof(IEventTryClientChangeName.OnTryClienChangeName)); + + _eventService.RegisterLuaEventAlias("changeFallDamage", nameof(IEventChangeFallDamage.OnChangeFallDamage)); + + _eventService.RegisterLuaEventAlias("canUseVoiceRadio", nameof(IEventCanUseVoiceRadio.OnCanUseVoiceRadio)); + _eventService.RegisterLuaEventAlias("changeLocalVoiceRange", nameof(IEventChangeLocalVoiceRange.OnChangeLocalVoiceRange)); _eventService.RegisterLuaEventAlias("roundStart", nameof(IEventRoundStarted.OnRoundStart)); _eventService.RegisterLuaEventAlias("roundEnd", nameof(IEventRoundEnded.OnRoundEnd)); @@ -190,6 +209,8 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _eventService.RegisterLuaEventAlias("item.removed", nameof(IEventItemRemoved.OnItemRemoved)); _eventService.RegisterLuaEventAlias("item.use", nameof(IEventItemUse.OnItemUsed)); _eventService.RegisterLuaEventAlias("item.secondaryUse", nameof(IEventItemSecondaryUse.OnItemSecondaryUsed)); + _eventService.RegisterLuaEventAlias("item.readPropertyChange", nameof(IEventItemReadPropertyChange.OnItemReadPropertyChange)); + _eventService.RegisterLuaEventAlias("item.deconstructed", nameof(IEventItemDeconstructed.OnItemDeconstructed)); _eventService.RegisterLuaEventAlias("inventoryPutItem", nameof(IEventInventoryPutItem.OnInventoryPutItem)); _eventService.RegisterLuaEventAlias("inventoryItemSwap", nameof(IEventInventoryItemSwap.OnInventoryItemSwap)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs index 94b8bd03e..cdf46bbda 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs @@ -1,13 +1,15 @@ using Barotrauma.Extensions; using Barotrauma.Items.Components; +using Barotrauma.LuaCs.Events; +using Barotrauma.Networking; using FarseerPhysics; using FarseerPhysics.Dynamics; using Microsoft.Xna.Framework; +using MoonSharp.Interpreter; using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; -using MoonSharp.Interpreter; namespace Barotrauma { @@ -883,8 +885,8 @@ namespace Barotrauma if (Math.Max(hull1.WorldSurface + hull1.WaveY[hull1.WaveY.Length - 1], hull2.WorldSurface + hull2.WaveY[0]) > WorldRect.Y) { return; } } - var should = GameMain.LuaCs.Hook.Call("gapOxygenUpdate", this, hull1, hull2); - + bool? should = null; + GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnGapOxygenUpdate(this, hull1, hull2) ?? should); if (should != null && should.Value) return; float totalOxygen = hull1.Oxygen + hull2.Oxygen; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs index 8f67b919d..a9d9ab0d6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/RespawnManager.cs @@ -213,9 +213,6 @@ namespace Barotrauma.Networking public void Update(float deltaTime) { - var result = GameMain.LuaCs.Hook.Call("respawnManager.update"); - if (result != null && result.Value) { return; } - foreach (var teamSpecificState in teamSpecificStates.Values) { if (RespawnShuttles.None()) From 124b1e545d6ce028799956c079f07ca9364c924b Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Fri, 20 Feb 2026 19:32:03 -0300 Subject: [PATCH 170/288] Fix missing ILuaCsNetworking API --- .../ClientSource/LuaCs/Services/NetworkingService.cs | 3 +++ .../ServerSource/LuaCs/Services/NetworkingService.cs | 3 +++ .../SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs | 9 ++++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs index 4cde8a52b..49acd7436 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs @@ -76,6 +76,9 @@ partial class NetworkingService : INetworkingService, IEventServerConnected, IEv GameMain.Client.ClientPeer.Send(netMessage, deliveryMethod); } + public void Send(IWriteMessage netMessage, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable) + => SendToServer(netMessage, deliveryMethod); + private void RequestId(NetId netId) { if (idToPacket.ContainsKey(netId)) { return; } diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs index fd5bcd90c..756b29c83 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs @@ -180,4 +180,7 @@ partial class NetworkingService : INetworkingService, IEventClientRawNetMessageR GameMain.Server.ServerPeer.Send(netMessage, connection, deliveryMethod); } } + + public void Send(IWriteMessage netMessage, NetworkConnection connection = null, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable) + => SendToClient(netMessage, connection, deliveryMethod); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs index aafcd5712..cbdbbb0bc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs @@ -1,6 +1,13 @@ -namespace Barotrauma.LuaCs.Compatibility; +using Barotrauma.Networking; + +namespace Barotrauma.LuaCs.Compatibility; public interface ILuaCsNetworking : ILuaCsShim { void Receive(string netId, LuaCsAction action); +#if SERVER + void Send(IWriteMessage mesage, NetworkConnection connection = null, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable); +#elif CLIENT + void Send(IWriteMessage mesage, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable); +#endif } From ddd9dac2fbe1def93bcd977f8b73a991343dd12c Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Fri, 20 Feb 2026 19:32:15 -0300 Subject: [PATCH 171/288] Fix Descriptors not being populated correctly --- .../LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua | 2 +- .../SharedSource/LuaCs/_Services/_Lua/LuaUserDataService.cs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua index 8b83b4fd5..eee2287b6 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua @@ -20,7 +20,7 @@ AddTableToGlobal(dofile(path .. "/Lua/CompatibilityLib.lua")) dofile(path .. "/Lua/DefaultHook.lua") -Descriptors = LuaSetup.LuaUserData +Descriptors = LuaUserData dofile(path .. "/Lua/DefaultLib/Utils/Math.lua") dofile(path .. "/Lua/DefaultLib/Utils/String.lua") diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaUserDataService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaUserDataService.cs index 3efb28799..e155784a8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaUserDataService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaUserDataService.cs @@ -87,7 +87,10 @@ public class LuaUserDataService : ILuaUserDataService throw new ScriptRuntimeException($"tried to register a type that doesn't exist: {typeName}."); } - return UserData.RegisterType(type); + var descriptor = UserData.RegisterType(type); + descriptors.TryAdd(typeName, descriptor); + + return descriptor; } public void RegisterExtensionType(string typeName) From da5b9389db3547bf1220dcf5e0f55bec32643cea Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Fri, 20 Feb 2026 20:20:15 -0300 Subject: [PATCH 172/288] Fix missing HookMethod APIs in the interface --- .../SharedSource/LuaCs/_Services/_Lua/ILuaPatcher.cs | 9 ++++++++- .../LuaCs/_Services/_Lua/LuaPatcherCompat.cs | 8 ++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaPatcher.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaPatcher.cs index 4de514875..3d385a9d3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaPatcher.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaPatcher.cs @@ -1,4 +1,6 @@ -using System.Reflection; +using Barotrauma.LuaCs.Compatibility; +using System; +using System.Reflection; using static Barotrauma.LuaCs.Compatibility.ILuaCsHook; using LuaCsCompatPatchFunc = Barotrauma.LuaCsPatch; @@ -12,5 +14,10 @@ public interface ILuaPatcher : IReusableService string Patch(string className, string methodName, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before); bool RemovePatch(string identifier, string className, string methodName, string[] parameterTypes, HookMethodType hookType); bool RemovePatch(string identifier, string className, string methodName, HookMethodType hookType); + void HookMethod(string identifier, MethodBase method, LuaCsCompatPatchFunc patch, HookMethodType hookType = HookMethodType.Before, IAssemblyPlugin owner = null); + public void HookMethod(string identifier, string className, string methodName, string[] parameterNames, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before); + public void HookMethod(string identifier, string className, string methodName, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before); + public void HookMethod(string className, string methodName, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before); + public void HookMethod(string className, string methodName, string[] parameterNames, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaPatcherCompat.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaPatcherCompat.cs index 4acf68f33..c3f56a272 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaPatcherCompat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaPatcherCompat.cs @@ -197,7 +197,7 @@ namespace Barotrauma.LuaCs } } } - protected void HookMethod(string identifier, string className, string methodName, string[] parameterNames, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before) + public void HookMethod(string identifier, string className, string methodName, string[] parameterNames, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before) { var method = ResolveMethod(className, methodName, parameterNames); if (method == null) return; @@ -207,11 +207,11 @@ namespace Barotrauma.LuaCs } HookMethod(identifier, method, patch, hookMethodType); } - protected void HookMethod(string identifier, string className, string methodName, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before) => + public void HookMethod(string identifier, string className, string methodName, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before) => HookMethod(identifier, className, methodName, null, patch, hookMethodType); - protected void HookMethod(string className, string methodName, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before) => + public void HookMethod(string className, string methodName, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before) => HookMethod("", className, methodName, null, patch, hookMethodType); - protected void HookMethod(string className, string methodName, string[] parameterNames, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before) => + public void HookMethod(string className, string methodName, string[] parameterNames, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before) => HookMethod("", className, methodName, parameterNames, patch, hookMethodType); From a7e9dc8c9ca494117e907366f2069620e8bd1a8f Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Fri, 20 Feb 2026 20:39:53 -0300 Subject: [PATCH 173/288] oops --- .../LuaCs/_Services/EventService.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs index 224cc8128..d12f4f545 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs @@ -354,5 +354,25 @@ public partial class EventService : IEventService { _luaPatcher.HookMethod(identifier, method, patch, hookType, owner); } + + public void HookMethod(string identifier, string className, string methodName, string[] parameterNames, LuaCsPatch patch, LuaCsHook.HookMethodType hookMethodType = LuaCsHook.HookMethodType.Before) + { + _luaPatcher.HookMethod(identifier, className, methodName, parameterNames, patch, hookMethodType); + } + + public void HookMethod(string identifier, string className, string methodName, LuaCsPatch patch, LuaCsHook.HookMethodType hookMethodType = LuaCsHook.HookMethodType.Before) + { + _luaPatcher.HookMethod(identifier, className, methodName, patch, hookMethodType); + } + + public void HookMethod(string className, string methodName, LuaCsPatch patch, LuaCsHook.HookMethodType hookMethodType = LuaCsHook.HookMethodType.Before) + { + _luaPatcher.HookMethod(className, methodName, patch, hookMethodType); + } + + public void HookMethod(string className, string methodName, string[] parameterNames, LuaCsPatch patch, LuaCsHook.HookMethodType hookMethodType = LuaCsHook.HookMethodType.Before) + { + _luaPatcher.HookMethod(className, methodName, parameterNames, patch, hookMethodType); + } #endregion } From 91992194a9a50ca6406448da805996e4b8532a4f Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Fri, 20 Feb 2026 20:40:06 -0300 Subject: [PATCH 174/288] Fix return events not working in Lua --- .../SharedSource/LuaCs/IEvents.cs | 100 +++++++++--------- .../_Services/LuaScriptManagementService.cs | 2 + 2 files changed, 50 insertions(+), 52 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index 12fb85f04..c12c5669f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -3,6 +3,7 @@ using Barotrauma.LuaCs.Data; using Barotrauma.Networking; using FarseerPhysics.Dynamics; using Microsoft.Xna.Framework; +using MoonSharp.Interpreter; using Steamworks.Ugc; using System; using System.Collections.Generic; @@ -258,10 +259,10 @@ internal interface IEventChatMessage : IEvent public bool? OnChatMessage(string messageText, Client sender, ChatMessageType type, ChatMessage message) { - var result = LuaFuncs[nameof(OnChatMessage)](messageText, sender, type, message); - if (result is bool b && b) + object result = LuaFuncs[nameof(OnChatMessage)](messageText, sender, type, message); + if (result is DynValue dynValue && dynValue.Type == DataType.Boolean) { - return true; + return dynValue.Boolean; } return null; @@ -286,9 +287,9 @@ internal interface IEventTryClientChangeName : IEvent public bool? OnTryClienChangeName(Client client, string newName, Identifier newJob, CharacterTeamType newTeam) { var result = LuaFuncs[nameof(OnTryClienChangeName)](client, newName, newJob, newTeam); - if (result is bool b) + if (result is DynValue dynValue && dynValue.Type == DataType.Boolean) { - return b; + return dynValue.Boolean; } return null; @@ -313,9 +314,9 @@ internal interface IEventChangeFallDamage : IEvent public float? OnChangeFallDamage(float impactDamage, Character character, Vector2 impactPos, Vector2 velocity) { var result = LuaFuncs[nameof(OnChangeFallDamage)](impactDamage, character, impactPos, velocity); - if (result is float f) + if (result is DynValue dynValue && dynValue.Type == DataType.Number) { - return f; + return (float)dynValue.Number; } return null; @@ -340,9 +341,9 @@ internal interface IEventGapOxygenUpdate : IEvent public bool? OnGapOxygenUpdate(Gap gap, Hull hull1, Hull hull2) { var result = LuaFuncs[nameof(OnGapOxygenUpdate)](gap, hull1, hull2); - if (result is bool b) + if (result is DynValue dynValue && dynValue.Type == DataType.Boolean) { - return b; + return dynValue.Boolean; } return null; @@ -367,9 +368,9 @@ internal interface IEventCharacterApplyDamage : IEvent public bool? OnCanUseVoiceRadio(Client sender, Client recipient) { var result = LuaFuncs[nameof(OnCanUseVoiceRadio)](sender, recipient); - if (result is bool b) + if (result is DynValue dynValue && dynValue.Type == DataType.Boolean) { - return b; + return dynValue.Boolean; } return null; @@ -475,9 +476,9 @@ internal interface IEventChangeLocalVoiceRange : IEvent public bool? OnItemDeconstructed(Item item, Deconstructor deconstructor, Character user, bool allowRemove) { var result = LuaFuncs[nameof(OnItemDeconstructed)](item, deconstructor, user, allowRemove); - if (result is bool b) + if (result is DynValue dynValue && dynValue.Type == DataType.Boolean) { - return b; + return dynValue.Boolean; } return null; @@ -529,9 +530,9 @@ internal interface IEventWifiSignalTransmitted : IEvent public bool? OnItemUsed(Item item, Character user, Limb targetLimb, Entity useTarget) { var result = LuaFuncs[nameof(OnItemUsed)](item, user, targetLimb, useTarget); - if (result is bool b) + if (result is DynValue dynValue && dynValue.Type == DataType.Boolean) { - return b; - } - else - { - return null; + return dynValue.Boolean; } + + return null; } } } @@ -817,14 +816,12 @@ interface IEventItemSecondaryUse : IEvent public bool? OnItemSecondaryUsed(Item item, Character user) { var result = LuaFuncs[nameof(OnItemSecondaryUsed)](item, user); - if (result is bool b) + if (result is DynValue dynValue && dynValue.Type == DataType.Boolean) { - return b; - } - else - { - return null; + return dynValue.Boolean; } + + return null; } } } @@ -844,15 +841,18 @@ interface IEventCharacterDamageLimb : IEvent public AttackResult? OnCharacterDamageLimb(Character character, Vector2 worldPosition, Limb hitLimb, IEnumerable afflictions, float stun, bool playSound, Vector2 attackImpulse, Character attacker = null, float damageMultiplier = 1, bool allowStacking = true, float penetration = 0f, bool shouldImplode = false) { - var result = LuaFuncs[nameof(OnCharacterDamageLimb)](character, worldPosition, hitLimb, afflictions, stun, playSound, attackImpulse, attacker, damageMultiplier, allowStacking, penetration, shouldImplode); + object result = LuaFuncs[nameof(OnCharacterDamageLimb)](character, worldPosition, hitLimb, afflictions, stun, playSound, attackImpulse, attacker, damageMultiplier, allowStacking, penetration, shouldImplode); + if (result is DynValue dynValue) + { + result = dynValue.ToObject(); + } + if (result is AttackResult attackResult) { return attackResult; } - else - { - return null; - } + + return null; } } } @@ -873,14 +873,12 @@ interface IEventInventoryPutItem : IEvent public bool? OnInventoryPutItem(Inventory inventory, Item item, Character user, int i, bool removeItem) { var result = LuaFuncs[nameof(OnInventoryPutItem)](inventory, item, user, i, removeItem); - if (result is bool b) + if (result is DynValue dynValue && dynValue.Type == DataType.Boolean) { - return b; - } - else - { - return null; + return dynValue.Boolean; } + + return null; } } } @@ -901,14 +899,12 @@ interface IEventInventoryItemSwap : IEvent public bool? OnInventoryItemSwap(Inventory inventory, Item item, Character user, int i, bool swapWholeStack) { var result = LuaFuncs[nameof(OnInventoryItemSwap)](inventory, item, user, i, swapWholeStack); - if (result is bool b) + if (result is DynValue dynValue && dynValue.Type == DataType.Boolean) { - return b; - } - else - { - return null; + return dynValue.Boolean; } + + return null; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 6fbb41a35..cceb83a13 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -196,6 +196,8 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _eventService.RegisterLuaEventAlias("changeFallDamage", nameof(IEventChangeFallDamage.OnChangeFallDamage)); + _eventService.RegisterLuaEventAlias("chatMessage", nameof(IEventChatMessage.OnChatMessage)); + _eventService.RegisterLuaEventAlias("canUseVoiceRadio", nameof(IEventCanUseVoiceRadio.OnCanUseVoiceRadio)); _eventService.RegisterLuaEventAlias("changeLocalVoiceRange", nameof(IEventChangeLocalVoiceRange.OnChangeLocalVoiceRange)); From f4138d2398ffdc0092783f3d013fb888f52123a9 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 22 Feb 2026 06:35:37 -0500 Subject: [PATCH 175/288] - Added networking permission checks to SettingEntry. - Removed error logging for missing save data for settings. --- .../SharedSource/LuaCs/Data/SettingEntry.cs | 59 ++++++++++++++----- .../LuaCs/_Services/ConfigService.cs | 5 ++ 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs index 40709b429..fc06a3cf5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs @@ -51,6 +51,36 @@ public class SettingEntry : SettingBase, ISettingBase, INetworkSyncVar whe public T DefaultValue { get; protected set; } public virtual bool TrySetValue(T value) + { +#if CLIENT + if (SyncType is NetSync.ServerAuthority && NetworkingService is not null + && GameMain.IsMultiplayer + && !GameMain.Client.HasPermission(this.WritePermissions)) + { + return false; + } +#endif + + if (!TrySetValueInternal(value)) + { + return false; + } + +#if CLIENT + if (GameMain.IsMultiplayer && SyncType is NetSync.ClientOneWay or NetSync.TwoWay) + { + NetworkingService?.SendNetVar(this); + } +#elif SERVER + if (GameMain.IsMultiplayer && SyncType is NetSync.TwoWay or NetSync.ServerAuthority) + { + NetworkingService?.SendNetVar(this); + } +#endif + return true; + } + + private bool TrySetValueInternal(T value) { if (value is null) { @@ -122,7 +152,8 @@ public class SettingEntry : SettingBase, ISettingBase, INetworkSyncVar whe public NetSync SyncType => ConfigInfo.NetSync; // needs to be added IConfigInfo - public ClientPermissions WritePermissions => throw new NotImplementedException(); + public ClientPermissions WritePermissions => ClientPermissions.ManageSettings; + public void ReadNetMessage(IReadMessage message) { if (SyncType == NetSync.None || NetworkingService is null) @@ -134,7 +165,7 @@ public class SettingEntry : SettingBase, ISettingBase, INetworkSyncVar whe { if (typeof(T).IsEnum) { - TrySetValue((T)(object)message.ReadInt32()); + TrySetValueInternal((T)(object)message.ReadInt32()); } // No...there's no better way to do this... @@ -142,42 +173,42 @@ public class SettingEntry : SettingBase, ISettingBase, INetworkSyncVar whe switch (typeCode) { case TypeCode.Boolean: - TrySetValue((T)Convert.ChangeType(message.ReadBoolean(), typeCode)); + TrySetValueInternal((T)Convert.ChangeType(message.ReadBoolean(), typeCode)); return; case TypeCode.Byte: - TrySetValue((T)Convert.ChangeType(message.ReadByte(), typeCode)); + TrySetValueInternal((T)Convert.ChangeType(message.ReadByte(), typeCode)); return; // SByte not supported by interface case TypeCode.SByte: - TrySetValue((T)Convert.ChangeType(message.ReadInt16(), typeCode)); + TrySetValueInternal((T)Convert.ChangeType(message.ReadInt16(), typeCode)); return; case TypeCode.Int16: - TrySetValue((T)Convert.ChangeType(message.ReadInt16(), typeCode)); + TrySetValueInternal((T)Convert.ChangeType(message.ReadInt16(), typeCode)); return; case TypeCode.Char: case TypeCode.UInt16: - TrySetValue((T)Convert.ChangeType(message.ReadUInt16(), typeCode)); + TrySetValueInternal((T)Convert.ChangeType(message.ReadUInt16(), typeCode)); return; case TypeCode.Int32: - TrySetValue((T)Convert.ChangeType(message.ReadInt32(), typeCode)); + TrySetValueInternal((T)Convert.ChangeType(message.ReadInt32(), typeCode)); return; case TypeCode.UInt32: - TrySetValue((T)Convert.ChangeType(message.ReadUInt32(), typeCode)); + TrySetValueInternal((T)Convert.ChangeType(message.ReadUInt32(), typeCode)); return; case TypeCode.Int64: - TrySetValue((T)Convert.ChangeType(message.ReadInt64(), typeCode)); + TrySetValueInternal((T)Convert.ChangeType(message.ReadInt64(), typeCode)); return; case TypeCode.UInt64: - TrySetValue((T)Convert.ChangeType(message.ReadUInt64(), typeCode)); + TrySetValueInternal((T)Convert.ChangeType(message.ReadUInt64(), typeCode)); return; case TypeCode.Single: - TrySetValue((T)Convert.ChangeType(message.ReadSingle(), typeCode)); + TrySetValueInternal((T)Convert.ChangeType(message.ReadSingle(), typeCode)); return; case TypeCode.Double: - TrySetValue((T)Convert.ChangeType(message.ReadDouble(), typeCode)); + TrySetValueInternal((T)Convert.ChangeType(message.ReadDouble(), typeCode)); return; case TypeCode.String: - TrySetValue((T)Convert.ChangeType(message.ReadString(), typeCode)); + TrySetValueInternal((T)Convert.ChangeType(message.ReadString(), typeCode)); return; case TypeCode.Decimal: default: diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs index 445da3fc0..cfb25e227 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs @@ -306,7 +306,12 @@ public sealed partial class ConfigService : IConfigService foreach (var settingBase in cfgValues) { +#if DEBUG + // log in debug only. ret.WithReasons(LoadSavedValueForConfig(settingBase).Reasons); +#else + LoadSavedValueForConfig(settingBase); +#endif } return ret; From a28333ff4e7d53cf8f4b60b337fe3e0dbca5ed32 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Mon, 23 Feb 2026 01:01:23 -0500 Subject: [PATCH 176/288] Implemented ConfigProfiles internally. --- .../LuaCs/_Services/ConfigService.cs | 53 ++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs index cfb25e227..df3ac12d9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs @@ -121,12 +121,17 @@ public sealed partial class ConfigService : IConfigService private const string SaveDataFileName = "SettingsData.xml"; + // --- Settings private readonly ConcurrentDictionary<(ContentPackage OwnerPackage, string InternalName), ISettingBase> _settingsInstances = new(); private readonly ConcurrentDictionary> _instanceFactory = new(); private readonly ConcurrentDictionary> _settingsInstancesByPackage = new(); + + // --- Profiles + private readonly ConcurrentDictionary<(ContentPackage Package, string ProfileName), IConfigProfileInfo> + _settingsProfiles = new(); private IStorageService _storageService; private ILoggerService _logger; @@ -170,7 +175,7 @@ public sealed partial class ConfigService : IConfigService public async Task LoadConfigsAsync(ImmutableArray configResources) { - using var lck = _operationLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + using var lck = await _operationLock.AcquireReaderLock(); IService.CheckDisposed(this); if (configResources.IsDefaultOrEmpty) { @@ -258,11 +263,47 @@ public sealed partial class ConfigService : IConfigService public async Task LoadConfigsProfilesAsync(ImmutableArray configProfileResources) { -#if DEBUG - // TODO: Implement profiles. - return FluentResults.Result.Ok(); -#endif - throw new NotImplementedException(); + using var _ = await _operationLock.AcquireReaderLock(); + IService.CheckDisposed(this); + if (configProfileResources.IsDefaultOrEmpty) + { + ThrowHelper.ThrowArgumentNullException($"{nameof(LoadConfigsProfilesAsync)}: {nameof(configProfileResources)} is empty."); + } + + var result = new FluentResults.Result(); + + foreach (var resource in configProfileResources) + { + var r = await _configProfileInfoParserService.TryParseResourcesAsync(resource); + if (r.IsFailed) + { + result.WithErrors(r.Errors); + continue; + } + + foreach (var info in r.Value) + { + if (!_settingsProfiles.TryAdd((info.OwnerPackage, info.InternalName), info)) + { + result.WithErrors(r.Errors); + continue; + } + + if (info.InternalName.Equals("default", StringComparison.InvariantCultureIgnoreCase)) + { + //apply it + foreach (var value in info.ProfileValues) + { + if (_settingsInstances.TryGetValue((info.OwnerPackage, value.SettingName), out var instance)) + { + instance.TrySetValue(value.Element); + } + } + } + } + } + + return result; } public FluentResults.Result LoadSavedValueForConfig(ISettingBase setting) From 394856fa0472050890ee93b136e8c56844652f55 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 23 Feb 2026 17:40:59 -0500 Subject: [PATCH 177/288] - Final commit before migration to /dev - Localization files added. --- .../LocalMods/LuaCsForBarotrauma/filelist.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/filelist.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/filelist.xml index 4a679f48e..9b389d76a 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/filelist.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/filelist.xml @@ -1,3 +1,5 @@  - - \ No newline at end of file + + + + From d9d980122d72df68fa1c5db3cb112476d27b0827 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 24 Feb 2026 15:26:49 -0500 Subject: [PATCH 178/288] - Moved all console commands to their respective services and added via injector service. - Fixed LuaCs IsCsEnabled prompt not working. - Add settings profiles support. --- .../ClientSource/DebugConsole.cs | 20 --- .../ClientSource/LuaCs/LuaCsSetup.cs | 34 +++- .../SharedSource/DebugConsole.cs | 88 +-------- .../SharedSource/LuaCs/LuaCsSetup.cs | 3 +- .../LuaCs/_Services/ConfigService.cs | 169 +++++++++++++++++- .../LuaCs/_Services/ConsoleCommandsService.cs | 61 +++++++ .../_Services/LuaScriptManagementService.cs | 32 +++- .../_Services/_Interfaces/IConfigService.cs | 1 + .../_Interfaces/IConsoleCommandsService.cs | 11 ++ 9 files changed, 298 insertions(+), 121 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConsoleCommandsService.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConsoleCommandsService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index 38e2bb6f7..46fa01e72 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -4240,28 +4240,8 @@ namespace Barotrauma var result = GameMain.LuaCs.LuaScriptManagementService.DoString(string.Join(" ", args)); GameMain.LuaCs.Logger.LogResults(result.ToResult()); })); - - commands.Add(new Command("cl_reloadlua|cl_reloadcs|cl_reloadluacs", "Re-initializes the LuaCs environment.", (string[] args) => - { - GameMain.LuaCs.EventService.PublishEvent(sub => sub.OnReloadAllPackages()); - })); - - commands.Add(new Command("cl_toggleluadebug", "Toggles the MoonSharp Debug Server.", (string[] args) => - { - int port = 41912; - - if (args.Length > 0) - { - int.TryParse(args[0], out port); - } - - throw new NotImplementedException(); - //GameMain.LuaCs.ToggleDebugger(port); - })); } - - private static void ReloadWearables(Character character, int variant = 0) { foreach (var limb in character.AnimController.Limbs) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index d1fb680a0..31c633b81 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -53,15 +53,39 @@ namespace Barotrauma msg.Buttons[0].OnClicked = (GUIButton button, object obj) => { - this.IsCsEnabled = true; - isCsValueChanged = true; - return true; + try + { + this.IsCsEnabled = true; + isCsValueChanged = true; + CoroutineManager.Invoke(() => + { + if (CurrentRunState >= RunState.Running) + { + var currentRunState = CurrentRunState; + SetRunState(RunState.LoadedNoExec); + SetRunState(currentRunState); + } + }, 0f); + return true; + } + finally + { + msg.Close(); + } }; msg.Buttons[1].OnClicked = (GUIButton button, object obj) => { - this.IsCsEnabled = false; - return true; + try + { + // avoid a TOCTOU scenario. + this.IsCsEnabled = false; + return true; + } + finally + { + msg.Close(); + } }; return isCsValueChanged; diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index 51cf0444f..bca95cd66 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -2311,93 +2311,7 @@ namespace Barotrauma NewMessage($"Start item set changed to \"{AutoItemPlacer.DefaultStartItemSet}\""); }, isCheat: false)); - commands.Add(new Command("cfg_getvalue", "cfg_getvalue [Content Package] [InternalName] [ValueString]: gets a config value.", (string[] args) => - { - if (args.Length < 1) - { - ThrowError("Please specify the name of the package to set the config."); - return; - } - - if (args.Length < 2) - { - ThrowError("Please specify the name of the config."); - return; - } - - var package = ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Name == args[0]); - if (package == null) - { - ThrowError($"Could not find the package {args[0]}!"); - return; - } - - string internalName = args[1]; - - if (!GameMain.LuaCs.ConfigService.TryGetConfig(package, internalName, out LuaCs.Data.ISettingBase setting)) - { - ThrowError($"Could not get config with name {internalName}"); - return; - } - - NewMessage($"config {internalName} value is {setting.GetStringValue()}", Color.Green); - }, getValidArgs: () => new[] - { - ContentPackageManager.RegularPackages.Select(p => p.Name).ToArray() - })); - - commands.Add(new Command("cfg_setvalue", "cfg_setvalue [Content Package] [InternalName] [ValueString]: sets a config.", (string[] args) => - { - if (args.Length < 1) - { - ThrowError("Please specify the name of the package to set the config."); - return; - } - - if (args.Length < 2) - { - ThrowError("Please specify the name of the config."); - return; - } - - if (args.Length < 3) - { - ThrowError("Please specify the value to set the config to."); - return; - } - - var package = ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Name == args[0]); - if (package == null) - { - ThrowError($"Could not find the package {args[0]}!"); - return; - } - - string internalName = args[1]; - string valueString = args[2]; - - if (!GameMain.LuaCs.ConfigService.TryGetConfig(package, internalName, out LuaCs.Data.ISettingBase setting)) - { - ThrowError($"Could not get config with name {internalName}"); - return; - } - - if (setting.TrySetValue(valueString)) - { - NewMessage($"Set config {internalName} value to {valueString}", Color.Green); - if (GameMain.LuaCs.ConfigService.SaveConfigValue(setting) is { IsFailed: true } res) - { - NewMessage($"Failed to save new config data to disk. Reasons: {res.ToString()}"); - } - } - else - { - ThrowError($"Failed to set config value"); - } - }, getValidArgs: () => new[] - { - ContentPackageManager.RegularPackages.Select(p => p.Name).ToArray() - })); + //"dummy commands" that only exist so that the server can give clients permissions to use them //TODO: alphabetical order? diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index b7e5b1f3b..f81efda0d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -181,7 +181,8 @@ namespace Barotrauma servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); - + servicesProvider.RegisterServiceType(ServiceLifetime.Transient); + // Extension/Sub Services servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs index df3ac12d9..d83690172 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs @@ -14,6 +14,7 @@ using Barotrauma.LuaCs.Events; using Barotrauma.LuaCs; using FluentResults; using Microsoft.Toolkit.Diagnostics; +using Microsoft.Xna.Framework; namespace Barotrauma.LuaCs; @@ -71,12 +72,14 @@ public sealed partial class ConfigService : IConfigService _settingsInstances.Clear(); _instanceFactory.Clear(); _settingsInstancesByPackage.Clear(); + _commandsService.Dispose(); _storageService = null; _logger = null; _eventService = null; _configInfoParserService = null; _configProfileInfoParserService = null; + _commandsService = null; } public FluentResults.Result Reset() @@ -136,7 +139,7 @@ public sealed partial class ConfigService : IConfigService private IStorageService _storageService; private ILoggerService _logger; private IEventService _eventService; - private ILuaCsInfoProvider _luaCsInfoProvider; + private IConsoleCommandsService _commandsService; private IParserServiceOneToManyAsync _configInfoParserService; private IParserServiceOneToManyAsync _configProfileInfoParserService; @@ -145,16 +148,143 @@ public sealed partial class ConfigService : IConfigService IParserServiceOneToManyAsync configInfoParserService, IParserServiceOneToManyAsync configProfileInfoParserService, IEventService eventService, - ILuaCsInfoProvider luaCsInfoProvider) + IConsoleCommandsService commandsService) { _logger = logger; _storageService = storageService; _configInfoParserService = configInfoParserService; _configProfileInfoParserService = configProfileInfoParserService; _eventService = eventService; - _luaCsInfoProvider = luaCsInfoProvider; + _commandsService = commandsService; _storageService.UseCaching = true; + InjectCommands(commandsService); + } + + private void InjectCommands(IConsoleCommandsService commandsService) + { + commandsService.RegisterCommand("cfg_getvalue", "cfg_getvalue [Content Package] [InternalName] [ValueString]: gets a config value.", (string[] args) => + { + if (args.Length < 1) + { + _logger.LogError("Please specify the name of the package to set the config."); + return; + } + + if (args.Length < 2) + { + _logger.LogError("Please specify the name of the config."); + return; + } + + var package = ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Name == args[0]); + if (package == null) + { + _logger.LogError($"Could not find the package {args[0]}!"); + return; + } + + string internalName = args[1]; + + if (!TryGetConfig(package, internalName, out ISettingBase setting)) + { + _logger.LogError($"Could not get config with name {internalName}"); + return; + } + + _logger.LogMessage($"config {internalName} value is {setting.GetStringValue()}", Color.Green); + }, getValidArgs: () => new[] + { + ContentPackageManager.RegularPackages.Select(p => p.Name).ToArray() + }); + + commandsService.RegisterCommand("cfg_setvalue", "cfg_setvalue [Content Package] [InternalName] [ValueString]: sets a config.", (string[] args) => + { + if (args.Length < 1) + { + _logger.LogError("Please specify the name of the package to set the config."); + return; + } + + if (args.Length < 2) + { + _logger.LogError("Please specify the name of the config."); + return; + } + + if (args.Length < 3) + { + _logger.LogError("Please specify the value to set the config to."); + return; + } + + var package = ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Name == args[0]); + if (package == null) + { + _logger.LogError($"Could not find the package {args[0]}!"); + return; + } + + string internalName = args[1]; + string valueString = args[2]; + + if (!TryGetConfig(package, internalName, out ISettingBase setting)) + { + _logger.LogError($"Could not get config with name {internalName}"); + return; + } + + if (setting.TrySetValue(valueString)) + { + _logger.LogMessage($"Set config {internalName} value to {valueString}", Color.Green); + if (SaveConfigValue(setting) is { IsFailed: true } res) + { + _logger.LogMessage($"Failed to save new config data to disk. Reasons: {res.ToString()}"); + } + } + else + { + _logger.LogError($"Failed to set config value"); + } + }, getValidArgs: () => new[] + { + ContentPackageManager.RegularPackages.Select(p => p.Name).ToArray() + }); + + commandsService.RegisterCommand("cfg_setprofile", "cfg_setprofile [ContentPackage] [InternalProfileName]", + (string[] args) => + { + if (args.Length < 1 || args[0].IsNullOrWhiteSpace()) + { + _logger.LogError("Please specify the name of the package of the profile."); + return; + } + + if (args.Length < 2 || args[1].IsNullOrWhiteSpace()) + { + _logger.LogError("Please specify the name of the profile."); + return; + } + + var package = ContentPackageManager.RegularPackages.FirstOrDefault(p => p.Name == args[0], null); + if (package == null) + { + _logger.LogError($"Could not find the package {args[0]}!"); + return; + } + + var res = ApplyConfigProfile(package, args[1]); + if (res.IsFailed) + { + _logger.LogError($"Errors while applying profile {args[1]}!"); + _logger.LogResults(res); + return; + } + _logger.Log($"Profile {args[1]} applied successfully!", Color.Green); + }, getValidArgs: () => new[] + { + ContentPackageManager.RegularPackages.Select(p => p.Name).ToArray() + }, false); } @@ -357,7 +487,38 @@ public sealed partial class ConfigService : IConfigService return ret; } - + + public FluentResults.Result ApplyConfigProfile(ContentPackage package, string internalName) + { + Guard.IsNotNull(package, nameof(package)); + Guard.IsNotNullOrWhiteSpace(internalName, nameof(internalName)); + using var _ = _operationLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + if (!_settingsProfiles.TryGetValue((package, internalName), out var setting)) + { + return FluentResults.Result.Fail($"{nameof(ApplyConfigProfile)}: Could not find profile [{package.Name}.{internalName}]"); + } + + var result = new FluentResults.Result(); + + foreach (var profileValue in setting.ProfileValues) + { + if (!_settingsInstances.TryGetValue((package, profileValue.SettingName), out var instance)) + { + result.WithError(new Error($"{nameof(ApplyConfigProfile)}: Could not find setting [{profileValue.SettingName}].")); + continue; + } + + if (!instance.TrySetValue(profileValue.Element)) + { + result.WithError(new Error($"{nameof(ApplyConfigProfile)}: Failed to set value for [{profileValue.SettingName}].")); + } + } + + return result; + } + public FluentResults.Result SaveConfigValue(ISettingBase setting) { XDocument cpCfgValues; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConsoleCommandsService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConsoleCommandsService.cs new file mode 100644 index 000000000..a6754ad2a --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConsoleCommandsService.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Barotrauma.LuaCs; + +public class ConsoleCommandsService : IConsoleCommandsService +{ + private readonly ConcurrentDictionary _registeredCommands = new(); + + public void Dispose() + { + if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) + { + return; + } + foreach (var cmd in _registeredCommands.Values.ToImmutableArray()) + { + DebugConsole.Commands.Remove(cmd); + } + _registeredCommands.Clear(); + } + + private int _isDisposed = 0; + public bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } + public FluentResults.Result RegisterCommand(string name, string help, Action onExecute, Func getValidArgs = null, bool isCheat = false) + { + IService.CheckDisposed(this); + var cmd = new DebugConsole.Command(name, help, onExecute, getValidArgs, isCheat); + if (!_registeredCommands.TryAdd(name, cmd)) + { + return FluentResults.Result.Fail($"{nameof(RegisterCommand)}: A command with the name '{name}' is already added."); + } + DebugConsole.Commands.Add(cmd); + return FluentResults.Result.Ok(); + } + + public void RemoveCommand(string name) + { + IService.CheckDisposed(this); + if (_registeredCommands.TryRemove(name, out DebugConsole.Command cmd)) + { + DebugConsole.Commands.Remove(cmd); + } + } + + public void RemoveRegisteredCommands() + { + IService.CheckDisposed(this); + foreach (var cmd in _registeredCommands.Values.ToImmutableArray()) + { + DebugConsole.Commands.Remove(cmd); + } + _registeredCommands.Clear(); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index cceb83a13..43a140e1d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -50,6 +50,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService private readonly IDefaultLuaRegistrar _defaultLuaRegistrar; private readonly IPluginManagementService _pluginManagementService; private readonly INetworkingService _networkingService; + private readonly IConsoleCommandsService _commandsService; //private readonly ILuaCsUtility _luaCsUtility; public LuaScriptManagementService( @@ -64,7 +65,8 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService LuaGame luaGame, IEventService eventService, //ILuaCsUtility luaCsUtility, - ILuaCsTimer luaCsTimer + ILuaCsTimer luaCsTimer, + IConsoleCommandsService commandsService ) { _luaScriptLoader = loader; @@ -78,13 +80,33 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _luaGame = luaGame; _eventService = eventService; - //_luaCsNetworking = luaCsNetworking; - //_luaCsUtility = luaCsUtility; + _commandsService = commandsService; _luaCsTimer = luaCsTimer; RegisterLuaEvents(); } + private void RegisterConsoleCommands(IConsoleCommandsService commands) + { + commands.RegisterCommand("cl_reloadlua|cl_reloadcs|cl_reloadluacs", "Re-initializes the LuaCs environment.", (string[] args) => + { + GameMain.LuaCs.EventService.PublishEvent(sub => sub.OnReloadAllPackages()); + }); + + commands.RegisterCommand("cl_toggleluadebug", "Toggles the MoonSharp Debug Server.", (string[] args) => + { + int port = 41912; + + if (args.Length > 0) + { + int.TryParse(args[0], out port); + } + + throw new NotImplementedException(); + //GameMain.LuaCs.ToggleDebugger(port); + }); + } + public bool IsDisposed { get; private set; } public async Task LoadScriptResourcesAsync(ImmutableArray resourcesInfo) @@ -400,6 +422,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService public FluentResults.Result Reset() { + IService.CheckDisposed(this); _luaScriptLoader.ClearCaches(); _userDataService.Reset(); _luaCsTimer.Reset(); @@ -409,9 +432,10 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService public void Dispose() { + IsDisposed = true; _userDataService.Dispose(); _luaScriptLoader.Dispose(); - IsDisposed = true; + _commandsService.Dispose(); } public object? GetGlobalTableValue(string tableName) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConfigService.cs index 7e271c625..83f8a3d43 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConfigService.cs @@ -19,6 +19,7 @@ public partial interface IConfigService : IReusableService, ILuaConfigService Task LoadConfigsProfilesAsync(ImmutableArray configProfileResources); FluentResults.Result LoadSavedValueForConfig(ISettingBase setting); FluentResults.Result LoadSavedConfigsValues(); + FluentResults.Result ApplyConfigProfile(ContentPackage package, string internalName); FluentResults.Result SaveConfigValue(ISettingBase setting); FluentResults.Result DisposePackageData(ContentPackage package); FluentResults.Result DisposeAllPackageData(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConsoleCommandsService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConsoleCommandsService.cs new file mode 100644 index 000000000..998411b93 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConsoleCommandsService.cs @@ -0,0 +1,11 @@ +using System; + +namespace Barotrauma.LuaCs; + +public interface IConsoleCommandsService : IService +{ + FluentResults.Result RegisterCommand(string name, string help, Action onExecute, Func getValidArgs = null, + bool isCheat = false); + void RemoveCommand(string name); + void RemoveRegisteredCommands(); +} From 94556fd6e707af4b3f4f7c19ecbd51b8383e094a Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 25 Feb 2026 15:19:01 -0500 Subject: [PATCH 179/288] - SettingsMenuService added. - Added Settings Tab to vanilla SettingsMenu - Fixed ISystem implementation. --- .../ClientSource/LuaCs/LuaCsSetup.cs | 1 + .../LuaCs/Services/SettingsMenuSystem.cs | 94 +++++++++++++++++++ .../{ => _Interfaces}/IClientLoggerService.cs | 0 .../{ => _Interfaces}/IConfigService.cs | 0 .../_Interfaces/ISettingsMenuService.cs | 6 ++ .../{ => _Interfaces}/IUIStylesCollection.cs | 0 .../{ => _Interfaces}/IUIStylesService.cs | 0 .../Screens/MainMenuScreen/MainMenuScreen.cs | 4 +- .../ClientSource/Settings/SettingsMenu.cs | 32 +++---- .../LocalMods/LuaCsForBarotrauma/filelist.xml | 2 +- .../LuaCs/_Services/ServicesProvider.cs | 31 +++--- 11 files changed, 133 insertions(+), 37 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/SettingsMenuSystem.cs rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/{ => _Interfaces}/IClientLoggerService.cs (100%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/{ => _Interfaces}/IConfigService.cs (100%) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/ISettingsMenuService.cs rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/{ => _Interfaces}/IUIStylesCollection.cs (100%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/{ => _Interfaces}/IUIStylesService.cs (100%) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index 31c633b81..96490b7ce 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -98,6 +98,7 @@ namespace Barotrauma //serviceProvider.RegisterServiceType(ServiceLifetime.Transient); serviceProvider.RegisterServiceType, ModConfigFileParserService>(ServiceLifetime.Transient); serviceProvider.RegisterServiceType(ServiceLifetime.Transient); + serviceProvider.RegisterServiceType(ServiceLifetime.Singleton); } /// diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/SettingsMenuSystem.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/SettingsMenuSystem.cs new file mode 100644 index 000000000..e3cdf5f1e --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/SettingsMenuSystem.cs @@ -0,0 +1,94 @@ +using System; +using Barotrauma.Extensions; +using HarmonyLib; +using Microsoft.Xna.Framework; + +namespace Barotrauma.LuaCs; + +public class SettingsMenuSystem : ISettingsMenuSystem +{ + private GUIFrame _menuFrame; + private GUIButton _menuOpenButton; + private readonly Harmony _harmony; + private static SettingsMenuSystem _systemInstance; + + public SettingsMenuSystem() + { + _systemInstance = this; + _harmony = Harmony.CreateAndPatchAll(typeof(SettingsMenuSystem)); + } + + [HarmonyPatch(typeof(SettingsMenu), "CreateModsTab"), HarmonyPostfix] + private static void SettingsMenu_CreateModsTab_Post(SettingsMenu __instance) + { + _systemInstance.CreateSettingsMenu(__instance); + } + + private void CreateSettingsMenu(SettingsMenu __instance) + { + var tabIndex = (SettingsMenu.Tab)Enum.GetValues().Length; + var contentFrame = CreateNewContentFrame(tabIndex); + contentFrame.RectTransform.RelativeSize = Vector2.One; + + + + GUIFrame CreateNewContentFrame(SettingsMenu.Tab tab) + { + if (__instance.tabContents.TryGetValue(tab, out (GUIButton Button, GUIFrame Content) tabContent)) + { + return tabContent.Content; + } + + var contentFr = new GUIFrame(new RectTransform(Vector2.One * 0.95f, __instance.contentFrame.RectTransform, Anchor.Center, Pivot.Center), style: null); + + var button = new GUIButton(new RectTransform(Vector2.One, __instance.tabber.RectTransform, Anchor.TopLeft, Pivot.TopLeft, scaleBasis: ScaleBasis.Smallest), "", style: $"SettingsMenuTab.Mods") + { + ToolTip = TextManager.Get($"LuaCsForBarotrauma.SettingsMenu.ModSettingsButton"), + OnClicked = (b, _) => + { + __instance.SelectTab(tab); + return false; + } + }; + button.RectTransform.MaxSize = RectTransform.MaxPoint; + button.Children.ForEach(c => c.RectTransform.MaxSize = RectTransform.MaxPoint); + + __instance.tabContents.Add(tab, (button, contentFr)); + + return contentFr; + } + } + + private void DisposeMenuFrame() + { + if (_menuFrame is not null) + { + _menuFrame.Parent.RemoveChild(_menuFrame); + _menuFrame = null; + } + } + + #region DISPOSAL + + public void Dispose() + { + if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) + { + return; + } + DisposeMenuFrame(); + GC.SuppressFinalize(this); + } + private int _isDisposed = 0; + public bool IsDisposed + { + get => ModUtils.Threading.GetBool(ref _isDisposed); + private set => ModUtils.Threading.SetBool(ref _isDisposed, value); + } + public FluentResults.Result Reset() + { + throw new NotImplementedException(); + } + + #endregion +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IClientLoggerService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IClientLoggerService.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IClientLoggerService.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IClientLoggerService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IConfigService.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IConfigService.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IConfigService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/ISettingsMenuService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/ISettingsMenuService.cs new file mode 100644 index 000000000..906fa7971 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/ISettingsMenuService.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs; + +public interface ISettingsMenuSystem : ISystem +{ + +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesCollection.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IUIStylesCollection.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesCollection.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IUIStylesCollection.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IUIStylesService.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/IUIStylesService.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IUIStylesService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs index 233f6b666..45a7c141f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs @@ -531,7 +531,7 @@ namespace Barotrauma } }; #endif - new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(40, 50) }, + /*new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(40, 50) }, $"Open LuaCs Settings", style: "MainMenuGUIButton", color: GUIStyle.Red) { IgnoreLayoutGroups = true, @@ -540,7 +540,7 @@ namespace Barotrauma LuaCsSettingsMenu.Open(Frame.RectTransform); return true; } - }; + };*/ // TODO: Implement version reading. //string version = File.Exists(LuaCsSetup.VersionFile) ? File.ReadAllText(LuaCsSetup.VersionFile) : "Github"; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs index 34ebe4aca..7f9a9214a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Settings/SettingsMenu.cs @@ -33,10 +33,10 @@ namespace Barotrauma private GameSettings.Config unsavedConfig; - private readonly GUIFrame mainFrame; + public readonly GUIFrame mainFrame; - private readonly GUILayoutGroup tabber; - private readonly GUIFrame contentFrame; + public readonly GUILayoutGroup tabber; + public readonly GUIFrame contentFrame; private readonly GUILayoutGroup bottom; public readonly WorkshopMenu WorkshopMenu; @@ -103,7 +103,7 @@ namespace Barotrauma newContent.Visible = true; } - private readonly Dictionary tabContents; + public readonly Dictionary tabContents; public void SelectTab(Tab tab) { @@ -149,7 +149,7 @@ namespace Barotrauma return content; } - private static (GUILayoutGroup Left, GUILayoutGroup Right) CreateSidebars(GUIFrame parent, bool split = false) + public static (GUILayoutGroup Left, GUILayoutGroup Right) CreateSidebars(GUIFrame parent, bool split = false) { GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(Vector2.One, parent.RectTransform), isHorizontal: true); GUILayoutGroup left = new GUILayoutGroup(new RectTransform((0.4875f, 1.0f), layout.RectTransform), isHorizontal: false); @@ -166,29 +166,29 @@ namespace Barotrauma return (left, right); } - private static GUILayoutGroup CreateCenterLayout(GUIFrame parent) + public static GUILayoutGroup CreateCenterLayout(GUIFrame parent) { return new GUILayoutGroup(new RectTransform((0.5f, 1.0f), parent.RectTransform, Anchor.TopCenter, Pivot.TopCenter)) { ChildAnchor = Anchor.TopCenter }; } - private static RectTransform NewItemRectT(GUILayoutGroup parent) + public static RectTransform NewItemRectT(GUILayoutGroup parent) => new RectTransform((1.0f, 0.06f), parent.RectTransform, Anchor.CenterLeft); - private static void Spacer(GUILayoutGroup parent) + public static void Spacer(GUILayoutGroup parent) { new GUIFrame(new RectTransform((1.0f, 0.03f), parent.RectTransform, Anchor.CenterLeft), style: null); } - private static GUITextBlock Label(GUILayoutGroup parent, LocalizedString str, GUIFont font) + public static GUITextBlock Label(GUILayoutGroup parent, LocalizedString str, GUIFont font) { return new GUITextBlock(NewItemRectT(parent), str, font: font); } - private static void DropdownEnum(GUILayoutGroup parent, Func textFunc, Func? tooltipFunc, T currentValue, + public static void DropdownEnum(GUILayoutGroup parent, Func textFunc, Func? tooltipFunc, T currentValue, Action setter) where T : Enum => Dropdown(parent, textFunc, tooltipFunc, (T[])Enum.GetValues(typeof(T)), currentValue, setter); - private static GUIDropDown Dropdown(GUILayoutGroup parent, Func textFunc, Func? tooltipFunc, IReadOnlyList values, T currentValue, Action setter) + public static GUIDropDown Dropdown(GUILayoutGroup parent, Func textFunc, Func? tooltipFunc, IReadOnlyList values, T currentValue, Action setter) { var dropdown = new GUIDropDown(NewItemRectT(parent), elementCount: values.Count); values.ForEach(v => dropdown.AddItem(text: textFunc(v), userData: v, toolTip: tooltipFunc?.Invoke(v) ?? null)); @@ -204,7 +204,7 @@ namespace Barotrauma return dropdown; } - private static (GUIScrollBar slider, GUITextBlock label) Slider(GUILayoutGroup parent, Vector2 range, int steps, Func labelFunc, float currentValue, Action setter, LocalizedString? tooltip = null) + public static (GUIScrollBar slider, GUITextBlock label) Slider(GUILayoutGroup parent, Vector2 range, int steps, Func labelFunc, float currentValue, Action setter, LocalizedString? tooltip = null) { var layout = new GUILayoutGroup(NewItemRectT(parent), isHorizontal: true); var slider = new GUIScrollBar(new RectTransform((0.72f, 1.0f), layout.RectTransform), style: "GUISlider") @@ -229,7 +229,7 @@ namespace Barotrauma return (slider, label); } - private static GUITickBox Tickbox(GUILayoutGroup parent, LocalizedString label, LocalizedString tooltip, bool currentValue, Action setter) + public static GUITickBox Tickbox(GUILayoutGroup parent, LocalizedString label, LocalizedString tooltip, bool currentValue, Action setter) { return new GUITickBox(NewItemRectT(parent), label) { @@ -243,9 +243,9 @@ namespace Barotrauma }; } - private string Percentage(float v) => ToolBox.GetFormattedPercentage(v); + public string Percentage(float v) => ToolBox.GetFormattedPercentage(v); - private static int Round(float v) => MathUtils.RoundToInt(v); + public static int Round(float v) => MathUtils.RoundToInt(v); private void CreateGraphicsTab() { @@ -965,4 +965,4 @@ namespace Barotrauma GUI.SettingsMenuOpen = false; } } -} \ No newline at end of file +} diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/filelist.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/filelist.xml index 9b389d76a..445a2ad00 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/filelist.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/filelist.xml @@ -1,5 +1,5 @@  - + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ServicesProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ServicesProvider.cs index 9452de187..8bfad23f2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ServicesProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ServicesProvider.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; using LightInject; +using Microsoft.Toolkit.Diagnostics; namespace Barotrauma.LuaCs; @@ -15,14 +16,11 @@ public class ServicesProvider : IServicesProvider { private ServiceContainer _serviceContainerInst; private ServiceContainer ServiceContainer => _serviceContainerInst; - /// - /// Definition: [Key: InterfaceType, Value: ConcreteTypes] - /// - private readonly ConcurrentDictionary> _systemTypeDefs = new(); + /// /// Definition: [Key: ConcreteType, Value: TypeInstance] /// - private readonly ConcurrentDictionary _systemInstances = new(); + private ImmutableArray _systemInstances = ImmutableArray.Empty; private readonly ReaderWriterLockSlim _serviceLock = new(); public ServicesProvider() @@ -41,7 +39,6 @@ public class ServicesProvider : IServicesProvider if (typeof(TSvcInterface).IsAssignableTo(typeof(ISystem))) { lifetimeInstance = new PerContainerLifetime(); - _systemTypeDefs.GetOrAdd(typeof(TSvcInterface), (type) => new ConcurrentBag()); } if (lifetimeInstance is null) @@ -87,10 +84,9 @@ public class ServicesProvider : IServicesProvider } // ISystem services must run as a lifetime singleton - if (typeof(TSvcInterface).IsAssignableTo(typeof(ISystem))) + if (typeof(TService).IsAssignableTo(typeof(ISystem))) { lifetimeInstance = new PerContainerLifetime(); - _systemTypeDefs.GetOrAdd(typeof(TSvcInterface), (type) => new ConcurrentBag()); } if (lifetimeInstance is null) @@ -142,15 +138,15 @@ public class ServicesProvider : IServicesProvider try { _serviceLock.EnterWriteLock(); - ServiceContainer?.Compile(); - foreach (var typeDef in _systemTypeDefs.Values.SelectMany(type => type)) + ServiceContainer!.Compile(); + if (!_systemInstances.IsDefaultOrEmpty) { - if (_systemInstances.ContainsKey(typeDef)) - { - continue; - } - _systemInstances[typeDef] = (ISystem)ServiceContainer?.TryGetInstance(typeDef); + ThrowHelper.ThrowInvalidOperationException($"Systems are already instanced!"); } + + _systemInstances = ServiceContainer.GetAllInstances(typeof(ISystem)) + .Select(obj => (ISystem)obj) + .ToImmutableArray(); } finally { @@ -250,7 +246,7 @@ public class ServicesProvider : IServicesProvider try { _serviceLock.EnterWriteLock(); - foreach (var system in _systemInstances.Values) + foreach (var system in _systemInstances) { try { @@ -261,8 +257,7 @@ public class ServicesProvider : IServicesProvider // ignored, no logging services available. } } - _systemInstances.Clear(); - _systemTypeDefs.Clear(); + _systemInstances = ImmutableArray.Empty; _serviceContainerInst?.Dispose(); _serviceContainerInst = new ServiceContainer(); } From 6e556a02d440f3ec3730e266bcb30ef24d5eadb6 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 26 Feb 2026 19:16:50 -0500 Subject: [PATCH 180/288] I'm cooked, will finish this later. --- .../LuaCs/Configuration/ISettingControl.cs | 2 +- .../LuaCs/Configuration/SettingControl.cs | 146 ++++++++++++++++++ .../ClientSource/LuaCs/GUIUtil.cs | 123 +++++++++++++++ .../Services/ModsControlsSettingsMenu.cs | 17 ++ .../Services/ModsGameplaySettingsMenu.cs | 17 ++ .../LuaCs/Services/ModsSettingsMenu.cs | 35 +++++ .../LuaCs/Services/SettingsMenuSystem.cs | 99 +++++++----- .../LuaCsForBarotrauma/Texts/English.xml | 9 ++ .../LuaCsForBarotrauma/Texts/Portuguese.xml | 3 + 9 files changed, 409 insertions(+), 42 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/SettingControl.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/GUIUtil.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsControlsSettingsMenu.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsSettingsMenu.cs create mode 100644 Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml create mode 100644 Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/Portuguese.xml diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/ISettingControl.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/ISettingControl.cs index a560e4fc4..c0202b2e8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/ISettingControl.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/ISettingControl.cs @@ -4,8 +4,8 @@ namespace Barotrauma.LuaCs.Data; public interface ISettingControl : ISettingBase { - event Action OnDown; KeyOrMouse Value { get; } bool TrySetValue(KeyOrMouse value); bool IsDown(); + bool IsHit(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/SettingControl.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/SettingControl.cs new file mode 100644 index 000000000..b0812f425 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/SettingControl.cs @@ -0,0 +1,146 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Xml.Linq; +using Barotrauma.LuaCs.Data; +using Microsoft.Xna.Framework.Input; +using OneOf; + +namespace Barotrauma.LuaCs.Configuration; + +public class SettingControl : ISettingControl +{ + public string InternalName { get; } + public ContentPackage OwnerPackage { get; } + public bool Equals(ISettingBase other) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + public IConfigDisplayInfo GetDisplayInfo() + { + throw new NotImplementedException(); + } + + public Type GetValueType() => typeof(KeyOrMouse); + public string GetStringValue() => Value.ToString(); + + public string GetDefaultStringValue() => new KeyOrMouse(Keys.NumLock).ToString(); + + public bool TrySetValue(OneOf value) + { + var newVal = value.Match( + (string v) => GetKeyOrMouse(v), + (XElement e) => e.GetAttributeKeyOrMouse("Value", null)); + + if (newVal is null) + { + return false; + } + + Value = newVal; + OnValueChanged?.Invoke(this); + return true; + + KeyOrMouse GetKeyOrMouse(string strValue) + { + strValue ??= string.Empty; + if (Enum.TryParse(strValue, true, out Microsoft.Xna.Framework.Input.Keys key)) + { + return key; + } + else if (Enum.TryParse(strValue, out MouseButton mouseButton)) + { + return mouseButton; + } + else if (int.TryParse(strValue, NumberStyles.Any, CultureInfo.InvariantCulture, out int mouseButtonInt) && + Enum.GetValues().Contains((MouseButton)mouseButtonInt)) + { + return (MouseButton)mouseButtonInt; + } + else if (string.Equals(strValue, "LeftMouse", StringComparison.OrdinalIgnoreCase)) + { + return !PlayerInput.MouseButtonsSwapped() ? MouseButton.PrimaryMouse : MouseButton.SecondaryMouse; + } + else if (string.Equals(strValue, "RightMouse", StringComparison.OrdinalIgnoreCase)) + { + return !PlayerInput.MouseButtonsSwapped() ? MouseButton.SecondaryMouse : MouseButton.PrimaryMouse; + } + + return null; + } + + } + + public event Action OnValueChanged; + public OneOf GetSerializableValue() + { + return Value.ToString(); + } + + public KeyOrMouse Value { get; private set; } = new KeyOrMouse(Keys.NumLock); + + public bool TrySetValue(KeyOrMouse value) + { + Value = value; + OnValueChanged?.Invoke(this); + return true; + } + + public bool IsDown() + { + if (this.Value is null) + return false; + switch (this.Value.MouseButton) + { + case MouseButton.None: + return Barotrauma.PlayerInput.KeyDown(this.Value.Key); + case MouseButton.PrimaryMouse: + return Barotrauma.PlayerInput.PrimaryMouseButtonHeld(); + case MouseButton.SecondaryMouse: + return Barotrauma.PlayerInput.SecondaryMouseButtonHeld(); + case MouseButton.MiddleMouse: + return Barotrauma.PlayerInput.MidButtonHeld(); + case MouseButton.MouseButton4: + return Barotrauma.PlayerInput.Mouse4ButtonHeld(); + case MouseButton.MouseButton5: + return Barotrauma.PlayerInput.Mouse5ButtonHeld(); + case MouseButton.MouseWheelUp: + return Barotrauma.PlayerInput.MouseWheelUpClicked(); + case MouseButton.MouseWheelDown: + return Barotrauma.PlayerInput.MouseWheelDownClicked(); + } + return false; + } + + public bool IsHit() + { + if (this.Value is null) + return false; + switch (this.Value.MouseButton) + { + case MouseButton.None: + return Barotrauma.PlayerInput.KeyHit(this.Value.Key); + case MouseButton.PrimaryMouse: + return Barotrauma.PlayerInput.PrimaryMouseButtonClicked(); + case MouseButton.SecondaryMouse: + return Barotrauma.PlayerInput.SecondaryMouseButtonClicked(); + case MouseButton.MiddleMouse: + return Barotrauma.PlayerInput.MidButtonClicked(); + case MouseButton.MouseButton4: + return Barotrauma.PlayerInput.Mouse4ButtonClicked(); + case MouseButton.MouseButton5: + return Barotrauma.PlayerInput.Mouse5ButtonClicked(); + case MouseButton.MouseWheelUp: + return Barotrauma.PlayerInput.MouseWheelUpClicked(); + case MouseButton.MouseWheelDown: + return Barotrauma.PlayerInput.MouseWheelDownClicked(); + } + return false; + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/GUIUtil.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/GUIUtil.cs new file mode 100644 index 000000000..f18897c08 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/GUIUtil.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; +#nullable enable + +namespace Barotrauma.LuaCs; + +/// +/// A collection of helper GUI functions. Mostly ripped from "Barotrauma/ClientSource/Settings/SettingsMenu.cs" +/// +public static class GUIUtil +{ + public static (GUILayoutGroup Left, GUILayoutGroup Right) CreateSidebars(GUIFrame parent, bool split = false) + { + GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(Vector2.One, parent.RectTransform), isHorizontal: true); + GUILayoutGroup left = new GUILayoutGroup(new RectTransform((0.4875f, 1.0f), layout.RectTransform), isHorizontal: false); + var centerFrame = new GUIFrame(new RectTransform((0.025f, 1.0f), layout.RectTransform), style: null); + if (split) + { + new GUICustomComponent(new RectTransform(Vector2.One, centerFrame.RectTransform), + onDraw: (sb, c) => + { + sb.DrawLine((c.Rect.Center.X, c.Rect.Top), + (c.Rect.Center.X, c.Rect.Bottom), + GUIStyle.TextColorDim, + 2f); + }); + } + GUILayoutGroup right = new GUILayoutGroup(new RectTransform((0.4875f, 1.0f), layout.RectTransform), isHorizontal: false); + return (left, right); + } + + public static GUILayoutGroup CreateCenterLayout(GUIFrame parent) + => new GUILayoutGroup(new RectTransform((0.5f, 1.0f), parent.RectTransform, Anchor.TopCenter, Pivot.TopCenter)) { ChildAnchor = Anchor.TopCenter }; + + public static RectTransform NewItemRectT(GUILayoutGroup parent, Vector2 adjustRatio) + => new RectTransform((1.0f * adjustRatio.X, 0.06f * adjustRatio.Y), parent.RectTransform, Anchor.CenterLeft); + + public static void Spacer(GUILayoutGroup parent, Vector2 adjustRatio) + => new GUIFrame(new RectTransform((1.0f * adjustRatio.X, 0.03f * adjustRatio.Y), parent.RectTransform, Anchor.CenterLeft), style: null); + + public static void ClearChildElements(GUIComponent component, bool clearSelfFromParent = false) + { + component.GetAllChildren().ForEachMod(c => + { + c.Visible = false; + component.RemoveChild(c); + }); + if (clearSelfFromParent && component.Parent is not null) + component.Parent.RemoveChild(component); + } + + public static GUITextBlock Label(GUILayoutGroup parent, LocalizedString str, GUIFont font, Vector2 adjustRatio) + => new GUITextBlock(NewItemRectT(parent, adjustRatio), str, font: font); + + public static GUIDropDown DropdownEnum(GUILayoutGroup parent, Func textFunc, Func? tooltipFunc, T currentValue, + Action setter, Vector2 adjustRatio) where T : Enum + => Dropdown(parent, textFunc, tooltipFunc, (T[])Enum.GetValues(typeof(T)), currentValue, setter, adjustRatio); + + public static GUIDropDown Dropdown(GUILayoutGroup parent, Func textFunc, Func? tooltipFunc, IReadOnlyList values, T currentValue, Action setter, Vector2 adjustRatio) + { + var dropdown = new GUIDropDown(NewItemRectT(parent, adjustRatio)); + values.ForEach(v => dropdown.AddItem(text: textFunc(v), userData: v, toolTip: tooltipFunc?.Invoke(v) ?? null)); + int childIndex = values.IndexOf(currentValue); + dropdown.Select(childIndex); + dropdown.ListBox.ForceLayoutRecalculation(); + dropdown.ListBox.ScrollToElement(dropdown.ListBox.Content.GetChild(childIndex)); + dropdown.OnSelected = (dd, obj) => + { + setter((T)obj); + return true; + }; + return dropdown; + } + + public static (GUIScrollBar, GUITextBlock) Slider(GUILayoutGroup parent, Vector2 range, int steps, Func labelFunc, float currentValue, Action setter, LocalizedString? tooltip, Vector2 adjustRatio) + { + var layout = new GUILayoutGroup(NewItemRectT(parent, adjustRatio), isHorizontal: true); + var slider = new GUIScrollBar(new RectTransform((0.72f, 1.0f), layout.RectTransform), style: "GUISlider") + { + Range = range, + BarScrollValue = currentValue, + Step = 1.0f / (float)(steps - 1), + BarSize = 1.0f / steps + }; + if (tooltip != null) + { + slider.ToolTip = tooltip; + } + var label = new GUITextBlock(new RectTransform((0.28f, 1.0f), layout.RectTransform), + labelFunc(currentValue), wrap: false, textAlignment: Alignment.Center); + slider.OnMoved = (sb, val) => + { + label.Text = labelFunc(sb.BarScrollValue); + setter(sb.BarScrollValue); + return true; + }; + return (slider, label); + } + + public static GUITickBox Tickbox(GUILayoutGroup parent, LocalizedString label, LocalizedString tooltip, + bool currentValue, Action setter, Vector2 adjustRatio) + { + var tickbox = new GUITickBox(NewItemRectT(parent, adjustRatio), label) + { + Selected = currentValue, + ToolTip = tooltip, + OnSelected = (tb) => + { + setter(tb.Selected); + return true; + } + }; + return tickbox; + } + + public static string Percentage(float v) => ToolBox.GetFormattedPercentage(v); + + public static int Round(float v) => (int)MathF.Round(v); +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsControlsSettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsControlsSettingsMenu.cs new file mode 100644 index 000000000..8b8806b7e --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsControlsSettingsMenu.cs @@ -0,0 +1,17 @@ +namespace Barotrauma.LuaCs; + +internal sealed class ModsControlsSettingsMenu : ModsSettingsMenu +{ + public ModsControlsSettingsMenu(GUIFrame contentFrame, + IPackageManagementService packageManagementService, + IConfigService configService, + SettingsMenu settingsMenuInstance) : base(contentFrame, packageManagementService, configService, settingsMenuInstance) + { + + } + + protected override void DisposeInternal() + { + // TODO: Finish this later. + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs new file mode 100644 index 000000000..e54f384dd --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs @@ -0,0 +1,17 @@ +namespace Barotrauma.LuaCs; + +internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenu +{ + public ModsGameplaySettingsMenu(GUIFrame contentFrame, + IPackageManagementService packageManagementService, + IConfigService configService, + SettingsMenu settingsMenuInstance) : base(contentFrame, packageManagementService, configService, settingsMenuInstance) + { + + } + + protected override void DisposeInternal() + { + // TODO: Finish this later. + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsSettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsSettingsMenu.cs new file mode 100644 index 000000000..b228d1da5 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsSettingsMenu.cs @@ -0,0 +1,35 @@ +using System; +using Barotrauma.Extensions; +using Microsoft.Xna.Framework; + +namespace Barotrauma.LuaCs; + +internal abstract class ModsSettingsMenu : IDisposable +{ + public GUIFrame ContentFrame { get; private set; } + protected IPackageManagementService PackageManagementService { get; private set; } + protected IConfigService ConfigService { get; private set; } + protected SettingsMenu SettingsMenuInstance { get; private set; } + + protected ModsSettingsMenu(GUIFrame contentFrame, + IPackageManagementService packageManagementService, + IConfigService configService, SettingsMenu settingsMenuInstance) + { + ContentFrame = contentFrame; + PackageManagementService = packageManagementService; + ConfigService = configService; + SettingsMenuInstance = settingsMenuInstance; + } + + protected abstract void DisposeInternal(); + + public void Dispose() + { + DisposeInternal(); + ContentFrame?.Parent.RemoveChild(ContentFrame); + SettingsMenuInstance = null; + ContentFrame = null; + PackageManagementService = null; + ConfigService = null; + } +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/SettingsMenuSystem.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/SettingsMenuSystem.cs index e3cdf5f1e..bd798e7b8 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/SettingsMenuSystem.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/SettingsMenuSystem.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Barotrauma.Extensions; using HarmonyLib; using Microsoft.Xna.Framework; @@ -7,65 +8,81 @@ namespace Barotrauma.LuaCs; public class SettingsMenuSystem : ISettingsMenuSystem { - private GUIFrame _menuFrame; - private GUIButton _menuOpenButton; - private readonly Harmony _harmony; - private static SettingsMenuSystem _systemInstance; - public SettingsMenuSystem() + private ModsControlsSettingsMenu _controlsMenuInstance; + private ModsGameplaySettingsMenu _gameplayMenuInstance; + + private readonly Harmony _harmony; + private readonly IPackageManagementService _packageManagementService; + private readonly IConfigService _configService; + private static SettingsMenuSystem SystemInstance; + + public SettingsMenuSystem(IPackageManagementService packageManagementService, IConfigService configService) { - _systemInstance = this; + _packageManagementService = packageManagementService; + _configService = configService; + SystemInstance = this; _harmony = Harmony.CreateAndPatchAll(typeof(SettingsMenuSystem)); } [HarmonyPatch(typeof(SettingsMenu), "CreateModsTab"), HarmonyPostfix] private static void SettingsMenu_CreateModsTab_Post(SettingsMenu __instance) { - _systemInstance.CreateSettingsMenu(__instance); + SystemInstance.CreateSettingsMenu(__instance); } private void CreateSettingsMenu(SettingsMenu __instance) { - var tabIndex = (SettingsMenu.Tab)Enum.GetValues().Length; - var contentFrame = CreateNewContentFrame(tabIndex); - contentFrame.RectTransform.RelativeSize = Vector2.One; - + DisposeMenuFrames(); + var tabCount = Enum.GetValues().Length; + var tabGameplayIndex = (SettingsMenu.Tab)tabCount; + var tabControlsIndex = (SettingsMenu.Tab)tabCount+1; - GUIFrame CreateNewContentFrame(SettingsMenu.Tab tab) + var gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance, + "SettingsMenuTab.Mods", "LuaCsForBarotrauma.SettingsMenu.ModGameplayButton"); + var controlsContentFrame = CreateNewContentTab(tabControlsIndex, __instance, + "SettingsMenuTab.Mods", "LuaCsForBarotrauma.SettingsMenu.ModControlsButton"); + + _gameplayMenuInstance = new ModsGameplaySettingsMenu(gameplayContentFrame, _packageManagementService, _configService, __instance); + _controlsMenuInstance = new ModsControlsSettingsMenu(controlsContentFrame, _packageManagementService, _configService, __instance); + + + } + + private GUIFrame CreateNewContentTab(SettingsMenu.Tab tab, SettingsMenu settingsMenuInstance, string settingsMenuTabName, string settingMenuHoverTextIdent) + { + if (settingsMenuInstance.tabContents.TryGetValue(tab, out (GUIButton Button, GUIFrame Content) tabContent)) { - if (__instance.tabContents.TryGetValue(tab, out (GUIButton Button, GUIFrame Content) tabContent)) - { - return tabContent.Content; - } - - var contentFr = new GUIFrame(new RectTransform(Vector2.One * 0.95f, __instance.contentFrame.RectTransform, Anchor.Center, Pivot.Center), style: null); - - var button = new GUIButton(new RectTransform(Vector2.One, __instance.tabber.RectTransform, Anchor.TopLeft, Pivot.TopLeft, scaleBasis: ScaleBasis.Smallest), "", style: $"SettingsMenuTab.Mods") - { - ToolTip = TextManager.Get($"LuaCsForBarotrauma.SettingsMenu.ModSettingsButton"), - OnClicked = (b, _) => - { - __instance.SelectTab(tab); - return false; - } - }; - button.RectTransform.MaxSize = RectTransform.MaxPoint; - button.Children.ForEach(c => c.RectTransform.MaxSize = RectTransform.MaxPoint); - - __instance.tabContents.Add(tab, (button, contentFr)); - - return contentFr; + return tabContent.Content; } + + var contentFr = new GUIFrame(new RectTransform(Vector2.One * 0.95f, settingsMenuInstance.contentFrame.RectTransform, Anchor.Center, Pivot.Center), style: null); + + var button = new GUIButton(new RectTransform(Vector2.One, settingsMenuInstance.tabber.RectTransform, + Anchor.TopLeft, Pivot.TopLeft, scaleBasis: ScaleBasis.Smallest), "", style: $"SettingsMenuTab.Mods") + { + ToolTip = TextManager.Get($"LuaCsForBarotrauma.SettingsMenu.ModSettingsButton"), + OnClicked = (b, _) => + { + settingsMenuInstance.SelectTab(tab); + return false; + } + }; + button.RectTransform.MaxSize = RectTransform.MaxPoint; + button.Children.ForEach(c => c.RectTransform.MaxSize = RectTransform.MaxPoint); + + settingsMenuInstance.tabContents.Add(tab, (button, contentFr)); + + return contentFr; } - private void DisposeMenuFrame() + private void DisposeMenuFrames() { - if (_menuFrame is not null) - { - _menuFrame.Parent.RemoveChild(_menuFrame); - _menuFrame = null; - } + _controlsMenuInstance?.Dispose(); + _gameplayMenuInstance?.Dispose(); + _controlsMenuInstance = null; + _gameplayMenuInstance = null; } #region DISPOSAL @@ -76,7 +93,7 @@ public class SettingsMenuSystem : ISettingsMenuSystem { return; } - DisposeMenuFrame(); + DisposeMenuFrames(); GC.SuppressFinalize(this); } private int _isDisposed = 0; diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml new file mode 100644 index 000000000..982c44b0b --- /dev/null +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml @@ -0,0 +1,9 @@ + + + Mod Controls Settings + Mod Gameplay Settings + Suppress GUI Popup on Error + Are C# Mods Allowed + Hide Local OS Account Name In Logs + Where to Save Local Data + diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/Portuguese.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/Portuguese.xml new file mode 100644 index 000000000..4b149c002 --- /dev/null +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/Portuguese.xml @@ -0,0 +1,3 @@ + + + From ebf2935fb4e8cd0ddb87d0d2bb5f500283b5bb93 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 27 Feb 2026 15:21:40 -0500 Subject: [PATCH 181/288] - Some menu implementation. --- .../Services/ModsGameplaySettingsMenu.cs | 28 ++++++++++++++++++- .../LuaCs/Services/SettingsMenuSystem.cs | 6 ++-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs index e54f384dd..2ccfa55f3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs @@ -1,4 +1,6 @@ -namespace Barotrauma.LuaCs; +using Microsoft.Xna.Framework; + +namespace Barotrauma.LuaCs; internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenu { @@ -7,6 +9,30 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenu IConfigService configService, SettingsMenu settingsMenuInstance) : base(contentFrame, packageManagementService, configService, settingsMenuInstance) { + 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)); + + // page contents + var contentAreaLayoutGroup = new GUILayoutGroup( + new RectTransform(new Vector2(1f, 0.94f), mainLayoutGroup.RectTransform, Anchor.BottomLeft), false, + 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)); + var searchBar = new GUITextBox( + new RectTransform(new Vector2(0.85f, 0.1f), searchBarLayoutGroup.RectTransform, Anchor.TopLeft), + createClearButton: true) + { + OnEnterPressed = (btn, txt) => + { + return true; + } + }; + } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/SettingsMenuSystem.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/SettingsMenuSystem.cs index bd798e7b8..df664031c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/SettingsMenuSystem.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/SettingsMenuSystem.cs @@ -46,8 +46,6 @@ public class SettingsMenuSystem : ISettingsMenuSystem _gameplayMenuInstance = new ModsGameplaySettingsMenu(gameplayContentFrame, _packageManagementService, _configService, __instance); _controlsMenuInstance = new ModsControlsSettingsMenu(controlsContentFrame, _packageManagementService, _configService, __instance); - - } private GUIFrame CreateNewContentTab(SettingsMenu.Tab tab, SettingsMenu settingsMenuInstance, string settingsMenuTabName, string settingMenuHoverTextIdent) @@ -60,9 +58,9 @@ public class SettingsMenuSystem : ISettingsMenuSystem var contentFr = new GUIFrame(new RectTransform(Vector2.One * 0.95f, settingsMenuInstance.contentFrame.RectTransform, Anchor.Center, Pivot.Center), style: null); var button = new GUIButton(new RectTransform(Vector2.One, settingsMenuInstance.tabber.RectTransform, - Anchor.TopLeft, Pivot.TopLeft, scaleBasis: ScaleBasis.Smallest), "", style: $"SettingsMenuTab.Mods") + Anchor.TopLeft, Pivot.TopLeft, scaleBasis: ScaleBasis.Smallest), "", style: settingsMenuTabName) { - ToolTip = TextManager.Get($"LuaCsForBarotrauma.SettingsMenu.ModSettingsButton"), + ToolTip = TextManager.Get(settingMenuHoverTextIdent), OnClicked = (b, _) => { settingsMenuInstance.SelectTab(tab); From 9b61cda6cf0512728fa99ab39cd4b155ff4d4ed3 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 27 Feb 2026 17:25:25 -0500 Subject: [PATCH 182/288] - SettingsMenu work. - Fixed NRE on NetSync in SettingEntry --- .../ClientSource/LuaCs/GUIUtil.cs | 24 +++- .../Services/ModsGameplaySettingsMenu.cs | 15 ++- .../SharedSource/LuaCs/Data/SettingEntry.cs | 2 +- WindowsSolution.sln | 115 +----------------- 4 files changed, 38 insertions(+), 118 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/GUIUtil.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/GUIUtil.cs index f18897c08..d42d71d1c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/GUIUtil.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/GUIUtil.cs @@ -31,6 +31,26 @@ public static class GUIUtil return (left, right); } + public static (GUILayoutGroup Left, GUILayoutGroup Right) CreateSidebars(GUILayoutGroup parent, bool split = false) + { + GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(Vector2.One, parent.RectTransform), isHorizontal: true); + GUILayoutGroup left = new GUILayoutGroup(new RectTransform((0.4875f, 1.0f), layout.RectTransform), isHorizontal: false); + var centerFrame = new GUIFrame(new RectTransform((0.025f, 1.0f), layout.RectTransform), style: null); + if (split) + { + new GUICustomComponent(new RectTransform(Vector2.One, centerFrame.RectTransform), + onDraw: (sb, c) => + { + sb.DrawLine((c.Rect.Center.X, c.Rect.Top), + (c.Rect.Center.X, c.Rect.Bottom), + GUIStyle.TextColorDim, + 2f); + }); + } + GUILayoutGroup right = new GUILayoutGroup(new RectTransform((0.4875f, 1.0f), layout.RectTransform), isHorizontal: false); + return (left, right); + } + public static GUILayoutGroup CreateCenterLayout(GUIFrame parent) => new GUILayoutGroup(new RectTransform((0.5f, 1.0f), parent.RectTransform, Anchor.TopCenter, Pivot.TopCenter)) { ChildAnchor = Anchor.TopCenter }; @@ -59,9 +79,9 @@ public static class GUIUtil => Dropdown(parent, textFunc, tooltipFunc, (T[])Enum.GetValues(typeof(T)), currentValue, setter, adjustRatio); public static GUIDropDown Dropdown(GUILayoutGroup parent, Func textFunc, Func? tooltipFunc, IReadOnlyList values, T currentValue, Action setter, Vector2 adjustRatio) + LocalizedString>? tooltipFunc, IReadOnlyList values, T currentValue, Action setter, Vector2 adjustRatio, float listBoxScale = 1) { - var dropdown = new GUIDropDown(NewItemRectT(parent, adjustRatio)); + var dropdown = new GUIDropDown(NewItemRectT(parent, adjustRatio), listBoxScale: listBoxScale); values.ForEach(v => dropdown.AddItem(text: textFunc(v), userData: v, toolTip: tooltipFunc?.Invoke(v) ?? null)); int childIndex = values.IndexOf(currentValue); dropdown.Select(childIndex); diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs index 2ccfa55f3..6770e78ab 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs @@ -1,4 +1,5 @@ using Microsoft.Xna.Framework; +using System.Linq; namespace Barotrauma.LuaCs; @@ -32,10 +33,20 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenu return true; } }; - - + // display area + var settingsContentAreaGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.90f), contentAreaLayoutGroup.RectTransform, Anchor.BottomCenter)); + GUIUtil.Spacer(settingsContentAreaGroup, Vector2.One); + var (modCategoryDisplayGroup, settingsDisplayGroup) = GUIUtil.CreateSidebars(settingsContentAreaGroup, true); + modCategoryDisplayGroup.RectTransform.RelativeSize = new Vector2(0.3f, 1f); + settingsDisplayGroup.RectTransform.RelativeSize = new Vector2(0.7f, 1f); + var cpList = packageManagementService.GetAllLoadedPackages().OrderBy(cp => cp.Name == "Vanilla" ? 0 : 1).ThenBy(cp => cp.Name).ToList(); + var modSelectDropDown = GUIUtil.Dropdown(modCategoryDisplayGroup, cp => cp.Name == "Vanilla" ? "All" : cp.Name, null, cpList, cpList[0], cp => + { + // filter selections + }, Vector2.One, 2); } + protected override void DisposeInternal() { // TODO: Finish this later. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs index fc06a3cf5..e495ec43e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs @@ -150,7 +150,7 @@ public class SettingEntry : SettingBase, ISettingBase, INetworkSyncVar whe NetworkingService.RegisterNetVar(this); } - public NetSync SyncType => ConfigInfo.NetSync; + public NetSync SyncType => ConfigInfo?.NetSync ?? NetSync.None; // needs to be added IConfigInfo public ClientPermissions WritePermissions => ClientPermissions.ManageSettings; diff --git a/WindowsSolution.sln b/WindowsSolution.sln index 37ce06f53..d6d248b21 100644 --- a/WindowsSolution.sln +++ b/WindowsSolution.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.32014.148 +# Visual Studio Version 18 +VisualStudioVersion = 18.3.11520.95 d18.3 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D32A29D8-AC7B-4189-B734-8ED9EB4120D0}" ProjectSection(SolutionItems) = preProject @@ -58,228 +58,117 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EosInterface.Implementation EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 - Unstable|Any CPU = Unstable|Any CPU Unstable|x64 = Unstable|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E1BBC67C-DC2A-40E8-89F3-B57299D7B16C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E1BBC67C-DC2A-40E8-89F3-B57299D7B16C}.Debug|Any CPU.Build.0 = Debug|Any CPU {E1BBC67C-DC2A-40E8-89F3-B57299D7B16C}.Debug|x64.ActiveCfg = Debug|x64 {E1BBC67C-DC2A-40E8-89F3-B57299D7B16C}.Debug|x64.Build.0 = Debug|x64 - {E1BBC67C-DC2A-40E8-89F3-B57299D7B16C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E1BBC67C-DC2A-40E8-89F3-B57299D7B16C}.Release|Any CPU.Build.0 = Release|Any CPU {E1BBC67C-DC2A-40E8-89F3-B57299D7B16C}.Release|x64.ActiveCfg = Release|x64 {E1BBC67C-DC2A-40E8-89F3-B57299D7B16C}.Release|x64.Build.0 = Release|x64 - {E1BBC67C-DC2A-40E8-89F3-B57299D7B16C}.Unstable|Any CPU.ActiveCfg = Debug|Any CPU - {E1BBC67C-DC2A-40E8-89F3-B57299D7B16C}.Unstable|Any CPU.Build.0 = Debug|Any CPU {E1BBC67C-DC2A-40E8-89F3-B57299D7B16C}.Unstable|x64.ActiveCfg = Release|x64 {E1BBC67C-DC2A-40E8-89F3-B57299D7B16C}.Unstable|x64.Build.0 = Release|x64 - {95C4D59D-9BE4-4278-B4F8-46C0BA1A3916}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {95C4D59D-9BE4-4278-B4F8-46C0BA1A3916}.Debug|Any CPU.Build.0 = Debug|Any CPU {95C4D59D-9BE4-4278-B4F8-46C0BA1A3916}.Debug|x64.ActiveCfg = Debug|x64 {95C4D59D-9BE4-4278-B4F8-46C0BA1A3916}.Debug|x64.Build.0 = Debug|x64 - {95C4D59D-9BE4-4278-B4F8-46C0BA1A3916}.Release|Any CPU.ActiveCfg = Release|Any CPU - {95C4D59D-9BE4-4278-B4F8-46C0BA1A3916}.Release|Any CPU.Build.0 = Release|Any CPU {95C4D59D-9BE4-4278-B4F8-46C0BA1A3916}.Release|x64.ActiveCfg = Release|x64 {95C4D59D-9BE4-4278-B4F8-46C0BA1A3916}.Release|x64.Build.0 = Release|x64 - {95C4D59D-9BE4-4278-B4F8-46C0BA1A3916}.Unstable|Any CPU.ActiveCfg = Debug|Any CPU - {95C4D59D-9BE4-4278-B4F8-46C0BA1A3916}.Unstable|Any CPU.Build.0 = Debug|Any CPU {95C4D59D-9BE4-4278-B4F8-46C0BA1A3916}.Unstable|x64.ActiveCfg = Release|x64 {95C4D59D-9BE4-4278-B4F8-46C0BA1A3916}.Unstable|x64.Build.0 = Release|x64 - {AD30AE95-7BF6-4CE5-AEED-B6C30A88F139}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AD30AE95-7BF6-4CE5-AEED-B6C30A88F139}.Debug|Any CPU.Build.0 = Debug|Any CPU {AD30AE95-7BF6-4CE5-AEED-B6C30A88F139}.Debug|x64.ActiveCfg = Debug|x64 {AD30AE95-7BF6-4CE5-AEED-B6C30A88F139}.Debug|x64.Build.0 = Debug|x64 - {AD30AE95-7BF6-4CE5-AEED-B6C30A88F139}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AD30AE95-7BF6-4CE5-AEED-B6C30A88F139}.Release|Any CPU.Build.0 = Release|Any CPU {AD30AE95-7BF6-4CE5-AEED-B6C30A88F139}.Release|x64.ActiveCfg = Release|x64 {AD30AE95-7BF6-4CE5-AEED-B6C30A88F139}.Release|x64.Build.0 = Release|x64 - {AD30AE95-7BF6-4CE5-AEED-B6C30A88F139}.Unstable|Any CPU.ActiveCfg = Debug|Any CPU - {AD30AE95-7BF6-4CE5-AEED-B6C30A88F139}.Unstable|Any CPU.Build.0 = Debug|Any CPU {AD30AE95-7BF6-4CE5-AEED-B6C30A88F139}.Unstable|x64.ActiveCfg = Release|x64 {AD30AE95-7BF6-4CE5-AEED-B6C30A88F139}.Unstable|x64.Build.0 = Release|x64 - {894D3518-A0E3-4B88-B9BF-9E1AFC3F9523}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {894D3518-A0E3-4B88-B9BF-9E1AFC3F9523}.Debug|Any CPU.Build.0 = Debug|Any CPU {894D3518-A0E3-4B88-B9BF-9E1AFC3F9523}.Debug|x64.ActiveCfg = Debug|x64 {894D3518-A0E3-4B88-B9BF-9E1AFC3F9523}.Debug|x64.Build.0 = Debug|x64 - {894D3518-A0E3-4B88-B9BF-9E1AFC3F9523}.Release|Any CPU.ActiveCfg = Release|Any CPU - {894D3518-A0E3-4B88-B9BF-9E1AFC3F9523}.Release|Any CPU.Build.0 = Release|Any CPU {894D3518-A0E3-4B88-B9BF-9E1AFC3F9523}.Release|x64.ActiveCfg = Release|x64 {894D3518-A0E3-4B88-B9BF-9E1AFC3F9523}.Release|x64.Build.0 = Release|x64 - {894D3518-A0E3-4B88-B9BF-9E1AFC3F9523}.Unstable|Any CPU.ActiveCfg = Debug|Any CPU - {894D3518-A0E3-4B88-B9BF-9E1AFC3F9523}.Unstable|Any CPU.Build.0 = Debug|Any CPU {894D3518-A0E3-4B88-B9BF-9E1AFC3F9523}.Unstable|x64.ActiveCfg = Release|x64 {894D3518-A0E3-4B88-B9BF-9E1AFC3F9523}.Unstable|x64.Build.0 = Release|x64 - {ED2873CA-C209-4CBC-ADD4-DAA753DFEEAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ED2873CA-C209-4CBC-ADD4-DAA753DFEEAF}.Debug|Any CPU.Build.0 = Debug|Any CPU {ED2873CA-C209-4CBC-ADD4-DAA753DFEEAF}.Debug|x64.ActiveCfg = Debug|x64 {ED2873CA-C209-4CBC-ADD4-DAA753DFEEAF}.Debug|x64.Build.0 = Debug|x64 - {ED2873CA-C209-4CBC-ADD4-DAA753DFEEAF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ED2873CA-C209-4CBC-ADD4-DAA753DFEEAF}.Release|Any CPU.Build.0 = Release|Any CPU {ED2873CA-C209-4CBC-ADD4-DAA753DFEEAF}.Release|x64.ActiveCfg = Release|x64 {ED2873CA-C209-4CBC-ADD4-DAA753DFEEAF}.Release|x64.Build.0 = Release|x64 - {ED2873CA-C209-4CBC-ADD4-DAA753DFEEAF}.Unstable|Any CPU.ActiveCfg = Debug|Any CPU - {ED2873CA-C209-4CBC-ADD4-DAA753DFEEAF}.Unstable|Any CPU.Build.0 = Debug|Any CPU {ED2873CA-C209-4CBC-ADD4-DAA753DFEEAF}.Unstable|x64.ActiveCfg = Release|x64 {ED2873CA-C209-4CBC-ADD4-DAA753DFEEAF}.Unstable|x64.Build.0 = Release|x64 - {978633A8-094A-4623-9B82-8533FC8BA1CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {978633A8-094A-4623-9B82-8533FC8BA1CC}.Debug|Any CPU.Build.0 = Debug|Any CPU {978633A8-094A-4623-9B82-8533FC8BA1CC}.Debug|x64.ActiveCfg = Debug|x64 {978633A8-094A-4623-9B82-8533FC8BA1CC}.Debug|x64.Build.0 = Debug|x64 - {978633A8-094A-4623-9B82-8533FC8BA1CC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {978633A8-094A-4623-9B82-8533FC8BA1CC}.Release|Any CPU.Build.0 = Release|Any CPU {978633A8-094A-4623-9B82-8533FC8BA1CC}.Release|x64.ActiveCfg = Release|x64 {978633A8-094A-4623-9B82-8533FC8BA1CC}.Release|x64.Build.0 = Release|x64 - {978633A8-094A-4623-9B82-8533FC8BA1CC}.Unstable|Any CPU.ActiveCfg = Unstable|Any CPU - {978633A8-094A-4623-9B82-8533FC8BA1CC}.Unstable|Any CPU.Build.0 = Unstable|Any CPU {978633A8-094A-4623-9B82-8533FC8BA1CC}.Unstable|x64.ActiveCfg = Unstable|x64 {978633A8-094A-4623-9B82-8533FC8BA1CC}.Unstable|x64.Build.0 = Unstable|x64 - {39E52316-D6C1-4D1F-95FF-37F41C9AB5A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {39E52316-D6C1-4D1F-95FF-37F41C9AB5A7}.Debug|Any CPU.Build.0 = Debug|Any CPU {39E52316-D6C1-4D1F-95FF-37F41C9AB5A7}.Debug|x64.ActiveCfg = Debug|x64 {39E52316-D6C1-4D1F-95FF-37F41C9AB5A7}.Debug|x64.Build.0 = Debug|x64 - {39E52316-D6C1-4D1F-95FF-37F41C9AB5A7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {39E52316-D6C1-4D1F-95FF-37F41C9AB5A7}.Release|Any CPU.Build.0 = Release|Any CPU {39E52316-D6C1-4D1F-95FF-37F41C9AB5A7}.Release|x64.ActiveCfg = Release|x64 {39E52316-D6C1-4D1F-95FF-37F41C9AB5A7}.Release|x64.Build.0 = Release|x64 - {39E52316-D6C1-4D1F-95FF-37F41C9AB5A7}.Unstable|Any CPU.ActiveCfg = Debug|Any CPU - {39E52316-D6C1-4D1F-95FF-37F41C9AB5A7}.Unstable|Any CPU.Build.0 = Debug|Any CPU {39E52316-D6C1-4D1F-95FF-37F41C9AB5A7}.Unstable|x64.ActiveCfg = Release|x64 {39E52316-D6C1-4D1F-95FF-37F41C9AB5A7}.Unstable|x64.Build.0 = Release|x64 - {D379BF8E-D696-4AB9-A27F-4D0C493BF484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D379BF8E-D696-4AB9-A27F-4D0C493BF484}.Debug|Any CPU.Build.0 = Debug|Any CPU {D379BF8E-D696-4AB9-A27F-4D0C493BF484}.Debug|x64.ActiveCfg = Debug|x64 {D379BF8E-D696-4AB9-A27F-4D0C493BF484}.Debug|x64.Build.0 = Debug|x64 - {D379BF8E-D696-4AB9-A27F-4D0C493BF484}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D379BF8E-D696-4AB9-A27F-4D0C493BF484}.Release|Any CPU.Build.0 = Release|Any CPU {D379BF8E-D696-4AB9-A27F-4D0C493BF484}.Release|x64.ActiveCfg = Release|x64 {D379BF8E-D696-4AB9-A27F-4D0C493BF484}.Release|x64.Build.0 = Release|x64 - {D379BF8E-D696-4AB9-A27F-4D0C493BF484}.Unstable|Any CPU.ActiveCfg = Debug|Any CPU - {D379BF8E-D696-4AB9-A27F-4D0C493BF484}.Unstable|Any CPU.Build.0 = Debug|Any CPU {D379BF8E-D696-4AB9-A27F-4D0C493BF484}.Unstable|x64.ActiveCfg = Release|x64 {D379BF8E-D696-4AB9-A27F-4D0C493BF484}.Unstable|x64.Build.0 = Release|x64 - {47848C6E-C7A8-4EC3-96C2-3BC8A4234AFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {47848C6E-C7A8-4EC3-96C2-3BC8A4234AFA}.Debug|Any CPU.Build.0 = Debug|Any CPU {47848C6E-C7A8-4EC3-96C2-3BC8A4234AFA}.Debug|x64.ActiveCfg = Debug|x64 {47848C6E-C7A8-4EC3-96C2-3BC8A4234AFA}.Debug|x64.Build.0 = Debug|x64 - {47848C6E-C7A8-4EC3-96C2-3BC8A4234AFA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {47848C6E-C7A8-4EC3-96C2-3BC8A4234AFA}.Release|Any CPU.Build.0 = Release|Any CPU {47848C6E-C7A8-4EC3-96C2-3BC8A4234AFA}.Release|x64.ActiveCfg = Release|x64 {47848C6E-C7A8-4EC3-96C2-3BC8A4234AFA}.Release|x64.Build.0 = Release|x64 - {47848C6E-C7A8-4EC3-96C2-3BC8A4234AFA}.Unstable|Any CPU.ActiveCfg = Unstable|Any CPU - {47848C6E-C7A8-4EC3-96C2-3BC8A4234AFA}.Unstable|Any CPU.Build.0 = Unstable|Any CPU {47848C6E-C7A8-4EC3-96C2-3BC8A4234AFA}.Unstable|x64.ActiveCfg = Unstable|x64 {47848C6E-C7A8-4EC3-96C2-3BC8A4234AFA}.Unstable|x64.Build.0 = Unstable|x64 - {1F318AC4-F808-4130-867F-B98DF9AA8F95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1F318AC4-F808-4130-867F-B98DF9AA8F95}.Debug|Any CPU.Build.0 = Debug|Any CPU {1F318AC4-F808-4130-867F-B98DF9AA8F95}.Debug|x64.ActiveCfg = Debug|x64 {1F318AC4-F808-4130-867F-B98DF9AA8F95}.Debug|x64.Build.0 = Debug|x64 - {1F318AC4-F808-4130-867F-B98DF9AA8F95}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1F318AC4-F808-4130-867F-B98DF9AA8F95}.Release|Any CPU.Build.0 = Release|Any CPU {1F318AC4-F808-4130-867F-B98DF9AA8F95}.Release|x64.ActiveCfg = Release|x64 {1F318AC4-F808-4130-867F-B98DF9AA8F95}.Release|x64.Build.0 = Release|x64 - {1F318AC4-F808-4130-867F-B98DF9AA8F95}.Unstable|Any CPU.ActiveCfg = Debug|Any CPU - {1F318AC4-F808-4130-867F-B98DF9AA8F95}.Unstable|Any CPU.Build.0 = Debug|Any CPU {1F318AC4-F808-4130-867F-B98DF9AA8F95}.Unstable|x64.ActiveCfg = Release|x64 {1F318AC4-F808-4130-867F-B98DF9AA8F95}.Unstable|x64.Build.0 = Release|x64 - {6911872D-40EF-400C-B0A1-9985A19ED488}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6911872D-40EF-400C-B0A1-9985A19ED488}.Debug|Any CPU.Build.0 = Debug|Any CPU {6911872D-40EF-400C-B0A1-9985A19ED488}.Debug|x64.ActiveCfg = Debug|x64 {6911872D-40EF-400C-B0A1-9985A19ED488}.Debug|x64.Build.0 = Debug|x64 - {6911872D-40EF-400C-B0A1-9985A19ED488}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6911872D-40EF-400C-B0A1-9985A19ED488}.Release|Any CPU.Build.0 = Release|Any CPU {6911872D-40EF-400C-B0A1-9985A19ED488}.Release|x64.ActiveCfg = Release|x64 {6911872D-40EF-400C-B0A1-9985A19ED488}.Release|x64.Build.0 = Release|x64 - {6911872D-40EF-400C-B0A1-9985A19ED488}.Unstable|Any CPU.ActiveCfg = Debug|Any CPU - {6911872D-40EF-400C-B0A1-9985A19ED488}.Unstable|Any CPU.Build.0 = Debug|Any CPU {6911872D-40EF-400C-B0A1-9985A19ED488}.Unstable|x64.ActiveCfg = Release|x64 {6911872D-40EF-400C-B0A1-9985A19ED488}.Unstable|x64.Build.0 = Release|x64 - {C7212AE2-A925-4225-A639-AE0653EF65B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C7212AE2-A925-4225-A639-AE0653EF65B0}.Debug|Any CPU.Build.0 = Debug|Any CPU {C7212AE2-A925-4225-A639-AE0653EF65B0}.Debug|x64.ActiveCfg = Debug|Any CPU {C7212AE2-A925-4225-A639-AE0653EF65B0}.Debug|x64.Build.0 = Debug|Any CPU - {C7212AE2-A925-4225-A639-AE0653EF65B0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C7212AE2-A925-4225-A639-AE0653EF65B0}.Release|Any CPU.Build.0 = Release|Any CPU {C7212AE2-A925-4225-A639-AE0653EF65B0}.Release|x64.ActiveCfg = Release|Any CPU {C7212AE2-A925-4225-A639-AE0653EF65B0}.Release|x64.Build.0 = Release|Any CPU - {C7212AE2-A925-4225-A639-AE0653EF65B0}.Unstable|Any CPU.ActiveCfg = Release|Any CPU - {C7212AE2-A925-4225-A639-AE0653EF65B0}.Unstable|Any CPU.Build.0 = Release|Any CPU {C7212AE2-A925-4225-A639-AE0653EF65B0}.Unstable|x64.ActiveCfg = Release|Any CPU {C7212AE2-A925-4225-A639-AE0653EF65B0}.Unstable|x64.Build.0 = Release|Any CPU - {2EEF2610-64A3-4E5D-95ED-0E181C1A34ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2EEF2610-64A3-4E5D-95ED-0E181C1A34ED}.Debug|Any CPU.Build.0 = Debug|Any CPU {2EEF2610-64A3-4E5D-95ED-0E181C1A34ED}.Debug|x64.ActiveCfg = Debug|Any CPU {2EEF2610-64A3-4E5D-95ED-0E181C1A34ED}.Debug|x64.Build.0 = Debug|Any CPU - {2EEF2610-64A3-4E5D-95ED-0E181C1A34ED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2EEF2610-64A3-4E5D-95ED-0E181C1A34ED}.Release|Any CPU.Build.0 = Release|Any CPU {2EEF2610-64A3-4E5D-95ED-0E181C1A34ED}.Release|x64.ActiveCfg = Release|Any CPU {2EEF2610-64A3-4E5D-95ED-0E181C1A34ED}.Release|x64.Build.0 = Release|Any CPU - {2EEF2610-64A3-4E5D-95ED-0E181C1A34ED}.Unstable|Any CPU.ActiveCfg = Debug|Any CPU - {2EEF2610-64A3-4E5D-95ED-0E181C1A34ED}.Unstable|Any CPU.Build.0 = Debug|Any CPU {2EEF2610-64A3-4E5D-95ED-0E181C1A34ED}.Unstable|x64.ActiveCfg = Debug|Any CPU {2EEF2610-64A3-4E5D-95ED-0E181C1A34ED}.Unstable|x64.Build.0 = Debug|Any CPU - {C98FE0D0-BC7D-4806-B592-734B53016FD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C98FE0D0-BC7D-4806-B592-734B53016FD8}.Debug|Any CPU.Build.0 = Debug|Any CPU {C98FE0D0-BC7D-4806-B592-734B53016FD8}.Debug|x64.ActiveCfg = Debug|Any CPU {C98FE0D0-BC7D-4806-B592-734B53016FD8}.Debug|x64.Build.0 = Debug|Any CPU - {C98FE0D0-BC7D-4806-B592-734B53016FD8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C98FE0D0-BC7D-4806-B592-734B53016FD8}.Release|Any CPU.Build.0 = Release|Any CPU {C98FE0D0-BC7D-4806-B592-734B53016FD8}.Release|x64.ActiveCfg = Release|Any CPU {C98FE0D0-BC7D-4806-B592-734B53016FD8}.Release|x64.Build.0 = Release|Any CPU - {C98FE0D0-BC7D-4806-B592-734B53016FD8}.Unstable|Any CPU.ActiveCfg = Release|Any CPU - {C98FE0D0-BC7D-4806-B592-734B53016FD8}.Unstable|Any CPU.Build.0 = Release|Any CPU {C98FE0D0-BC7D-4806-B592-734B53016FD8}.Unstable|x64.ActiveCfg = Release|Any CPU {C98FE0D0-BC7D-4806-B592-734B53016FD8}.Unstable|x64.Build.0 = Release|Any CPU - {AF484604-D20F-4D87-B298-1A712052D0D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AF484604-D20F-4D87-B298-1A712052D0D9}.Debug|Any CPU.Build.0 = Debug|Any CPU {AF484604-D20F-4D87-B298-1A712052D0D9}.Debug|x64.ActiveCfg = Debug|Any CPU {AF484604-D20F-4D87-B298-1A712052D0D9}.Debug|x64.Build.0 = Debug|Any CPU - {AF484604-D20F-4D87-B298-1A712052D0D9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AF484604-D20F-4D87-B298-1A712052D0D9}.Release|Any CPU.Build.0 = Release|Any CPU {AF484604-D20F-4D87-B298-1A712052D0D9}.Release|x64.ActiveCfg = Release|Any CPU {AF484604-D20F-4D87-B298-1A712052D0D9}.Release|x64.Build.0 = Release|Any CPU - {AF484604-D20F-4D87-B298-1A712052D0D9}.Unstable|Any CPU.ActiveCfg = Debug|Any CPU - {AF484604-D20F-4D87-B298-1A712052D0D9}.Unstable|Any CPU.Build.0 = Debug|Any CPU {AF484604-D20F-4D87-B298-1A712052D0D9}.Unstable|x64.ActiveCfg = Debug|Any CPU {AF484604-D20F-4D87-B298-1A712052D0D9}.Unstable|x64.Build.0 = Debug|Any CPU - {FA273D62-455C-4BF7-B020-D0EBDE9EB565}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FA273D62-455C-4BF7-B020-D0EBDE9EB565}.Debug|Any CPU.Build.0 = Debug|Any CPU {FA273D62-455C-4BF7-B020-D0EBDE9EB565}.Debug|x64.ActiveCfg = Debug|Any CPU {FA273D62-455C-4BF7-B020-D0EBDE9EB565}.Debug|x64.Build.0 = Debug|Any CPU - {FA273D62-455C-4BF7-B020-D0EBDE9EB565}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FA273D62-455C-4BF7-B020-D0EBDE9EB565}.Release|Any CPU.Build.0 = Release|Any CPU {FA273D62-455C-4BF7-B020-D0EBDE9EB565}.Release|x64.ActiveCfg = Release|Any CPU {FA273D62-455C-4BF7-B020-D0EBDE9EB565}.Release|x64.Build.0 = Release|Any CPU - {FA273D62-455C-4BF7-B020-D0EBDE9EB565}.Unstable|Any CPU.ActiveCfg = Release|Any CPU - {FA273D62-455C-4BF7-B020-D0EBDE9EB565}.Unstable|Any CPU.Build.0 = Release|Any CPU {FA273D62-455C-4BF7-B020-D0EBDE9EB565}.Unstable|x64.ActiveCfg = Release|Any CPU {FA273D62-455C-4BF7-B020-D0EBDE9EB565}.Unstable|x64.Build.0 = Release|Any CPU - {38C5D23D-0858-4254-B7B7-145221A8AB75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {38C5D23D-0858-4254-B7B7-145221A8AB75}.Debug|Any CPU.Build.0 = Debug|Any CPU {38C5D23D-0858-4254-B7B7-145221A8AB75}.Debug|x64.ActiveCfg = Debug|Any CPU {38C5D23D-0858-4254-B7B7-145221A8AB75}.Debug|x64.Build.0 = Debug|Any CPU - {38C5D23D-0858-4254-B7B7-145221A8AB75}.Release|Any CPU.ActiveCfg = Release|Any CPU - {38C5D23D-0858-4254-B7B7-145221A8AB75}.Release|Any CPU.Build.0 = Release|Any CPU {38C5D23D-0858-4254-B7B7-145221A8AB75}.Release|x64.ActiveCfg = Release|Any CPU {38C5D23D-0858-4254-B7B7-145221A8AB75}.Release|x64.Build.0 = Release|Any CPU - {38C5D23D-0858-4254-B7B7-145221A8AB75}.Unstable|Any CPU.ActiveCfg = Release|Any CPU - {38C5D23D-0858-4254-B7B7-145221A8AB75}.Unstable|Any CPU.Build.0 = Release|Any CPU {38C5D23D-0858-4254-B7B7-145221A8AB75}.Unstable|x64.ActiveCfg = Release|Any CPU {38C5D23D-0858-4254-B7B7-145221A8AB75}.Unstable|x64.Build.0 = Release|Any CPU - {B411A619-1643-4C5F-A95D-9427D59BE010}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B411A619-1643-4C5F-A95D-9427D59BE010}.Debug|Any CPU.Build.0 = Debug|Any CPU {B411A619-1643-4C5F-A95D-9427D59BE010}.Debug|x64.ActiveCfg = Debug|Any CPU {B411A619-1643-4C5F-A95D-9427D59BE010}.Debug|x64.Build.0 = Debug|Any CPU - {B411A619-1643-4C5F-A95D-9427D59BE010}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B411A619-1643-4C5F-A95D-9427D59BE010}.Release|Any CPU.Build.0 = Release|Any CPU {B411A619-1643-4C5F-A95D-9427D59BE010}.Release|x64.ActiveCfg = Release|Any CPU {B411A619-1643-4C5F-A95D-9427D59BE010}.Release|x64.Build.0 = Release|Any CPU - {B411A619-1643-4C5F-A95D-9427D59BE010}.Unstable|Any CPU.ActiveCfg = Release|Any CPU - {B411A619-1643-4C5F-A95D-9427D59BE010}.Unstable|Any CPU.Build.0 = Release|Any CPU {B411A619-1643-4C5F-A95D-9427D59BE010}.Unstable|x64.ActiveCfg = Release|Any CPU {B411A619-1643-4C5F-A95D-9427D59BE010}.Unstable|x64.Build.0 = Release|Any CPU EndGlobalSection From c676233dfd4ecab0a013958b2cf6740640fa67c7 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 27 Feb 2026 17:56:47 -0500 Subject: [PATCH 183/288] [Save/Sync] Work on SettingsMenu --- .../Services/ModsGameplaySettingsMenu.cs | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs index 6770e78ab..97a156c25 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs @@ -28,22 +28,41 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenu new RectTransform(new Vector2(0.85f, 0.1f), searchBarLayoutGroup.RectTransform, Anchor.TopLeft), createClearButton: true) { - OnEnterPressed = (btn, txt) => + OnTextChangedDelegate = (btn, txt) => { + // TODO: Execute filter here return true; } }; - // display area + // main display area var settingsContentAreaGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.90f), contentAreaLayoutGroup.RectTransform, Anchor.BottomCenter)); GUIUtil.Spacer(settingsContentAreaGroup, Vector2.One); var (modCategoryDisplayGroup, settingsDisplayGroup) = GUIUtil.CreateSidebars(settingsContentAreaGroup, true); modCategoryDisplayGroup.RectTransform.RelativeSize = new Vector2(0.3f, 1f); settingsDisplayGroup.RectTransform.RelativeSize = new Vector2(0.7f, 1f); + + // Mods & Category Selectors var cpList = packageManagementService.GetAllLoadedPackages().OrderBy(cp => cp.Name == "Vanilla" ? 0 : 1).ThenBy(cp => cp.Name).ToList(); var modSelectDropDown = GUIUtil.Dropdown(modCategoryDisplayGroup, cp => cp.Name == "Vanilla" ? "All" : cp.Name, null, cpList, cpList[0], cp => { - // filter selections + // TODO: filter selections by adding it to the search bar }, Vector2.One, 2); + + + void GenerateDisplayFromFilter(string text) + { + + } + + void GenerateCategoryListDisplay(GUILayoutGroup layoutGroup) + { + + } + + void GenerateSettingsListDisplay(GUILayoutGroup layoutGroup) + { + + } } From 8e8b8eb8aa3063c01bd3da61774a50675561d5de Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 28 Feb 2026 16:05:20 -0300 Subject: [PATCH 184/288] GameMain.LuaCs is no more --- .../ClientSource/DebugConsole.cs | 8 +++---- .../ClientSource/GUI/ChatBox.cs | 2 +- .../BarotraumaClient/ClientSource/GameMain.cs | 9 +++----- .../ClientSource/LuaCs/LuaCsSettingsMenu.cs | 12 +++++----- .../ClientSource/Networking/GameClient.cs | 2 +- .../ServerSource/Characters/Character.cs | 2 +- .../Characters/CharacterNetworking.cs | 2 +- .../ServerSource/DebugConsole.cs | 6 ++--- .../BarotraumaServer/ServerSource/GameMain.cs | 14 +++++------- .../ServerSource/Networking/ChatMessage.cs | 2 +- .../ServerSource/Networking/GameServer.cs | 10 ++++----- .../ServerSource/Networking/RespawnManager.cs | 8 +++---- .../Networking/Voip/VoipServer.cs | 4 ++-- .../Animation/HumanoidAnimController.cs | 4 ++-- .../Characters/Animation/Ragdoll.cs | 2 +- .../SharedSource/Characters/Character.cs | 4 ++-- .../Health/Afflictions/AfflictionHusk.cs | 4 ++-- .../Characters/Health/CharacterHealth.cs | 4 ++-- .../Items/Components/Holdable/MeleeWeapon.cs | 2 +- .../Components/Machines/Deconstructor.cs | 2 +- .../Items/Components/Signal/WifiComponent.cs | 2 +- .../SharedSource/Items/Item.cs | 2 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 12 +++++++++- .../SharedSource/LuaCs/ModUtils.cs | 12 +++++----- .../SharedSource/LuaCs/_Plugins/ACsMod.cs | 2 +- .../LuaCs/_Services/LoggerService.cs | 2 +- .../LuaCs/_Services/LuaCsInfoProvider.cs | 14 ++++++------ .../_Services/LuaScriptManagementService.cs | 2 +- .../_Lua/LuaClasses/LuaBarotraumaAdditions.cs | 2 +- .../_Services/_Lua/LuaClasses/LuaCsLogger.cs | 22 +++++++++---------- .../_Services/_Lua/LuaClasses/LuaCsUtility.cs | 2 +- .../LuaCs/_Services/_Lua/LuaPatcherService.cs | 6 ++--- .../BarotraumaShared/SharedSource/Map/Gap.cs | 2 +- .../SharedSource/Map/MapEntity.cs | 4 ++-- .../StatusEffects/StatusEffect.cs | 6 ++--- 35 files changed, 99 insertions(+), 96 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index 46fa01e72..ecf7d8dd5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -223,7 +223,7 @@ namespace Barotrauma private static bool IsCommandPermitted(Identifier command, GameClient client) { - if (GameMain.LuaCs.Game.IsCustomCommandPermitted(command)) { return true; } + if (LuaCsSetup.Instance.Game.IsCustomCommandPermitted(command)) { return true; } switch (command.Value.ToLowerInvariant()) { @@ -4231,14 +4231,14 @@ namespace Barotrauma return; } - if (GameMain.LuaCs.CurrentRunState != RunState.Running) + if (LuaCsSetup.Instance.CurrentRunState != RunState.Running) { ThrowError("LuaCs not initialized, use the console command cl_reloadluacs to force initialization."); return; } - var result = GameMain.LuaCs.LuaScriptManagementService.DoString(string.Join(" ", args)); - GameMain.LuaCs.Logger.LogResults(result.ToResult()); + var result = LuaCsSetup.Instance.LuaScriptManagementService.DoString(string.Join(" ", args)); + LuaCsSetup.Instance.Logger.LogResults(result.ToResult()); })); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs index a8e5e33bf..5514fa1a0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs @@ -415,7 +415,7 @@ namespace Barotrauma if (GameMain.IsSingleplayer) { bool? should = null; - GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnChatMessage(message.Text, message.SenderClient, message.Type, message) ?? should); + LuaCsSetup.Instance.EventService.PublishEvent(x => should = x.OnChatMessage(message.Text, message.SenderClient, message.Type, message) ?? should); if (should != null && should.Value) { return; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs index 10086bdb0..b8ddf8f72 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs @@ -24,8 +24,6 @@ namespace Barotrauma { class GameMain : Game { - private static LuaCsSetup _luaCs; - public static LuaCsSetup LuaCs => _luaCs ??= new LuaCsSetup(); public static bool ShowFPS; public static bool ShowPerf; public static bool DebugDraw; @@ -298,7 +296,7 @@ namespace Barotrauma Window.FileDropped += OnFileDropped; - LuaCs.GetType(); + LuaCsSetup.Instance.GetType(); } public static void ExecuteAfterContentFinishedLoading(Action action) @@ -1296,10 +1294,9 @@ namespace Barotrauma CreatureMetrics.Save(); try { - if (_luaCs is not null) + if (LuaCsSetup.Instance is not null) { - _luaCs.Dispose(); - _luaCs = null; + LuaCsSetup.Instance.Dispose(); } } catch (Exception e) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs index 5f1e6d90e..8b1004165 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs @@ -19,33 +19,33 @@ namespace Barotrauma new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Enable CSharp Scripting") { - Selected = GameMain.LuaCs.IsCsEnabled, + Selected = LuaCsSetup.Instance.IsCsEnabled, ToolTip = "This enables CSharp Scripting for mods to use, WARNING: CSharp is NOT sandboxed, be careful with what mods you download.", OnSelected = (GUITickBox tick) => { - GameMain.LuaCs.IsCsEnabled = tick.Selected; + LuaCsSetup.Instance.IsCsEnabled = tick.Selected; return true; } }; new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Disable Error GUI Overlay") { - Selected = GameMain.LuaCs.DisableErrorGUIOverlay, + Selected = LuaCsSetup.Instance.DisableErrorGUIOverlay, ToolTip = "", OnSelected = (GUITickBox tick) => { - GameMain.LuaCs.DisableErrorGUIOverlay = tick.Selected; + LuaCsSetup.Instance.DisableErrorGUIOverlay = tick.Selected; return true; } }; new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Hide usernames In Error Logs") { - Selected = GameMain.LuaCs.HideUserNamesInLogs, + Selected = LuaCsSetup.Instance.HideUserNamesInLogs, ToolTip = "Hides the operating system username when displaying error logs (eg your username on windows).", OnSelected = (GUITickBox tick) => { - GameMain.LuaCs.HideUserNamesInLogs = tick.Selected; + LuaCsSetup.Instance.HideUserNamesInLogs = tick.Selected; return true; } }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs index 3111861d4..5e64723bf 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs @@ -3004,7 +3004,7 @@ namespace Barotrauma.Networking public override void AddChatMessage(ChatMessage message) { bool? should = null; - GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnChatMessage(message.Text, message.SenderClient, message.Type, message) ?? should); + LuaCsSetup.Instance.EventService.PublishEvent(x => should = x.OnChatMessage(message.Text, message.SenderClient, message.Type, message) ?? should); if (should != null && should.Value) { return; } if (string.IsNullOrEmpty(message.Text)) { return; } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs index e173c7a45..5ab01aeee 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/Character.cs @@ -65,7 +65,7 @@ namespace Barotrauma var owner = GameMain.Server.ConnectedClients.Find(c => c.Character == this); if (owner != null) { - if (!GameMain.LuaCs.Game.overrideTraitors) + if (!LuaCsSetup.Instance.Game.overrideTraitors) { GameMain.Server.SendDirectChatMessage(TextManager.FormatServerMessage("KilledByTraitorNotification"), owner, ChatMessageType.ServerMessageBoxInGame); } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs index 8aa2c1809..65d87e934 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs @@ -811,7 +811,7 @@ namespace Barotrauma var tempBuffer = new ReadWriteMessage(); WriteStatus(tempBuffer, forceAfflictionData: true); - if (msgLengthBeforeStatus + tempBuffer.LengthBytes >= 255 && restrictMessageSize && (GameMain.LuaCs.RestrictMessageSize)) + if (msgLengthBeforeStatus + tempBuffer.LengthBytes >= 255 && restrictMessageSize && (LuaCsSetup.Instance.RestrictMessageSize)) { msg.WriteBoolean(false); if (msgLengthBeforeStatus < 255) diff --git a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs index 507bacc83..6a04212ff 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs @@ -1291,14 +1291,14 @@ namespace Barotrauma commands.Add(new Command("lua", "lua: Runs a string.", (string[] args) => { - var result = GameMain.LuaCs.LuaScriptManagementService.DoString(string.Join(" ", args)); - GameMain.LuaCs.Logger.LogResults(result.ToResult()); + var result = LuaCsSetup.Instance.LuaScriptManagementService.DoString(string.Join(" ", args)); + LuaCsSetup.Instance.Logger.LogResults(result.ToResult()); })); commands.Add(new Command("reloadlua|reloadcs|reloadluacs", "Re-initializes the LuaCs environment.", (string[] args) => { //GameMain.LuaCs.Initialize(); - GameMain.LuaCs.EventService.PublishEvent(sub => sub.OnReloadAllPackages()); + LuaCsSetup.Instance.EventService.PublishEvent(sub => sub.OnReloadAllPackages()); })); commands.Add(new Command("toggleluadebug", "Toggles the MoonSharp Debug Server.", (string[] args) => diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs index c875eed2f..2fc1138e4 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs @@ -35,9 +35,6 @@ namespace Barotrauma set { world = value; } } - private static LuaCsSetup _luaCs; - public static LuaCsSetup LuaCs => _luaCs ??= new LuaCsSetup(); - public static GameServer Server; public static NetworkMember NetworkMember { @@ -115,7 +112,7 @@ namespace Barotrauma MainThread = Thread.CurrentThread; - LuaCs.GetType(); + LuaCsSetup.Instance.GetType(); } public void Init() @@ -367,9 +364,9 @@ namespace Barotrauma CoroutineManager.Update(paused: false, (float)Timing.Step); performanceCounterTimer.Stop(); - if (GameMain.LuaCs.PerformanceCounter.EnablePerformanceCounter) + if (LuaCsSetup.Instance.PerformanceCounter.EnablePerformanceCounter) { - GameMain.LuaCs.PerformanceCounter.AddElapsedTicks(new SimplePerformanceData("Update", performanceCounterTimer.ElapsedTicks)); + LuaCsSetup.Instance.PerformanceCounter.AddElapsedTicks(new SimplePerformanceData("Update", performanceCounterTimer.ElapsedTicks)); } performanceCounterTimer.Reset(); @@ -455,10 +452,9 @@ namespace Barotrauma ShouldRun = false; try { - if (_luaCs is not null) + if (LuaCsSetup.Instance is not null) { - _luaCs.Dispose(); - _luaCs = null; + LuaCsSetup.Instance.Dispose(); } } catch (Exception e) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs index 83898e882..1d15c4b60 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs @@ -89,7 +89,7 @@ namespace Barotrauma.Networking if (flaggedAsSpam) { return; } bool? should = null; - GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnChatMessage(txt, c, type, ChatMessage.Create(c.Name, txt, type, null, c)) ?? should); + LuaCsSetup.Instance.EventService.PublishEvent(x => should = x.OnChatMessage(txt, c, type, ChatMessage.Create(c.Name, txt, type, null, c)) ?? should); if (should != null && should.Value) { return; } if (type == ChatMessageType.Order) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 89a92c077..ac6d9374a 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -440,7 +440,7 @@ namespace Barotrauma.Networking (permadeathMode && (!character.IsDead || character.CauseOfDeath?.Type == CauseOfDeathType.Disconnected))); if (!character.IsDead) { - if (!GameMain.LuaCs.Game.disableDisconnectCharacter) + if (!LuaCsSetup.Instance.Game.disableDisconnectCharacter) { character.KillDisconnectedTimer += deltaTime; character.SetStun(1.0f); @@ -3178,7 +3178,7 @@ namespace Barotrauma.Networking } TraitorManager.Initialize(GameMain.GameSession.EventManager, Level.Loaded); - if (GameMain.LuaCs.Game.overrideTraitors) + if (LuaCsSetup.Instance.Game.overrideTraitors) { TraitorManager.Enabled = false; } @@ -3512,7 +3512,7 @@ namespace Barotrauma.Networking } bool? result = null; - GameMain.LuaCs.EventService.PublishEvent(x => result = x.OnTryClienChangeName(c, newName, newJob, newTeam) ?? result); + LuaCsSetup.Instance.EventService.PublishEvent(x => result = x.OnTryClienChangeName(c, newName, newJob, newTeam) ?? result); if (result != null) { @@ -3964,7 +3964,7 @@ namespace Barotrauma.Networking senderName = null; senderCharacter = null; } - else if (type == ChatMessageType.Radio && !GameMain.LuaCs.Game.overrideSignalRadio) + else if (type == ChatMessageType.Radio && !LuaCsSetup.Instance.Game.overrideSignalRadio) { //send to chat-linked wifi components Signal s = new Signal(message, sender: senderCharacter, source: senderRadio.Item); @@ -4771,7 +4771,7 @@ namespace Barotrauma.Networking { if (GameMain.Server == null || !GameMain.Server.ServerSettings.SaveServerLogs) { return; } - GameMain.LuaCs?.EventService.PublishEvent(x => x.OnServerLog(line, messageType)); + LuaCsSetup.Instance?.EventService.PublishEvent(x => x.OnServerLog(line, messageType)); GameMain.Server.ServerSettings.ServerLog.WriteLine(line, messageType); diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs index b83376d66..77f213cf3 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs @@ -18,7 +18,7 @@ namespace Barotrauma.Networking MultiPlayerCampaign campaign = GameMain.GameSession.GameMode as MultiPlayerCampaign; foreach (Client c in networkMember.ConnectedClients) { - if (GameMain.LuaCs.Game.overrideRespawnSub) + if (LuaCsSetup.Instance.Game.overrideRespawnSub) continue; if (!c.InGame) { continue; } @@ -169,7 +169,7 @@ namespace Barotrauma.Networking private bool ShouldStartRespawnCountdown(int characterToRespawnCount) { - if (GameMain.LuaCs.Game.overrideRespawnSub) + if (LuaCsSetup.Instance.Game.overrideRespawnSub) { characterToRespawnCount = 0; } @@ -187,7 +187,7 @@ namespace Barotrauma.Networking var teamId = teamSpecificState.TeamID; var respawnShuttle = GetShuttle(teamId); - if (respawnShuttle != null && !GameMain.LuaCs.Game.overrideRespawnSub) + if (respawnShuttle != null && !LuaCsSetup.Instance.Game.overrideRespawnSub) { respawnShuttle.Velocity = Vector2.Zero; } @@ -240,7 +240,7 @@ namespace Barotrauma.Networking if (RespawnShuttles.Any()) { ResetShuttle(teamSpecificState); - if (GameMain.LuaCs.Game.overrideRespawnSub) + if (LuaCsSetup.Instance.Game.overrideRespawnSub) { teamSpecificState.CurrentState = State.Waiting; } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voip/VoipServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voip/VoipServer.cs index a28f2701b..5970af2bf 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Voip/VoipServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Voip/VoipServer.cs @@ -100,7 +100,7 @@ namespace Barotrauma.Networking (recipientSpectating || ChatMessage.CanUseRadio(recipient.Character, out recipientRadio))) { bool? canUse = null; - GameMain.LuaCs.EventService.PublishEvent(x => canUse = x.OnCanUseVoiceRadio(sender, recipient) ?? canUse); + LuaCsSetup.Instance.EventService.PublishEvent(x => canUse = x.OnCanUseVoiceRadio(sender, recipient) ?? canUse); if (canUse != null) { @@ -121,7 +121,7 @@ namespace Barotrauma.Networking } float range = 1.0f; - GameMain.LuaCs.EventService.PublishEvent(x => range = x.OnChangeLocalVoiceRange(sender, recipient) ?? range); + LuaCsSetup.Instance.EventService.PublishEvent(x => range = x.OnChangeLocalVoiceRange(sender, recipient) ?? range); if (recipientSpectating) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs index 3c64fb44c..3c3bd5c48 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/HumanoidAnimController.cs @@ -1306,11 +1306,11 @@ namespace Barotrauma //increase oxygen and clamp it above zero // -> the character should be revived if there are no major afflictions in addition to lack of oxygen target.Oxygen = Math.Max(target.Oxygen + 10.0f, 10.0f); - GameMain.LuaCs.EventService.PublishEvent(x => x.OnCharacterCPRSuccess(this)); + LuaCsSetup.Instance.EventService.PublishEvent(x => x.OnCharacterCPRSuccess(this)); } else { - GameMain.LuaCs.EventService.PublishEvent(x => x.OnCharacterCPRFailed(this)); + LuaCsSetup.Instance.EventService.PublishEvent(x => x.OnCharacterCPRFailed(this)); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index 7517e00f4..f5e3a47f3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -859,7 +859,7 @@ namespace Barotrauma float impactDamage = GetImpactDamage(impact, impactTolerance); float? should = null; - GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnChangeFallDamage(impactDamage, character, impactPos, velocity) ?? should); + LuaCsSetup.Instance.EventService.PublishEvent(x => should = x.OnChangeFallDamage(impactDamage, character, impactPos, velocity) ?? should); if (should != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 198ff8d93..22a0e4382 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -3388,13 +3388,13 @@ namespace Barotrauma { for (int i = 0; i < CharacterList.Count; i++) { - if (GameMain.LuaCs.Game.UpdatePriorityCharacters.Contains(CharacterList[i])) continue; + if (LuaCsSetup.Instance.Game.UpdatePriorityCharacters.Contains(CharacterList[i])) continue; CharacterList[i].Update(deltaTime * CharacterUpdateInterval, cam); } } - foreach (Character character in GameMain.LuaCs.Game.UpdatePriorityCharacters) + foreach (Character character in LuaCsSetup.Instance.Game.UpdatePriorityCharacters) { if (character.Removed) { continue; } Debug.Assert(character is { Removed: false }); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs index 7974edd57..37b2f35f5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionHusk.cs @@ -338,13 +338,13 @@ namespace Barotrauma if (Prefab is AfflictionPrefabHusk huskPrefab) { - if (huskPrefab.ControlHusk || GameMain.LuaCs.Game.enableControlHusk) + if (huskPrefab.ControlHusk || LuaCsSetup.Instance.Game.enableControlHusk) { #if SERVER if (client != null) { GameMain.Server.SetClientCharacter(client, husk); - GameMain.LuaCs.EventService.PublishEvent(x => x.OnClientControlHusk(client, husk)); + LuaCsSetup.Instance.EventService.PublishEvent(x => x.OnClientControlHusk(client, husk)); } #else if (!character.IsRemotelyControlled && character == Character.Controlled) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index 2ddc7d8f9..61cff37a5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -658,7 +658,7 @@ namespace Barotrauma } bool? should = null; - GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnCharacterApplyDamage(this, attackResult, hitLimb, allowStacking) ?? should); + LuaCsSetup.Instance.EventService.PublishEvent(x => should = x.OnCharacterApplyDamage(this, attackResult, hitLimb, allowStacking) ?? should); if (should != null && should.Value) { return; } foreach (Affliction newAffliction in attackResult.Afflictions) @@ -830,7 +830,7 @@ namespace Barotrauma if (Character.Params.Health.ImmunityIdentifiers.Contains(newAffliction.Identifier)) { return; } bool? should = null; - GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnCharacterApplyAffliction(this, limbHealth, newAffliction, allowStacking) ?? should); + LuaCsSetup.Instance.EventService.PublishEvent(x => should = x.OnCharacterApplyAffliction(this, limbHealth, newAffliction, allowStacking) ?? should); if (should != null && should.Value) { return; } Affliction existingAffliction = null; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs index fe27ceb15..7212ba5de 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs @@ -436,7 +436,7 @@ namespace Barotrauma.Items.Components Structure targetStructure = target.UserData as Structure ?? targetFixture.UserData as Structure; Item targetItem = target.UserData is Holdable h ? h.Item : target.UserData as Item ?? targetFixture.UserData as Item; Entity targetEntity = targetCharacter ?? targetStructure ?? targetItem ?? target.UserData as Entity; - GameMain.LuaCs.EventService.PublishEvent(x => x.OnMeleeWeaponHandleImpact(this, target)); + LuaCsSetup.Instance.EventService.PublishEvent(x => x.OnMeleeWeaponHandleImpact(this, target)); if (Attack != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs index be847f047..8b04bf85d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs @@ -335,7 +335,7 @@ namespace Barotrauma.Items.Components } bool? should = null; - GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnItemDeconstructed(targetItem, this, user, allowRemove) ?? should); + LuaCsSetup.Instance.EventService.PublishEvent(x => should = x.OnItemDeconstructed(targetItem, this, user, allowRemove) ?? should); if (should == true) { return; } if (targetItem.AllowDeconstruct && allowRemove) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs index 7f8b4c75b..9d2104d73 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs @@ -232,7 +232,7 @@ namespace Barotrauma.Items.Components public void TransmitSignal(Signal signal, bool sentFromChat) { bool? should = null; - GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnWifiSignalTransmitted(this, signal, sentFromChat) ?? should); + LuaCsSetup.Instance.EventService.PublishEvent(x => should = x.OnWifiSignalTransmitted(this, signal, sentFromChat) ?? should); if (should != null && should.Value) { return; } bool chatMsgSent = false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 8c700265a..fbe4d9ae9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -3895,7 +3895,7 @@ namespace Barotrauma } bool? should = null; - GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnItemReadPropertyChange(this, property, parentObject, allowEditing, sender) ?? should); + LuaCsSetup.Instance.EventService.PublishEvent(x => should = x.OnItemReadPropertyChange(this, property, parentObject, allowEditing, sender) ?? should); if (should != null && should.Value) { return; } Type type = property.PropertyType; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index f81efda0d..9a92f8e49 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -22,8 +22,16 @@ namespace Barotrauma partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventEnabledPackageListChanged, IEventReloadAllPackages { - public LuaCsSetup() + private static LuaCsSetup _luaCsSetup; + public static LuaCsSetup Instance => _luaCsSetup ??= new LuaCsSetup(); + + private LuaCsSetup() { + if (_luaCsSetup != null) + { + throw new Exception("Tried to create another LuaCsSetup instance"); + } + // == startup _servicesProvider = SetupServicesProvider(); _runStateMachine = SetupStateMachine(); @@ -432,6 +440,8 @@ namespace Barotrauma Console.WriteLine(e); throw; } + + _luaCsSetup = null; GC.SuppressFinalize(this); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs index a6588444e..e2a08d806 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs @@ -74,27 +74,27 @@ namespace Barotrauma.LuaCs public static void PrintMessage(string s) { #if SERVER - GameMain.LuaCs.Logger.LogMessage($"{s}"); + LuaCsSetup.Instance.Logger.LogMessage($"{s}"); #else - GameMain.LuaCs.Logger.LogMessage($"{s}"); + LuaCsSetup.Instance.Logger.LogMessage($"{s}"); #endif } public static void PrintWarning(string s) { #if SERVER - GameMain.LuaCs.Logger.Log($"{s}", Color.Yellow); + LuaCsSetup.Instance.Logger.Log($"{s}", Color.Yellow); #else - GameMain.LuaCs.Logger.Log($"{s}", Color.Yellow); + LuaCsSetup.Instance.Logger.Log($"{s}", Color.Yellow); #endif } public static void PrintError(string s) { #if SERVER - GameMain.LuaCs.Logger.LogError($"{s}"); + LuaCsSetup.Instance.Logger.LogError($"{s}"); #else - GameMain.LuaCs.Logger.LogError($"{s}"); + LuaCsSetup.Instance.Logger.LogError($"{s}"); #endif } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs index 8065d4dcc..1a189e31b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/ACsMod.cs @@ -48,7 +48,7 @@ namespace Barotrauma } catch (Exception e) { - GameMain.LuaCs.Logger.HandleException(e); + LuaCsSetup.Instance.Logger.HandleException(e); } IsDisposed = true; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs index 305950efe..aeccae840 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LoggerService.cs @@ -60,7 +60,7 @@ public partial class LoggerService : ILoggerService if (!_isInsideLogCall) { _isInsideLogCall = true; - GameMain.LuaCs?.EventService.PublishEvent(x => x.OnServerLog(logMessage, log.MessageType)); + LuaCsSetup.Instance?.EventService.PublishEvent(x => x.OnServerLog(logMessage, log.MessageType)); _isInsideLogCall = false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs index 81ff151b0..edf63fc32 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs @@ -12,13 +12,13 @@ public sealed class LuaCsInfoProvider : ILuaCsInfoProvider } public bool IsDisposed => false; - public bool IsCsEnabled => GameMain.LuaCs.IsCsEnabled; - public bool DisableErrorGUIOverlay => GameMain.LuaCs.DisableErrorGUIOverlay; - public bool HideUserNamesInLogs => GameMain.LuaCs.HideUserNamesInLogs; - public ulong LuaForBarotraumaSteamId => GameMain.LuaCs.LuaForBarotraumaSteamId; - public bool RestrictMessageSize => GameMain.LuaCs.RestrictMessageSize; - public string LocalDataSavePath => GameMain.LuaCs.LocalDataSavePath; - public RunState CurrentRunState => GameMain.LuaCs.CurrentRunState; + public bool IsCsEnabled => LuaCsSetup.Instance.IsCsEnabled; + public bool DisableErrorGUIOverlay => LuaCsSetup.Instance.DisableErrorGUIOverlay; + public bool HideUserNamesInLogs => LuaCsSetup.Instance.HideUserNamesInLogs; + public ulong LuaForBarotraumaSteamId => LuaCsSetup.Instance.LuaForBarotraumaSteamId; + public bool RestrictMessageSize => LuaCsSetup.Instance.RestrictMessageSize; + public string LocalDataSavePath => LuaCsSetup.Instance.LocalDataSavePath; + public RunState CurrentRunState => LuaCsSetup.Instance.CurrentRunState; public ContentPackage LuaCsForBarotraumaPackage { get diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 43a140e1d..5bb2d4bc8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -90,7 +90,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService { commands.RegisterCommand("cl_reloadlua|cl_reloadcs|cl_reloadluacs", "Re-initializes the LuaCs environment.", (string[] args) => { - GameMain.LuaCs.EventService.PublishEvent(sub => sub.OnReloadAllPackages()); + LuaCsSetup.Instance.EventService.PublishEvent(sub => sub.OnReloadAllPackages()); }); commands.RegisterCommand("cl_toggleluadebug", "Toggles the MoonSharp Debug Server.", (string[] args) => diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaBarotraumaAdditions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaBarotraumaAdditions.cs index cacdf5bf9..a80aa9e99 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaBarotraumaAdditions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaBarotraumaAdditions.cs @@ -59,7 +59,7 @@ namespace Barotrauma { public object GetComponentString(string component) { - Type type = GameMain.LuaCs.PluginManagementService.GetType("Barotrauma.Items.Components." + component); + Type type = LuaCsSetup.Instance.PluginManagementService.GetType("Barotrauma.Items.Components." + component); if (type == null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs index 7179ece13..cf9dbbe8c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs @@ -17,41 +17,41 @@ namespace Barotrauma { public static void HandleException(Exception ex, LuaCsMessageOrigin origin) { - GameMain.LuaCs.Logger.HandleException(ex); + LuaCsSetup.Instance.Logger.HandleException(ex); } public static void LogError(string message, LuaCsMessageOrigin origin) { - GameMain.LuaCs.Logger.LogError(message); + LuaCsSetup.Instance.Logger.LogError(message); } public static void LogError(string message) { - GameMain.LuaCs.Logger.LogError(message); + LuaCsSetup.Instance.Logger.LogError(message); } public static void LogMessage(string message, Color? serverColor = null, Color? clientColor = null) { - GameMain.LuaCs.Logger.LogMessage(message, serverColor, clientColor); + LuaCsSetup.Instance.Logger.LogMessage(message, serverColor, clientColor); } public static void Log(string message, Color? color = null, ServerLog.MessageType messageType = ServerLog.MessageType.ServerMessage) { - GameMain.LuaCs.Logger.Log(message, color, messageType); + LuaCsSetup.Instance.Logger.Log(message, color, messageType); } } partial class LuaCsSetup { // Compatibility with cs mods that use this method. - public static void PrintLuaError(object message) => GameMain.LuaCs.Logger.LogError($"{message}"); - public static void PrintCsError(object message) => GameMain.LuaCs.Logger.LogError($"{message}"); - public static void PrintGenericError(object message) => GameMain.LuaCs.Logger.LogError($"{message}"); + public static void PrintLuaError(object message) => LuaCsSetup.Instance.Logger.LogError($"{message}"); + public static void PrintCsError(object message) => LuaCsSetup.Instance.Logger.LogError($"{message}"); + public static void PrintGenericError(object message) => LuaCsSetup.Instance.Logger.LogError($"{message}"); - internal void PrintMessage(object message) => GameMain.LuaCs.Logger.LogMessage($"{message}"); + internal void PrintMessage(object message) => LuaCsSetup.Instance.Logger.LogMessage($"{message}"); - public static void PrintCsMessage(object message) => GameMain.LuaCs.Logger.LogMessage($"{message}"); + public static void PrintCsMessage(object message) => LuaCsSetup.Instance.Logger.LogMessage($"{message}"); - internal void HandleException(Exception ex, LuaCsMessageOrigin origin) => GameMain.LuaCs.Logger.HandleException(ex); + internal void HandleException(Exception ex, LuaCsMessageOrigin origin) => LuaCsSetup.Instance.Logger.HandleException(ex); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsUtility.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsUtility.cs index 596f8de3b..b4aca75b9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsUtility.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsUtility.cs @@ -60,7 +60,7 @@ namespace Barotrauma foreach (var package in ContentPackageManager.AllPackages) { - if (package.UgcId.ValueEquals(new SteamWorkshopId(GameMain.LuaCs.LuaForBarotraumaSteamId)) + if (package.UgcId.ValueEquals(new SteamWorkshopId(LuaCsSetup.Instance.LuaForBarotraumaSteamId)) && pathStartsWith(getFullPath(package.Path))) { return false; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaPatcherService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaPatcherService.cs index 181e04df3..0bee7c96c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaPatcherService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaPatcherService.cs @@ -294,7 +294,7 @@ namespace Barotrauma.LuaCs private static MethodBase ResolveMethod(string className, string methodName, string[] parameters) { - var classType = GameMain.LuaCs.PluginManagementService.GetType(className); + var classType = LuaCsSetup.Instance.PluginManagementService.GetType(className); if (classType == null) throw new ScriptRuntimeException($"invalid class name '{className}'"); const BindingFlags BINDING_FLAGS = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; @@ -309,7 +309,7 @@ namespace Barotrauma.LuaCs for (int i = 0; i < parameters.Length; i++) { - Type type = GameMain.LuaCs.PluginManagementService.GetType(parameters[i]); + Type type = LuaCsSetup.Instance.PluginManagementService.GetType(parameters[i]); if (type == null) { throw new ScriptRuntimeException($"invalid parameter type '{parameters[i]}'"); @@ -670,7 +670,7 @@ namespace Barotrauma.LuaCs } var type = typeBuilder.CreateType(); - type.GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static).SetValue(null, GameMain.LuaCs); + type.GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static).SetValue(null, LuaCsSetup.Instance); return type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs index cdf46bbda..9457b77df 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs @@ -886,7 +886,7 @@ namespace Barotrauma } bool? should = null; - GameMain.LuaCs.EventService.PublishEvent(x => should = x.OnGapOxygenUpdate(this, hull1, hull2) ?? should); + LuaCsSetup.Instance.EventService.PublishEvent(x => should = x.OnGapOxygenUpdate(this, hull1, hull2) ?? should); if (should != null && should.Value) return; float totalOxygen = hull1.Oxygen + hull2.Oxygen; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs index 64f01bb58..eb906563d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/MapEntity.cs @@ -697,7 +697,7 @@ namespace Barotrauma { foreach (Item item in Item.ItemList) { - if (GameMain.LuaCs.Game.UpdatePriorityItems.Contains(item)) { continue; } + if (LuaCsSetup.Instance.Game.UpdatePriorityItems.Contains(item)) { continue; } lastUpdatedItem = item; item.Update(deltaTime * MapEntityUpdateInterval, cam); } @@ -712,7 +712,7 @@ namespace Barotrauma } } - foreach (var item in GameMain.LuaCs.Game.UpdatePriorityItems) + foreach (var item in LuaCsSetup.Instance.Game.UpdatePriorityItems) { if (item.Removed) continue; diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index a0cd1be2a..649e59e6e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -1787,14 +1787,14 @@ namespace Barotrauma { if (entity is Item item) { - var result = GameMain.LuaCs.Hook.Call("statusEffect.apply." + item.Prefab.Identifier, this, deltaTime, entity, targets, worldPosition); + var result = LuaCsSetup.Instance.Hook.Call("statusEffect.apply." + item.Prefab.Identifier, this, deltaTime, entity, targets, worldPosition); if (result != null && result.Value) { return; } } if (entity is Character character) { - var result = GameMain.LuaCs.Hook.Call("statusEffect.apply." + character.SpeciesName, this, deltaTime, entity, targets, worldPosition); + var result = LuaCsSetup.Instance.Hook.Call("statusEffect.apply." + character.SpeciesName, this, deltaTime, entity, targets, worldPosition); if (result != null && result.Value) { return; } } @@ -1804,7 +1804,7 @@ namespace Barotrauma { foreach ((string hookName, ContentXElement element) in luaHook) { - var result = GameMain.LuaCs.Hook.Call(hookName, this, deltaTime, entity, targets, worldPosition, element); + var result = LuaCsSetup.Instance.Hook.Call(hookName, this, deltaTime, entity, targets, worldPosition, element); if (result != null && result.Value) { return; } } From de73a1863775b6be41313ab2a5a0f0599c8f6b72 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 28 Feb 2026 16:13:07 -0300 Subject: [PATCH 185/288] Compatibility for in memory scripts that used GameMain.LuaCs --- .../LuaCs/_Services/PluginManagementService.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index ad32a8d6f..3af050db4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -560,9 +560,12 @@ public class PluginManagementService : IAssemblyManagementService compileWithInternalName = resourceInfo.UseInternalAccessName; CancellationToken token = CancellationToken.None; - + + string sourceCode = loadRes.Value; + sourceCode = DoSourceCodeTextCompatibilityPass(sourceCode); + syntaxTreesBuilder.Add(SyntaxFactory.ParseSyntaxTree( - text: loadRes.Value, + text: sourceCode, options: ScriptParseOptions, path: null, encoding: Encoding.Default, @@ -613,6 +616,11 @@ public class PluginManagementService : IAssemblyManagementService } } + private string DoSourceCodeTextCompatibilityPass(string sourceCode) + { + return sourceCode.Replace("GameMain.LuaCs", "LuaCsSetup.Instance"); + } + private IntPtr OnAssemblyLoaderResolvingUnmanaged(Assembly arg1, string arg2) { // TODO: Implement extern assembly lookup for Native/Unmanaged Assemblies. From 3192cc8b00c55f68742bef3e7a4ff72724f336ce Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 28 Feb 2026 16:47:18 -0300 Subject: [PATCH 186/288] Remove need to define custom network header in upstream --- .../LuaCs/Services/NetworkingService.cs | 8 ++--- .../LuaCs/Services/NetworkingService.cs | 10 +++--- .../LuaCs/_Services/NetworkingService.cs | 31 +++++++++++++++++++ .../SharedSource/Networking/NetworkMember.cs | 3 -- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs index 49acd7436..0e4a1eb38 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs @@ -18,7 +18,7 @@ partial class NetworkingService : INetworkingService, IEventServerConnected, IEv public void OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader) { - if (serverPacketHeader != ServerPacketHeader.LUA_NET_MESSAGE) + if (serverPacketHeader != ServerHeader) { return; } @@ -46,7 +46,7 @@ partial class NetworkingService : INetworkingService, IEventServerConnected, IEv if (GameMain.Client == null) { return; } WriteOnlyMessage message = new WriteOnlyMessage(); - message.WriteByte((byte)ClientPacketHeader.LUA_NET_MESSAGE); + message.WriteByte((byte)ClientHeader); message.WriteByte((byte)ClientToServer.RequestSync); GameMain.Client.ClientPeer.Send(message, DeliveryMethod.Reliable); } @@ -55,7 +55,7 @@ partial class NetworkingService : INetworkingService, IEventServerConnected, IEv { var message = new WriteOnlyMessage(); - message.WriteByte((byte)ClientPacketHeader.LUA_NET_MESSAGE); + message.WriteByte((byte)ClientHeader); if (idToPacket.ContainsKey(netId)) { @@ -86,7 +86,7 @@ partial class NetworkingService : INetworkingService, IEventServerConnected, IEv if (GameMain.Client == null) { return; } WriteOnlyMessage message = new WriteOnlyMessage(); - message.WriteByte((byte)ClientPacketHeader.LUA_NET_MESSAGE); + message.WriteByte((byte)ClientHeader); message.WriteByte((byte)ClientToServer.RequestSingleNetId); NetId.Write(message, netId); diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs index 756b29c83..895628f75 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs @@ -19,7 +19,7 @@ partial class NetworkingService : INetworkingService, IEventClientRawNetMessageR { var message = new WriteOnlyMessage(); - message.WriteByte((byte)ServerPacketHeader.LUA_NET_MESSAGE); + message.WriteByte((byte)ServerHeader); if (idToPacket.ContainsKey(netId)) { @@ -35,9 +35,9 @@ partial class NetworkingService : INetworkingService, IEventClientRawNetMessageR return message; } - public void OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader serverPacketHeader, NetworkConnection sender) + public void OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader clientPacketHeader, NetworkConnection sender) { - if (serverPacketHeader != ClientPacketHeader.LUA_NET_MESSAGE) + if (clientPacketHeader != ClientHeader) { return; } @@ -134,7 +134,7 @@ partial class NetworkingService : INetworkingService, IEventClientRawNetMessageR private void WriteIdToAll(ushort packet, NetId netId) { WriteOnlyMessage message = new WriteOnlyMessage(); - message.WriteByte((byte)ServerPacketHeader.LUA_NET_MESSAGE); + message.WriteByte((byte)ServerHeader); message.WriteByte((byte)ServerToClient.ReceiveNetIds); message.WriteUInt16(1); @@ -147,7 +147,7 @@ partial class NetworkingService : INetworkingService, IEventClientRawNetMessageR private void WriteSync(Client client) { WriteOnlyMessage message = new WriteOnlyMessage(); - message.WriteByte((byte)ServerPacketHeader.LUA_NET_MESSAGE); + message.WriteByte((byte)ServerHeader); message.WriteByte((byte)ServerToClient.ReceiveNetIds); message.WriteUInt16((ushort)packetToId.Count()); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs index afa9c7e7c..a3d4262f5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs @@ -6,6 +6,7 @@ using FluentResults; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using System.Security.Cryptography; using System.Text; @@ -48,6 +49,36 @@ internal partial class NetworkingService : INetworkingService ReceiveNetIds } + private ClientPacketHeader? clientHeader = null; + public ClientPacketHeader ClientHeader + { + get + { + if (clientHeader == null) + { + byte lastHeader = (byte)Enum.GetValues(typeof(ClientPacketHeader)).Cast().Last(); + clientHeader = (ClientPacketHeader)(lastHeader + 1); + } + + return (ClientPacketHeader)clientHeader; + } + } + + private ServerPacketHeader? serverHeader = null; + public ServerPacketHeader ServerHeader + { + get + { + if (serverHeader == null) + { + byte lastHeader = (byte)Enum.GetValues(typeof(ServerPacketHeader)).Cast().Last(); + serverHeader = (ServerPacketHeader)(lastHeader + 1); + } + + return (ServerPacketHeader)serverHeader; + } + } + private ConcurrentDictionary netVars = []; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs index c33e87c57..1b4402d9e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetworkMember.cs @@ -46,7 +46,6 @@ namespace Barotrauma.Networking TOGGLE_RESERVE_BENCH, REQUEST_BACKUP_INDICES, // client wants a list of available backups for a save file - LUA_NET_MESSAGE } enum ClientNetSegment @@ -105,8 +104,6 @@ namespace Barotrauma.Networking UNLOCKRECIPE, //unlocking a fabrication recipe SEND_BACKUP_INDICES, // the server sends a list of available backups for a save file - - LUA_NET_MESSAGE } enum ServerNetSegment { From e2c4282477605b2e7e3513c558c4168c60f64d47 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 28 Feb 2026 16:56:14 -0300 Subject: [PATCH 187/288] Add netMessageReceived hook back --- .../SharedSource/LuaCs/IEvents.cs | 39 ++++++++++++++++++- .../_Services/LuaScriptManagementService.cs | 4 ++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index c12c5669f..d7fa5761b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -7,6 +7,7 @@ using MoonSharp.Interpreter; using Steamworks.Ugc; using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; namespace Barotrauma.LuaCs.Events; @@ -917,7 +918,28 @@ interface IEventInventoryItemSwap : IEvent #if SERVER public interface IEventClientRawNetMessageReceived : IEvent { - void OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader serverPacketHeader, NetworkConnection sender); + void OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader clientPacketHeader, NetworkConnection sender); + + static IEventClientRawNetMessageReceived IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventClientRawNetMessageReceived + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader clientPacketHeader, NetworkConnection sender) + { + if (GameMain.Server == null) { return; } + + Client client = GameMain.Server.ConnectedClients.FirstOrDefault(c => c.Connection == sender); + + if (client == null) { return; } + + LuaFuncs[nameof(OnReceivedClientNetMessage)](netMessage, clientPacketHeader, client); + } + } } /// @@ -1007,6 +1029,21 @@ interface IEventJobsAssigned : IEvent public interface IEventServerRawNetMessageReceived : IEvent { void OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader); + + static IEventServerRawNetMessageReceived IEvent.GetLuaRunner(IDictionary luaFunc) + => new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventServerRawNetMessageReceived + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + public void OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader) + { + LuaFuncs[nameof(OnReceivedServerNetMessage)](netMessage, serverPacketHeader); + } + } } /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 5bb2d4bc8..b2d3c5db2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -243,6 +243,10 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _eventService.RegisterLuaEventAlias("client.connected", nameof(IEventClientConnected.OnClientConnected)); _eventService.RegisterLuaEventAlias("client.disconnected", nameof(IEventClientDisconnected.OnClientDisconnected)); _eventService.RegisterLuaEventAlias("jobsAssigned", nameof(IEventJobsAssigned.OnJobsAssigned)); + + _eventService.RegisterLuaEventAlias("netMessageReceived", nameof(IEventClientRawNetMessageReceived.OnReceivedClientNetMessage)); +#elif CLIENT + _eventService.RegisterLuaEventAlias("netMessageReceived", nameof(IEventServerRawNetMessageReceived.OnReceivedServerNetMessage)); #endif } From 28b355911df69d9a7f22e9d2bf4dfd91aac6eb92 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 28 Feb 2026 17:57:02 -0300 Subject: [PATCH 188/288] Add missing ILuaCsNetworking APIs --- .../LuaCs/Compatibility/ILuaCsNetworking.cs | 11 ++- .../LuaCs/_Services/NetworkingService.cs | 96 +++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs index cbdbbb0bc..0f2388df9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs @@ -1,11 +1,20 @@ using Barotrauma.Networking; +using System.Collections.Generic; namespace Barotrauma.LuaCs.Compatibility; -public interface ILuaCsNetworking : ILuaCsShim +internal interface ILuaCsNetworking : ILuaCsShim { + void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData); + ushort LastClientListUpdateID { get; set; } + void HttpRequest(string url, LuaCsAction callback, string data = null, string method = "POST", string contentType = "application/json", Dictionary headers = null, string savePath = null); + void HttpPost(string url, LuaCsAction callback, string data, string contentType = "application/json", Dictionary headers = null, string savePath = null); + void Receive(string netId, LuaCsAction action); #if SERVER + int FileSenderMaxPacketsPerUpdate { get; set; } + void ClientWriteLobby(Client client); + void UpdateClientPermissions(Client client); void Send(IWriteMessage mesage, NetworkConnection connection = null, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable); #elif CLIENT void Send(IWriteMessage mesage, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs index a3d4262f5..622f7395f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs @@ -6,7 +6,9 @@ using FluentResults; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Net.Http; using System.Security.Cryptography; using System.Text; @@ -275,4 +277,98 @@ internal partial class NetworkingService : INetworkingService { IsDisposed = true; } + + #region Compatiblity + + private static readonly HttpClient client = new HttpClient(); + + public async void HttpRequest(string url, LuaCsAction callback, string data = null, string method = "POST", string contentType = "application/json", Dictionary headers = null, string savePath = null) + { + try + { + HttpRequestMessage request = new HttpRequestMessage(new HttpMethod(method), url); + + if (headers != null) + { + foreach (var header in headers) + { + request.Headers.Add(header.Key, header.Value); + } + } + + if (data != null) + { + request.Content = new StringContent(data, Encoding.UTF8, contentType); + } + + HttpResponseMessage response = await client.SendAsync(request); + + if (savePath != null) + { + if (LuaCsFile.IsPathAllowedException(savePath)) + { + byte[] responseData = await response.Content.ReadAsByteArrayAsync(); + + using (var fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write)) + { + fileStream.Write(responseData, 0, responseData.Length); + } + } + } + + string responseBody = await response.Content.ReadAsStringAsync(); + + CrossThread.RequestExecutionOnMainThread(() => + { + callback(responseBody, (int)response.StatusCode, response.Headers); + }); + } + catch (HttpRequestException e) + { + CrossThread.RequestExecutionOnMainThread(() => { callback(e.Message, e.StatusCode, null); }); + } + catch (Exception e) + { + CrossThread.RequestExecutionOnMainThread(() => { callback(e.Message, null, null); }); + } + } + + public void HttpPost(string url, LuaCsAction callback, string data, string contentType = "application/json", Dictionary headers = null, string savePath = null) + { + HttpRequest(url, callback, data, "POST", contentType, headers, savePath); + } + + + public void HttpGet(string url, LuaCsAction callback, Dictionary headers = null, string savePath = null) + { + HttpRequest(url, callback, null, "GET", null, headers, savePath); + } + + public void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData) + { + GameMain.NetworkMember.CreateEntityEvent(entity, extraData); + } + + public ushort LastClientListUpdateID + { + get { return GameMain.NetworkMember.LastClientListUpdateID; } + set { GameMain.NetworkMember.LastClientListUpdateID = value; } + } + +#if SERVER + public void ClientWriteLobby(Client client) => GameMain.Server.ClientWriteLobby(client); + + public void UpdateClientPermissions(Client client) + { + GameMain.Server.UpdateClientPermissions(client); + } + + public int FileSenderMaxPacketsPerUpdate + { + get { return FileSender.FileTransferOut.MaxPacketsPerUpdate; } + set { FileSender.FileTransferOut.MaxPacketsPerUpdate = value; } + } +#endif + + #endregion } From 09bc2d0891357eafdb22c7c18b7bf195b0488ceb Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sat, 28 Feb 2026 22:10:29 -0500 Subject: [PATCH 189/288] - Weird LuaCs Settings Menu bug present: package is loaded on startup but is then unloaded if the settingsmenu is opened and the package is not in the enabled list. --- .../LuaCs/Configuration/IDisplayableConfig.cs | 142 ------------------ .../LuaCs/Configuration/SettingControl.cs | 32 ++-- .../LuaCs/Services/ConfigService.cs | 19 +-- .../Services/ModsGameplaySettingsMenu.cs | 25 ++- .../Services/_Interfaces/IConfigService.cs | 5 +- .../LuaCs/Data/ISettingTypeDef.cs | 1 + .../SharedSource/LuaCs/Data/SettingBase.cs | 8 +- .../SharedSource/LuaCs/Data/SettingEntry.cs | 9 +- .../LuaCs/_Services/ConfigService.cs | 2 + 9 files changed, 52 insertions(+), 191 deletions(-) delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayableConfig.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayableConfig.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayableConfig.cs deleted file mode 100644 index 789c00ac0..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/IDisplayableConfig.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.Collections.Generic; -using Barotrauma.LuaCs.Data; - -namespace Barotrauma.LuaCs.Configuration; - - -/// -/// Base type of all menu displayable types. -/// -public interface IDisplayableConfigBase : IDataInfo, IConfigDisplayInfo -{ - /// - /// Whether the current config is editable. - /// - bool IsEditable { get; } - /// - /// Used to indicate the implemented interface and targeted display logic. - /// - static virtual DisplayType DisplayOption => DisplayType.Undefined; -} - -public interface IDisplayableConfigBase : IDisplayableConfigBase -{ - void SetValue(TValue value); - TDisplay GetDisplayValue(); -} - -public interface IDisplayableConfigBool : IDisplayableConfigBase -{ - static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.Boolean; -} - -public interface IDisplayableConfigText : IDisplayableConfigBase -{ - static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.Text; -} - -public interface IDisplayableConfigInt : IDisplayableConfigBase -{ - static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.Integer; -} - -public interface IDisplayableConfigFloat : IDisplayableConfigBase -{ - static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.Float; -} - -public interface IDisplayableConfigSliderInt : IDisplayableConfigBase<(int Min, int Max, int Value, int Steps), int> -{ - static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.SliderInt; -} - -public interface IDisplayableConfigSliderFloat : IDisplayableConfigBase<(float Min, float Max, float Value, int Steps), float> -{ - static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.SliderFloat; -} - -public interface IDisplayableConfigDropdown : IDisplayableConfigBase, string> -{ - static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.Dropdown; -} - -/// -/// Allows completely custom-designed UI for this configuration component. -/// -public interface IDisplayableConfigCustom : IDisplayableConfigBase -{ - static DisplayType IDisplayableConfigBase.DisplayOption => DisplayType.Custom; - /// - /// Draw your menu settings option. - /// - /// Parent layout component. - void DrawComponent(GUILayoutGroup layoutGroup); - /// - /// Called when the config element is set to be disposed to allow for cleanup. - /// - void DisposeGUI(); - /// - /// Called when the UI indicates to save the current value as permanent. - /// - void OnValueSaved(); - /// - /// Called when the UI indicates to discard the currently displayed value and revert to the last saved value. - /// - void OnValueDiscarded(); -} - - - -/// -/// Indicates the intended display and feedback logic to be used by the . -///
[Important] -///
The type must implement the indicated interface for the selected option, or it will not be displayed. -///
-public enum DisplayType -{ - /// - /// Will not be displayed in menus. - /// - Undefined, - /// - /// Will be shown as a checkbox. - ///
[Requires()] - ///
- Boolean, - /// - /// Shown as an editable text input. - ///
[Requires()] - ///
- Text, - /// - /// Shown as number input (no decimal input). - ///
[Requires()] - ///
- Integer, - /// - /// Shown as a number input. - ///
[Requires()] - ///
- Float, - /// - /// Shown as a slider, values parsed as integers. - ///
[Requires()] - ///
- SliderInt, - /// - /// Shown as a slider, values parsed as single-precision decimal numbers. - ///
[Requires()] - ///
- SliderFloat, - /// - /// Shown as a menu, values parsed as strings. - ///
[Requires()] - ///
- Dropdown, - /// - /// UI Display is implemented by inheritor and actioned by a call to . - ///
[Requires()] - ///
- Custom -} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/SettingControl.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/SettingControl.cs index b0812f425..bf18aa122 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/SettingControl.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/SettingControl.cs @@ -8,31 +8,23 @@ using OneOf; namespace Barotrauma.LuaCs.Configuration; -public class SettingControl : ISettingControl +public class SettingControl : SettingBase, ISettingControl { - public string InternalName { get; } - public ContentPackage OwnerPackage { get; } - public bool Equals(ISettingBase other) + public SettingControl(IConfigInfo configInfo) : base(configInfo) { - throw new NotImplementedException(); } - public void Dispose() + protected override void OnDispose() { - throw new NotImplementedException(); + OnValueChanged = null; } - public IConfigDisplayInfo GetDisplayInfo() - { - throw new NotImplementedException(); - } + public override Type GetValueType() => typeof(KeyOrMouse); + public override string GetStringValue() => Value.ToString(); - public Type GetValueType() => typeof(KeyOrMouse); - public string GetStringValue() => Value.ToString(); + public override string GetDefaultStringValue() => new KeyOrMouse(Keys.NumLock).ToString(); - public string GetDefaultStringValue() => new KeyOrMouse(Keys.NumLock).ToString(); - - public bool TrySetValue(OneOf value) + public override bool TrySetValue(OneOf value) { var newVal = value.Match( (string v) => GetKeyOrMouse(v), @@ -77,12 +69,8 @@ public class SettingControl : ISettingControl } - public event Action OnValueChanged; - public OneOf GetSerializableValue() - { - return Value.ToString(); - } - + public override event Action OnValueChanged; + public override OneOf GetSerializableValue() => Value.ToString(); public KeyOrMouse Value { get; private set; } = new KeyOrMouse(Keys.NumLock); public bool TrySetValue(KeyOrMouse value) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs index f2294c49c..556b46757 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Immutable; +using System.Linq; using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; using Barotrauma.Networking; @@ -9,18 +10,14 @@ namespace Barotrauma.LuaCs; public sealed partial class ConfigService { - public ImmutableArray GetDisplayableConfigs() + public ImmutableArray GetDisplayableConfigs() { - throw new NotImplementedException(); - } + using var _ = _operationLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); - public ImmutableArray GetDisplayableConfigsForPackage(ContentPackage package) - { - throw new NotImplementedException(); - } - - public Result AddConfigControl(IConfigInfo configInfo) - { - throw new NotImplementedException(); + return _settingsInstances.Values + .Where(s => !s.IsDisposed) + .Where(s => s.GetDisplayInfo().ShowInMenus) + .ToImmutableArray(); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs index 97a156c25..7db7ae74f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs @@ -1,15 +1,25 @@ -using Microsoft.Xna.Framework; +using System.Collections.Immutable; +using Microsoft.Xna.Framework; using System.Linq; +using Barotrauma.LuaCs.Data; namespace Barotrauma.LuaCs; internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenu { + private readonly ImmutableArray _settingsInstancesGameplay; + public ModsGameplaySettingsMenu(GUIFrame contentFrame, IPackageManagementService packageManagementService, IConfigService configService, SettingsMenu settingsMenuInstance) : base(contentFrame, packageManagementService, configService, settingsMenuInstance) { + + _settingsInstancesGameplay = configService.GetDisplayableConfigs() + .Where(s => s is not ISettingControl) + .ToImmutableArray(); + + var mainLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), contentFrame.RectTransform, Anchor.Center), false, Anchor.TopLeft); // page title var menuTitleLayoutGroup = new GUILayoutGroup( @@ -30,7 +40,7 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenu { OnTextChangedDelegate = (btn, txt) => { - // TODO: Execute filter here + GenerateDisplayFromFilter(txt); return true; } }; @@ -45,21 +55,20 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenu var cpList = packageManagementService.GetAllLoadedPackages().OrderBy(cp => cp.Name == "Vanilla" ? 0 : 1).ThenBy(cp => cp.Name).ToList(); var modSelectDropDown = GUIUtil.Dropdown(modCategoryDisplayGroup, cp => cp.Name == "Vanilla" ? "All" : cp.Name, null, cpList, cpList[0], cp => { - // TODO: filter selections by adding it to the search bar + // TODO: apply filter text }, Vector2.One, 2); - - + void GenerateDisplayFromFilter(string text) { - + } - void GenerateCategoryListDisplay(GUILayoutGroup layoutGroup) + void GenerateCategoryListDisplay(GUILayoutGroup layoutGroup, ImmutableArray settings) { } - void GenerateSettingsListDisplay(GUILayoutGroup layoutGroup) + void GenerateSettingsListDisplay(GUILayoutGroup layoutGroup, ImmutableArray settings) { } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IConfigService.cs index 34b44c316..1a1ca2178 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IConfigService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IConfigService.cs @@ -10,8 +10,5 @@ namespace Barotrauma.LuaCs; public partial interface IConfigService { - ImmutableArray GetDisplayableConfigs(); - ImmutableArray GetDisplayableConfigsForPackage(ContentPackage package); - - FluentResults.Result AddConfigControl(IConfigInfo configInfo); + ImmutableArray GetDisplayableConfigs(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs index 766a4e02f..aca15df9d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs @@ -29,6 +29,7 @@ public interface ISettingBase : IDataInfo, IEquatable, IDisposable #if CLIENT IConfigDisplayInfo GetDisplayInfo(); #endif + bool IsDisposed { get; } Type GetValueType(); string GetStringValue(); string GetDefaultStringValue(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs index a79c453c5..7e9ee3697 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs @@ -30,12 +30,14 @@ public abstract class SettingBase : ISettingBase } private int _isDisposed = 0; - protected virtual bool IsDisposed + public virtual bool IsDisposed { get => ModUtils.Threading.GetBool(ref _isDisposed); private set => ModUtils.Threading.SetBool(ref _isDisposed, value); } + protected abstract void OnDispose(); + public virtual void Dispose() { if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) @@ -43,8 +45,8 @@ public abstract class SettingBase : ISettingBase return; } + OnDispose(); ConfigInfo = null; - OnValueChanged = null; GC.SuppressFinalize(this); } @@ -55,6 +57,6 @@ public abstract class SettingBase : ISettingBase public abstract string GetDefaultStringValue(); public abstract bool TrySetValue(OneOf value); - public event Action OnValueChanged; + public abstract event Action OnValueChanged; public abstract OneOf GetSerializableValue(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs index e495ec43e..82c748691 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs @@ -65,7 +65,7 @@ public class SettingEntry : SettingBase, ISettingBase, INetworkSyncVar whe { return false; } - + OnValueChanged?.Invoke(this); #if CLIENT if (GameMain.IsMultiplayer && SyncType is NetSync.ClientOneWay or NetSync.TwoWay) { @@ -96,6 +96,11 @@ public class SettingEntry : SettingBase, ISettingBase, INetworkSyncVar whe return true; } + protected override void OnDispose() + { + ValueChangePredicate = null; + } + public override Type GetValueType() => typeof(T); public override string GetStringValue() => Value.ToString(); @@ -135,6 +140,8 @@ public class SettingEntry : SettingBase, ISettingBase, INetworkSyncVar whe return !isFailed && TrySetValue(typeConvertedValue); } + public override event Action OnValueChanged; + public override OneOf GetSerializableValue() => Value.ToString(); // -- Networking diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs index d83690172..ec3c131c6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs @@ -339,6 +339,7 @@ public sealed partial class ConfigService : IConfigService var toProcessDocs = taskResults .Where(tr => !tr.IsDefaultOrEmpty) .SelectMany(tr => tr) + .Where(icf => icf is not null) .ToImmutableArray(); var instanceQueue = new Queue<(IConfigInfo configInfo, Func<(IConfigService ConfigService, IConfigInfo Info), ISettingBase> factory)>(); @@ -604,6 +605,7 @@ public sealed partial class ConfigService : IConfigService result.WithReasons(_eventService.PublishEvent(sub => sub.OnSettingInstanceDisposed(setting)).Reasons); try { + _settingsInstances.TryRemove((setting.OwnerPackage, setting.InternalName), out _); setting.Dispose(); } catch (Exception e) From e74f08330095285838a0549bcd1be8766f939066 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sat, 28 Feb 2026 22:32:01 -0500 Subject: [PATCH 190/288] LuaCs package bug fixed. --- .../BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 9a92f8e49..e10f9867b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -276,7 +276,7 @@ namespace Barotrauma SetRunState(RunState.LoadedNoExec); } - this.Logger.LogResults(PackageManagementService.SyncLoadedPackagesList(packages)); + this.Logger.LogResults(PackageManagementService.SyncLoadedPackagesList(GetLuaCsEnabledPackagesList(packages))); SetRunState(state); // restore } @@ -290,8 +290,11 @@ namespace Barotrauma } private ImmutableArray GetEnabledPackagesList() + => GetLuaCsEnabledPackagesList(ContentPackageManager.EnabledPackages.Regular + .ToImmutableArray()); + + private ImmutableArray GetLuaCsEnabledPackagesList(ImmutableArray enabledRegular) { - var enabledRegular = ContentPackageManager.EnabledPackages.Regular.ToImmutableArray(); if (!enabledRegular.Any( p => p.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase) || p.Name.Equals("Lua for Barotrauma", StringComparison.InvariantCultureIgnoreCase))) From 9b55bf4847cdb94cf2fafb8ea23c952b5f3d7e57 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 1 Mar 2026 06:03:31 -0500 Subject: [PATCH 191/288] [Save/Sync] Work on the settings menu. --- .../Services/ModsGameplaySettingsMenu.cs | 86 ++++++++++++++++--- .../LuaCsForBarotrauma/Texts/English.xml | 13 ++- 2 files changed, 82 insertions(+), 17 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs index 7db7ae74f..18c209c22 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs @@ -1,4 +1,5 @@ -using System.Collections.Immutable; +using System; +using System.Collections.Immutable; using Microsoft.Xna.Framework; using System.Linq; using Barotrauma.LuaCs.Data; @@ -8,6 +9,11 @@ namespace Barotrauma.LuaCs; internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenu { private readonly ImmutableArray _settingsInstancesGameplay; + // menu vars + private GUILayoutGroup _modCategoryDisplayGroup, _settingsDisplayGroup; + private string _selectedSearchQuery = string.Empty; + private ContentPackage _selectedContentPackage; + private string _selectedCategory = string.Empty; public ModsGameplaySettingsMenu(GUIFrame contentFrame, IPackageManagementService packageManagementService, @@ -47,30 +53,84 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenu // main display area var settingsContentAreaGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.90f), contentAreaLayoutGroup.RectTransform, Anchor.BottomCenter)); GUIUtil.Spacer(settingsContentAreaGroup, Vector2.One); - var (modCategoryDisplayGroup, settingsDisplayGroup) = GUIUtil.CreateSidebars(settingsContentAreaGroup, true); - modCategoryDisplayGroup.RectTransform.RelativeSize = new Vector2(0.3f, 1f); - settingsDisplayGroup.RectTransform.RelativeSize = new Vector2(0.7f, 1f); + (_modCategoryDisplayGroup, _settingsDisplayGroup) = GUIUtil.CreateSidebars(settingsContentAreaGroup, true); + _modCategoryDisplayGroup.RectTransform.RelativeSize = new Vector2(0.3f, 1f); + _settingsDisplayGroup.RectTransform.RelativeSize = new Vector2(0.7f, 1f); - // Mods & Category Selectors - var cpList = packageManagementService.GetAllLoadedPackages().OrderBy(cp => cp.Name == "Vanilla" ? 0 : 1).ThenBy(cp => cp.Name).ToList(); - var modSelectDropDown = GUIUtil.Dropdown(modCategoryDisplayGroup, cp => cp.Name == "Vanilla" ? "All" : cp.Name, null, cpList, cpList[0], cp => - { - // TODO: apply filter text - }, Vector2.One, 2); + GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetDisplayCategoriesList()); + GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList()); void GenerateDisplayFromFilter(string text) { - + _selectedSearchQuery = text; + GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetDisplayCategoriesList()); + GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList()); } - void GenerateCategoryListDisplay(GUILayoutGroup layoutGroup, ImmutableArray settings) + ImmutableArray GetDisplayCategoriesList() { + return _settingsInstancesGameplay + .Select(s => s.GetDisplayInfo().DisplayCategory) + .Distinct() + .ToImmutableArray(); + } + ImmutableArray GetDisplaySettingsList() + { + return _settingsInstancesGameplay + .Where(s => _selectedCategory.IsNullOrWhiteSpace() + || s.GetDisplayInfo().DisplayCategory == _selectedCategory) + .Where(s => _selectedContentPackage is null + || s.OwnerPackage == _selectedContentPackage) + .ToImmutableArray(); + } + + void GenerateCategoryListDisplay(GUILayoutGroup layoutGroup, ImmutableArray categoryIdents) + { + layoutGroup.ClearChildren(); + + var packages = _settingsInstancesGameplay.Select(s => s.OwnerPackage) + .Distinct() + .OrderBy(cp => cp.Name) + .ToImmutableArray(); + var packageSelectionList = GUIUtil.Dropdown(layoutGroup, cp => cp.Name, null, + packages, packages[0], cp => + { + _selectedContentPackage = cp; + _selectedCategory = string.Empty; + GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetDisplayCategoriesList()); + GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList()); + }, new Vector2(1f, 0.07f)); + var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.93f), layoutGroup.RectTransform)); + float size_y = MathF.Max(categoryIdents.Length * 0.122f, 1f); + var displayedCategoriesFrame = new GUIFrame(new RectTransform(new Vector2(1f, size_y), containerBox.Content.RectTransform), style: null, color: Color.Black) + { + CanBeFocused = false + }; + var displayCategoriesLayout = new GUILayoutGroup(new RectTransform(Vector2.One, displayedCategoriesFrame.RectTransform)); + + foreach (var category in categoryIdents) + { + DebugConsole.Log(category); + new GUIButton(new RectTransform(new Vector2(1f, 0.122f), displayCategoriesLayout.RectTransform), text: TextManager.Get(category)) + { + CanBeFocused = true, + CanBeSelected = true, + OnPressed = () => + { + _selectedCategory = category; + GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList()); + return true; + } + }; + } + + } void GenerateSettingsListDisplay(GUILayoutGroup layoutGroup, ImmutableArray settings) { - + } } diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml index 982c44b0b..1700c861f 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml @@ -2,8 +2,13 @@ Mod Controls Settings Mod Gameplay Settings - Suppress GUI Popup on Error - Are C# Mods Allowed - Hide Local OS Account Name In Logs - Where to Save Local Data + + Suppress GUI Popup on Error + General + Are C# Mods Allowed + General + Hide Local OS Account Name In Logs + General + Where to Save Local Data + General From 845fcefad7b899532cb33a8e4b8e75c49ee9ff12 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 1 Mar 2026 09:50:52 -0300 Subject: [PATCH 192/288] Fix Start(0 not returning an empty write only message --- .../LuaCs/Compatibility/ILuaCsNetworking.cs | 1 + .../LuaCs/_Services/NetworkingService.cs | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs index 0f2388df9..4aae5b61e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs @@ -15,6 +15,7 @@ internal interface ILuaCsNetworking : ILuaCsShim int FileSenderMaxPacketsPerUpdate { get; set; } void ClientWriteLobby(Client client); void UpdateClientPermissions(Client client); + IWriteMessage Start(); void Send(IWriteMessage mesage, NetworkConnection connection = null, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable); #elif CLIENT void Send(IWriteMessage mesage, DeliveryMethod deliveryMethod = DeliveryMethod.Reliable); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs index 622f7395f..8482bebc3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs @@ -127,8 +127,18 @@ internal partial class NetworkingService : INetworkingService public void Receive(string netIdString, NetMessageReceived callback) => Receive(new NetId(netIdString), callback); public void Receive(Guid netIdGuid, NetMessageReceived callback) => Receive(new NetId(netIdGuid.ToString()), callback); - public IWriteMessage Start(string netIdString) => Start(new NetId(netIdString)); + public IWriteMessage Start(string netIdString) + { + if (netIdString == null) + { + // idk why but Lua calls this method with null instead of the Start method with no arguments + return new WriteOnlyMessage(); + } + + return Start(new NetId(netIdString)); + } public IWriteMessage Start(Guid netIdGuid) => Start(new NetId(netIdGuid.ToString())); + public IWriteMessage Start() => new WriteOnlyMessage(); internal void Receive(NetId netId, NetMessageReceived callback) { From 22a74bf1fd4f3a1ec18dd5851f7afcf5b4653845 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 1 Mar 2026 12:43:59 -0300 Subject: [PATCH 193/288] Move compat hooks --- .../LuaCsForBarotrauma/Lua/CompatibilityLib.lua | 16 ---------------- .../_Services/LuaScriptManagementService.cs | 8 ++++++++ 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/CompatibilityLib.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/CompatibilityLib.lua index e266752b0..6ca382f63 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/CompatibilityLib.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/CompatibilityLib.lua @@ -78,20 +78,4 @@ end compatibilityLib["Player"] = luaPlayer -Hook.Add("character.created", "compatibility.character.created", function (character) - Hook.Call("characterCreated", character) -end) - -Hook.Add("character.death", "compatibility.character.death", function (character, causeOfDeathAffliction) - Hook.Call("characterDeath", character, causeOfDeathAffliction) -end) - -Hook.Add("client.connected", "compatibility.client.connected", function (client) - Hook.Call("clientConnected", client) -end) - -Hook.Add("client.disconnected", "compatibility.client.disconnected", function (client) - Hook.Call("clientDisconnected", client) -end) - return compatibilityLib \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index b2d3c5db2..5a8b76be9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -239,12 +239,20 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _eventService.RegisterLuaEventAlias("inventoryPutItem", nameof(IEventInventoryPutItem.OnInventoryPutItem)); _eventService.RegisterLuaEventAlias("inventoryItemSwap", nameof(IEventInventoryItemSwap.OnInventoryItemSwap)); + // Compatibility + _eventService.RegisterLuaEventAlias("characterCreated", nameof(IEventCharacterCreated.OnCharacterCreated)); + _eventService.RegisterLuaEventAlias("characterDeath", nameof(IEventCharacterDeath.OnCharacterDeath)); + #if SERVER _eventService.RegisterLuaEventAlias("client.connected", nameof(IEventClientConnected.OnClientConnected)); _eventService.RegisterLuaEventAlias("client.disconnected", nameof(IEventClientDisconnected.OnClientDisconnected)); _eventService.RegisterLuaEventAlias("jobsAssigned", nameof(IEventJobsAssigned.OnJobsAssigned)); _eventService.RegisterLuaEventAlias("netMessageReceived", nameof(IEventClientRawNetMessageReceived.OnReceivedClientNetMessage)); + + // Compatibility + _eventService.RegisterLuaEventAlias("clientConnected", nameof(IEventClientConnected.OnClientConnected)); + _eventService.RegisterLuaEventAlias("clientDisconnected", nameof(IEventClientDisconnected.OnClientDisconnected)); #elif CLIENT _eventService.RegisterLuaEventAlias("netMessageReceived", nameof(IEventServerRawNetMessageReceived.OnReceivedServerNetMessage)); #endif From 9ee4728e2a2dc89e4228bb748c0ddc107e59a505 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 1 Mar 2026 13:09:05 -0300 Subject: [PATCH 194/288] Better logging --- .../SharedSource/LuaCs/LuaCsSetup.cs | 16 +++++++++++++++- .../_Services/LuaScriptManagementService.cs | 4 ++-- .../LuaCs/_Services/PluginManagementService.cs | 7 ++++--- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index e10f9867b..042015714 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -325,6 +325,8 @@ namespace Barotrauma // ReSharper disable InconsistentNaming void RunStateUnloaded_OnEnter(State currentState) { + Logger.LogMessage("LuaCs unloaded state entered"); + if (PackageManagementService.IsAnyPackageRunning()) { Logger.LogResults(PackageManagementService.StopRunningPackages()); @@ -342,6 +344,8 @@ namespace Barotrauma PackageManagementService.Reset(); NetworkingService.Reset(); + Logger.LogMessage("Services have been reset"); + SubscribeToLuaCsEvents(); CurrentRunState = RunState.Unloaded; @@ -349,6 +353,8 @@ namespace Barotrauma void RunStateLoadedNoExec_OnEnter(State currentState) { + Logger.LogMessage("LuaCs no execution state entered"); + if (PackageManagementService.IsAnyPackageRunning()) { Logger.LogResults(PackageManagementService.StopRunningPackages()); @@ -371,6 +377,9 @@ namespace Barotrauma void RunStateRunning_OnEnter(State currentState) { + string csEnabled = IsCsEnabled ? "enabled" : "disabled"; + Logger.LogMessage($"LuaCs running state entered. Running under commit {AssemblyInfo.GitRevision}, CSharp is {csEnabled}"); + if (!PackageManagementService.IsAnyPackageLoaded()) { foreach (var registrationProvider in _servicesProvider.GetAllServices()) @@ -396,10 +405,15 @@ namespace Barotrauma #endif CurrentRunState = RunState.Running; } - + + void RunStateRunning_OnExit(State currentState) { + EventService.Call("stop"); + Logger.LogResults(PackageManagementService.StopRunningPackages()); + + Logger.LogMessage("LuaCs running state exited"); } // ReSharper restore InconsistentNaming } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 5a8b76be9..4ae22dad6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -348,7 +348,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService return FluentResults.Result.Fail("Tried to execute Lua scripts without unloading first."); } - _loggerService.LogMessage("Executing Lua scripts"); + _loggerService.LogMessage("[Lua] Executing scripts"); SetupEnvironment(enableSandbox); @@ -373,7 +373,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService { try { - _loggerService.LogMessage($"Run {filePath.Value}"); + _loggerService.LogMessage($"[Lua] - Run {filePath.Value}"); _script.Call(_script.LoadFile(filePath.FullPath), resource.OwnerPackage.Dir); } catch(Exception e) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index 3af050db4..6eb0be740 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -356,6 +356,8 @@ public class PluginManagementService : IAssemblyManagementService { return FluentResults.Result.Ok(); } + + _logger.LogMessage($"Activating {nameof(IAssemblyPlugin)} instances"); var loadedPackagePlugins = ImmutableArray.CreateBuilder<(ContentPackage Package, ImmutableArray Plugins)>(); @@ -368,6 +370,7 @@ public class PluginManagementService : IAssemblyManagementService { try { + _logger.LogMessage($"- Instantiating {pluginType.Name}"); var plugin = (IAssemblyPlugin)Activator.CreateInstance(pluginType); _pluginInjectorContainer.InjectProperties(plugin); _pluginInjectorContainer.Register(pluginType, fac => plugin); @@ -579,9 +582,7 @@ public class PluginManagementService : IAssemblyManagementService continue; } -#if DEBUG - _logger.Log($"[DEBUG] Compiling assembly for {scripts.Key}, in ContentPackage {contentPackRes.Key.Name}"); -#endif + _logger.LogMessage($"Compiling assembly for {scripts.Key}, in ContentPackage {contentPackRes.Key.Name}"); result.WithReasons(assemblyLoader.CompileScriptAssembly( assemblyName: scripts.Key, From f8ff97d2b73ae9509cf8b6732e058202075eccca Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 1 Mar 2026 13:26:36 -0300 Subject: [PATCH 195/288] Fix debug console commands --- .../ClientSource/DebugConsole.cs | 24 --------- .../ServerSource/DebugConsole.cs | 31 ----------- .../_Services/LuaScriptManagementService.cs | 53 +++++++++++++++++++ 3 files changed, 53 insertions(+), 55 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index ecf7d8dd5..e27829e50 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -660,12 +660,6 @@ namespace Barotrauma return; } - bool luaCsEnabled = true; - if (args.Length > 3) - { - bool.TryParse(args[3], out luaCsEnabled); - } - GameMain.MainMenuScreen.QuickStart(fixedSeed: false, subName, difficulty, levelGenerationParams); }, getValidArgs: () => new[] { SubmarineInfo.SavedSubmarines.Select(s => s.Name).Distinct().OrderBy(s => s).ToArray() })); @@ -4222,24 +4216,6 @@ namespace Barotrauma NewMessage("Minimum main path width: " + (Level.Loaded.LevelData?.MinMainPathWidth?.ToString() ?? "unknown")); } }); - - commands.Add(new Command("cl_lua", $"cl_lua: Runs a string on the client.", (string[] args) => - { - if (GameMain.Client != null && !GameMain.Client.HasPermission(ClientPermissions.ConsoleCommands)) - { - ThrowError("Command not permitted."); - return; - } - - if (LuaCsSetup.Instance.CurrentRunState != RunState.Running) - { - ThrowError("LuaCs not initialized, use the console command cl_reloadluacs to force initialization."); - return; - } - - var result = LuaCsSetup.Instance.LuaScriptManagementService.DoString(string.Join(" ", args)); - LuaCsSetup.Instance.Logger.LogResults(result.ToResult()); - })); } private static void ReloadWearables(Character character, int variant = 0) diff --git a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs index 6a04212ff..320e98b95 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs @@ -1288,37 +1288,6 @@ namespace Barotrauma GameMain.NetLobbyScreen.LevelSeed = string.Join(" ", args); })); - - commands.Add(new Command("lua", "lua: Runs a string.", (string[] args) => - { - var result = LuaCsSetup.Instance.LuaScriptManagementService.DoString(string.Join(" ", args)); - LuaCsSetup.Instance.Logger.LogResults(result.ToResult()); - })); - - commands.Add(new Command("reloadlua|reloadcs|reloadluacs", "Re-initializes the LuaCs environment.", (string[] args) => - { - //GameMain.LuaCs.Initialize(); - LuaCsSetup.Instance.EventService.PublishEvent(sub => sub.OnReloadAllPackages()); - })); - - commands.Add(new Command("toggleluadebug", "Toggles the MoonSharp Debug Server.", (string[] args) => - { - int port = 41912; - - if (args.Length > 0) - { - int.TryParse(args[0], out port); - } - - throw new NotImplementedException(); - //GameMain.LuaCs.ToggleDebugger(port); - })); - - commands.Add(new Command("install_cl_lua|install_cl|install_cl_cs|install_cl_luacs", "Installs Client-Side LuaCs into your client.", (string[] args) => - { - LuaCsInstaller.Install(); - })); - commands.Add(new Command("randomizeseed", "randomizeseed: Toggles level seed randomization on/off.", (string[] args) => { GameMain.Server.ServerSettings.RandomizeSeed = !GameMain.Server.ServerSettings.RandomizeSeed; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 4ae22dad6..b3770a90a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -84,15 +84,35 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _luaCsTimer = luaCsTimer; RegisterLuaEvents(); + RegisterConsoleCommands(_commandsService); } private void RegisterConsoleCommands(IConsoleCommandsService commands) { +#if CLIENT commands.RegisterCommand("cl_reloadlua|cl_reloadcs|cl_reloadluacs", "Re-initializes the LuaCs environment.", (string[] args) => { LuaCsSetup.Instance.EventService.PublishEvent(sub => sub.OnReloadAllPackages()); }); + commands.RegisterCommand("cl_lua", $"cl_lua: Runs a string on the client.", (string[] args) => + { + if (GameMain.Client != null && !GameMain.Client.HasPermission(ClientPermissions.ConsoleCommands)) + { + DebugConsole.ThrowError("Command not permitted."); + return; + } + + if (LuaCsSetup.Instance.CurrentRunState != RunState.Running) + { + DebugConsole.ThrowError("LuaCs not initialized, use the console command cl_reloadluacs to force initialization."); + return; + } + + var result = LuaCsSetup.Instance.LuaScriptManagementService.DoString(string.Join(" ", args)); + LuaCsSetup.Instance.Logger.LogResults(result.ToResult()); + }); + commands.RegisterCommand("cl_toggleluadebug", "Toggles the MoonSharp Debug Server.", (string[] args) => { int port = 41912; @@ -105,6 +125,39 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService throw new NotImplementedException(); //GameMain.LuaCs.ToggleDebugger(port); }); + +#elif SERVER + commands.RegisterCommand("lua", "lua: Runs a string.", (string[] args) => + { + var result = LuaCsSetup.Instance.LuaScriptManagementService.DoString(string.Join(" ", args)); + LuaCsSetup.Instance.Logger.LogResults(result.ToResult()); + }); + + commands.RegisterCommand("reloadlua|reloadcs|reloadluacs", "Re-initializes the LuaCs environment.", (string[] args) => + { + LuaCsSetup.Instance.EventService.PublishEvent(sub => sub.OnReloadAllPackages()); + }); + + commands.RegisterCommand("toggleluadebug", "Toggles the MoonSharp Debug Server.", (string[] args) => + { + int port = 41912; + + if (args.Length > 0) + { + int.TryParse(args[0], out port); + } + + throw new NotImplementedException(); + //GameMain.LuaCs.ToggleDebugger(port); + }); +#endif + +#if SERVER + commands.RegisterCommand("install_cl_lua|install_cl|install_cl_cs|install_cl_luacs", "Installs Client-Side LuaCs into your client.", (string[] args) => + { + LuaCsInstaller.Install(); + }); +#endif } public bool IsDisposed { get; private set; } From 580f26b526f82be2da40c018a5870e605a50cf82 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 1 Mar 2026 13:40:08 -0300 Subject: [PATCH 196/288] New CI files --- .github/workflows/publish-release.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 5d63b200d..7f7afbf5d 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -64,7 +64,11 @@ env: Mono.Cecil.Mdb.dll Mono.Cecil.Pdb.dll Mono.Cecil.Rocks.dll - Microsoft.CodeAnalysis.CSharp.Scripting.dll + LightInject.dll + OneOf.dll + FluentResults.dll + Basic.Reference.Assemblies.Net80.dll + Microsoft.Toolkit.Diagnostics.dll Microsoft.CodeAnalysis.CSharp.dll Microsoft.CodeAnalysis.dll Microsoft.CodeAnalysis.Scripting.dll @@ -73,6 +77,7 @@ env: System.Runtime.CompilerServices.Unsafe.dll mscordaccore_amd64_amd64_* Lua + LocalMods/LuaCsForBarotrauma jobs: build: From 800dec3b840f6cf9d930dff128c071fb40650138 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 1 Mar 2026 14:06:45 -0300 Subject: [PATCH 197/288] I guess this doesnt exist anymore --- .github/workflows/publish-release.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 7f7afbf5d..f02d51502 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -71,7 +71,6 @@ env: Microsoft.Toolkit.Diagnostics.dll Microsoft.CodeAnalysis.CSharp.dll Microsoft.CodeAnalysis.dll - Microsoft.CodeAnalysis.Scripting.dll System.Collections.Immutable.dll System.Reflection.Metadata.dll System.Runtime.CompilerServices.Unsafe.dll From 0452c1fc2dd91656f8d4992c174f7e91bb0f5da2 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 1 Mar 2026 14:18:51 -0300 Subject: [PATCH 198/288] oops --- .github/workflows/publish-release.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index f02d51502..aa1a77d41 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -75,7 +75,6 @@ env: System.Reflection.Metadata.dll System.Runtime.CompilerServices.Unsafe.dll mscordaccore_amd64_amd64_* - Lua LocalMods/LuaCsForBarotrauma jobs: From 5cbb635e5441b1e12f74d5becc604ac2330ac9f7 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 1 Mar 2026 14:39:07 -0300 Subject: [PATCH 199/288] Ok this should be the last one --- .github/workflows/publish-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index aa1a77d41..db119d882 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -68,6 +68,7 @@ env: OneOf.dll FluentResults.dll Basic.Reference.Assemblies.Net80.dll + Microsoft.Extensions.Logging.Abstractions.dll Microsoft.Toolkit.Diagnostics.dll Microsoft.CodeAnalysis.CSharp.dll Microsoft.CodeAnalysis.dll From 168ce83820134c74a8146946c409d5409d0d7868 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Sun, 1 Mar 2026 17:13:15 -0500 Subject: [PATCH 200/288] Fixed potential NRE that shouldn't happen so long as the LuaCsForBarotrauma exists. --- .../ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs index 18c209c22..43c3fc6a7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs @@ -94,7 +94,7 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenu .OrderBy(cp => cp.Name) .ToImmutableArray(); var packageSelectionList = GUIUtil.Dropdown(layoutGroup, cp => cp.Name, null, - packages, packages[0], cp => + packages, packages.Length > 0 ? packages[0] : null, cp => { _selectedContentPackage = cp; _selectedCategory = string.Empty; From 4b04131fe3cd7f057573d2481ba17bc23f66610a Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Mon, 2 Mar 2026 02:45:20 -0500 Subject: [PATCH 201/288] Added post-publishing publicized assemblies copy task. --- Barotrauma/BarotraumaShared/LuatraumaBuild.props | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/LuatraumaBuild.props b/Barotrauma/BarotraumaShared/LuatraumaBuild.props index 79440aadb..e629fff09 100644 --- a/Barotrauma/BarotraumaShared/LuatraumaBuild.props +++ b/Barotrauma/BarotraumaShared/LuatraumaBuild.props @@ -1,5 +1,5 @@ - + @@ -8,4 +8,13 @@ DestinationFolder="$(TargetDir)\LocalMods\LuaCsForBarotrauma\Publicized\%(RecursiveDir)" /> + + + + + + From 26b3827210f737697201d8c02547687cfa38d8c5 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 2 Mar 2026 08:46:03 -0500 Subject: [PATCH 202/288] - Made publicizer only copy the required assemblies. --- .../BarotraumaShared/LuatraumaBuild.props | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/Barotrauma/BarotraumaShared/LuatraumaBuild.props b/Barotrauma/BarotraumaShared/LuatraumaBuild.props index e629fff09..54a01bdad 100644 --- a/Barotrauma/BarotraumaShared/LuatraumaBuild.props +++ b/Barotrauma/BarotraumaShared/LuatraumaBuild.props @@ -1,20 +1,30 @@ - - - - + + + - - - + + From 83c198bc26c630ca7e44fd74085e7589c8475da9 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 3 Mar 2026 20:45:51 -0500 Subject: [PATCH 203/288] [Save/Sync] Commit work before .NET 10 tests. --- .../LuaCs/Services/ConfigService.cs | 2 + .../ModsControlsSettingsMenu.cs | 2 +- .../ModsGameplaySettingsMenu.cs | 101 ++++++++++++++---- .../ModsSettingsMenuBase.cs} | 4 +- .../{ => _SettingsMenu}/SettingsMenuSystem.cs | 2 +- .../LuaCs/Data/ISettingTypeDef.cs | 1 + .../SharedSource/LuaCs/Data/SettingBase.cs | 1 + .../LuaCs/_Services/ConfigService.cs | 6 +- 8 files changed, 91 insertions(+), 28 deletions(-) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/{ => _SettingsMenu}/ModsControlsSettingsMenu.cs (98%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/{ => _SettingsMenu}/ModsGameplaySettingsMenu.cs (60%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/{ModsSettingsMenu.cs => _SettingsMenu/ModsSettingsMenuBase.cs} (89%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/{ => _SettingsMenu}/SettingsMenuSystem.cs (97%) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs index 556b46757..a8f340eb1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs @@ -18,6 +18,8 @@ public sealed partial class ConfigService return _settingsInstances.Values .Where(s => !s.IsDisposed) .Where(s => s.GetDisplayInfo().ShowInMenus) + .Where(s => !GameMain.IsMultiplayer || s.GetConfigInfo().NetSync != NetSync.ServerAuthority) + .Where(s => s.GetConfigInfo().EditableStates >= _infoProvider.CurrentRunState) .ToImmutableArray(); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsControlsSettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsControlsSettingsMenu.cs similarity index 98% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsControlsSettingsMenu.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsControlsSettingsMenu.cs index 8b8806b7e..a1243feca 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsControlsSettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsControlsSettingsMenu.cs @@ -1,6 +1,6 @@ namespace Barotrauma.LuaCs; -internal sealed class ModsControlsSettingsMenu : ModsSettingsMenu +internal sealed class ModsControlsSettingsMenu : ModsSettingsMenuBase { public ModsControlsSettingsMenu(GUIFrame contentFrame, IPackageManagementService packageManagementService, diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs similarity index 60% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs index 43c3fc6a7..d46394e82 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsGameplaySettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs @@ -3,10 +3,11 @@ using System.Collections.Immutable; using Microsoft.Xna.Framework; using System.Linq; using Barotrauma.LuaCs.Data; +// ReSharper disable ObjectCreationAsStatement namespace Barotrauma.LuaCs; -internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenu +internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase { private readonly ImmutableArray _settingsInstancesGameplay; // menu vars @@ -57,65 +58,121 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenu _modCategoryDisplayGroup.RectTransform.RelativeSize = new Vector2(0.3f, 1f); _settingsDisplayGroup.RectTransform.RelativeSize = new Vector2(0.7f, 1f); - GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetDisplayCategoriesList()); + GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetTargetPackagesList(), GetDisplayCategoriesList()); GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList()); void GenerateDisplayFromFilter(string text) { _selectedSearchQuery = text; - GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetDisplayCategoriesList()); + GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetTargetPackagesList(), GetDisplayCategoriesList()); GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList()); } + + string GetLocalizedString(string identifier) + { + var lstr = TextManager.Get(identifier); + return lstr.IsNullOrWhiteSpace() ? "General" : lstr.Value; + } + // Filters by selected package and query text ImmutableArray GetDisplayCategoriesList() { - return _settingsInstancesGameplay - .Select(s => s.GetDisplayInfo().DisplayCategory) + return GetFilteredSettingsList() + .Select(s => GetLocalizedString(s.GetDisplayInfo().DisplayCategory)) .Distinct() .ToImmutableArray(); } - ImmutableArray GetDisplaySettingsList() + // Filters by query text + ImmutableArray GetTargetPackagesList() { return _settingsInstancesGameplay + .Where(s => SettingMatchesQuery(s, _selectedSearchQuery)) + .Select(s => s.OwnerPackage) + .Distinct() + .ToImmutableArray(); + } + + // Filters by selected package, query text, and selected category. + ImmutableArray GetDisplaySettingsList() + { + return GetFilteredSettingsList() .Where(s => _selectedCategory.IsNullOrWhiteSpace() - || s.GetDisplayInfo().DisplayCategory == _selectedCategory) + || GetLocalizedString(s.GetDisplayInfo().DisplayCategory) == _selectedCategory) + .ToImmutableArray(); + } + + // Filters by selected package and by query text. + ImmutableArray GetFilteredSettingsList() + { + return _settingsInstancesGameplay + .Where(s => SettingMatchesQuery(s, _selectedSearchQuery)) .Where(s => _selectedContentPackage is null + || _selectedContentPackage == ContentPackageManager.VanillaCorePackage // vanilla is treated as all packages || s.OwnerPackage == _selectedContentPackage) .ToImmutableArray(); } + + + bool SettingMatchesQuery(ISettingBase setting, string queryText) + { + if (queryText.IsNullOrWhiteSpace()) + { + return true; + } + + queryText = queryText.ToLowerInvariant().Trim(); - void GenerateCategoryListDisplay(GUILayoutGroup layoutGroup, ImmutableArray categoryIdents) + if (setting.InternalName.ToLowerInvariant().Trim().Contains(queryText) || setting.OwnerPackage.Name.ToLowerInvariant().Trim().Contains(queryText)) + { + return true; + } + + var displayInfo = setting.GetDisplayInfo(); + return TextManager.Get(displayInfo.DisplayName).Value.ToLowerInvariant().Trim().Contains(queryText) + || TextManager.Get(displayInfo.DisplayCategory).Value.ToLowerInvariant().Trim().Contains(queryText) + || TextManager.Get(displayInfo.Description).Value.ToLowerInvariant().Trim().Contains(queryText) + || TextManager.Get(displayInfo.Tooltip).Value.ToLowerInvariant().Trim().Contains(queryText); + } + + string GetPackageName(ContentPackage package) + { + return package is null || package == ContentPackageManager.VanillaCorePackage ? "All" : package.Name; + } + + void GenerateCategoryListDisplay(GUILayoutGroup layoutGroup, ImmutableArray packagesList, + ImmutableArray categories) { layoutGroup.ClearChildren(); - - var packages = _settingsInstancesGameplay.Select(s => s.OwnerPackage) - .Distinct() - .OrderBy(cp => cp.Name) - .ToImmutableArray(); - var packageSelectionList = GUIUtil.Dropdown(layoutGroup, cp => cp.Name, null, - packages, packages.Length > 0 ? packages[0] : null, cp => + var packageSelectionList = GUIUtil.Dropdown(layoutGroup, cp => GetPackageName(cp), null, + packagesList, packagesList.Length > 0 ? packagesList[0] : null, cp => { - _selectedContentPackage = cp; - _selectedCategory = string.Empty; - GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetDisplayCategoriesList()); + _selectedContentPackage = cp == ContentPackageManager.VanillaCorePackage ? null : cp; + _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.93f), layoutGroup.RectTransform)); - float size_y = MathF.Max(categoryIdents.Length * 0.122f, 1f); + float size_y = MathF.Max(categories.Length * 0.122f, 1f); var displayedCategoriesFrame = new GUIFrame(new RectTransform(new Vector2(1f, size_y), containerBox.Content.RectTransform), style: null, color: Color.Black) { CanBeFocused = false }; var displayCategoriesLayout = new GUILayoutGroup(new RectTransform(Vector2.One, displayedCategoriesFrame.RectTransform)); - foreach (var category in categoryIdents) + foreach (var category in categories) { DebugConsole.Log(category); - new GUIButton(new RectTransform(new Vector2(1f, 0.122f), displayCategoriesLayout.RectTransform), text: TextManager.Get(category)) + var btn = new GUIButton(new RectTransform(new Vector2(1f, 0.122f), 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 = () => { _selectedCategory = category; @@ -124,8 +181,6 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenu } }; } - - } void GenerateSettingsListDisplay(GUILayoutGroup layoutGroup, ImmutableArray settings) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsSettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsSettingsMenuBase.cs similarity index 89% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsSettingsMenu.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsSettingsMenuBase.cs index b228d1da5..dc7cd8b6f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModsSettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsSettingsMenuBase.cs @@ -4,14 +4,14 @@ using Microsoft.Xna.Framework; namespace Barotrauma.LuaCs; -internal abstract class ModsSettingsMenu : IDisposable +internal abstract class ModsSettingsMenuBase : IDisposable { public GUIFrame ContentFrame { get; private set; } protected IPackageManagementService PackageManagementService { get; private set; } protected IConfigService ConfigService { get; private set; } protected SettingsMenu SettingsMenuInstance { get; private set; } - protected ModsSettingsMenu(GUIFrame contentFrame, + protected ModsSettingsMenuBase(GUIFrame contentFrame, IPackageManagementService packageManagementService, IConfigService configService, SettingsMenu settingsMenuInstance) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/SettingsMenuSystem.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/SettingsMenuSystem.cs similarity index 97% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/SettingsMenuSystem.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/SettingsMenuSystem.cs index df664031c..055b206b4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/SettingsMenuSystem.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/SettingsMenuSystem.cs @@ -42,7 +42,7 @@ public class SettingsMenuSystem : ISettingsMenuSystem var gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance, "SettingsMenuTab.Mods", "LuaCsForBarotrauma.SettingsMenu.ModGameplayButton"); var controlsContentFrame = CreateNewContentTab(tabControlsIndex, __instance, - "SettingsMenuTab.Mods", "LuaCsForBarotrauma.SettingsMenu.ModControlsButton"); + "SettingsMenuTab.Controls", "LuaCsForBarotrauma.SettingsMenu.ModControlsButton"); _gameplayMenuInstance = new ModsGameplaySettingsMenu(gameplayContentFrame, _packageManagementService, _configService, __instance); _controlsMenuInstance = new ModsControlsSettingsMenu(controlsContentFrame, _packageManagementService, _configService, __instance); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs index aca15df9d..3cb0e0364 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs @@ -26,6 +26,7 @@ public interface ISettingBase : IDataInfo, IEquatable, IDisposable T CreateInstance([NotNull]IConfigInfo configInfo, Func, bool> valueChangePredicate); } + IConfigInfo GetConfigInfo(); #if CLIENT IConfigDisplayInfo GetDisplayInfo(); #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs index 7e9ee3697..ccfbe40df 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs @@ -17,6 +17,7 @@ public abstract class SettingBase : ISettingBase public string InternalName => ConfigInfo.InternalName; public ContentPackage OwnerPackage => ConfigInfo.OwnerPackage; + public IConfigInfo GetConfigInfo() => ConfigInfo; #if CLIENT public IConfigDisplayInfo GetDisplayInfo() => ConfigInfo; #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs index ec3c131c6..903e3103d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs @@ -80,6 +80,7 @@ public sealed partial class ConfigService : IConfigService _configInfoParserService = null; _configProfileInfoParserService = null; _commandsService = null; + _infoProvider = null; } public FluentResults.Result Reset() @@ -140,6 +141,7 @@ public sealed partial class ConfigService : IConfigService private ILoggerService _logger; private IEventService _eventService; private IConsoleCommandsService _commandsService; + private ILuaCsInfoProvider _infoProvider; private IParserServiceOneToManyAsync _configInfoParserService; private IParserServiceOneToManyAsync _configProfileInfoParserService; @@ -148,7 +150,8 @@ public sealed partial class ConfigService : IConfigService IParserServiceOneToManyAsync configInfoParserService, IParserServiceOneToManyAsync configProfileInfoParserService, IEventService eventService, - IConsoleCommandsService commandsService) + IConsoleCommandsService commandsService, + ILuaCsInfoProvider infoProvider) { _logger = logger; _storageService = storageService; @@ -156,6 +159,7 @@ public sealed partial class ConfigService : IConfigService _configProfileInfoParserService = configProfileInfoParserService; _eventService = eventService; _commandsService = commandsService; + _infoProvider = infoProvider; _storageService.UseCaching = true; InjectCommands(commandsService); From a66b9041ecb0db2b4b81f27ed796b0739787372b Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 3 Mar 2026 22:44:56 -0500 Subject: [PATCH 204/288] Fixed reference name error. --- .../SharedSource/LuaCs/_Services/PluginManagementService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index 6eb0be740..b9cc6d8cb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -96,7 +96,7 @@ public class PluginManagementService : IAssemblyManagementService .Union(AssemblyLoadContext.Default.Assemblies .Where(ass => !ass.IsDynamic && - !ass.GetName().FullName.EndsWith("Barotrauma.Core") && + !ass.GetName().FullName.EndsWith("BarotraumaCore") && !ass.GetName().FullName.EndsWith("Barotrauma") && !ass.GetName().FullName.EndsWith("DedicatedServer")) .Select(MetadataReference (ass) => MetadataReference.CreateFromFile(ass.Location))) From d0969cc723134f06721293db8e52caa041ec913d Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 4 Mar 2026 14:38:00 -0500 Subject: [PATCH 205/288] - Fixed assembly references not resolving for minor dll signature differences. - Fixed deadlock during assembly resolution. --- .../LuaCs/_Services/PluginManagementService.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index b9cc6d8cb..dd348ff0c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -96,9 +96,9 @@ public class PluginManagementService : IAssemblyManagementService .Union(AssemblyLoadContext.Default.Assemblies .Where(ass => !ass.IsDynamic && - !ass.GetName().FullName.EndsWith("BarotraumaCore") && - !ass.GetName().FullName.EndsWith("Barotrauma") && - !ass.GetName().FullName.EndsWith("DedicatedServer")) + !ass.GetName().FullName.StartsWith("BarotraumaCore") && + !ass.GetName().FullName.StartsWith("Barotrauma") && + !ass.GetName().FullName.StartsWith("DedicatedServer")) .Select(MetadataReference (ass) => MetadataReference.CreateFromFile(ass.Location))) .Where(ar => ar is not null) .ToImmutableArray(); @@ -630,7 +630,8 @@ public class PluginManagementService : IAssemblyManagementService private Assembly OnAssemblyLoaderResolvingManaged(IAssemblyLoaderService requestingLoader, AssemblyName searchName) { - using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + // This method is used during assembly instantiation, we cannot put a lock here. + //using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); foreach (var loader in _assemblyLoaders.Where(kvp => kvp.Value != requestingLoader) @@ -643,7 +644,7 @@ public class PluginManagementService : IAssemblyManagementService foreach (var assembly in loader.Assemblies) { - if (assembly.GetName().Equals(searchName)) + if (assembly.GetName().FullName == searchName.FullName) { return assembly; } From ce8b984542e3b7914911176afdf010aa93c74a77 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 4 Mar 2026 14:51:31 -0500 Subject: [PATCH 206/288] Fixed Csharp/Shared path resolution in ModConfigService. --- .../LuaCs/_Services/ModConfigService.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs index 6150ec52b..f9abe7371 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs @@ -266,7 +266,7 @@ public sealed class ModConfigService : IModConfigService LuaScripts = GetLuaScriptsLegacy(src) }; - ImmutableArray GetAssembliesLegacy(ContentPackage src) + ImmutableArray GetAssembliesLegacy(ContentPackage srcPackage) { var binSearchInd = new (string SubFolder, Target Targets, Platform Platforms)[] { @@ -282,19 +282,19 @@ public sealed class ModConfigService : IModConfigService foreach (var searchPathways in binSearchInd) { - if (_storageService.FindFilesInPackage(src, searchPathways.SubFolder, "*.dll", + if (_storageService.FindFilesInPackage(srcPackage, searchPathways.SubFolder, "*.dll", true) is { IsSuccess: true, Value.IsDefaultOrEmpty: false } result) { builder.Add(new AssemblyResourceInfo() { - OwnerPackage = src, + OwnerPackage = srcPackage, InternalName = searchPathways.SubFolder, SupportedPlatforms = searchPathways.Platforms, SupportedTargets = searchPathways.Targets, LoadPriority = 0, - FilePaths = result.Value.Select(fp => ContentPath.FromRaw(src, $"%ModDir%/{Path.GetRelativePath(src.Dir, fp)}".CleanUpPathCrossPlatform())) + FilePaths = result.Value.Select(fp => ContentPath.FromRaw(srcPackage, $"%ModDir%/{Path.GetRelativePath(srcPackage.Dir, fp)}".CleanUpPathCrossPlatform())) .ToImmutableArray(), - FriendlyName = $"{src.Name}.{searchPathways.SubFolder.Replace('/','.')}", + FriendlyName = $"{srcPackage.Name}.{searchPathways.SubFolder.Replace('/','.')}", IncompatiblePackages = ImmutableArray.Empty, RequiredPackages = ImmutableArray.Empty, IsScript = false, @@ -303,12 +303,12 @@ public sealed class ModConfigService : IModConfigService } } - var sharedResult = _storageService.FindFilesInPackage(src, - Path.Combine(src.Dir, "CSharp/Shared"), + var sharedResult = _storageService.FindFilesInPackage(srcPackage, + Path.Combine("CSharp/Shared"), "*.cs", true); var sharedFiles = sharedResult.IsSuccess && !sharedResult.Value.IsDefaultOrEmpty ? sharedResult.Value.Select(fp => - ContentPath.FromRaw(src, $"%ModDir%/{Path.GetRelativePath(src.Dir, fp)}".CleanUpPathCrossPlatform())) + ContentPath.FromRaw(srcPackage, $"%ModDir%/{Path.GetRelativePath(srcPackage.Dir, fp)}".CleanUpPathCrossPlatform())) .ToImmutableArray() : ImmutableArray.Empty; @@ -320,19 +320,19 @@ public sealed class ModConfigService : IModConfigService foreach (var searchPathways in srcSearchInd) { - if (_storageService.FindFilesInPackage(src, searchPathways.SubFolder, "*.cs", + if (_storageService.FindFilesInPackage(srcPackage, searchPathways.SubFolder, "*.cs", true) is { IsSuccess: true, Value.IsDefaultOrEmpty: false } result) { builder.Add(new AssemblyResourceInfo() { - OwnerPackage = src, + OwnerPackage = srcPackage, InternalName = searchPathways.SubFolder, SupportedPlatforms = searchPathways.Platforms, SupportedTargets = searchPathways.Targets, LoadPriority = 0, FilePaths = result.Value - .Select(fp => ContentPath.FromRaw(src, - $"%ModDir%/{Path.GetRelativePath(src.Dir, fp)}".CleanUpPathCrossPlatform())) + .Select(fp => ContentPath.FromRaw(srcPackage, + $"%ModDir%/{Path.GetRelativePath(srcPackage.Dir, fp)}".CleanUpPathCrossPlatform())) .Concat(sharedFiles).ToImmutableArray(), FriendlyName = IAssemblyLoaderService.InternalsAwareAssemblyName, IncompatiblePackages = ImmutableArray.Empty, From f38a7bd574c16fa5a4495c652d3f01e9d7bb4377 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 4 Mar 2026 20:39:13 -0500 Subject: [PATCH 207/288] The gameplay settings menu kinda works (only for luacsforbarotrauma). --- .../ClientSource/LuaCs/Data/IDisplayable.cs | 9 +++ .../ClientSource/LuaCs/Data/ISettingBase.cs | 6 ++ .../ISettingControl.cs | 0 .../{Configuration => Data}/SettingControl.cs | 2 +- .../LuaCs/Services/ConfigService.cs | 3 - .../Services/_Interfaces/IConfigService.cs | 1 - .../_SettingsMenu/ModsControlsSettingsMenu.cs | 5 ++ .../_SettingsMenu/ModsGameplaySettingsMenu.cs | 81 +++++++++++++++++-- .../_SettingsMenu/ModsSettingsMenuBase.cs | 5 ++ .../_SettingsMenu/SettingsMenuSystem.cs | 8 ++ .../Config/SettingsShared.xml | 2 +- .../LuaCsForBarotrauma/Texts/English.xml | 2 + .../LuaCs/Data/ISettingTypeDef.cs | 2 +- .../SharedSource/LuaCs/Data/SettingBase.cs | 14 ++++ .../SharedSource/LuaCs/Data/SettingEntry.cs | 56 ++++++++++++- .../_Services/SettingsFileParserService.cs | 2 +- 16 files changed, 182 insertions(+), 16 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IDisplayable.cs create mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/ISettingBase.cs rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/{Configuration => Data}/ISettingControl.cs (100%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/{Configuration => Data}/SettingControl.cs (99%) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IDisplayable.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IDisplayable.cs new file mode 100644 index 000000000..6d690ec50 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/IDisplayable.cs @@ -0,0 +1,9 @@ +using System; +using Microsoft.Xna.Framework; + +namespace Barotrauma.LuaCs.Data; + +public interface IDisplayable +{ + public void AddDisplayComponent(GUILayoutGroup layoutGroup, Vector2 relativeSize, Action onSerializedValue); +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/ISettingBase.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/ISettingBase.cs new file mode 100644 index 000000000..e61ad2e1d --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/ISettingBase.cs @@ -0,0 +1,6 @@ +namespace Barotrauma.LuaCs.Data; + +public partial interface ISettingBase : IDisplayable +{ + +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/ISettingControl.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/ISettingControl.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/ISettingControl.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/ISettingControl.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/SettingControl.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/SettingControl.cs similarity index 99% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/SettingControl.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/SettingControl.cs index bf18aa122..fb2dea794 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Configuration/SettingControl.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/SettingControl.cs @@ -6,7 +6,7 @@ using Barotrauma.LuaCs.Data; using Microsoft.Xna.Framework.Input; using OneOf; -namespace Barotrauma.LuaCs.Configuration; +namespace Barotrauma.LuaCs.Data; public class SettingControl : SettingBase, ISettingControl { diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs index a8f340eb1..16a67e301 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Immutable; using System.Linq; -using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; -using Barotrauma.Networking; -using FluentResults; namespace Barotrauma.LuaCs; diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IConfigService.cs index 1a1ca2178..98a23dd6d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IConfigService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IConfigService.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using Barotrauma.LuaCs.Configuration; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs; using Barotrauma.Networking; diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsControlsSettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsControlsSettingsMenu.cs index a1243feca..14b6f58d4 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsControlsSettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsControlsSettingsMenu.cs @@ -14,4 +14,9 @@ internal sealed class ModsControlsSettingsMenu : ModsSettingsMenuBase { // TODO: Finish this later. } + + public override void ApplyInstalledModChanges() + { + // TODO: Finish this later. + } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs index d46394e82..e9063408a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs @@ -1,8 +1,12 @@ using System; +using System.Collections.Concurrent; using System.Collections.Immutable; using Microsoft.Xna.Framework; using System.Linq; +using System.Numerics; using Barotrauma.LuaCs.Data; +using Vector2 = Microsoft.Xna.Framework.Vector2; + // ReSharper disable ObjectCreationAsStatement namespace Barotrauma.LuaCs; @@ -57,6 +61,9 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase (_modCategoryDisplayGroup, _settingsDisplayGroup) = GUIUtil.CreateSidebars(settingsContentAreaGroup, true); _modCategoryDisplayGroup.RectTransform.RelativeSize = new Vector2(0.3f, 1f); _settingsDisplayGroup.RectTransform.RelativeSize = new Vector2(0.7f, 1f); + + // default category + _selectedCategory = "All"; GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetTargetPackagesList(), GetDisplayCategoriesList()); GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList()); @@ -68,18 +75,20 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList()); } - string GetLocalizedString(string identifier) + string GetLocalizedString(string identifier, string defaultValue) { var lstr = TextManager.Get(identifier); - return lstr.IsNullOrWhiteSpace() ? "General" : lstr.Value; + return lstr.IsNullOrWhiteSpace() ? defaultValue : lstr.Value; } // Filters by selected package and query text ImmutableArray GetDisplayCategoriesList() { return GetFilteredSettingsList() - .Select(s => GetLocalizedString(s.GetDisplayInfo().DisplayCategory)) + .Select(s => GetLocalizedString(s.GetDisplayInfo().DisplayCategory, "General")) + .Concat(new []{ "All" }) .Distinct() + .OrderBy(s => s) .ToImmutableArray(); } @@ -89,7 +98,10 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase return _settingsInstancesGameplay .Where(s => SettingMatchesQuery(s, _selectedSearchQuery)) .Select(s => s.OwnerPackage) + .Concat(new[] { ContentPackageManager.VanillaCorePackage }) .Distinct() + .OrderBy(p => p == ContentPackageManager.VanillaCorePackage ? 1 : 0) + .ThenBy(p => p.Name) .ToImmutableArray(); } @@ -98,7 +110,8 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase { return GetFilteredSettingsList() .Where(s => _selectedCategory.IsNullOrWhiteSpace() - || GetLocalizedString(s.GetDisplayInfo().DisplayCategory) == _selectedCategory) + || _selectedCategory == "All" + || GetLocalizedString(s.GetDisplayInfo().DisplayCategory, "General") == _selectedCategory) .ToImmutableArray(); } @@ -147,12 +160,12 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase var packageSelectionList = GUIUtil.Dropdown(layoutGroup, cp => GetPackageName(cp), null, packagesList, packagesList.Length > 0 ? packagesList[0] : null, cp => { - _selectedContentPackage = cp == ContentPackageManager.VanillaCorePackage ? null : cp; + _selectedContentPackage = cp; _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.93f), layoutGroup.RectTransform)); + var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.945f), layoutGroup.RectTransform)); float size_y = MathF.Max(categories.Length * 0.122f, 1f); var displayedCategoriesFrame = new GUIFrame(new RectTransform(new Vector2(1f, size_y), containerBox.Content.RectTransform), style: null, color: Color.Black) { @@ -185,13 +198,67 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase void GenerateSettingsListDisplay(GUILayoutGroup layoutGroup, ImmutableArray settings) { + layoutGroup.ClearChildren(); + float settingHeight = 0.06f; + var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f), 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)); + + + } + } + + (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)); + GUILayoutGroup entryLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, entryFrame.RectTransform), isHorizontal: true); + + new GUITextBlock(new RectTransform(labelSize, entryLayoutGroup.RectTransform), + GetLocalizedString(setting.GetDisplayInfo().DisplayName, setting.GetDisplayInfo().DisplayName), + textColor: Color.PeachPuff, + font: GUIStyle.SmallFont, + textAlignment: Alignment.Left) + { + CanBeFocused = false + }; + + setting.AddDisplayComponent(entryLayoutGroup, controlSize, newValue => + { + NewValuesCache[setting] = newValue; + }); + return (entryFrame, entryLayoutGroup); } } protected override void DisposeInternal() { - // TODO: Finish this later. + NewValuesCache.Clear(); + _modCategoryDisplayGroup?.Parent.RemoveChild(_modCategoryDisplayGroup); + _settingsDisplayGroup?.Parent.RemoveChild(_settingsDisplayGroup); + _modCategoryDisplayGroup = null; + _settingsDisplayGroup = null; + } + + public override void ApplyInstalledModChanges() + { + foreach (var kvp in NewValuesCache) + { + if (kvp.Key.IsDisposed) + { + continue; + } + + kvp.Key.TrySetValue(kvp.Value); + } + NewValuesCache.Clear(); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsSettingsMenuBase.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsSettingsMenuBase.cs index dc7cd8b6f..9cad5c561 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsSettingsMenuBase.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsSettingsMenuBase.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Concurrent; using Barotrauma.Extensions; +using Barotrauma.LuaCs.Data; using Microsoft.Xna.Framework; namespace Barotrauma.LuaCs; @@ -10,6 +12,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 ModsSettingsMenuBase(GUIFrame contentFrame, IPackageManagementService packageManagementService, @@ -22,6 +25,7 @@ internal abstract class ModsSettingsMenuBase : IDisposable } protected abstract void DisposeInternal(); + public abstract void ApplyInstalledModChanges(); public void Dispose() { @@ -31,5 +35,6 @@ internal abstract class ModsSettingsMenuBase : IDisposable ContentFrame = null; PackageManagementService = null; ConfigService = null; + NewValuesCache.Clear(); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/SettingsMenuSystem.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/SettingsMenuSystem.cs index 055b206b4..5969ec0b5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/SettingsMenuSystem.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/SettingsMenuSystem.cs @@ -75,6 +75,14 @@ public class SettingsMenuSystem : ISettingsMenuSystem return contentFr; } + + [HarmonyPatch(typeof(SettingsMenu), nameof(SettingsMenu.ApplyInstalledModChanges)), HarmonyPostfix] + private static void SettingsMenu_ApplyInstalledModChanges_Post() + { + SystemInstance._gameplayMenuInstance?.ApplyInstalledModChanges(); + SystemInstance._controlsMenuInstance?.ApplyInstalledModChanges(); + } + private void DisposeMenuFrames() { _controlsMenuInstance?.Dispose(); diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml index e2241fa2f..d6a5afe2a 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml @@ -4,7 +4,7 @@ - + diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml index 1700c861f..222a150af 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml @@ -11,4 +11,6 @@ General Where to Save Local Data General + Limit Network Message Size + Networking diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs index 3cb0e0364..49974fdc9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/ISettingTypeDef.cs @@ -8,7 +8,7 @@ using Barotrauma.Networking; namespace Barotrauma.LuaCs.Data; -public interface ISettingBase : IDataInfo, IEquatable, IDisposable +public partial interface ISettingBase : IDataInfo, IEquatable, IDisposable { /// /// Settings production factory. Should be implemented by all types and registered with the Dependency Injector. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs index ccfbe40df..af16faa4d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs @@ -1,6 +1,7 @@ using System; using System.Xml.Linq; using Barotrauma.LuaCs.Data; +using Microsoft.Xna.Framework; using OneOf; namespace Barotrauma.LuaCs.Data; @@ -60,4 +61,17 @@ public abstract class SettingBase : ISettingBase public abstract bool TrySetValue(OneOf value); public abstract event Action OnValueChanged; public abstract OneOf GetSerializableValue(); +#if CLIENT + public virtual void AddDisplayComponent(GUILayoutGroup layoutGroup, Vector2 relativeSize, Action onSerializedValue) + { + new GUITextBox(new RectTransform(relativeSize, layoutGroup.RectTransform), font: GUIStyle.SmallFont) + { + OnEnterPressed = (box, txt) => + { + onSerializedValue?.Invoke(txt); + return true; + } + }; + } +#endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs index 82c748691..16394b6cd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs @@ -5,11 +5,12 @@ using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs; using Barotrauma.Networking; using Microsoft.Toolkit.Diagnostics; +using Microsoft.Xna.Framework; using OneOf; namespace Barotrauma.LuaCs.Data; -public class SettingEntry : SettingBase, ISettingBase, INetworkSyncVar where T : IEquatable, IConvertible +public partial class SettingEntry : SettingBase, ISettingBase, INetworkSyncVar where T : IEquatable, IConvertible { public class Factory : ISettingBase.IFactory> { @@ -302,4 +303,57 @@ public class SettingEntry : SettingBase, ISettingBase, INetworkSyncVar whe #endif } } + +#if CLIENT + public override void AddDisplayComponent(GUILayoutGroup layoutGroup, Vector2 relativeSize, Action onSerializedValue) + { + switch (Type.GetTypeCode(typeof(T))) + { + case TypeCode.Boolean: + new GUITickBox(new RectTransform(relativeSize, layoutGroup.RectTransform), "") + { + Selected = (bool)Convert.ChangeType(this.Value, TypeCode.Boolean), + OnSelected = (box) => + { + onSerializedValue?.Invoke(box.Selected.ToString()); + return true; + } + }; + break; + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Char: + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + new GUINumberInput(new RectTransform(relativeSize, layoutGroup.RectTransform), NumberType.Int) + { + IntValue = (int)Convert.ChangeType(this.Value, TypeCode.Int32)!, + OnValueChanged = (num) => + { + onSerializedValue?.Invoke(num.IntValue.ToString()); + } + }; + break; + case TypeCode.Single: + case TypeCode.Double: + new GUINumberInput(new RectTransform(relativeSize, layoutGroup.RectTransform), NumberType.Float) + { + FloatValue = (float)Convert.ChangeType(this.Value, TypeCode.Single)!, + OnValueChanged = (num) => + { + onSerializedValue?.Invoke(num.FloatValue.ToString()); + } + }; + break; + case TypeCode.String: + default: + base.AddDisplayComponent(layoutGroup, relativeSize, onSerializedValue); + break; + } + } +#endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SettingsFileParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SettingsFileParserService.cs index 70699e6c4..4e0231e16 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SettingsFileParserService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SettingsFileParserService.cs @@ -75,7 +75,7 @@ public sealed class SettingsFileParserService : continue; } - var packageIdent = res.path.ContentPackage.ToIdentifier().ToString(); + var packageIdent = res.path.ContentPackage!.Name; foreach (var element in settingElements) { From 1fe68aa861376ee4a9ffc4c9d8005848faaed259 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 5 Mar 2026 15:45:00 -0500 Subject: [PATCH 208/288] - Added float, double settingentry types. Adjusted display formatting. - Added menu refresh on Apply button. - Fixed package name resolution for invalid chars. - Added helper console command to get xml-safe name for use in localization files. - Made error messages for bad settings xml more descriptive. - Modified GUISlider. - Converted HarmonyEventPatchesService to a System. - Fixed package name resolution for incompatible names in Xml and files, in many places. - Fixed base textbox implementation for settings. --- .../ClientSource/LuaCs/GUIUtil.cs | 2 +- .../_SettingsMenu/ModsGameplaySettingsMenu.cs | 48 +++++++++++++++++-- .../_SettingsMenu/SettingsMenuSystem.cs | 12 +++-- .../[DebugOnlyTest]TestLuaMod/ModConfig.xml | 3 +- .../[DebugOnlyTest]TestLuaMod/Settings.xml | 29 +++++++++++ .../Texts/English.xml | 13 +++++ .../[DebugOnlyTest]TestLuaMod/filelist.xml | 3 +- .../SharedSource/LuaCs/Data/SettingBase.cs | 3 +- .../LuaCs/Data/SettingRangeEntry.cs | 28 +++++++++++ .../SettingsFactoryRegistrationProvider.cs | 3 ++ .../SharedSource/LuaCs/LuaCsSetup.cs | 5 +- .../LuaCs/_Services/ConfigService.cs | 19 ++++++-- .../_Services/HarmonyEventPatchesService.cs | 25 ++++++++-- .../_Services/PackageManagementService.cs | 28 +++++++++++ .../_Services/SettingsFileParserService.cs | 6 +-- .../LuaCs/_Services/StorageService.cs | 3 +- 16 files changed, 203 insertions(+), 27 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Texts/English.xml diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/GUIUtil.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/GUIUtil.cs index d42d71d1c..f9f56ed67 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/GUIUtil.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/GUIUtil.cs @@ -98,7 +98,7 @@ public static class GUIUtil public static (GUIScrollBar, GUITextBlock) Slider(GUILayoutGroup parent, Vector2 range, int steps, Func labelFunc, float currentValue, Action setter, LocalizedString? tooltip, Vector2 adjustRatio) { - var layout = new GUILayoutGroup(NewItemRectT(parent, adjustRatio), isHorizontal: true); + var layout = new GUILayoutGroup(new RectTransform(adjustRatio, parent.RectTransform), isHorizontal: true); var slider = new GUIScrollBar(new RectTransform((0.72f, 1.0f), layout.RectTransform), style: "GUISlider") { Range = range, diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs index e9063408a..adca18b3b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs @@ -13,19 +13,20 @@ namespace Barotrauma.LuaCs; internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase { - private readonly ImmutableArray _settingsInstancesGameplay; + private ImmutableArray _settingsInstancesGameplay; // menu vars private GUILayoutGroup _modCategoryDisplayGroup, _settingsDisplayGroup; private string _selectedSearchQuery = string.Empty; private ContentPackage _selectedContentPackage; private string _selectedCategory = string.Empty; + + private event Action OnApplyInstalledModsChanges; public ModsGameplaySettingsMenu(GUIFrame contentFrame, IPackageManagementService packageManagementService, IConfigService configService, SettingsMenu settingsMenuInstance) : base(contentFrame, packageManagementService, configService, settingsMenuInstance) { - _settingsInstancesGameplay = configService.GetDisplayableConfigs() .Where(s => s is not ISettingControl) .ToImmutableArray(); @@ -65,6 +66,21 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase // default category _selectedCategory = "All"; + OnApplyInstalledModsChanges = () => + { + _settingsInstancesGameplay = configService.GetDisplayableConfigs() + .Where(s => s is not ISettingControl) + .ToImmutableArray(); + if (_selectedContentPackage is not null && !GetTargetPackagesList().Contains(_selectedContentPackage)) + { + _selectedContentPackage = null; + _selectedCategory = string.Empty; + } + + GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetTargetPackagesList(), GetDisplayCategoriesList()); + GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList()); + }; + GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetTargetPackagesList(), GetDisplayCategoriesList()); GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList()); @@ -100,7 +116,7 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase .Select(s => s.OwnerPackage) .Concat(new[] { ContentPackageManager.VanillaCorePackage }) .Distinct() - .OrderBy(p => p == ContentPackageManager.VanillaCorePackage ? 1 : 0) + .OrderByDescending(p => p == ContentPackageManager.VanillaCorePackage ? 0 : 1) .ThenBy(p => p.Name) .ToImmutableArray(); } @@ -153,12 +169,33 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase return package is null || package == ContentPackageManager.VanillaCorePackage ? "All" : package.Name; } + ContentPackage GetCurrentSelectedPackage(ImmutableArray packages) + { + if (_selectedContentPackage is null) + { + return ContentPackageManager.VanillaCorePackage; + } + + if (packages.Contains(_selectedContentPackage)) + { + return _selectedContentPackage; + } + + if (packages.Length > 0) + { + _selectedContentPackage = packages[0]; + return packages[0]; + } + + return null; + } + void GenerateCategoryListDisplay(GUILayoutGroup layoutGroup, ImmutableArray packagesList, ImmutableArray categories) { layoutGroup.ClearChildren(); var packageSelectionList = GUIUtil.Dropdown(layoutGroup, cp => GetPackageName(cp), null, - packagesList, packagesList.Length > 0 ? packagesList[0] : null, cp => + packagesList, GetCurrentSelectedPackage(packagesList), cp => { _selectedContentPackage = cp; _selectedCategory = string.Empty; @@ -246,6 +283,7 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase _settingsDisplayGroup?.Parent.RemoveChild(_settingsDisplayGroup); _modCategoryDisplayGroup = null; _settingsDisplayGroup = null; + } public override void ApplyInstalledModChanges() @@ -258,7 +296,9 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase } kvp.Key.TrySetValue(kvp.Value); + ConfigService.SaveConfigValue(kvp.Key); } NewValuesCache.Clear(); + OnApplyInstalledModsChanges?.Invoke(); } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/SettingsMenuSystem.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/SettingsMenuSystem.cs index 5969ec0b5..4f5575f36 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/SettingsMenuSystem.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/SettingsMenuSystem.cs @@ -11,6 +11,9 @@ public class SettingsMenuSystem : ISettingsMenuSystem private ModsControlsSettingsMenu _controlsMenuInstance; private ModsGameplaySettingsMenu _gameplayMenuInstance; + private GUIFrame _gameplayContentFrame; + private GUIFrame _controlsContentFrame; + private SettingsMenu _settingsMenuInstance; private readonly Harmony _harmony; private readonly IPackageManagementService _packageManagementService; @@ -28,6 +31,7 @@ public class SettingsMenuSystem : ISettingsMenuSystem [HarmonyPatch(typeof(SettingsMenu), "CreateModsTab"), HarmonyPostfix] private static void SettingsMenu_CreateModsTab_Post(SettingsMenu __instance) { + SystemInstance._settingsMenuInstance = __instance; SystemInstance.CreateSettingsMenu(__instance); } @@ -39,13 +43,13 @@ public class SettingsMenuSystem : ISettingsMenuSystem var tabGameplayIndex = (SettingsMenu.Tab)tabCount; var tabControlsIndex = (SettingsMenu.Tab)tabCount+1; - var gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance, + _gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance, "SettingsMenuTab.Mods", "LuaCsForBarotrauma.SettingsMenu.ModGameplayButton"); - var controlsContentFrame = CreateNewContentTab(tabControlsIndex, __instance, + _controlsContentFrame = CreateNewContentTab(tabControlsIndex, __instance, "SettingsMenuTab.Controls", "LuaCsForBarotrauma.SettingsMenu.ModControlsButton"); - _gameplayMenuInstance = new ModsGameplaySettingsMenu(gameplayContentFrame, _packageManagementService, _configService, __instance); - _controlsMenuInstance = new ModsControlsSettingsMenu(controlsContentFrame, _packageManagementService, _configService, __instance); + _gameplayMenuInstance = new ModsGameplaySettingsMenu(_gameplayContentFrame, _packageManagementService, _configService, __instance); + _controlsMenuInstance = new ModsControlsSettingsMenu(_controlsContentFrame, _packageManagementService, _configService, __instance); } private GUIFrame CreateNewContentTab(SettingsMenu.Tab tab, SettingsMenu settingsMenuInstance, string settingsMenuTabName, string settingMenuHoverTextIdent) diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml index 3db2aa78f..8889ae392 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml @@ -1,4 +1,5 @@ - \ No newline at end of file + + diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml new file mode 100644 index 000000000..a3651960b --- /dev/null +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Texts/English.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Texts/English.xml new file mode 100644 index 000000000..a6c96ff2f --- /dev/null +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Texts/English.xml @@ -0,0 +1,13 @@ + + + <_x005B_DebugOnlyTest_x005D_TestLuaMod.TestTickbox.DisplayName>Test TickBox + <_x005B_DebugOnlyTest_x005D_TestLuaMod.TestTickbox.DisplayCategory>Tests + <_x005B_DebugOnlyTest_x005D_TestLuaMod.TestFloat.DisplayName>Test Float + <_x005B_DebugOnlyTest_x005D_TestLuaMod.TestFloat.DisplayCategory>Tests + <_x005B_DebugOnlyTest_x005D_TestLuaMod.TestRangeFloat.DisplayName>Test Range Float + <_x005B_DebugOnlyTest_x005D_TestLuaMod.TestRangeFloat.DisplayCategory>Tests + <_x005B_DebugOnlyTest_x005D_TestLuaMod.TestRangeInt.DisplayName>Test Range Int + <_x005B_DebugOnlyTest_x005D_TestLuaMod.TestRangeInt.DisplayCategory>Tests + <_x005B_DebugOnlyTest_x005D_TestLuaMod.TestString.DisplayName>Test String + <_x005B_DebugOnlyTest_x005D_TestLuaMod.TestString.DisplayCategory>Tests + diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/filelist.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/filelist.xml index 0a9ea0fd3..dfb60968b 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/filelist.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/filelist.xml @@ -1,3 +1,4 @@  - \ No newline at end of file + + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs index af16faa4d..d103ad0fe 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingBase.cs @@ -66,7 +66,8 @@ public abstract class SettingBase : ISettingBase { new GUITextBox(new RectTransform(relativeSize, layoutGroup.RectTransform), font: GUIStyle.SmallFont) { - OnEnterPressed = (box, txt) => + Text = GetStringValue(), + OnTextChangedDelegate = (box, txt) => { onSerializedValue?.Invoke(txt); return true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingRangeEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingRangeEntry.cs index 1b094674f..f2c1909b9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingRangeEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingRangeEntry.cs @@ -1,7 +1,9 @@ using System; +using System.Globalization; using System.Xml.Linq; using Barotrauma.LuaCs.Data; using Microsoft.Toolkit.Diagnostics; +using Microsoft.Xna.Framework; using OneOf; namespace Barotrauma.LuaCs.Data; @@ -44,6 +46,19 @@ public class SettingRangeFloat : SettingRangeBase } return base.TrySetValue(value); } + +#if CLIENT + public override void AddDisplayComponent(GUILayoutGroup layoutGroup, Vector2 relativeSize, Action onSerializedValue) + { + GUIUtil.Slider(layoutGroup, new Vector2(MinValue, MaxValue), IncrementalSteps, labelFunc: val => + { + return val.ToString("G4", CultureInfo.InvariantCulture); + }, Value, setter: val => + { + onSerializedValue?.Invoke(val.ToString()); + }, TextManager.Get(this.GetDisplayInfo().Tooltip), relativeSize); + } +#endif } public class SettingRangeInt : SettingRangeBase @@ -73,4 +88,17 @@ public class SettingRangeInt : SettingRangeBase } return base.TrySetValue(value); } + +#if CLIENT + public override void AddDisplayComponent(GUILayoutGroup layoutGroup, Vector2 relativeSize, Action onSerializedValue) + { + GUIUtil.Slider(layoutGroup, new Vector2(MinValue, MaxValue), IncrementalSteps, labelFunc: val => + { + return ((int)val).ToString(); + }, Value, setter: val => + { + onSerializedValue?.Invoke(((int)val).ToString()); + }, TextManager.Get(this.GetDisplayInfo().Tooltip), relativeSize); + } +#endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs index 2c79a6b78..1cf612366 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs @@ -34,6 +34,9 @@ public class SettingsEntryRegistrar : ISettingsRegistrationProvider RegisterSettingEntry(configService, "long", valueChangePredicate); RegisterSettingEntry(configService, "ulong", valueChangePredicate); RegisterSettingEntry(configService, "string", valueChangePredicate); + RegisterSettingEntry(configService, "float", valueChangePredicate); + RegisterSettingEntry(configService, "single", valueChangePredicate); + RegisterSettingEntry(configService, "double", valueChangePredicate); // ISettingRangeBase configService.RegisterSettingTypeInitializer("rangeInt", cfgInfo => diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 042015714..e96f239f8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -9,6 +9,7 @@ using System.Collections.Immutable; using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Threading; using AssemblyLoader = Barotrauma.LuaCs.AssemblyLoader; [assembly: InternalsVisibleTo("ImpromptuInterfaceDynamicAssembly")] @@ -18,13 +19,14 @@ namespace Barotrauma internal delegate void LuaCsMessageLogger(string message); internal delegate void LuaCsErrorHandler(Exception ex, LuaCsMessageOrigin origin); internal delegate void LuaCsExceptionHandler(Exception ex, LuaCsMessageOrigin origin); + partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventEnabledPackageListChanged, IEventReloadAllPackages { private static LuaCsSetup _luaCsSetup; public static LuaCsSetup Instance => _luaCsSetup ??= new LuaCsSetup(); - + private LuaCsSetup() { if (_luaCsSetup != null) @@ -35,7 +37,6 @@ namespace Barotrauma // == startup _servicesProvider = SetupServicesProvider(); _runStateMachine = SetupStateMachine(); - _servicesProvider.GetService(); SubscribeToLuaCsEvents(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs index 903e3103d..d84e32473 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using System.Xml; using System.Xml.Linq; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Events; @@ -447,12 +448,20 @@ public sealed partial class ConfigService : IConfigService using var lck = _operationLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); - if (_storageService.LoadLocalXml(setting.OwnerPackage, SaveDataFileName) is not { } saveFileResult - || saveFileResult is { IsFailed: true }) + if (_storageService.LoadLocalXml(setting.OwnerPackage, SaveDataFileName) is not { } saveFileResult) { return FluentResults.Result.Fail( $"{nameof(LoadSavedValueForConfig)}: Could not open save file for setting [{setting.OwnerPackage.Name}.{setting.InternalName}]"); } + + if (saveFileResult is { IsFailed: true }) + { +#if DEBUG + _logger.LogResults(saveFileResult.ToResult()); +#endif + return FluentResults.Result.Fail( + $"{nameof(LoadSavedValueForConfig)}: Could not open save file for setting [{setting.OwnerPackage.Name}.{setting.InternalName}]"); + } if (saveFileResult.Value.Root is not {} rootElement || !string.Equals(rootElement.Name.LocalName, "Configuration", StringComparison.InvariantCultureIgnoreCase)) @@ -460,8 +469,8 @@ public sealed partial class ConfigService : IConfigService return FluentResults.Result.Fail($"{nameof(LoadSavedValueForConfig)}: Root invalid for setting [{setting.OwnerPackage.Name}.{setting.InternalName}]"); } - if (rootElement.GetChildElement(setting.OwnerPackage.Name, StringComparison.InvariantCulture) - ?.GetChildElement(setting.InternalName, StringComparison.InvariantCulture) is not {} cfgValueElement) + if (rootElement.GetChildElement(XmlConvert.EncodeLocalName(setting.OwnerPackage.Name.Trim()), StringComparison.InvariantCultureIgnoreCase) + ?.GetChildElement(setting.InternalName, StringComparison.InvariantCultureIgnoreCase) is not {} cfgValueElement) { return FluentResults.Result.Fail($"{nameof(LoadSavedValueForConfig)}: Could not find saved value for setting:[{setting.OwnerPackage.Name}.{setting.InternalName}]"); } @@ -547,7 +556,7 @@ public sealed partial class ConfigService : IConfigService return FluentResults.Result.Fail($"{nameof(SaveConfigValue)}: Bad save file format for setting: [{setting.OwnerPackage.Name}.{setting.InternalName}]"); } - XElement currentTarget = GetOrAddElement(cpCfgValues.Root, setting.OwnerPackage.Name, name => new XElement(name)); + XElement currentTarget = GetOrAddElement(cpCfgValues.Root, XmlConvert.EncodeLocalName(setting.OwnerPackage.Name.Trim()), name => new XElement(name)); currentTarget = GetOrAddElement(currentTarget, setting.InternalName, name => new XElement(name)); var ret = setting.GetSerializableValue().Match(str => diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs index 75b6a4e04..86425a828 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs @@ -13,9 +13,15 @@ using static Barotrauma.ContentPackageManager; namespace Barotrauma.LuaCs; [HarmonyPatch] -internal class HarmonyEventPatchesService : IService +internal class HarmonyEventPatchesService : ISystem { public bool IsDisposed { get; private set; } + public FluentResults.Result Reset() + { + Unpatch(); + Patch(); + return FluentResults.Result.Ok(); + } private static IEventService _eventService; private static ILoggerService _loggerService; @@ -26,12 +32,23 @@ internal class HarmonyEventPatchesService : IService _eventService = eventService; _loggerService = loggerService; Harmony = new Harmony("LuaCsForBarotrauma.Events"); - Harmony.PatchAll(typeof(HarmonyEventPatchesService)); + Patch(); + } + + private void Patch() + { + this.Harmony?.PatchAll(typeof(HarmonyEventPatchesService)); #if SERVER - Harmony.PatchAll(typeof(HarmonyEventPatchesService.Patch_StartGame_End)); + this.Harmony?.PatchAll(typeof(HarmonyEventPatchesService.Patch_StartGame_End)); #endif } + private void Unpatch() + { + this.Harmony?.UnpatchSelf(); + } + + [HarmonyPatch(typeof(CoroutineManager), nameof(CoroutineManager.Update)), HarmonyPostfix] public static void CoroutineManager_Update_Post() { @@ -308,7 +325,7 @@ internal class HarmonyEventPatchesService : IService public void Dispose() { IsDisposed = true; - Harmony.UnpatchSelf(); + this.Harmony?.UnpatchSelf(); } #if SERVER diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs index a5e8218be..596539c3c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs @@ -5,6 +5,7 @@ using System.Collections.Immutable; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; +using System.Xml; using Barotrauma.Extensions; using Barotrauma.LuaCs.Data; using FluentResults; @@ -20,6 +21,7 @@ public sealed class PackageManagementService : IPackageManagementService private IConfigService _configService; private ILuaScriptManagementService _luaScriptManagementService; private IPluginManagementService _pluginManagementService; + private IConsoleCommandsService _commandsService; #if CLIENT private IUIStylesService _uiStylesService; #endif @@ -44,6 +46,7 @@ public sealed class PackageManagementService : IPackageManagementService ILuaScriptManagementService luaScriptManagementService, IPluginManagementService pluginManagementService, IConfigService configService, + IConsoleCommandsService commandsService, #if CLIENT IUIStylesService uiStylesService, #endif @@ -58,6 +61,31 @@ public sealed class PackageManagementService : IPackageManagementService #if CLIENT _uiStylesService = uiStylesService; #endif + _commandsService = commandsService; + commandsService.RegisterCommand("pms_getxmlname", + "Gets the XML encoded name for the given package, as used in localization.", + onExecute: args => + { + if (args.Length < 1) + { + _logger.LogError("Please specify the name of the package."); + return; + } + + if (ContentPackageManager.AllPackages.FirstOrDefault(p => p.Name == args[0]) is { } pkg) + { + _logger.Log($"Package Xml Name: '{XmlConvert.EncodeLocalName(pkg.Name)}'"); + return; + } + _logger.Log($"Could not find package with the name '{args[0]}'"); + }, + getValidArgs: () => + { + return new[] + { + this._loadedPackages.Keys.Select(p => p.Name).ToArray() + }; + }); } public void Dispose() diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SettingsFileParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SettingsFileParserService.cs index 4e0231e16..8ef819f26 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SettingsFileParserService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/SettingsFileParserService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; +using System.Xml; using System.Xml.Linq; using Barotrauma.LuaCs.Data; using FluentResults; @@ -75,7 +76,7 @@ public sealed class SettingsFileParserService : continue; } - var packageIdent = res.path.ContentPackage!.Name; + var packageIdent = XmlConvert.EncodeLocalName(res.path.ContentPackage!.Name); foreach (var element in settingElements) { @@ -108,7 +109,7 @@ public sealed class SettingsFileParserService : }; if (!IsInfoValid(newSetting)) { - return ReturnFail($"A setting was invalid. ContentPackage: {res.path.ContentPackage}"); + return ReturnFail($"A setting was invalid. ContentPackage: {res.path.ContentPackage.Name}. Name: {newSetting?.InternalName}"); } parsedInfo.Add(newSetting); } @@ -128,7 +129,6 @@ public sealed class SettingsFileParserService : return info.OwnerPackage != null && !info.InternalName.IsNullOrWhiteSpace() && !info.DataType.IsNullOrWhiteSpace() - && !info.DataType.IsNullOrWhiteSpace() && info.Element != null #if CLIENT && !info.DisplayName.IsNullOrWhiteSpace() diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/StorageService.cs index 1d3e43ce5..a55fd8506 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/StorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/StorageService.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Xml; using System.Xml.Linq; using Barotrauma.LuaCs.Data; using Barotrauma.Networking; @@ -128,7 +129,7 @@ public class StorageService : IStorageService try { var path = System.IO.Path.GetFullPath(Path.Combine( - ConfigData.LocalPackageDataPath.Replace(ConfigData.LocalDataPathRegex, package.Name).CleanUpPathCrossPlatform(), + ConfigData.LocalPackageDataPath.Replace(ConfigData.LocalDataPathRegex, XmlConvert.EncodeLocalName(package.Name)).CleanUpPathCrossPlatform(), localFilePath.CleanUpPathCrossPlatform())); if (!path.StartsWith(Path.GetFullPath(ConfigData.LocalDataSavePath))) ThrowHelper.ThrowUnauthorizedAccessException($"{nameof(GetAbsolutePathForLocal)}: The local path of '{path}' is not a local path!"); From 368b18d44028080662714137d71b7230fc4ff0cf Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 5 Mar 2026 17:48:27 -0500 Subject: [PATCH 209/288] - Fixed shet. --- .../ClientSource/LuaCs/Data/SettingControl.cs | 23 ++++++++++++++++--- .../_SettingsMenu/ModsGameplaySettingsMenu.cs | 7 +++--- .../_SettingsMenu/SettingsMenuSystem.cs | 5 ++-- .../[DebugOnlyTest]TestLuaMod/Settings.xml | 1 + .../SettingsFactoryRegistrationProvider.cs | 10 ++++++-- 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/SettingControl.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/SettingControl.cs index fb2dea794..bceb15068 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/SettingControl.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/SettingControl.cs @@ -3,15 +3,27 @@ using System.Globalization; using System.Linq; using System.Xml.Linq; using Barotrauma.LuaCs.Data; +using Microsoft.Toolkit.Diagnostics; using Microsoft.Xna.Framework.Input; using OneOf; namespace Barotrauma.LuaCs.Data; -public class SettingControl : SettingBase, ISettingControl +public sealed class SettingControl : SettingBase, ISettingControl { - public SettingControl(IConfigInfo configInfo) : base(configInfo) + public class Factory : ISettingBase.IFactory { + public ISettingBase CreateInstance(IConfigInfo configInfo, Func, bool> valueChangePredicate) + { + Guard.IsNotNull(configInfo, nameof(configInfo)); + return new SettingControl(configInfo, valueChangePredicate); + } + } + + public SettingControl(IConfigInfo configInfo, Func, bool> valueChangePredicate) : base(configInfo) + { + _valueChangePredicate = valueChangePredicate; + TrySetValue(configInfo.Element); } protected override void OnDispose() @@ -19,9 +31,9 @@ public class SettingControl : SettingBase, ISettingControl OnValueChanged = null; } + private Func, bool> _valueChangePredicate; public override Type GetValueType() => typeof(KeyOrMouse); public override string GetStringValue() => Value.ToString(); - public override string GetDefaultStringValue() => new KeyOrMouse(Keys.NumLock).ToString(); public override bool TrySetValue(OneOf value) @@ -35,6 +47,11 @@ public class SettingControl : SettingBase, ISettingControl return false; } + if (_valueChangePredicate is not null && !_valueChangePredicate.Invoke(newVal)) + { + return false; + } + Value = newVal; OnValueChanged?.Invoke(this); return true; diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs index adca18b3b..d6e543508 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Numerics; using Barotrauma.LuaCs.Data; using Vector2 = Microsoft.Xna.Framework.Vector2; +using Vector4 = Microsoft.Xna.Framework.Vector4; // ReSharper disable ObjectCreationAsStatement @@ -28,7 +29,6 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase SettingsMenu settingsMenuInstance) : base(contentFrame, packageManagementService, configService, settingsMenuInstance) { _settingsInstancesGameplay = configService.GetDisplayableConfigs() - .Where(s => s is not ISettingControl) .ToImmutableArray(); @@ -69,7 +69,6 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase OnApplyInstalledModsChanges = () => { _settingsInstancesGameplay = configService.GetDisplayableConfigs() - .Where(s => s is not ISettingControl) .ToImmutableArray(); if (_selectedContentPackage is not null && !GetTargetPackagesList().Contains(_selectedContentPackage)) { @@ -128,6 +127,7 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase .Where(s => _selectedCategory.IsNullOrWhiteSpace() || _selectedCategory == "All" || GetLocalizedString(s.GetDisplayInfo().DisplayCategory, "General") == _selectedCategory) + .OrderBy(s => GetLocalizedString(s.GetDisplayInfo().DisplayName, s.InternalName)) .ToImmutableArray(); } @@ -139,6 +139,7 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase .Where(s => _selectedContentPackage is null || _selectedContentPackage == ContentPackageManager.VanillaCorePackage // vanilla is treated as all packages || s.OwnerPackage == _selectedContentPackage) + .OrderBy(s => GetLocalizedString(s.GetDisplayInfo().DisplayName, s.InternalName)) .ToImmutableArray(); } @@ -212,7 +213,6 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase foreach (var category in categories) { - DebugConsole.Log(category); var btn = new GUIButton(new RectTransform(new Vector2(1f, 0.122f), displayCategoriesLayout.RectTransform), text: category, color: Color.TransparentBlack) { @@ -264,6 +264,7 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase font: GUIStyle.SmallFont, textAlignment: Alignment.Left) { + Padding = new Vector4(0.02f,0,0,0), CanBeFocused = false }; diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/SettingsMenuSystem.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/SettingsMenuSystem.cs index 4f5575f36..9347809c7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/SettingsMenuSystem.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/SettingsMenuSystem.cs @@ -45,11 +45,12 @@ public class SettingsMenuSystem : ISettingsMenuSystem _gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance, "SettingsMenuTab.Mods", "LuaCsForBarotrauma.SettingsMenu.ModGameplayButton"); - _controlsContentFrame = CreateNewContentTab(tabControlsIndex, __instance, + /*_controlsContentFrame = CreateNewContentTab(tabControlsIndex, __instance, "SettingsMenuTab.Controls", "LuaCsForBarotrauma.SettingsMenu.ModControlsButton"); + */ _gameplayMenuInstance = new ModsGameplaySettingsMenu(_gameplayContentFrame, _packageManagementService, _configService, __instance); - _controlsMenuInstance = new ModsControlsSettingsMenu(_controlsContentFrame, _packageManagementService, _configService, __instance); + //_controlsMenuInstance = new ModsControlsSettingsMenu(_controlsContentFrame, _packageManagementService, _configService, __instance); } private GUIFrame CreateNewContentTab(SettingsMenu.Tab tab, SettingsMenu settingsMenuInstance, string settingsMenuTabName, string settingMenuHoverTextIdent) diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml index a3651960b..17f0857cb 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml @@ -7,6 +7,7 @@ + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs index 1cf612366..31d85e8b0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs @@ -50,8 +50,14 @@ public class SettingsEntryRegistrar : ISettingsRegistrationProvider return new SettingRangeFloat.RangeFactory().CreateInstance(cfgInfo.Info, (val) => IsValueChangeAllowed(cfgInfo.Info, val, valueChangePredicate)); }); - - // ISettingList : Not Implemented yet + +#if CLIENT + configService.RegisterSettingTypeInitializer("control" , cfgInfo => + { + return new SettingControl.Factory().CreateInstance(cfgInfo.Info, val => + IsValueChangeAllowed(cfgInfo.Info, val, valueChangePredicate)); + }); +#endif } private void RegisterSettingEntry(IConfigService configService, string typeName, Func, bool> valueChangePredicate) where T : IEquatable, IConvertible From 947a48140095644fdcf49f3ab6ad7dcb052ed8ab Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 5 Mar 2026 19:43:04 -0500 Subject: [PATCH 210/288] Added spacer to settings labels. --- .../Services/_SettingsMenu/ModsGameplaySettingsMenu.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs index d6e543508..b885d1873 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs @@ -258,13 +258,15 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase GUIFrame entryFrame = new GUIFrame(new RectTransform(new Vector2(1f, settingHeight), parent)); GUILayoutGroup entryLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, entryFrame.RectTransform), isHorizontal: true); - new GUITextBlock(new RectTransform(labelSize, entryLayoutGroup.RectTransform), + new GUIFrame(new RectTransform(new Vector2(0.05f, 1f), entryLayoutGroup.RectTransform), + color: Color.TransparentBlack); + + new GUITextBlock(new RectTransform(labelSize - new Vector2(0.05f, 0f), entryLayoutGroup.RectTransform), GetLocalizedString(setting.GetDisplayInfo().DisplayName, setting.GetDisplayInfo().DisplayName), textColor: Color.PeachPuff, font: GUIStyle.SmallFont, textAlignment: Alignment.Left) { - Padding = new Vector4(0.02f,0,0,0), CanBeFocused = false }; From 8470e81dc7340f76ade0fc278e6a6c31eb900e84 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 6 Mar 2026 04:57:00 -0500 Subject: [PATCH 211/288] Added tooltips and SettingControl input listener to the settings menu. Minor cosmetic adjustments. --- .../ClientSource/LuaCs/Data/SettingControl.cs | 101 ++++++++++++++++++ .../_SettingsMenu/ModsGameplaySettingsMenu.cs | 5 +- .../Config/SettingsShared.xml | 2 +- .../LuaCsForBarotrauma/Texts/English.xml | 4 +- 4 files changed, 107 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/SettingControl.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/SettingControl.cs index bceb15068..65ed65ecd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/SettingControl.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Data/SettingControl.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Xml.Linq; using Barotrauma.LuaCs.Data; using Microsoft.Toolkit.Diagnostics; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using OneOf; @@ -148,4 +149,104 @@ public sealed class SettingControl : SettingBase, ISettingControl } return false; } + +#if CLIENT + private static GUICustomComponent InputListener; + + public override void AddDisplayComponent(GUILayoutGroup layoutGroup, Vector2 relativeSize, Action onSerializedValue) + { + var inputButton = new GUIButton(new RectTransform(relativeSize, layoutGroup.RectTransform), Alignment.Center, + style: "GUITextBoxNoIcon") + { + Text = this.Value.ToString(), + OnClicked = (btn, obj) => + { + if (InputListener is not null) + { + // Another button is active + return true; + } + CoroutineManager.Invoke(() => + { + CreateListener(btn); + }, 0f); // delay one frame for button inputs + return true; + } + }; + inputButton.OutlineColor = Color.PeachPuff; + inputButton.TextColor = Color.White; + + + void ClearListener() + { + InputListener?.Parent.RemoveChild(InputListener); + InputListener = null; + } + + void CreateListener(GUIButton button) + { + ClearListener(); + InputListener = new GUICustomComponent(new RectTransform(Vector2.Zero, layoutGroup.RectTransform), + onUpdate: (deltaTime, component) => + { + var pressedKeys = PlayerInput.GetKeyboardState.GetPressedKeys(); + if (pressedKeys?.Any() ?? false) + { + if (pressedKeys.Contains(Keys.Escape)) + { + ClearListener(); + return; + } + + ApplyValue(pressedKeys.First(), button); + return; + } + + if (PlayerInput.PrimaryMouseButtonClicked() && + (GUI.MouseOn == null || !(GUI.MouseOn is GUIButton) || GUI.MouseOn.IsChildOf(layoutGroup))) + { + ApplyValue(MouseButton.PrimaryMouse, button); + return; + } + else if (PlayerInput.SecondaryMouseButtonClicked()) + { + ApplyValue(MouseButton.SecondaryMouse, button); + return; + } + else if (PlayerInput.MidButtonClicked()) + { + ApplyValue(MouseButton.MiddleMouse, button); + return; + } + else if (PlayerInput.Mouse4ButtonClicked()) + { + ApplyValue(MouseButton.MouseButton4, button); + return; + } + else if (PlayerInput.Mouse5ButtonClicked()) + { + ApplyValue(MouseButton.MouseButton5, button); + return; + } + else if (PlayerInput.MouseWheelUpClicked()) + { + ApplyValue(MouseButton.MouseWheelUp, button); + return; + } + else if (PlayerInput.MouseWheelDownClicked()) + { + ApplyValue(MouseButton.MouseWheelDown, button); + return; + } + }); + } + + void ApplyValue(KeyOrMouse input, GUIButton button) + { + button.Text = input.ToString(); + onSerializedValue?.Invoke(input.ToString()); + ClearListener(); + } + } +#endif } diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs index b885d1873..118e51f31 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs @@ -258,7 +258,8 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase GUIFrame entryFrame = new GUIFrame(new RectTransform(new Vector2(1f, settingHeight), parent)); GUILayoutGroup entryLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, entryFrame.RectTransform), isHorizontal: true); - new GUIFrame(new RectTransform(new Vector2(0.05f, 1f), entryLayoutGroup.RectTransform), + // padding + new GUIFrame(new RectTransform(new Vector2(0.02f, 1f), entryLayoutGroup.RectTransform), color: Color.TransparentBlack); new GUITextBlock(new RectTransform(labelSize - new Vector2(0.05f, 0f), entryLayoutGroup.RectTransform), @@ -267,7 +268,7 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase font: GUIStyle.SmallFont, textAlignment: Alignment.Left) { - CanBeFocused = false + ToolTip = GetLocalizedString(setting.GetDisplayInfo().Tooltip, string.Empty) }; setting.AddDisplayComponent(entryLayoutGroup, controlSize, newValue => diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml index d6a5afe2a..4fcdaf06f 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml @@ -3,7 +3,7 @@ - + diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml index 222a150af..84d297395 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml @@ -6,11 +6,11 @@ Suppress GUI Popup on Error General Are C# Mods Allowed + Should unsandboxed scripts and dlls be allowed to run. General Hide Local OS Account Name In Logs General - Where to Save Local Data - General Limit Network Message Size + Limits the maximum network message size to avoid buffer size issues. Networking From 5a452709c3275726ebc2813556bfc82d73507dde Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 6 Mar 2026 08:31:09 -0500 Subject: [PATCH 212/288] Minor adjustment to setting entry height and styles. --- .../_SettingsMenu/ModsGameplaySettingsMenu.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs index 118e51f31..8ff436ac3 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs @@ -204,8 +204,9 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList()); }, new Vector2(1f, 0.07f)); var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.945f), layoutGroup.RectTransform)); - float size_y = MathF.Max(categories.Length * 0.122f, 1f); - var displayedCategoriesFrame = new GUIFrame(new RectTransform(new Vector2(1f, size_y), containerBox.Content.RectTransform), style: null, color: Color.Black) + const float entryHeight = 0.122f; + float sizeY = MathF.Max(categories.Length * entryHeight, 1f); + var displayedCategoriesFrame = new GUIFrame(new RectTransform(new Vector2(1f, sizeY), containerBox.Content.RectTransform), style: null, color: Color.Black) { CanBeFocused = false }; @@ -213,7 +214,7 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase foreach (var category in categories) { - var btn = new GUIButton(new RectTransform(new Vector2(1f, 0.122f), displayCategoriesLayout.RectTransform), + var btn = new GUIButton(new RectTransform(new Vector2(1f, entryHeight), displayCategoriesLayout.RectTransform), text: category, color: Color.TransparentBlack) { CanBeFocused = true, @@ -236,7 +237,7 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase void GenerateSettingsListDisplay(GUILayoutGroup layoutGroup, ImmutableArray settings) { layoutGroup.ClearChildren(); - float settingHeight = 0.06f; + const float settingHeight = 0.0625f; var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f), layoutGroup.RectTransform)); foreach (var setting in settings) @@ -255,7 +256,10 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase (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)) + { + Color = Color.DarkGray + }; GUILayoutGroup entryLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, entryFrame.RectTransform), isHorizontal: true); // padding From 5b52d22b1faf37963acd24d7de46698e7f65fc24 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 6 Mar 2026 18:15:44 -0500 Subject: [PATCH 213/288] Added references cache for some hot paths services. --- .../BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index e96f239f8..be0f0006c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -73,8 +73,12 @@ namespace Barotrauma public IPluginManagementService PluginManagementService => _servicesProvider.GetService(); public ILuaScriptManagementService LuaScriptManagementService => _servicesProvider.GetService(); public INetworkingService NetworkingService => _servicesProvider.GetService(); - public IEventService EventService => _servicesProvider.GetService(); - public LuaGame Game => _servicesProvider.GetService(); + // hotpath performance ref cache + private IEventService _eventService = null; + public IEventService EventService => _eventService ??= _servicesProvider.GetService(); + // hotpath performance ref cache + private LuaGame _game; + public LuaGame Game => _game ??= _servicesProvider.GetService(); internal IStorageService StorageService => _servicesProvider.GetService(); @@ -451,6 +455,8 @@ namespace Barotrauma //NetworkingService.Dispose(); EventService.Dispose(); + _eventService = null; + _game = null; _servicesProvider.DisposeAndReset(); } catch (Exception e) From e26cfa2e521748d6a448498efb83a8a5eab42567 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Fri, 6 Mar 2026 18:49:55 -0500 Subject: [PATCH 214/288] [Untested][Save/Sync] - Fix for IsCsEnabled check. --- .../ClientSource/LuaCs/LuaCsSetup.cs | 108 ++++++++++-------- 1 file changed, 61 insertions(+), 47 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index 96490b7ce..4dcd33bc7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -14,25 +14,23 @@ namespace Barotrauma { partial class LuaCsSetup { + private bool _isClientPromptActive; + /// /// /// - /// Returns whether the IsCsEnabled has been changed to true/enabled. Returns false if already enabled. - public bool CheckCsEnabled() + /// Returns whether execution should continue. + public bool CheckReadyToRun() { // fast exit if enabled or unavailable. if (this.IsCsEnabled) { - return false; + return true; } - bool isCsValueChanged = false; - - var csharpMods = PackageManagementService.GetLoadedAssemblyPackages(); - StringBuilder sb = new StringBuilder(); - foreach (ContentPackage cp in csharpMods) + foreach (ContentPackage cp in PackageManagementService.GetLoadedAssemblyPackages()) { if (cp.UgcId.TryUnwrap(out ContentPackageId id)) sb.AppendLine($"- {cp.Name} ({id})"); @@ -42,53 +40,65 @@ namespace Barotrauma if (GameMain.Client == null || GameMain.Client.IsServerOwner) { - new GUIMessageBox("", $"You have CSharp mods enabled but don't have the CSharp Scripting enabled, those mods might not work, go to the Main Menu, click on LuaCs Settings and check Enable CSharp Scripting.\n\n{sb}"); - return false; + DisplayCsModsPromptServer(sb); + } + else if (!_isClientPromptActive) + { + _isClientPromptActive = true; + DisplayCsModsPromptClient(sb); } - GUIMessageBox msg = new GUIMessageBox( - "Confirm", - $"This server has the following CSharp mods installed: \n{sb}\nDo you wish to run them? Cs mods are not sandboxed so make sure you trust these mods.", - new LocalizedString[2] { "Run", "Don't Run" }); + return false; + - msg.Buttons[0].OnClicked = (GUIButton button, object obj) => + + void DisplayCsModsPromptServer(StringBuilder sb) { - try + new GUIMessageBox("", $"You have CSharp mods enabled but don't have the CSharp Scripting enabled, those mods might not work, go to the Main Menu, click on LuaCs Settings and check Enable CSharp Scripting.\n\n{sb}"); + } + + void DisplayCsModsPromptClient(StringBuilder sb) + { + GUIMessageBox msg = new GUIMessageBox( + "Confirm", + $"This server has the following CSharp mods installed: \n{sb}\nDo you wish to run them? Cs mods are not sandboxed so make sure you trust these mods.", + new LocalizedString[2] { "Run", "Don't Run" }); + + msg.Buttons[0].OnClicked = (GUIButton button, object obj) => { - this.IsCsEnabled = true; - isCsValueChanged = true; - CoroutineManager.Invoke(() => + try { - if (CurrentRunState >= RunState.Running) + this.IsCsEnabled = true; + this._isClientPromptActive = false; + + CoroutineManager.Invoke(() => { - var currentRunState = CurrentRunState; SetRunState(RunState.LoadedNoExec); - SetRunState(currentRunState); - } - }, 0f); - return true; - } - finally - { - msg.Close(); - } - }; + SetRunState(RunState.Running); + }, 0f); + return true; + } + finally + { + msg.Close(); + } + }; - msg.Buttons[1].OnClicked = (GUIButton button, object obj) => - { - try + msg.Buttons[1].OnClicked = (GUIButton button, object obj) => { - // avoid a TOCTOU scenario. - this.IsCsEnabled = false; - return true; - } - finally - { - msg.Close(); - } - }; - - return isCsValueChanged; + try + { + // avoid a TOCTOU scenario. + this.IsCsEnabled = false; + this._isClientPromptActive = false; + return true; + } + finally + { + msg.Close(); + } + }; + } } private void SetupServicesProviderClient(IServicesProvider serviceProvider) @@ -132,9 +142,13 @@ namespace Barotrauma case SpriteEditorScreen: case SubEditorScreen: case TestScreen: // notes: TestScreen is a Linux edge case editor screen and is deprecated. - if (CheckCsEnabled() && this.CurrentRunState >= RunState.Running) + if (!CheckReadyToRun()) { - SetRunState(RunState.LoadedNoExec); + if (CurrentRunState >= RunState.Running) + { + SetRunState(RunState.LoadedNoExec); + } + return; } SetRunState(RunState.Running); From 8190b3f31280a9077a399ccc8f8524f7af9ccab2 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 8 Mar 2026 22:07:56 -0400 Subject: [PATCH 215/288] Fixed cs enabled prompt for hosts. --- .../ClientSource/LuaCs/LuaCsSetup.cs | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index 4dcd33bc7..83e158ff0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -38,23 +38,37 @@ namespace Barotrauma sb.AppendLine($"- {cp.Name} (Not On Workshop)"); } - if (GameMain.Client == null || GameMain.Client.IsServerOwner) - { - DisplayCsModsPromptServer(sb); - } - else if (!_isClientPromptActive) + if (!_isClientPromptActive) { _isClientPromptActive = true; - DisplayCsModsPromptClient(sb); + if (GameMain.Client == null || GameMain.Client.IsServerOwner) + { + DisplayCsModsPromptServer(sb); + } + else + { + DisplayCsModsPromptClient(sb); + } } - + return false; void DisplayCsModsPromptServer(StringBuilder sb) { - new GUIMessageBox("", $"You have CSharp mods enabled but don't have the CSharp Scripting enabled, those mods might not work, go to the Main Menu, click on LuaCs Settings and check Enable CSharp Scripting.\n\n{sb}"); + var msg = new GUIMessageBox("", $"You have CSharp mods enabled but don't have the CSharp Scripting enabled, " + + $"those mods might not work, go to the Main Menu, click on LuaCs Settings and check Enable CSharp Scripting.\n\n{sb}"); + foreach (var button in msg.Buttons) + { + var old = button.OnClicked; + button.OnClicked = (btn, obj) => + { + var ret = old?.Invoke(btn, obj); + _isClientPromptActive = false; + return ret ?? true; + }; + } } void DisplayCsModsPromptClient(StringBuilder sb) @@ -67,13 +81,12 @@ namespace Barotrauma msg.Buttons[0].OnClicked = (GUIButton button, object obj) => { try - { - this.IsCsEnabled = true; + { this._isClientPromptActive = false; - CoroutineManager.Invoke(() => { SetRunState(RunState.LoadedNoExec); + this.IsCsEnabled = true; SetRunState(RunState.Running); }, 0f); return true; From e5aa381e38b5a075b0bb588d0314d1bc9cc59852 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 10 Mar 2026 03:22:30 -0400 Subject: [PATCH 216/288] performance fixes for IDE/attached debugger stuttering. --- .../_Services/LuaScriptManagementService.cs | 25 +++++++++++- .../_Services/_Lua/DefaultLuaRegistrar.cs | 38 +++++++++++++++++-- Libraries/moonsharp | 2 +- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index b3770a90a..39e238e6e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -26,7 +26,7 @@ using System.Diagnostics; namespace Barotrauma.LuaCs; -class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService +internal sealed class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService { public Script? InternalScript => _script; @@ -335,6 +335,29 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService UserData.RegisterType(typeof(IResourceInfo)); UserData.RegisterType(typeof(IUserDataDescriptor)); UserData.RegisterType(typeof(INetworkingService)); + + // Config/Settings + UserData.RegisterType(typeof(IConfigService)); + UserData.RegisterType(typeof(ISettingBase)); + UserData.RegisterType(typeof(ISettingBase)); + UserData.RegisterType(typeof(ISettingBase)); + UserData.RegisterType(typeof(ISettingBase)); + UserData.RegisterType(typeof(ISettingBase)); + UserData.RegisterType(typeof(ISettingBase)); + UserData.RegisterType(typeof(ISettingBase)); + UserData.RegisterType(typeof(ISettingBase)); + UserData.RegisterType(typeof(ISettingBase)); + UserData.RegisterType(typeof(ISettingBase)); + UserData.RegisterType(typeof(ISettingBase)); + UserData.RegisterType(typeof(ISettingBase)); + UserData.RegisterType(typeof(ISettingBase)); + UserData.RegisterType(typeof(ISettingBase)); + UserData.RegisterType(typeof(ISettingRangeBase)); + UserData.RegisterType(typeof(ISettingRangeBase)); +#if CLIENT + UserData.RegisterType(typeof(ISettingControl)); +#endif + new LuaConverters(this).RegisterLuaConverters(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs index ff92e09ec..ed854722f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; +using Barotrauma.LuaCs.Data; namespace Barotrauma.LuaCs; @@ -41,17 +42,48 @@ public class DefaultLuaRegistrar : IDefaultLuaRegistrar _userDataService.RegisterType("Barotrauma.Range`1"); _userDataService.RegisterType("Barotrauma.ItemPrefab"); - List assembliesToScan = [typeof(DefaultLuaRegistrar).Assembly, typeof(Identifier).Assembly, typeof(Microsoft.Xna.Framework.Vector2).Assembly]; + List assembliesToScan = [ + typeof(DefaultLuaRegistrar).Assembly, + typeof(Identifier).Assembly, + //causes tons of lag (high lookup time) + //typeof(Microsoft.Xna.Framework.Vector2).Assembly + ]; - foreach (var type in assembliesToScan.SelectMany(a => a.GetTypes())) + foreach (var type in assembliesToScan.SelectMany(a => a.GetSafeTypes())) { if (type.IsEnum || type.Name.StartsWith("<") || type.IsDefined(typeof(CompilerGeneratedAttribute)) || !_safeUserDataService.IsAllowed(type.FullName)) { continue; } - + + if (type.FullName?.StartsWith("Barotrauma.LuaCs") ?? false) + { + continue; + } + _userDataService.RegisterType(type.FullName); } + + _userDataService.RegisterType(typeof(IConfigService).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingRangeBase).FullName); + _userDataService.RegisterType(typeof(ISettingRangeBase).FullName); +#if CLIENT + _userDataService.RegisterType(typeof(ISettingControl).FullName); +#endif _userDataService.RegisterType("Barotrauma.LuaSByte"); _userDataService.RegisterType("Barotrauma.LuaByte"); diff --git a/Libraries/moonsharp b/Libraries/moonsharp index e3c2270e8..7b78e0883 160000 --- a/Libraries/moonsharp +++ b/Libraries/moonsharp @@ -1 +1 @@ -Subproject commit e3c2270e8277de98b0ec2b42b42909e6e6c8afd9 +Subproject commit 7b78e0883c0be1771f7d1ec6d4a94b96c24dd735 From dd51bdae3f0ccb05c454209ffe6543a6bcc1a214 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 14 Mar 2026 12:04:29 -0300 Subject: [PATCH 217/288] Revert "performance fixes for IDE/attached debugger stuttering." This reverts commit e5aa381e38b5a075b0bb588d0314d1bc9cc59852. --- .../_Services/LuaScriptManagementService.cs | 25 +----------- .../_Services/_Lua/DefaultLuaRegistrar.cs | 38 ++----------------- Libraries/moonsharp | 2 +- 3 files changed, 5 insertions(+), 60 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 39e238e6e..b3770a90a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -26,7 +26,7 @@ using System.Diagnostics; namespace Barotrauma.LuaCs; -internal sealed class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService +class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService { public Script? InternalScript => _script; @@ -335,29 +335,6 @@ internal sealed class LuaScriptManagementService : ILuaScriptManagementService, UserData.RegisterType(typeof(IResourceInfo)); UserData.RegisterType(typeof(IUserDataDescriptor)); UserData.RegisterType(typeof(INetworkingService)); - - // Config/Settings - UserData.RegisterType(typeof(IConfigService)); - UserData.RegisterType(typeof(ISettingBase)); - UserData.RegisterType(typeof(ISettingBase)); - UserData.RegisterType(typeof(ISettingBase)); - UserData.RegisterType(typeof(ISettingBase)); - UserData.RegisterType(typeof(ISettingBase)); - UserData.RegisterType(typeof(ISettingBase)); - UserData.RegisterType(typeof(ISettingBase)); - UserData.RegisterType(typeof(ISettingBase)); - UserData.RegisterType(typeof(ISettingBase)); - UserData.RegisterType(typeof(ISettingBase)); - UserData.RegisterType(typeof(ISettingBase)); - UserData.RegisterType(typeof(ISettingBase)); - UserData.RegisterType(typeof(ISettingBase)); - UserData.RegisterType(typeof(ISettingBase)); - UserData.RegisterType(typeof(ISettingRangeBase)); - UserData.RegisterType(typeof(ISettingRangeBase)); -#if CLIENT - UserData.RegisterType(typeof(ISettingControl)); -#endif - new LuaConverters(this).RegisterLuaConverters(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs index ed854722f..ff92e09ec 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; -using Barotrauma.LuaCs.Data; namespace Barotrauma.LuaCs; @@ -42,48 +41,17 @@ public class DefaultLuaRegistrar : IDefaultLuaRegistrar _userDataService.RegisterType("Barotrauma.Range`1"); _userDataService.RegisterType("Barotrauma.ItemPrefab"); - List assembliesToScan = [ - typeof(DefaultLuaRegistrar).Assembly, - typeof(Identifier).Assembly, - //causes tons of lag (high lookup time) - //typeof(Microsoft.Xna.Framework.Vector2).Assembly - ]; + List assembliesToScan = [typeof(DefaultLuaRegistrar).Assembly, typeof(Identifier).Assembly, typeof(Microsoft.Xna.Framework.Vector2).Assembly]; - foreach (var type in assembliesToScan.SelectMany(a => a.GetSafeTypes())) + foreach (var type in assembliesToScan.SelectMany(a => a.GetTypes())) { if (type.IsEnum || type.Name.StartsWith("<") || type.IsDefined(typeof(CompilerGeneratedAttribute)) || !_safeUserDataService.IsAllowed(type.FullName)) { continue; } - - if (type.FullName?.StartsWith("Barotrauma.LuaCs") ?? false) - { - continue; - } - + _userDataService.RegisterType(type.FullName); } - - _userDataService.RegisterType(typeof(IConfigService).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingRangeBase).FullName); - _userDataService.RegisterType(typeof(ISettingRangeBase).FullName); -#if CLIENT - _userDataService.RegisterType(typeof(ISettingControl).FullName); -#endif _userDataService.RegisterType("Barotrauma.LuaSByte"); _userDataService.RegisterType("Barotrauma.LuaByte"); diff --git a/Libraries/moonsharp b/Libraries/moonsharp index 7b78e0883..e3c2270e8 160000 --- a/Libraries/moonsharp +++ b/Libraries/moonsharp @@ -1 +1 @@ -Subproject commit 7b78e0883c0be1771f7d1ec6d4a94b96c24dd735 +Subproject commit e3c2270e8277de98b0ec2b42b42909e6e6c8afd9 From 103f28481dd9b5c1f649aae9047acd8aec64b7e3 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 14 Mar 2026 12:07:23 -0300 Subject: [PATCH 218/288] Register setting types --- .../_Services/_Lua/DefaultLuaRegistrar.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs index ff92e09ec..dabf3792e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs @@ -1,4 +1,5 @@ -using Barotrauma.Networking; +using Barotrauma.LuaCs.Data; +using Barotrauma.Networking; using MoonSharp.Interpreter; using Sigil; using System.Collections.Generic; @@ -53,6 +54,27 @@ public class DefaultLuaRegistrar : IDefaultLuaRegistrar _userDataService.RegisterType(type.FullName); } + _userDataService.RegisterType(typeof(IConfigService).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingBase).FullName); + _userDataService.RegisterType(typeof(ISettingRangeBase).FullName); + _userDataService.RegisterType(typeof(ISettingRangeBase).FullName); +#if CLIENT + _userDataService.RegisterType(typeof(ISettingControl).FullName); +#endif + _userDataService.RegisterType("Barotrauma.LuaSByte"); _userDataService.RegisterType("Barotrauma.LuaByte"); _userDataService.RegisterType("Barotrauma.LuaInt16"); From d4b774642b822e43f634c089482ae424783252d0 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 14 Mar 2026 13:41:12 -0300 Subject: [PATCH 219/288] ModUtils namespace compatibility --- Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs index e2a08d806..25314760a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs @@ -21,7 +21,7 @@ using Platform = Barotrauma.LuaCs.Data.Platform; // ReSharper disable ConvertClosureToMethodGroup // This file is cursed, we put everything in it, and I'm not sorry about it. -namespace Barotrauma.LuaCs +namespace Barotrauma { public static class ModUtils From ecc4d8a6c11585b5a5802c67de1aee356b2a0816 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 14 Mar 2026 14:00:59 -0300 Subject: [PATCH 220/288] Update moonsharp --- Libraries/moonsharp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/moonsharp b/Libraries/moonsharp index e3c2270e8..3a71aa309 160000 --- a/Libraries/moonsharp +++ b/Libraries/moonsharp @@ -1 +1 @@ -Subproject commit e3c2270e8277de98b0ec2b42b42909e6e6c8afd9 +Subproject commit 3a71aa3096c8d6f1be5a42c2ab241934a63bc0b3 From f802356b0a65916c8d91970e94bcf299d5bad6c5 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 14 Mar 2026 14:31:03 -0300 Subject: [PATCH 221/288] Add legacy redirect for LuaCsSetup.Timer --- Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index be0f0006c..2445f1e2a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -429,6 +429,7 @@ namespace Barotrauma public ILuaCsHook Hook => this.EventService; public INetworkingService Networking => this.NetworkingService; + public ILuaCsTimer Timer => _servicesProvider.GetService(); #endregion From cf2c8b8e0e36e31a87408316d0b89c443ed8b660 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 14 Mar 2026 14:45:03 -0300 Subject: [PATCH 222/288] Add missing HttpGet in the interface --- .../SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs index 4aae5b61e..9ed27fc32 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs @@ -9,6 +9,7 @@ internal interface ILuaCsNetworking : ILuaCsShim ushort LastClientListUpdateID { get; set; } void HttpRequest(string url, LuaCsAction callback, string data = null, string method = "POST", string contentType = "application/json", Dictionary headers = null, string savePath = null); void HttpPost(string url, LuaCsAction callback, string data, string contentType = "application/json", Dictionary headers = null, string savePath = null); + void HttpGet(string url, LuaCsAction callback, Dictionary headers = null, string savePath = null); void Receive(string netId, LuaCsAction action); #if SERVER From 80832459b9b3fc36285dd46821a70724f7bc2e6b Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sat, 14 Mar 2026 23:47:44 -0400 Subject: [PATCH 223/288] Added api for getting content package associatged with a plugin type. --- .../LuaCs/_Services/PluginManagementService.cs | 10 ++++++++++ .../_Services/_Interfaces/IPluginManagementService.cs | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index dd348ff0c..c102bcae1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -145,6 +145,7 @@ public class PluginManagementService : IAssemblyManagementService } } _pluginInstances.Clear(); + _pluginPackageLookup.Clear(); _pluginInjectorContainer?.Dispose(); _pluginInjectorContainer = null; @@ -194,6 +195,7 @@ public class PluginManagementService : IAssemblyManagementService private Lazy _configService; private Lazy _luaScriptManagementService; private readonly ConcurrentDictionary _assemblyLoaders = new(); + private readonly ConcurrentDictionary _pluginPackageLookup = new(); private readonly ConcurrentDictionary> _pluginInstances = new(); private readonly ConditionalWeakTable _unloadingAssemblyLoaders = new(); private readonly AsyncReaderWriterLock _operationsLock = new(); @@ -226,6 +228,7 @@ public class PluginManagementService : IAssemblyManagementService container.Register(fac => _logger); container.Register(fac => _storageService); container.Register(fac => _eventService.Value); + container.Register(fac => this); container.Register(fac => _luaScriptManagementService.Value); container.Register(fac => _configService.Value); @@ -275,6 +278,11 @@ public class PluginManagementService : IAssemblyManagementService } } + public bool TryGetPackageForPlugin(out ContentPackage ownerPackage) + { + return _pluginPackageLookup.TryGetValue(typeof(TPlugin), out ownerPackage); + } + public Type GetType(string typeName, bool isByRefType = false, bool includeInterfaces = false, bool includeDefaultContext = true) { @@ -375,6 +383,7 @@ public class PluginManagementService : IAssemblyManagementService _pluginInjectorContainer.InjectProperties(plugin); _pluginInjectorContainer.Register(pluginType, fac => plugin); loadedTypes.Add(plugin); + _pluginPackageLookup.TryAdd(pluginType, packageTypes.Key); } catch (Exception e) { @@ -724,6 +733,7 @@ public class PluginManagementService : IAssemblyManagementService } _pluginInstances.Clear(); + _pluginPackageLookup.Clear(); return results; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPluginManagementService.cs index 51453255f..a26c49ff6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPluginManagementService.cs @@ -21,6 +21,14 @@ public interface IPluginManagementService : IReusableService bool includeInterfaces = false, bool includeAbstractTypes = false, bool includeDefaultContext = true); + + /// + /// Gets the that contains the plugin type. + /// + /// + /// + /// + bool TryGetPackageForPlugin(out ContentPackage ownerPackage); /// /// Tries to find the type given the fully qualified name and filters. From b402e09b825542ba9dac36cfd08f102d3ef5ee9d Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 15 Mar 2026 17:52:40 -0300 Subject: [PATCH 224/288] Move MainMenu code to a separate service that just injects our stuff into the mainmenu --- .../Screens/MainMenuScreen/MainMenuScreen.cs | 19 ------ .../SharedSource/LuaCs/LuaCsSetup.cs | 4 +- .../LuaCs/_Services/MainMenuPatch.cs | 67 +++++++++++++++++++ 3 files changed, 70 insertions(+), 20 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/MainMenuPatch.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs index 45a7c141f..e287d991b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs @@ -531,25 +531,6 @@ namespace Barotrauma } }; #endif - /*new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(40, 50) }, - $"Open LuaCs Settings", style: "MainMenuGUIButton", color: GUIStyle.Red) - { - IgnoreLayoutGroups = true, - OnClicked = (tb, userdata) => - { - LuaCsSettingsMenu.Open(Frame.RectTransform); - return true; - } - };*/ - - // TODO: Implement version reading. - //string version = File.Exists(LuaCsSetup.VersionFile) ? File.ReadAllText(LuaCsSetup.VersionFile) : "Github"; - string version = "NOT_IMPLEMENTED"; - - new GUITextBlock(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(10, 10) }, $"Using LuaCsForBarotrauma revision {AssemblyInfo.GitRevision} version {version}", Color.Red) - { - IgnoreLayoutGroups = false - }; var minButtonSize = new Point(120, 20); var maxButtonSize = new Point(480, 80); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 2445f1e2a..451f8a959 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -195,7 +195,8 @@ namespace Barotrauma servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); - + servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + // Extension/Sub Services servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); @@ -348,6 +349,7 @@ namespace Barotrauma LuaScriptManagementService.Reset(); PackageManagementService.Reset(); NetworkingService.Reset(); + _servicesProvider.GetService().Reset(); Logger.LogMessage("Services have been reset"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/MainMenuPatch.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/MainMenuPatch.cs new file mode 100644 index 000000000..44172628d --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/MainMenuPatch.cs @@ -0,0 +1,67 @@ +using Barotrauma; +using Barotrauma.LuaCs; +using Barotrauma.LuaCs.Events; +using FluentResults; +using HarmonyLib; +using Microsoft.Xna.Framework; + +[HarmonyPatch] +internal class MainMenuPatch : ISystem, IEventScreenSelected +{ + public bool IsDisposed { get; private set; } + + private readonly IEventService _eventService; + + public MainMenuPatch(IEventService eventService) + { + _eventService = eventService; + + RegisterEvents(); + +#if CLIENT + if (Screen.Selected is MainMenuScreen mainMenuScreen) + { + AddToMainMenu(mainMenuScreen); + } +#endif + } + + public void OnScreenSelected(Screen screen) + { +#if CLIENT + if (screen is MainMenuScreen mainMenuScreen) + { + AddToMainMenu(mainMenuScreen); + } +#endif + } + +#if CLIENT + private void AddToMainMenu(MainMenuScreen screen) + { + 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) + { + IgnoreLayoutGroups = false + }; + } +#endif + + private void RegisterEvents() + { + _eventService.Subscribe(this); + } + + public void Dispose() + { + _eventService.Unsubscribe(this); + + IsDisposed = true; + } + + public FluentResults.Result Reset() + { + RegisterEvents(); + + return FluentResults.Result.Ok(); + } +} From 8717706b1cfb2278646bf3ba089a843ed679aef2 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 15 Mar 2026 18:44:15 -0300 Subject: [PATCH 225/288] Fix check ready to run not working properly --- .../ClientSource/LuaCs/LuaCsSetup.cs | 33 ++++++++++++++----- .../SharedSource/LuaCs/LuaCsSetup.cs | 8 +++-- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index 83e158ff0..8a20415ab 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -15,14 +15,13 @@ namespace Barotrauma partial class LuaCsSetup { private bool _isClientPromptActive; - + /// - /// + /// Returns whether execution should continue /// - /// Returns whether execution should continue. public bool CheckReadyToRun() { - // fast exit if enabled or unavailable. + // Fast exit if enabled if (this.IsCsEnabled) { return true; @@ -32,10 +31,24 @@ namespace Barotrauma foreach (ContentPackage cp in PackageManagementService.GetLoadedAssemblyPackages()) { + if (cp.NameMatches(PackageId)) + { + continue; + } + if (cp.UgcId.TryUnwrap(out ContentPackageId id)) + { sb.AppendLine($"- {cp.Name} ({id})"); + } else + { sb.AppendLine($"- {cp.Name} (Not On Workshop)"); + } + } + + if (string.IsNullOrEmpty(sb.ToString())) + { + return true; } if (!_isClientPromptActive) @@ -44,16 +57,18 @@ namespace Barotrauma if (GameMain.Client == null || GameMain.Client.IsServerOwner) { DisplayCsModsPromptServer(sb); + return true; } else { DisplayCsModsPromptClient(sb); + return false; } } - - return false; - - + else + { + return false; + } void DisplayCsModsPromptServer(StringBuilder sb) { @@ -104,6 +119,8 @@ namespace Barotrauma // avoid a TOCTOU scenario. this.IsCsEnabled = false; this._isClientPromptActive = false; + SetRunState(RunState.LoadedNoExec); + SetRunState(RunState.Running); return true; } finally diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 451f8a959..f5cc64f15 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -24,6 +24,8 @@ namespace Barotrauma partial class LuaCsSetup : IDisposable, IEventScreenSelected, IEventEnabledPackageListChanged, IEventReloadAllPackages { + public const string PackageId = "LuaCsForBarotrauma"; + private static LuaCsSetup _luaCsSetup; public static LuaCsSetup Instance => _luaCsSetup ??= new LuaCsSetup(); @@ -145,9 +147,9 @@ namespace Barotrauma void LoadLuaCsConfig() { - var luaCsPackage = ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches("LuaCsForBarotrauma"), null) - ?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches("LuaCsForBarotrauma")) - ?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches("LuaCsForBarotrauma")); + var luaCsPackage = ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(PackageId), null) + ?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(PackageId)) + ?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(PackageId)); _isCsEnabled = ConfigService.TryGetConfig>(luaCsPackage, "IsCsEnabled", out var val1) From fe34dcb0bd07f03d6de7623feb78f7f337d8d3e7 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 17 Mar 2026 10:58:48 -0400 Subject: [PATCH 226/288] Added null location check to assembly inclusion list. --- .../SharedSource/LuaCs/_Services/PluginManagementService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index c102bcae1..639f27045 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -99,6 +99,7 @@ public class PluginManagementService : IAssemblyManagementService !ass.GetName().FullName.StartsWith("BarotraumaCore") && !ass.GetName().FullName.StartsWith("Barotrauma") && !ass.GetName().FullName.StartsWith("DedicatedServer")) + .Where(ass => !ass.Location.IsNullOrWhiteSpace()) .Select(MetadataReference (ass) => MetadataReference.CreateFromFile(ass.Location))) .Where(ar => ar is not null) .ToImmutableArray(); From b0e3faa2adddb52e254ac6071d18dd3c575d530b Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 21 Mar 2026 15:59:48 -0300 Subject: [PATCH 227/288] Make LuaCsLogger public --- .../LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs index cf9dbbe8c..b8533c663 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsLogger.cs @@ -5,7 +5,7 @@ using MoonSharp.Interpreter; namespace Barotrauma { - internal enum LuaCsMessageOrigin + public enum LuaCsMessageOrigin { LuaCs, Unknown, @@ -13,7 +13,7 @@ namespace Barotrauma CSharpMod, } - partial class LuaCsLogger + public partial class LuaCsLogger { public static void HandleException(Exception ex, LuaCsMessageOrigin origin) { From 994610869dd81b9c9fd30d6dc28cb0d5013843cb Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sat, 21 Mar 2026 23:59:19 -0400 Subject: [PATCH 228/288] Fixed deadlock scenario caused by unsubscribing during an event. --- .../SharedSource/LuaCs/_Services/EventService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs index d12f4f545..9971b369a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs @@ -264,7 +264,7 @@ public partial class EventService : IEventService public void Unsubscribe(T subscriber) where T : class, IEvent { Guard.IsNotNull(subscriber, nameof(subscriber)); - using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); if (!_subscribers.TryGetValue(typeof(T), out var evtSubscribers)) From de53b4514e4ca583bf553c4ab75e53d5414422b1 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 22 Mar 2026 12:42:09 -0300 Subject: [PATCH 229/288] Fix console command registration --- .../ClientSource/DebugConsole.cs | 2 - .../SharedSource/LuaCs/LuaCsSetup.cs | 1 + .../LuaCs/_Services/ConsoleCommandsService.cs | 44 ++++++++++-- .../_Services/HarmonyEventPatchesService.cs | 20 ++++++ .../_Interfaces/IConsoleCommandsService.cs | 11 ++- .../_Services/_Lua/LuaClasses/LuaGame.cs | 69 ++++++------------- 6 files changed, 89 insertions(+), 58 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs index e27829e50..06179beb2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs @@ -223,8 +223,6 @@ namespace Barotrauma private static bool IsCommandPermitted(Identifier command, GameClient client) { - if (LuaCsSetup.Instance.Game.IsCustomCommandPermitted(command)) { return true; } - switch (command.Value.ToLowerInvariant()) { case "kick": diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index f5cc64f15..ec9caba49 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -351,6 +351,7 @@ namespace Barotrauma LuaScriptManagementService.Reset(); PackageManagementService.Reset(); NetworkingService.Reset(); + Game.Reset(); _servicesProvider.GetService().Reset(); Logger.LogMessage("Services have been reset"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConsoleCommandsService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConsoleCommandsService.cs index a6754ad2a..9aefed1d8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConsoleCommandsService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConsoleCommandsService.cs @@ -1,11 +1,15 @@ -using System; +using Barotrauma.LuaCs.Events; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; namespace Barotrauma.LuaCs; -public class ConsoleCommandsService : IConsoleCommandsService +internal class ConsoleCommandsService : IConsoleCommandsService { private readonly ConcurrentDictionary _registeredCommands = new(); @@ -15,10 +19,12 @@ public class ConsoleCommandsService : IConsoleCommandsService { return; } + foreach (var cmd in _registeredCommands.Values.ToImmutableArray()) { DebugConsole.Commands.Remove(cmd); } + _registeredCommands.Clear(); } @@ -28,18 +34,46 @@ public class ConsoleCommandsService : IConsoleCommandsService get => ModUtils.Threading.GetBool(ref _isDisposed); private set => ModUtils.Threading.SetBool(ref _isDisposed, value); } - public FluentResults.Result RegisterCommand(string name, string help, Action onExecute, Func getValidArgs = null, bool isCheat = false) + + public void RegisterCommand(string name, string help, Action onExecute, Func getValidArgs = null, bool isCheat = false) { IService.CheckDisposed(this); var cmd = new DebugConsole.Command(name, help, onExecute, getValidArgs, isCheat); if (!_registeredCommands.TryAdd(name, cmd)) { - return FluentResults.Result.Fail($"{nameof(RegisterCommand)}: A command with the name '{name}' is already added."); + throw new ArgumentException($"A command with the name '{name}' is already registered."); } DebugConsole.Commands.Add(cmd); - return FluentResults.Result.Ok(); } + public void AssignOnExecute(string names, Action onExecute) + { + var matchingCommand = DebugConsole.Commands.Find(c => c.Names.Intersect(names.Split('|').ToIdentifiers()).Any()); + if (matchingCommand == null) + { + throw new Exception("AssignOnExecute failed. Command matching the name(s) \"" + names + "\" not found."); + } + else + { + matchingCommand.OnExecute = onExecute; + } + } + +#if SERVER + public void AssignOnClientRequestExecute(string names, Action onClientRequestExecute) + { + var matchingCommand = DebugConsole.Commands.Find(c => c.Names.Intersect(names.Split('|').ToIdentifiers()).Any()); + if (matchingCommand == null) + { + throw new Exception("AssignOnClientRequestExecute failed. Command matching the name(s) \"" + names + "\" not found."); + } + else + { + matchingCommand.OnClientRequestExecute = onClientRequestExecute; + } + } +#endif + public void RemoveCommand(string name) { IService.CheckDisposed(this); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs index 86425a828..478ce7434 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs @@ -27,12 +27,16 @@ 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() @@ -136,6 +140,22 @@ internal class HarmonyEventPatchesService : ISystem { _eventService.PublishEvent(x => x.OnKeyUpdate(deltaTime)); } + + [HarmonyPatch(typeof(DebugConsole), "IsCommandPermitted"), HarmonyPrefix] + public static bool DebugConsole_IsCommandPermitted(Identifier command, ref bool __result) + { + DebugConsole.Command c = DebugConsole.FindCommand(command.Value); + + if (DebugConsole.Commands.IndexOf(c) >= debugConsoleCommandVanillaIndex) + { + __result = true; + return false; + } + + return true; + } + + #elif SERVER [HarmonyPatch(typeof(GameServer), "ReadDataMessage"), HarmonyPrefix] public static void GameServer_ReadDataMessage_Pre(NetworkConnection sender, IReadMessage inc) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConsoleCommandsService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConsoleCommandsService.cs index 998411b93..2d4bad468 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConsoleCommandsService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConsoleCommandsService.cs @@ -1,11 +1,16 @@ -using System; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; +using System; namespace Barotrauma.LuaCs; public interface IConsoleCommandsService : IService { - FluentResults.Result RegisterCommand(string name, string help, Action onExecute, Func getValidArgs = null, - bool isCheat = false); + void RegisterCommand(string name, string help, Action onExecute, Func getValidArgs = null, bool isCheat = false); + void AssignOnExecute(string names, Action onExecute); +#if SERVER + internal void AssignOnClientRequestExecute(string names, Action onClientRequestExecute); +#endif void RemoveCommand(string name); void RemoveRegisteredCommands(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs index 3c566aa3f..39f65c314 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs @@ -147,7 +147,6 @@ namespace Barotrauma.LuaCs public bool disableSpamFilter = false; public bool disableDisconnectCharacter = false; public bool enableControlHusk = false; - public int MapEntityUpdateInterval { get { return MapEntity.MapEntityUpdateInterval; } @@ -270,10 +269,12 @@ namespace Barotrauma.LuaCs } #endif - public LuaGame() + private readonly IConsoleCommandsService _consoleCommands; + + public LuaGame(IConsoleCommandsService consoleCommands) { - /*LuaUserData.MakeFieldAccessible(UserData.RegisterType(typeof(GameSettings)), "currentConfig"); - Settings = UserData.CreateStatic(typeof(GameSettings));*/ + Settings = UserData.CreateStatic(typeof(GameSettings)); + _consoleCommands = consoleCommands; } public void OverrideTraitors(bool o) @@ -405,38 +406,16 @@ namespace Barotrauma.LuaCs return new Signal(value, stepsTaken, sender, source, power, strength); } - private List luaAddedCommand = new List(); - public IEnumerable LuaAddedCommand { get { return luaAddedCommand; } } - - public bool IsCustomCommandPermitted(Identifier command) - { - DebugConsole.Command[] permitted = new DebugConsole.Command[] - { - DebugConsole.FindCommand("cl_reloadluacs"), - DebugConsole.FindCommand("cl_lua"), - DebugConsole.FindCommand("cl_toggleluadebug"), - }; - - foreach (var consoleCommand in LuaAddedCommand.Concat(permitted.AsEnumerable())) - { - if (consoleCommand.Names.Contains(command)) - { - return true; - } - } - - return false; - } - public void RemoveCommand(string name) { - for (var i = 0; i < DebugConsole.Commands.Count; i++) + _consoleCommands.RemoveCommand(name); + + for (var i = DebugConsole.Commands.Count - 1; i >= 0; i--) { foreach (var cmdname in DebugConsole.Commands[i].Names) { if (cmdname == name) { - luaAddedCommand.Remove(DebugConsole.Commands[i]); DebugConsole.Commands.RemoveAt(i); continue; } @@ -446,32 +425,28 @@ namespace Barotrauma.LuaCs public void AddCommand(string name, string help, LuaCsAction onExecute, LuaCsFunc getValidArgs = null, bool isCheat = false) { - var cmd = new DebugConsole.Command(name, help, (string[] arg1) => onExecute(new object[] { arg1 }), + _consoleCommands.RegisterCommand(name, help, + (string[] args) => + { + onExecute(new object[] { args }); + }, () => { - if (getValidArgs == null) return null; + if (getValidArgs == null) { return null; } var validArgs = getValidArgs(); if (validArgs is DynValue luaValue) { return luaValue.ToObject(); } return (string[][])validArgs; - }, isCheat); - - luaAddedCommand.Add(cmd); - DebugConsole.Commands.Add(cmd); + } + ); } - public List Commands => DebugConsole.Commands; - public bool IsDisposed => throw new NotImplementedException(); - public void AssignOnExecute(string names, object onExecute) => DebugConsole.AssignOnExecute(names, - (string[] a) => - { - //GameMain.LuaCs.CallLuaFunction(onExecute, new object[] { a }); - throw new NotImplementedException(); - }); + public void AssignOnExecute(string names, LuaCsAction onExecute) => + _consoleCommands.AssignOnExecute(names, args => onExecute(args)); public void SaveGame(string path) { @@ -532,7 +507,8 @@ namespace Barotrauma.LuaCs GameMain.Server.EndGame(); } - //public void AssignOnClientRequestExecute(string names, object onExecute) => DebugConsole.AssignOnClientRequestExecute(names, (Client a, Vector2 b, string[] c) => { GameMain.LuaCs.CallLuaFunction(onExecute, new object[] { a, b, c }); }); + public void AssignOnClientRequestExecute(string names, LuaCsAction onExecute) => + _consoleCommands.AssignOnClientRequestExecute(names, (Client client, Vector2 position, string[] args) => onExecute(client, position, args)); #endif @@ -541,10 +517,7 @@ namespace Barotrauma.LuaCs MapEntityUpdateInterval = 1; CharacterUpdateInterval = 1; - foreach (var cmd in luaAddedCommand) - { - DebugConsole.Commands.Remove(cmd); - } + _consoleCommands.RemoveRegisteredCommands(); } public FluentResults.Result Reset() From 23e0ff7aa68eccfa277bf0d3aaf5325f5ab15480 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 22 Mar 2026 12:42:23 -0300 Subject: [PATCH 230/288] Remove types that have been registered when unloading --- .../LuaCs/_Services/LuaScriptManagementService.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index b3770a90a..7542b1bd0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -23,10 +23,11 @@ using System.Threading.Tasks; using Barotrauma.LuaCs; using Barotrauma.LuaCs.Events; using System.Diagnostics; +using System.Reflection; namespace Barotrauma.LuaCs; -class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService +class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, IEventAssemblyUnloading { public Script? InternalScript => _script; @@ -463,8 +464,6 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _script = null; - // todo unregister everything - return FluentResults.Result.Ok(); } @@ -509,4 +508,12 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService return _script.Globals[tableName]; } + + public void OnAssemblyUnloading(Assembly assembly) + { + foreach (Type type in assembly.SafeGetTypes()) + { + UserData.UnregisterType(type, deleteHistory: true); + } + } } From 44a9ce241781475eeba3008f0a635acdfc898f83 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 22 Mar 2026 12:44:17 -0300 Subject: [PATCH 231/288] Guh? --- .../SharedSource/LuaCs/_Services/LuaScriptManagementService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 7542b1bd0..754946019 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -469,7 +469,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, public FluentResults.Result DisposePackageResources(ContentPackage package) { - return FluentResults.Result.Fail("Not supported for Lua"); + return FluentResults.Result.Ok(); } public FluentResults.Result DisposeAllPackageResources() From e62450c763cebd295534034b77c46bd5b5d20a95 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 22 Mar 2026 17:24:02 -0300 Subject: [PATCH 232/288] Actually subscribe to IEventAssemblyUnloading --- .../SharedSource/LuaCs/_Services/LuaScriptManagementService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 754946019..04c58caad 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -247,6 +247,8 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, private void RegisterLuaEvents() { + _eventService.Subscribe(this); + _eventService.RegisterLuaEventAlias("think", nameof(IEventUpdate.OnUpdate)); _eventService.RegisterLuaEventAlias("keyUpdate", nameof(IEventKeyUpdate.OnKeyUpdate)); _eventService.RegisterLuaEventAlias("afflictionUpdate", nameof(IEventAfflictionUpdate.OnAfflictionUpdate)); From ac329a70a4f9f1fca16f40adb9fd2f4d32183ce4 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Sun, 22 Mar 2026 18:01:19 -0400 Subject: [PATCH 233/288] - Reduced console spam when failing to load config file in release builds. - Added console logging for failing to unload assembly load ctx. - Removed unused StorageService instance. - Removed unused luacs settings. - Added plugin event service and event service dispatcher api. --- .../ClientSource/LuaCs/LuaCsSettingsMenu.cs | 82 ------------------ .../Config/SettingsShared.xml | 4 - .../SharedSource/LuaCs/LuaCsSetup.cs | 69 ++------------- .../LuaCs/_Services/ConfigService.cs | 9 +- .../LuaCs/_Services/EventService.cs | 24 ++++++ .../LuaCs/_Services/LuaCsInfoProvider.cs | 26 +----- .../_Services/LuaScriptManagementService.cs | 2 + .../_Services/PluginManagementService.cs | 84 ++++++++++++++----- .../_Services/_Interfaces/IEventService.cs | 12 +++ .../_Interfaces/ILuaCsInfoProvider.cs | 20 ----- .../_Services/_Lua/LuaClasses/LuaCsUtility.cs | 10 +-- 11 files changed, 123 insertions(+), 219 deletions(-) delete mode 100644 Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs deleted file mode 100644 index 8b1004165..000000000 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSettingsMenu.cs +++ /dev/null @@ -1,82 +0,0 @@ -using Microsoft.Xna.Framework; - -namespace Barotrauma -{ - static class LuaCsSettingsMenu - { - private static GUIFrame frame; - - public static void Open(RectTransform rectTransform) - { - Close(); - - frame = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.6f), rectTransform, Anchor.Center)); - - GUIListBox list = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.95f), frame.RectTransform, Anchor.Center), false); - - new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), list.Content.RectTransform), "LuaCs Settings", textAlignment: Alignment.Center); - - - new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Enable CSharp Scripting") - { - Selected = LuaCsSetup.Instance.IsCsEnabled, - ToolTip = "This enables CSharp Scripting for mods to use, WARNING: CSharp is NOT sandboxed, be careful with what mods you download.", - OnSelected = (GUITickBox tick) => - { - LuaCsSetup.Instance.IsCsEnabled = tick.Selected; - return true; - } - }; - - new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Disable Error GUI Overlay") - { - Selected = LuaCsSetup.Instance.DisableErrorGUIOverlay, - ToolTip = "", - OnSelected = (GUITickBox tick) => - { - LuaCsSetup.Instance.DisableErrorGUIOverlay = tick.Selected; - return true; - } - }; - - new GUITickBox(new RectTransform(new Vector2(0.8f, 0.1f), list.Content.RectTransform), "Hide usernames In Error Logs") - { - Selected = LuaCsSetup.Instance.HideUserNamesInLogs, - ToolTip = "Hides the operating system username when displaying error logs (eg your username on windows).", - OnSelected = (GUITickBox tick) => - { - LuaCsSetup.Instance.HideUserNamesInLogs = tick.Selected; - return true; - } - }; - - new GUIButton(new RectTransform(new Vector2(1f, 0.1f), list.Content.RectTransform), $"Remove Client-Side LuaCs", style: "GUIButtonSmall") - { - ToolTip = "Remove Client-Side LuaCs.", - OnClicked = (tb, userdata) => - { - LuaCsInstaller.Uninstall(); - return true; - } - }; - - new GUIButton(new RectTransform(new Vector2(0.8f, 0.01f), frame.RectTransform, Anchor.BottomCenter) - { - RelativeOffset = new Vector2(0f, 0.05f) - }, "Close") - { - OnClicked = (GUIButton button, object obj) => - { - Close(); - return true; - } - }; - } - - public static void Close() - { - frame?.Parent.RemoveChild(frame); - frame = null; - } - } -} diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml index 4fcdaf06f..6546e5370 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml @@ -1,11 +1,7 @@  - - - - diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index ec9caba49..a813a33c3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -82,7 +82,6 @@ namespace Barotrauma private LuaGame _game; public LuaGame Game => _game ??= _servicesProvider.GetService(); - internal IStorageService StorageService => _servicesProvider.GetService(); /// /// Whether C# plugin code is enabled. @@ -95,16 +94,6 @@ namespace Barotrauma private ISettingBase _isCsEnabled; - /// - /// Whether the popup error GUI should be hidden/suppressed. - /// - public bool DisableErrorGUIOverlay - { - get => _disableErrorGUIOverlay?.Value ?? false; - internal set => _disableErrorGUIOverlay?.TrySetValue(value); - } - private ISettingBase _disableErrorGUIOverlay; - /// /// Whether usernames are anonymized or show in logs. /// @@ -115,66 +104,25 @@ namespace Barotrauma } private ISettingBase _hideUserNamesInLogs; - /// - /// The SteamId of the Workshop LuaCs CPackage in use, if available. - /// - public ulong LuaForBarotraumaSteamId + public static ContentPackage GetLuaCsPackage() { - get => _luaForBarotraumaSteamId?.Value ?? 0; - internal set => _luaForBarotraumaSteamId?.TrySetValue(value); - } - private ISettingBase _luaForBarotraumaSteamId; - - /// - /// Whether the maximum message size over the network should be restricted. - /// - public bool RestrictMessageSize - { - get => _restrictMessageSize?.Value ?? false; - internal set => _restrictMessageSize?.TrySetValue(value); - } - private ISettingBase _restrictMessageSize; - - /// - /// The local save path for all local data storage for mods. - /// - public string LocalDataSavePath - { - get => _localDataSavePath?.Value ?? Path.Combine(Directory.GetCurrentDirectory(), "/Data/Mods"); - internal set => _localDataSavePath?.TrySetValue(value); - } - private ISettingBase _localDataSavePath; - - void LoadLuaCsConfig() - { - var luaCsPackage = ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(PackageId), null) + return ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(PackageId), null) ?? ContentPackageManager.LocalPackages.FirstOrDefault(cp => cp.NameMatches(PackageId)) ?? ContentPackageManager.WorkshopPackages.FirstOrDefault(cp => cp.NameMatches(PackageId)); + } + + void LoadLuaCsConfig() + { + var luaCsPackage = GetLuaCsPackage(); _isCsEnabled = ConfigService.TryGetConfig>(luaCsPackage, "IsCsEnabled", out var val1) ? val1 : null; - _disableErrorGUIOverlay = - ConfigService.TryGetConfig>(luaCsPackage, "DisableErrorGUIOverlay", out var val3) - ? val3 - : null; _hideUserNamesInLogs = ConfigService.TryGetConfig>(luaCsPackage, "HideUserNamesInLogs", out var val4) ? val4 : null; - _luaForBarotraumaSteamId = - ConfigService.TryGetConfig>(luaCsPackage, "LuaForBarotraumaSteamId", out var val5) - ? val5 - : null; - _restrictMessageSize = - ConfigService.TryGetConfig>(luaCsPackage, "RestrictMessageSize", out var val7) - ? val7 - : null; - _localDataSavePath = - ConfigService.TryGetConfig>(luaCsPackage, "LocalDataSavePath", out var val8) - ? val8 - : null; } private IServicesProvider SetupServicesProvider() @@ -485,10 +433,7 @@ namespace Barotrauma void DisposeLuaCsConfig() { _isCsEnabled = null; - _disableErrorGUIOverlay = null; _hideUserNamesInLogs = null; - _luaForBarotraumaSteamId = null; - _restrictMessageSize = null; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs index d84e32473..8357d31b9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs @@ -450,17 +450,21 @@ public sealed partial class ConfigService : IConfigService if (_storageService.LoadLocalXml(setting.OwnerPackage, SaveDataFileName) is not { } saveFileResult) { +#if DEBUG return FluentResults.Result.Fail( $"{nameof(LoadSavedValueForConfig)}: Could not open save file for setting [{setting.OwnerPackage.Name}.{setting.InternalName}]"); +#endif + return FluentResults.Result.Ok(); } if (saveFileResult is { IsFailed: true }) { #if DEBUG _logger.LogResults(saveFileResult.ToResult()); -#endif return FluentResults.Result.Fail( $"{nameof(LoadSavedValueForConfig)}: Could not open save file for setting [{setting.OwnerPackage.Name}.{setting.InternalName}]"); +#endif + return FluentResults.Result.Ok(); } if (saveFileResult.Value.Root is not {} rootElement @@ -472,7 +476,10 @@ public sealed partial class ConfigService : IConfigService if (rootElement.GetChildElement(XmlConvert.EncodeLocalName(setting.OwnerPackage.Name.Trim()), StringComparison.InvariantCultureIgnoreCase) ?.GetChildElement(setting.InternalName, StringComparison.InvariantCultureIgnoreCase) is not {} cfgValueElement) { +#if DEBUG return FluentResults.Result.Fail($"{nameof(LoadSavedValueForConfig)}: Could not find saved value for setting:[{setting.OwnerPackage.Name}.{setting.InternalName}]"); +#endif + return FluentResults.Result.Ok(); } return FluentResults.Result.OkIf(setting.TrySetValue(cfgValueElement), new Error($"Failed to set value for [{setting.OwnerPackage.Name}.{setting.InternalName}]")); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs index 9971b369a..862f507c6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs @@ -6,6 +6,8 @@ using OneOf; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; @@ -55,6 +57,7 @@ public partial class EventService : IEventService private readonly ConcurrentDictionary, IEvent>> _subscribers = new(); private readonly ConcurrentDictionary RunnerFactory)> _luaAliasEventFactory = new(); private readonly ConcurrentDictionary> _luaLegacyEventsSubscribers = new(); + private readonly ConcurrentDictionary _subscribedEventDispatchers = new(); #region LifeCycle @@ -316,9 +319,30 @@ public partial class EventService : IEventService } } + foreach (var dispatchers in _subscribedEventDispatchers.ToImmutableArray()) + { + dispatchers.Value.PublishEvent(action); + } + return results; } + public void AddDispatcherEventService(IEventService eventService) + { + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + _subscribedEventDispatchers.TryAdd(eventService, eventService); + } + + public void RemoveDispatcherEventService(IEventService eventService) + { + using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + _subscribedEventDispatchers.TryRemove(eventService, out _); + } + #region LuaPatcherAdapter public string Patch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, LuaCsHook.HookMethodType hookType = LuaCsHook.HookMethodType.Before) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs index edf63fc32..5225e710b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs @@ -13,35 +13,15 @@ public sealed class LuaCsInfoProvider : ILuaCsInfoProvider public bool IsDisposed => false; public bool IsCsEnabled => LuaCsSetup.Instance.IsCsEnabled; - public bool DisableErrorGUIOverlay => LuaCsSetup.Instance.DisableErrorGUIOverlay; public bool HideUserNamesInLogs => LuaCsSetup.Instance.HideUserNamesInLogs; - public ulong LuaForBarotraumaSteamId => LuaCsSetup.Instance.LuaForBarotraumaSteamId; - public bool RestrictMessageSize => LuaCsSetup.Instance.RestrictMessageSize; - public string LocalDataSavePath => LuaCsSetup.Instance.LocalDataSavePath; public RunState CurrentRunState => LuaCsSetup.Instance.CurrentRunState; public ContentPackage LuaCsForBarotraumaPackage { get { - var luaCs = FirstOrDefaultLua(ContentPackageManager.EnabledPackages.All); - if (luaCs == null) - { - luaCs = FirstOrDefaultLua(ContentPackageManager.LocalPackages.Regular); - } - - if (luaCs == null) - { - luaCs = FirstOrDefaultLua(ContentPackageManager.WorkshopPackages.Regular); - } - - return luaCs; - - ContentPackage FirstOrDefaultLua(IEnumerable packages) - { - return packages.FirstOrDefault(p => - p.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase) - || p.Name.Equals("Lua for Barotrauma", StringComparison.InvariantCultureIgnoreCase)); - } + 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)); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 04c58caad..502021b23 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -214,11 +214,13 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, if (!LuaCsFile.CanReadFromPath(file)) { + // TODO: Replace with LuaScriptLoader IsFileAccessible. throw new ScriptRuntimeException($"dofile: File access to {file} not allowed."); } if (!LuaCsFile.Exists(file)) { + // TODO: Replace with LuaScriptLoader IsFileAccessible. throw new ScriptRuntimeException($"dofile: File {file} not found."); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index 639f27045..db7d40f17 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -195,6 +195,8 @@ public class PluginManagementService : IAssemblyManagementService private Lazy _eventService; private Lazy _configService; private Lazy _luaScriptManagementService; + private IEventService _pluginEventService; + private Lazy _pluginLuaPatcherService; private readonly ConcurrentDictionary _assemblyLoaders = new(); private readonly ConcurrentDictionary _pluginPackageLookup = new(); private readonly ConcurrentDictionary> _pluginInstances = new(); @@ -208,7 +210,8 @@ public class PluginManagementService : IAssemblyManagementService ILoggerService logger, Lazy eventService, Lazy luaScriptManagementService, - Lazy configService) + Lazy configService, + Lazy pluginLuaPatcherService) { _assemblyLoaderFactory = assemblyLoaderFactory; _storageService = storageService; @@ -216,19 +219,22 @@ public class PluginManagementService : IAssemblyManagementService _eventService = eventService; _luaScriptManagementService = luaScriptManagementService; _configService = configService; + _pluginLuaPatcherService = pluginLuaPatcherService; } private ServiceContainer CreatePluginServiceContainer() { var container = new ServiceContainer(new ContainerOptions() { - EnablePropertyInjection = true, - + EnablePropertyInjection = true }); + _pluginEventService ??= new EventService(_logger, _pluginLuaPatcherService.Value); + _eventService.Value.AddDispatcherEventService(_pluginEventService); + container.Register(fac => _logger); container.Register(fac => _storageService); - container.Register(fac => _eventService.Value); + container.Register(fac => _pluginEventService); container.Register(fac => this); container.Register(fac => _luaScriptManagementService.Value); container.Register(fac => _configService.Value); @@ -707,6 +713,37 @@ public class PluginManagementService : IAssemblyManagementService _assemblyLoaders.Clear(); GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true); + +#if DEBUG + // Print still loaded assembly load ctx after giving some time + CoroutineManager.Invoke(() => + { + if (!_unloadingAssemblyLoaders.Any()) + { + return; + } + + StringBuilder sb = new StringBuilder(); + + sb.AppendLine("The following ContentPackages have not unloaded their assemblies:"); + + foreach (var kvp in _unloadingAssemblyLoaders.ToImmutableArray()) + { + sb.AppendLine($"- '{kvp.Value.Name}'"); + } + + + // Use DebugConsole in case logger is null by the time this executes. + if (_logger is null) + { + DebugConsole.LogError(sb.ToString()); + } + else + { + _logger.LogWarning(sb.ToString()); + } + }, 3.0f); +#endif return results; } @@ -714,24 +751,29 @@ public class PluginManagementService : IAssemblyManagementService private FluentResults.Result UnsafeDisposeManagedTypeInstances() { var results = new FluentResults.Result(); + + if (!_pluginInstances.IsEmpty) + { + foreach (var instance in _pluginInstances.SelectMany(kvp => kvp.Value)) + { + try + { + instance.Dispose(); + } + catch (Exception e) + { + results.WithError(new ExceptionalError(e)); + continue; + } + } + } + + if (_pluginEventService is not null) + { + _eventService.Value.RemoveDispatcherEventService(_pluginEventService); + _pluginEventService = null; + } _pluginInjectorContainer = null; - if (_pluginInstances.IsEmpty) - { - return FluentResults.Result.Ok(); - } - - foreach (var instance in _pluginInstances.SelectMany(kvp => kvp.Value)) - { - try - { - instance.Dispose(); - } - catch (Exception e) - { - results.WithError(new ExceptionalError(e)); - continue; - } - } _pluginInstances.Clear(); _pluginPackageLookup.Clear(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IEventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IEventService.cs index 39c7ad347..867742321 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IEventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IEventService.cs @@ -37,4 +37,16 @@ public interface IEventService : IReusableService, ILuaEventService /// /// FluentResults.Result PublishEvent(Action action) where T : class, IEvent; + + /// + /// Adds an event service that will receive all published events. + /// + /// + void AddDispatcherEventService(IEventService eventService); + + /// + /// Removes an event service from the dispatcher list. + /// + /// + void RemoveDispatcherEventService(IEventService eventService); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaCsInfoProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaCsInfoProvider.cs index 2cf93ed94..bf5e38c08 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaCsInfoProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaCsInfoProvider.cs @@ -10,30 +10,10 @@ public interface ILuaCsInfoProvider : IService /// public bool IsCsEnabled { get; } - /// - /// Whether the popup error GUI should be hidden/suppressed. - /// - public bool DisableErrorGUIOverlay { get; } - /// /// Whether usernames are anonymized or show in logs. /// public bool HideUserNamesInLogs { get; } - - /// - /// The SteamId of the Workshop LuaCs CPackage in use, if available. - /// - public ulong LuaForBarotraumaSteamId { get; } - - /// - /// Restrict the maximum size of messages sent over the network. - /// - public bool RestrictMessageSize { get; } - - /// - /// The local save path for all local data storage for mods. - /// - public string LocalDataSavePath { get; } /// /// The current state of the Execution State Machine. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsUtility.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsUtility.cs index b4aca75b9..dc7b0b835 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsUtility.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaCsUtility.cs @@ -52,19 +52,17 @@ namespace Barotrauma public static bool CanWriteToPath(string path) { + const long LuaCsPackageId = 2559634234; + string getFullPath(string p) => System.IO.Path.GetFullPath(p).CleanUpPath(); path = getFullPath(path); bool pathStartsWith(string prefix) => path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase); - foreach (var package in ContentPackageManager.AllPackages) + if (pathStartsWith(getFullPath(LuaCsSetup.GetLuaCsPackage().Path))) { - if (package.UgcId.ValueEquals(new SteamWorkshopId(LuaCsSetup.Instance.LuaForBarotraumaSteamId)) - && pathStartsWith(getFullPath(package.Path))) - { - return false; - } + return false; } if (pathStartsWith(getFullPath(string.IsNullOrEmpty(GameSettings.CurrentConfig.SavePath) ? SaveUtil.DefaultSaveFolder : GameSettings.CurrentConfig.SavePath))) From cf3a50d6b544fa11da8036e2a5ee361ce8d44fbe Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Sun, 22 Mar 2026 18:05:21 -0400 Subject: [PATCH 234/288] - Added back modifyChatMessage. - Added missing commit file for previous commit. --- .../ServerSource/Characters/CharacterNetworking.cs | 2 +- .../BarotraumaServer/ServerSource/Networking/GameServer.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs index 65d87e934..e17618343 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Characters/CharacterNetworking.cs @@ -811,7 +811,7 @@ namespace Barotrauma var tempBuffer = new ReadWriteMessage(); WriteStatus(tempBuffer, forceAfflictionData: true); - if (msgLengthBeforeStatus + tempBuffer.LengthBytes >= 255 && restrictMessageSize && (LuaCsSetup.Instance.RestrictMessageSize)) + if (msgLengthBeforeStatus + tempBuffer.LengthBytes >= 255 && restrictMessageSize) { msg.WriteBoolean(false); if (msgLengthBeforeStatus < 255) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index ac6d9374a..041b1936d 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -3969,7 +3969,11 @@ namespace Barotrauma.Networking //send to chat-linked wifi components Signal s = new Signal(message, sender: senderCharacter, source: senderRadio.Item); senderRadio.TransmitSignal(s, sentFromChat: true); - } + } + + var hookChatMsg = ChatMessage.Create(senderName, message, (ChatMessageType)type, senderCharacter, senderClient, changeType); + + var should = LuaCsSetup.Instance.Hook.Call("modifyChatMessage", hookChatMsg, senderRadio); //check which clients can receive the message and apply distance effects foreach (Client client in ConnectedClients) From 7c8dd452cfb9c7a4c2fa9f9591df0da125420a42 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 22 Mar 2026 01:54:54 -0400 Subject: [PATCH 235/288] SettingsList implementation. Merge conflict resolution. --- .../SharedSource/LuaCs/Data/SettingList.cs | 98 +++++++++++++++++++ .../SettingsFactoryRegistrationProvider.cs | 25 ++++- 2 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingList.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingList.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingList.cs new file mode 100644 index 000000000..ad4c89665 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingList.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Xml.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Toolkit.Diagnostics; + +namespace Barotrauma.LuaCs.Data; + +public class SettingList : SettingEntry, ISettingList where T : IEquatable, IConvertible +{ + public class LFactory : ISettingBase.IFactory> + { + public ISettingList CreateInstance(IConfigInfo configInfo, Func, bool> valueChangePredicate) + { + Guard.IsNotNull(configInfo, nameof(configInfo)); + return new SettingList(configInfo, valueChangePredicate); + } + } + + public SettingList(IConfigInfo configInfo, Func, bool> valueChangePredicate) : base(configInfo, valueChangePredicate) + { + if (!( + typeof(T).IsEnum || + typeof(T).IsPrimitive || + typeof(T) == typeof(string))) + { + ThrowHelper.ThrowArgumentException($"{nameof(ISettingBase)}: The type of {nameof(T)} is not an allowed type."); + } + ValueChangePredicate = valueChangePredicate; + + var valuesElements = ConfigInfo.Element.GetChildElement("Values")?.GetChildElements("Value")?.ToImmutableArray(); + + Guard.IsNotNull(valuesElements, this.InternalName); + if (valuesElements.Value.IsEmpty) + { + ThrowHelper.ThrowArgumentNullException($"{this.InternalName}: Could not find any values in list!"); + } + + foreach (var element in valuesElements.Value) + { + if (!TryConvert(element, out var v1)) + { + ThrowHelper.ThrowArgumentException($"{this.InternalName}: Error while parsing list values"); + } + _valuesList.Add(v1); + } + + if (TryConvert(ConfigInfo.Element, out var v) && _valuesList.Contains(v)) + { + Value = v; + DefaultValue = v; + } + else + { + Value = _valuesList[0]; + DefaultValue = _valuesList[0]; + } + + + bool TryConvert(XElement element, out T value) + { + try + { + value = (T)Convert.ChangeType(element.GetAttributeString("Value", null), typeof(T)); + return true; + } + catch (Exception e) when (e is InvalidCastException or ArgumentNullException) + { + value = default(T); + return false; + } + } + } + + private readonly List _valuesList = new(); + + public override bool TrySetValue(T value) + { + if (!_valuesList.Contains(value)) + { + return false; + } + + return base.TrySetValue(value); + } + + public bool TrySetValueByIndex(int index) + { + throw new NotImplementedException(); + } + + public IReadOnlyList Options => _valuesList.AsReadOnly(); + + public IReadOnlyList StringOptions => _valuesList.Select(e => e.ToString()).ToImmutableArray(); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs index 31d85e8b0..883699e8b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs @@ -22,8 +22,6 @@ public class SettingsEntryRegistrar : ISettingsRegistrationProvider public void RegisterTypeProviders(IConfigService configService, Func, bool> valueChangePredicate) { - // TODO: Replace this with a proper IFactory lookup pattern. - // ISettingBase RegisterSettingEntry(configService, "bool", valueChangePredicate); RegisterSettingEntry(configService, "byte", valueChangePredicate); RegisterSettingEntry(configService, "sbyte", valueChangePredicate); @@ -58,8 +56,31 @@ public class SettingsEntryRegistrar : ISettingsRegistrationProvider IsValueChangeAllowed(cfgInfo.Info, val, valueChangePredicate)); }); #endif + + RegisterSettingList(configService, "listBool", valueChangePredicate); + RegisterSettingList(configService, "listByte", valueChangePredicate); + RegisterSettingList(configService, "listSbyte", valueChangePredicate); + RegisterSettingList(configService, "listShort", valueChangePredicate); + RegisterSettingList(configService, "listUshort", valueChangePredicate); + RegisterSettingList(configService, "listInt", valueChangePredicate); + RegisterSettingList(configService, "listUint", valueChangePredicate); + RegisterSettingList(configService, "listLong", valueChangePredicate); + RegisterSettingList(configService, "listUlong", valueChangePredicate); + RegisterSettingList(configService, "listString", valueChangePredicate); + RegisterSettingList(configService, "listFloat", valueChangePredicate); + RegisterSettingList(configService, "listSingle", valueChangePredicate); + RegisterSettingList(configService, "listDouble", valueChangePredicate); } + private void RegisterSettingList(IConfigService configService, string typeName, Func, bool> valueChangePredicate) where T : IEquatable, IConvertible + { + configService.RegisterSettingTypeInitializer(typeName, cfgInfo => + { + return new SettingList.LFactory().CreateInstance(cfgInfo.Info, (val) => + IsValueChangeAllowed(cfgInfo.Info, val, valueChangePredicate)); + }); + } + private void RegisterSettingEntry(IConfigService configService, string typeName, Func, bool> valueChangePredicate) where T : IEquatable, IConvertible { configService.RegisterSettingTypeInitializer(typeName, cfgInfo => From cf6104f630f6134620e1f64ccc5f3443a0b5da4d Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 22 Mar 2026 23:12:23 -0400 Subject: [PATCH 236/288] Added Dropdown menu implementation for settings list. --- .../SharedSource/LuaCs/Data/SettingList.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingList.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingList.cs index ad4c89665..82e7af94f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingList.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingList.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Xml.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.Toolkit.Diagnostics; +using Microsoft.Xna.Framework; namespace Barotrauma.LuaCs.Data; @@ -95,4 +96,15 @@ public class SettingList : SettingEntry, ISettingList where T : IEquata public IReadOnlyList Options => _valuesList.AsReadOnly(); public IReadOnlyList StringOptions => _valuesList.Select(e => e.ToString()).ToImmutableArray(); + +#if CLIENT + public override void AddDisplayComponent(GUILayoutGroup layoutGroup, Vector2 relativeSize, Action onSerializedValue) + { + GUIUtil.Dropdown(layoutGroup, (T val) => val.ToString(), null, Options, Value, (T val) => + { + onSerializedValue?.Invoke(val.ToString()); + }, new Vector2(relativeSize.X, 1f)); + } +#endif + } From 5c136aab4138592ab590fb5d11ba37a1447a82ae Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 22 Mar 2026 23:54:36 -0400 Subject: [PATCH 237/288] Added test debug settings for settings list. --- .../LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml index 17f0857cb..7a5f97a3e 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml @@ -8,6 +8,14 @@ + + + + + + + + From 26ac37ed468391efb3c3ee00ae3eca21e98d5783 Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sat, 21 Mar 2026 21:45:41 -0400 Subject: [PATCH 238/288] Added fallback native assembly resolver. --- .../LuaCs/_Plugins/IAssemblyLoaderService.cs | 4 ++ .../_Services/LuaScriptManagementService.cs | 5 +- .../_Services/PluginManagementService.cs | 56 ++++++++++++++++++- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs index 8a6115bc1..00cc5042b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs @@ -44,6 +44,10 @@ public interface IAssemblyLoaderService : IService /// Guid Id { get; } /// + /// The owner content package. + /// + ContentPackage OwnerPackage { get; } + /// /// Indicates that the assemblies in this load context are metadata references only and not /// intended for execution. /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 502021b23..ccae1545c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -116,7 +116,8 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, commands.RegisterCommand("cl_toggleluadebug", "Toggles the MoonSharp Debug Server.", (string[] args) => { - int port = 41912; + DebugConsole.Log($"This command is currently not implemented. Please open a github issue if you need this feature."); + /*int port = 41912; if (args.Length > 0) { @@ -124,7 +125,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, } throw new NotImplementedException(); - //GameMain.LuaCs.ToggleDebugger(port); + //GameMain.LuaCs.ToggleDebugger(port);*/ }); #elif SERVER diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index db7d40f17..2cbb1a657 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Loader; using System.Text; using System.Threading; @@ -201,6 +202,7 @@ public class PluginManagementService : IAssemblyManagementService private readonly ConcurrentDictionary _pluginPackageLookup = new(); private readonly ConcurrentDictionary> _pluginInstances = new(); private readonly ConditionalWeakTable _unloadingAssemblyLoaders = new(); + private readonly ConcurrentBag _loadedNativeLibraries = new(); private readonly AsyncReaderWriterLock _operationsLock = new(); private ServiceContainer _pluginInjectorContainer; @@ -638,10 +640,39 @@ public class PluginManagementService : IAssemblyManagementService return sourceCode.Replace("GameMain.LuaCs", "LuaCsSetup.Instance"); } - private IntPtr OnAssemblyLoaderResolvingUnmanaged(Assembly arg1, string arg2) + private IntPtr OnAssemblyLoaderResolvingUnmanaged(Assembly callerAssembly, string targetAssemblyName) { - // TODO: Implement extern assembly lookup for Native/Unmanaged Assemblies. - throw new NotImplementedException(); + Guard.IsNull(callerAssembly, nameof(callerAssembly)); + Guard.IsNullOrWhiteSpace(targetAssemblyName, nameof(targetAssemblyName)); + + if (AssemblyLoadContext.GetLoadContext(callerAssembly) is not IAssemblyLoaderService loaderService) + { + return IntPtr.Zero; + } + + var targetDirectory = Path.GetFullPath(loaderService.OwnerPackage.Dir); + if (!targetAssemblyName.TrimEnd().EndsWith(".dll")) + { + targetAssemblyName += ".dll"; + } + + var res = _storageService.FindFilesInPackage(loaderService.OwnerPackage, string.Empty, targetAssemblyName, true); + + if (res.IsFailed || !res.Value.Any()) + { + return IntPtr.Zero; + } + + foreach (var path in res.Value) + { + if (System.Runtime.InteropServices.NativeLibrary.TryLoad(path, out IntPtr asmPtr)) + { + _loadedNativeLibraries.Add(asmPtr); + return asmPtr; + } + } + + return IntPtr.Zero; } private Assembly OnAssemblyLoaderResolvingManaged(IAssemblyLoaderService requestingLoader, AssemblyName searchName) @@ -745,6 +776,25 @@ public class PluginManagementService : IAssemblyManagementService }, 3.0f); #endif + // clear native libraries + if (_loadedNativeLibraries.Any()) + { + foreach (var ptr in _loadedNativeLibraries) + { + try + { + System.Runtime.InteropServices.NativeLibrary.Free(ptr); + } + catch + { + // ignored + continue; + } + } + + _loadedNativeLibraries.Clear(); + } + return results; } From 3dd9cfa741d07485d64febc4aaf9fcf0e445dc8b Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Sat, 21 Mar 2026 04:27:02 -0400 Subject: [PATCH 239/288] Implemented plugin assembly lookup api. --- .../_Services/PluginManagementService.cs | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index 2cbb1a657..36d7e8353 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -833,6 +833,36 @@ public class PluginManagementService : IAssemblyManagementService public Result GetLoadedAssembly(OneOf assemblyName, in Guid[] excludedContexts) { - throw new NotImplementedException(); + using var _ = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + IService.CheckDisposed(this); + + var guids = excludedContexts; + return assemblyName.Match((AssemblyName asm) => + { + foreach (var ass in _assemblyLoaders.Values + .Where(al => guids.Length == 0 || !guids.Contains(al.Id)) + .SelectMany(al => al.Assemblies) + .ToImmutableArray()) + { + if (ass.GetName() == asm) + { + return ass; + } + } + + return null; + }, + (string asmName) => + { + foreach (var ass in _assemblyLoaders.Values.SelectMany(al => al.Assemblies)) + { + if (ass.GetName().Name?.Equals(asmName) ?? ass.GetName().FullName.Equals(asmName)) + { + return ass; + } + } + + return null; + }); } } From 1e76377b634ebe17a62f1e7fec07093ab712e33e Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 23 Mar 2026 07:51:49 -0400 Subject: [PATCH 240/288] - Added full implementation of lua event "modifyChatMessage" in IEvents. --- .../ServerSource/Networking/GameServer.cs | 14 +++++++- .../SharedSource/LuaCs/IEvents.cs | 33 +++++++++++++++++++ .../_Services/LuaScriptManagementService.cs | 1 + 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs index 041b1936d..ff207c9f4 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs @@ -3973,8 +3973,20 @@ namespace Barotrauma.Networking var hookChatMsg = ChatMessage.Create(senderName, message, (ChatMessageType)type, senderCharacter, senderClient, changeType); - var should = LuaCsSetup.Instance.Hook.Call("modifyChatMessage", hookChatMsg, senderRadio); + bool shouldSkip = false; + LuaCsSetup.Instance.EventService.PublishEvent(sub => + { + if (sub.OnModifyMessagePredicate(hookChatMsg, senderRadio) is true) + { + shouldSkip = true; + } + }); + if (shouldSkip) + { + return; + } + //check which clients can receive the message and apply distance effects foreach (Client client in ConnectedClients) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index d7fa5761b..095ec22bd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -79,6 +79,39 @@ internal interface IEventSettingInstanceLifetime : IEvent +/// Allows the user to modify a chat message on the server before it is sent to clients, or reject the message altogether. +/// +/// Legacy Lua Event Name: "modifyChatMessage" +internal interface IEventModifyChatMessage : IEvent +{ + bool? OnModifyMessagePredicate(ChatMessage message, WifiComponent senderRadio); + + static IEventModifyChatMessage IEvent.GetLuaRunner(IDictionary luaFunc) => + new LuaWrapper(luaFunc); + + public sealed class LuaWrapper : LuaWrapperBase, IEventModifyChatMessage + { + public LuaWrapper(IDictionary luaFuncs) : base(luaFuncs) + { + } + + /// + /// Called before a chat message is sent to clients. + /// + /// Message to be sent. + /// [CanBeNull] The source , if any. + /// Whether to reject the message. + public bool? OnModifyMessagePredicate(ChatMessage message, WifiComponent senderRadio) + { + return (bool?)LuaFuncs[nameof(OnModifyMessagePredicate)](message, senderRadio); + } + } +} + +#endif + internal interface IEventAfflictionUpdate : IEvent { void OnAfflictionUpdate(Affliction affliction, CharacterHealth characterHealth, Limb targetLimb, float deltaTime); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index ccae1545c..af8d61df0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -312,6 +312,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, // Compatibility _eventService.RegisterLuaEventAlias("clientConnected", nameof(IEventClientConnected.OnClientConnected)); _eventService.RegisterLuaEventAlias("clientDisconnected", nameof(IEventClientDisconnected.OnClientDisconnected)); + _eventService.RegisterLuaEventAlias("modifyChatMessage", nameof(IEventModifyChatMessage.OnModifyMessagePredicate)); #elif CLIENT _eventService.RegisterLuaEventAlias("netMessageReceived", nameof(IEventServerRawNetMessageReceived.OnReceivedServerNetMessage)); #endif From 28acf02de03821301f74ab8d07aae3004ab7037b Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Mon, 23 Mar 2026 08:47:19 -0400 Subject: [PATCH 241/288] Updated client-side/common files tracking list for luacsinstaller. --- .../SharedSource/LuaCs/LuaCsInstaller.cs | 44 +++++++++++++++---- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsInstaller.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsInstaller.cs index ee5c43e83..002b059b3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsInstaller.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsInstaller.cs @@ -9,10 +9,19 @@ namespace Barotrauma { private static string[] trackingFiles = new string[] { - "Barotrauma.dll", "Barotrauma.deps.json", "Barotrauma.pdb", "BarotraumaCore.dll", "BarotraumaCore.pdb", - "0Harmony.dll", "Mono.Cecil.dll", + /* Barotrauma */ + "Barotrauma.dll", + "Barotrauma.deps.json", + "Barotrauma.pdb", + "BarotraumaCore.dll", + "BarotraumaCore.pdb", + + /* HarmonyX Package */ + "0Harmony.dll", + "Mono.Cecil.dll", "Sigil.dll", - "Mono.Cecil.Mdb.dll", "Mono.Cecil.Pdb.dll", + "Mono.Cecil.Mdb.dll", + "Mono.Cecil.Pdb.dll", "Mono.Cecil.Rocks.dll", "MonoMod.Backports.dll", "MonoMod.Core.dll", @@ -20,15 +29,32 @@ namespace Barotrauma "MonoMod.RuntimeDetour.dll", "MonoMod.Utils.dll", "MonoMod.Iced.dll", - "MoonSharp.Interpreter.dll", "MoonSharp.VsCodeDebugger.dll", + + /* MoonSharp */ + "MoonSharp.Interpreter.dll", + "MoonSharp.VsCodeDebugger.dll", - "Microsoft.CodeAnalysis.dll", "Microsoft.CodeAnalysis.CSharp.dll", - "Microsoft.CodeAnalysis.CSharp.Scripting.dll", "Microsoft.CodeAnalysis.Scripting.dll", - - "System.Reflection.Metadata.dll", "System.Collections.Immutable.dll", + /* Microsoft SDKs */ + "Microsoft.CodeAnalysis.dll", + "Microsoft.CodeAnalysis.CSharp.dll", + "Microsoft.CodeAnalysis.CSharp.Scripting.dll", + "Microsoft.CodeAnalysis.Scripting.dll", + "Microsoft.Toolkit.Diagnostics.dll", + "Microsoft.Extensions.Logging.Abstractions.dll", + "System.Reflection.Metadata.dll", + "System.Collections.Immutable.dll", "System.Runtime.CompilerServices.Unsafe.dll", - "Publicized/DedicatedServer.dll", "Publicized/Barotrauma.dll" + /* Assembly Script Dependencies */ + "Publicized/DedicatedServer.dll", + "Publicized/Barotrauma.dll", + "Publicized/BarotraumaCore.dll", + + /* Other NuGet Packages */ + "Basic.Reference.Assemblies.Net80.dll", + "FluentResults.dll", + "LightInject.dll", + "OneOf.dll" }; private static void CreateMissingDirectory() From b70b6bf3265263787840eff01f74524336dca5c1 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 24 Mar 2026 02:44:08 -0400 Subject: [PATCH 242/288] Fixed deadlock scenario in the event dispatcher CRUD API. --- .../SharedSource/LuaCs/_Services/EventService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs index 862f507c6..34102b8cc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs @@ -280,7 +280,7 @@ public partial class EventService : IEventService public void ClearAllEventSubscribers() where T : class, IEvent { - using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); _subscribers.TryRemove(typeof(T), out _); } @@ -329,7 +329,7 @@ public partial class EventService : IEventService public void AddDispatcherEventService(IEventService eventService) { - using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); _subscribedEventDispatchers.TryAdd(eventService, eventService); @@ -337,7 +337,7 @@ public partial class EventService : IEventService public void RemoveDispatcherEventService(IEventService eventService) { - using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); + using var lck = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); IService.CheckDisposed(this); _subscribedEventDispatchers.TryRemove(eventService, out _); From c1fdedf955afa04cd37bfc2203882b6e3f397754 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 24 Mar 2026 10:36:46 -0400 Subject: [PATCH 243/288] Fixed ConfigService not filtering out configs that it shouldn't be loading. --- .../LuaCs/_Services/ConfigService.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs index 8357d31b9..5685571b4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs @@ -291,8 +291,7 @@ public sealed partial class ConfigService : IConfigService ContentPackageManager.RegularPackages.Select(p => p.Name).ToArray() }, false); } - - + public void RegisterSettingTypeInitializer(string typeIdentifier, Func<(IConfigService ConfigService, IConfigInfo Info), T> settingFactory) where T : class, ISettingBase { Guard.IsNotNullOrWhiteSpace(typeIdentifier, nameof(typeIdentifier)); @@ -307,6 +306,16 @@ public sealed partial class ConfigService : IConfigService _instanceFactory[typeIdentifier] = settingFactory; } + + private static ImmutableArray SelectCompatible(ImmutableArray resources) where T : IBaseResourceInfo + { + return resources + .Where(r => r.SupportedPlatforms.HasFlag(ModUtils.Environment.CurrentPlatform)) + .Where(r => r.SupportedTargets.HasFlag(ModUtils.Environment.CurrentTarget)) + .OrderBy(r => r.Optional ? 1 : 0) // optional content last + .ThenBy(r => r.LoadPriority) + .ToImmutableArray(); + } public async Task LoadConfigsAsync(ImmutableArray configResources) { @@ -320,7 +329,7 @@ public sealed partial class ConfigService : IConfigService var taskBuilder = ImmutableArray.CreateBuilder>>(); var toProcessErrors = new ConcurrentStack(); - foreach (var resource in configResources) + foreach (var resource in SelectCompatible(configResources)) { taskBuilder.Add(await Task.Factory.StartNew>>(async Task> () => { @@ -408,7 +417,7 @@ public sealed partial class ConfigService : IConfigService var result = new FluentResults.Result(); - foreach (var resource in configProfileResources) + foreach (var resource in SelectCompatible(configProfileResources)) { var r = await _configProfileInfoParserService.TryParseResourcesAsync(resource); if (r.IsFailed) From 57daa3ccb7dd96301d528016cdc887b46022491c Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Fri, 27 Mar 2026 21:40:44 -0300 Subject: [PATCH 244/288] Fix missing type register --- .../SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs index dabf3792e..a0bb8ef31 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs @@ -42,6 +42,8 @@ public class DefaultLuaRegistrar : IDefaultLuaRegistrar _userDataService.RegisterType("Barotrauma.Range`1"); _userDataService.RegisterType("Barotrauma.ItemPrefab"); + _userDataService.RegisterType("Barotrauma.InputType"); + List assembliesToScan = [typeof(DefaultLuaRegistrar).Assembly, typeof(Identifier).Assembly, typeof(Microsoft.Xna.Framework.Vector2).Assembly]; foreach (var type in assembliesToScan.SelectMany(a => a.GetTypes())) From bb2490eb1359d0572084d7d2dc08e668fff797e7 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Fri, 27 Mar 2026 21:58:06 -0300 Subject: [PATCH 245/288] Fix wrong arguments in RemovePatch --- .../SharedSource/LuaCs/_Services/EventService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs index 34102b8cc..8803ca2c8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs @@ -366,12 +366,12 @@ public partial class EventService : IEventService public bool RemovePatch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsHook.HookMethodType hookType) { - return _luaPatcher.RemovePatch(className, methodName, methodName, parameterTypes, hookType); + return _luaPatcher.RemovePatch(className, className, methodName, parameterTypes, hookType); } public bool RemovePatch(string identifier, string className, string methodName, LuaCsHook.HookMethodType hookType) { - return _luaPatcher.RemovePatch(className, methodName, methodName, hookType); + return _luaPatcher.RemovePatch(className, className, methodName, hookType); } public void HookMethod(string identifier, MethodBase method, LuaCsPatch patch, LuaCsHook.HookMethodType hookType = LuaCsHook.HookMethodType.Before, IAssemblyPlugin owner = null) From 2ea97d3d5e87e96f497b92fa3173d1559eea0611 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 28 Mar 2026 10:52:33 -0300 Subject: [PATCH 246/288] Add CallLuaFunction legacy redirect --- Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index a813a33c3..0e8dcb1bc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -3,6 +3,7 @@ using Barotrauma.LuaCs.Compatibility; using Barotrauma.LuaCs.Data; using Barotrauma.LuaCs.Events; using LightInject; +using MoonSharp.Interpreter; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -383,6 +384,7 @@ namespace Barotrauma public ILuaCsHook Hook => this.EventService; public INetworkingService Networking => this.NetworkingService; public ILuaCsTimer Timer => _servicesProvider.GetService(); + public DynValue CallLuaFunction(object function, params object[] args) => LuaScriptManagementService.CallFunctionSafe(function, args); #endregion From 5bfa15564a30976b7f55ffad9d9d2e66115509dc Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 28 Mar 2026 12:51:53 -0300 Subject: [PATCH 247/288] Move partial classes to extension methods, the ones that can't, turn into Lua compatibility members --- .../LuaCs/Lua/LuaBarotraumaAdditions.cs | 107 ----------------- .../LuaCs/Services/NetworkingService.cs | 2 +- .../SharedSource/Items/Components/Quality.cs | 2 +- .../LuaCs/BarotraumaExtensions.cs | 107 +++++++++++++++++ .../SharedSource/LuaCs/ModUtils.cs | 60 ++++++++++ .../_Services/PluginManagementService.cs | 4 +- .../_Services/_Lua/DefaultLuaRegistrar.cs | 49 ++++++++ .../_Lua/LuaClasses/LuaBarotraumaAdditions.cs | 110 ------------------ .../_Services/_Lua/LuaUserDataService.cs | 9 +- 9 files changed, 226 insertions(+), 224 deletions(-) delete mode 100644 Barotrauma/BarotraumaServer/ServerSource/LuaCs/Lua/LuaBarotraumaAdditions.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/BarotraumaExtensions.cs delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaBarotraumaAdditions.cs diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Lua/LuaBarotraumaAdditions.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Lua/LuaBarotraumaAdditions.cs deleted file mode 100644 index 62289624f..000000000 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Lua/LuaBarotraumaAdditions.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Barotrauma.Networking -{ - partial class Client - { - public void SetClientCharacter(Character character) - { - GameMain.Server.SetClientCharacter(this, character); - } - - public void Kick(string reason = "") - { - GameMain.Server.KickClient(this.Connection, reason); - } - - public void Ban(string reason = "", float seconds = -1) - { - if (seconds == -1) - { - GameMain.Server.BanClient(this, reason, null); - } - else - { - GameMain.Server.BanClient(this, reason, TimeSpan.FromSeconds(seconds)); - } - } - - public static void UnbanPlayer(string playerName) - { - GameMain.Server.UnbanPlayer(playerName); - } - - public static void BanPlayer(string player, string reason, bool range = false, float seconds = -1) - { - if (seconds == -1) - { - GameMain.Server.BanPlayer(player, reason, null); - } - else - { - GameMain.Server.BanPlayer(player, reason, TimeSpan.FromSeconds(seconds)); - } - } - - public bool CheckPermission(ClientPermissions permissions) - { - return this.Permissions.HasFlag(permissions); - } - } -} - -namespace Barotrauma -{ - using Microsoft.Xna.Framework; - using System.Reflection; - - partial class Item - { - public object CreateServerEventString(string component) - { - var comp = GetComponentString(component); - - if (comp == null) - return null; - - MethodInfo method = typeof(Item).GetMethod(nameof(Item.CreateServerEvent), new Type[]{ Type.MakeGenericMethodParameter(0) }); - MethodInfo generic = method.MakeGenericMethod(comp.GetType()); - return generic.Invoke(this, new object[]{ comp }); - } - - public object CreateServerEventString(string component, object[] extraData) - { - var comp = GetComponentString(component); - - if (comp == null) - return null; - - MethodInfo method = typeof(Item).GetMethod(nameof(Item.CreateServerEvent), new Type[]{ Type.MakeGenericMethodParameter(0), typeof(object[]) }); - MethodInfo generic = method.MakeGenericMethod(comp.GetType()); - return generic.Invoke(this, new object[]{comp, extraData }); - } - } -} - -namespace Barotrauma.Items.Components -{ - using Barotrauma.Networking; - - partial struct Signal - { - public static Signal Create(string value, int stepsTaken = 0, Character sender = null, Item source = null, float power = 0.0f, float strength = 1.0f) - { - return new Signal(value, stepsTaken, sender, source, power, strength); - } - } - - partial class Quality - { - public void SetValue(StatType statType, float value) - { - statValues[statType] = value; - } - } -} diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs index 895628f75..661a99d2b 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs @@ -170,7 +170,7 @@ partial class NetworkingService : INetworkingService, IEventClientRawNetMessageR { if (connection == null) { - foreach (NetworkConnection conn in Client.ClientList.Select(c => c.Connection)) + foreach (NetworkConnection conn in ModUtils.Client.ClientList.Select(c => c.Connection)) { GameMain.Server.ServerPeer.Send(netMessage, conn, deliveryMethod); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs index 03b727bc3..758b29585 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Quality.cs @@ -25,7 +25,7 @@ namespace Barotrauma.Items.Components FiringRateMultiplier } - private readonly Dictionary statValues = new Dictionary(); + public readonly Dictionary statValues = new Dictionary(); private int qualityLevel; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/BarotraumaExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/BarotraumaExtensions.cs new file mode 100644 index 000000000..00349d44f --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/BarotraumaExtensions.cs @@ -0,0 +1,107 @@ +using Barotrauma; +using Barotrauma.Items.Components; +using Barotrauma.Networking; +using System; +using System.Reflection; +using static Barotrauma.Items.Components.Quality; + +namespace Barotrauma; + +static class MapEntityExtensions +{ + public static void AddLinked(this MapEntity entity, MapEntity other) + { + entity.linkedTo.Add(other); + } +} + + +static class ClientExtensions +{ +#if SERVER + public static void SetClientCharacter(this Client client, Character character) + { + GameMain.Server.SetClientCharacter(client, character); + } + + public static void Kick(this Client client, string reason = "") + { + GameMain.Server.KickClient(client.Connection, reason); + } + + public static void Ban(this Client client, string reason = "", float seconds = -1) + { + if (seconds == -1) + { + GameMain.Server.BanClient(client, reason, null); + } + else + { + GameMain.Server.BanClient(client, reason, TimeSpan.FromSeconds(seconds)); + } + } + + public static bool CheckPermission(this Client client, ClientPermissions permissions) + { + return client.Permissions.HasFlag(permissions); + } +#endif +} + +static class ItemExtensions +{ + public static object GetComponentString(this Item item, string component) + { + Type type = LuaCsSetup.Instance.PluginManagementService + .GetType("Barotrauma.Items.Components." + component); + + if (type == null) + { + return null; + } + + MethodInfo method = typeof(Item).GetMethod(nameof(Item.GetComponent)); + MethodInfo generic = method.MakeGenericMethod(type); + return generic.Invoke(item, null); + } + +#if SERVER + public static object CreateServerEventString(this Item item, string component) + { + var comp = item.GetComponentString(component); + + if (comp == null) + return null; + + MethodInfo method = typeof(Item).GetMethod( + nameof(Item.CreateServerEvent), + new Type[] { Type.MakeGenericMethodParameter(0) }); + + MethodInfo generic = method.MakeGenericMethod(comp.GetType()); + return generic.Invoke(item, new object[] { comp }); + } + + public static object CreateServerEventString(this Item item, string component, object[] extraData) + { + var comp = item.GetComponentString(component); + + if (comp == null) + return null; + + MethodInfo method = typeof(Item).GetMethod( + nameof(Item.CreateServerEvent), + new Type[] { Type.MakeGenericMethodParameter(0), typeof(object[]) }); + + MethodInfo generic = method.MakeGenericMethod(comp.GetType()); + return generic.Invoke(item, new object[] { comp, extraData }); + } +#endif +} + +static class QualityExtensions +{ + public static void SetValue(this Quality quality, StatType statType, float value) + { + quality.statValues[statType] = value; + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs index 25314760a..5e8202e20 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs @@ -26,6 +26,66 @@ namespace Barotrauma public static class ModUtils { + public static class Item + { + internal static ItemPrefab GetItemPrefab(string itemNameOrId) + { + ItemPrefab itemPrefab = + (MapEntityPrefab.Find(itemNameOrId, identifier: null, showErrorMessages: false) ?? + MapEntityPrefab.Find(null, identifier: itemNameOrId, showErrorMessages: false)) as ItemPrefab; + + return itemPrefab; + } + } + + public static class Client + { + internal static ulong GetSteamId(Barotrauma.Networking.Client client) + { + if (client.AccountId.TryUnwrap(out AccountId outValue) && outValue is SteamId steamId) + { + return steamId.Value; + } + else + { + return 0; + } + } + +#if SERVER + internal static void UnbanPlayer(string playerName) + { + GameMain.Server.UnbanPlayer(playerName); + } + + internal static void BanPlayer(string player, string reason, bool range = false, float seconds = -1) + { + if (seconds == -1) + { + GameMain.Server.BanPlayer(player, reason, null); + } + else + { + GameMain.Server.BanPlayer(player, reason, TimeSpan.FromSeconds(seconds)); + } + } +#endif + + internal static IReadOnlyList ClientList + { + get + { + if (GameMain.IsSingleplayer) { return new List(); } + +#if SERVER + return GameMain.Server.ConnectedClients; +#else + return GameMain.Client.ConnectedClients; +#endif + } + } + } + public static class Definitions { public const string LuaCsForBarotrauma = nameof(LuaCsForBarotrauma); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index 36d7e8353..b38e8b220 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -637,7 +637,9 @@ public class PluginManagementService : IAssemblyManagementService private string DoSourceCodeTextCompatibilityPass(string sourceCode) { - return sourceCode.Replace("GameMain.LuaCs", "LuaCsSetup.Instance"); + return sourceCode + .Replace("GameMain.LuaCs", "LuaCsSetup.Instance") + .Replace("Client.ClientList", "ModUtils.Client.ClientList"); } private IntPtr OnAssemblyLoaderResolvingUnmanaged(Assembly callerAssembly, string targetAssemblyName) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs index a0bb8ef31..9af1972c9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs @@ -1,8 +1,11 @@ using Barotrauma.LuaCs.Data; using Barotrauma.Networking; using MoonSharp.Interpreter; +using MoonSharp.Interpreter.Interop; +using MoonSharp.Interpreter.Interop.BasicDescriptors; using Sigil; using System.Collections.Generic; +using System.Data; using System.Linq; using System.Numerics; using System.Reflection; @@ -23,6 +26,30 @@ public class DefaultLuaRegistrar : IDefaultLuaRegistrar private readonly ISafeLuaUserDataService _safeUserDataService; private readonly ILoggerService _loggerService; + private class SteamIDMemberDescriptor : IMemberDescriptor + { + public bool IsStatic => false; + + public string Name => "SteamID"; + + public MemberDescriptorAccess MemberAccess => MemberDescriptorAccess.CanRead; + + public DynValue GetValue(Script script, object obj) + { + if (obj is Client client) + { + return DynValue.FromObject(script, ModUtils.Client.GetSteamId(client)); + } + + throw new System.NotImplementedException(); + } + + public void SetValue(Script script, object obj, DynValue value) + { + throw new System.NotImplementedException(); + } + } + public DefaultLuaRegistrar(ILoggerService loggerService, ILuaUserDataService userDataService, ISafeLuaUserDataService safeUserDataService) { _userDataService = userDataService; @@ -152,6 +179,28 @@ public class DefaultLuaRegistrar : IDefaultLuaRegistrar _userDataService.RegisterType("Barotrauma.PrefabSelector`1"); _userDataService.RegisterType("Barotrauma.Pair`2"); + _userDataService.RegisterExtensionType("Barotrauma.MathUtils"); + _userDataService.RegisterExtensionType("Barotrauma.XMLExtensions"); + + var itemDescriptor = (StandardUserDataDescriptor)_userDataService.RegisterType("Barotrauma.Item"); + itemDescriptor.AddMember("GetItemPrefab", new MethodMemberDescriptor(typeof(ModUtils.Item).GetMethod(nameof(ModUtils.Item.GetItemPrefab), BindingFlags.NonPublic | BindingFlags.Static))); + + var clientDescriptor = (StandardUserDataDescriptor)_userDataService.RegisterType("Barotrauma.Networking.Client"); + clientDescriptor.AddMember("ClientList", new PropertyMemberDescriptor(typeof(ModUtils.Client).GetProperty(nameof(ModUtils.Client.ClientList), BindingFlags.NonPublic | BindingFlags.Static), InteropAccessMode.LazyOptimized)); + clientDescriptor.AddMember("SteamID", new SteamIDMemberDescriptor()); + + +#if SERVER + clientDescriptor.AddMember("UnbanPlayer", new MethodMemberDescriptor(typeof(ModUtils.Client).GetMethod(nameof(ModUtils.Client.UnbanPlayer), BindingFlags.NonPublic | BindingFlags.Static), InteropAccessMode.LazyOptimized)); + clientDescriptor.AddMember("BanPlayer", new MethodMemberDescriptor(typeof(ModUtils.Client).GetMethod(nameof(ModUtils.Client.BanPlayer), BindingFlags.NonPublic | BindingFlags.Static), InteropAccessMode.LazyOptimized)); +#endif + + _userDataService.RegisterExtensionType(typeof(ClientExtensions).FullName); + _userDataService.RegisterExtensionType(typeof(ItemExtensions).FullName); + _userDataService.RegisterExtensionType(typeof(MapEntityExtensions).FullName); + _userDataService.RegisterExtensionType(typeof(QualityExtensions).FullName); + + var toolBox = UserData.RegisterType(typeof(ToolBox)); #if CLIENT _userDataService.RemoveMember(toolBox, "OpenFileWithShell"); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaBarotraumaAdditions.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaBarotraumaAdditions.cs deleted file mode 100644 index a80aa9e99..000000000 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaBarotraumaAdditions.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using MoonSharp.Interpreter; -using Microsoft.Xna.Framework; -using Barotrauma.Networking; - -namespace Barotrauma.Networking -{ - partial class Client - { - public static IReadOnlyList ClientList - { - get - { - if (GameMain.IsSingleplayer) { return new List(); } - -#if SERVER - return GameMain.Server.ConnectedClients; -#else - return GameMain.Client.ConnectedClients; -#endif - } - } - - public ulong SteamID - { - get - { - if (AccountId.TryUnwrap(out AccountId outValue) && outValue is SteamId steamId) - { - return steamId.Value; - } - else - { - return 0; - } - } - } - - } - -} - -namespace Barotrauma -{ - using Barotrauma.Networking; - using System.Linq; - using System.Reflection; - - - - partial class Character - { - - } - - partial class Item - { - public object GetComponentString(string component) - { - Type type = LuaCsSetup.Instance.PluginManagementService.GetType("Barotrauma.Items.Components." + component); - - if (type == null) - { - return null; - } - - MethodInfo method = typeof(Item).GetMethod(nameof(Item.GetComponent)); - MethodInfo generic = method.MakeGenericMethod(type); - return generic.Invoke(this, null); - } - - } - - partial class ItemPrefab - { - - public static ItemPrefab GetItemPrefab(string itemNameOrId) - { - ItemPrefab itemPrefab = - (MapEntityPrefab.Find(itemNameOrId, identifier: null, showErrorMessages: false) ?? - MapEntityPrefab.Find(null, identifier: itemNameOrId, showErrorMessages: false)) as ItemPrefab; - - return itemPrefab; - } - } - - abstract partial class MapEntity - { - public void AddLinked(MapEntity entity) - { - linkedTo.Add(entity); - } - } - -} - -namespace Barotrauma.Items.Components -{ - using Barotrauma.Networking; - - partial class CustomInterface - { - } - - partial struct Signal - { - } -} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaUserDataService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaUserDataService.cs index e155784a8..366249f21 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaUserDataService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaUserDataService.cs @@ -322,10 +322,11 @@ public class LuaUserDataService : ILuaUserDataService descriptor.RemoveMember(methodName); descriptor.AddMember(methodName, new ObjectCallbackMemberDescriptor(methodName, (object arg1, ScriptExecutionContext arg2, CallbackArguments arg3) => { - /*if (GameMain.LuaCs != null) - return GameMain.LuaCs.CallLuaFunction(function, arg3.GetArray()); - return null;*/ - throw new NotImplementedException(); + if (LuaCsSetup.Instance != null) + { + return LuaCsSetup.Instance.CallLuaFunction(function, arg3.GetArray()); + } + return null; })); } From 64818775cb1ac2c17a77afb6837c48151a3f6e72 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 28 Mar 2026 13:04:04 -0300 Subject: [PATCH 248/288] Whoops --- .../SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs index 9af1972c9..c0fe49393 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs @@ -182,8 +182,8 @@ public class DefaultLuaRegistrar : IDefaultLuaRegistrar _userDataService.RegisterExtensionType("Barotrauma.MathUtils"); _userDataService.RegisterExtensionType("Barotrauma.XMLExtensions"); - var itemDescriptor = (StandardUserDataDescriptor)_userDataService.RegisterType("Barotrauma.Item"); - itemDescriptor.AddMember("GetItemPrefab", new MethodMemberDescriptor(typeof(ModUtils.Item).GetMethod(nameof(ModUtils.Item.GetItemPrefab), BindingFlags.NonPublic | BindingFlags.Static))); + var itemPrefabDescriptor = (StandardUserDataDescriptor)_userDataService.RegisterType("Barotrauma.ItemPrefab"); + itemPrefabDescriptor.AddMember("GetItemPrefab", new MethodMemberDescriptor(typeof(ModUtils.Item).GetMethod(nameof(ModUtils.Item.GetItemPrefab), BindingFlags.NonPublic | BindingFlags.Static))); var clientDescriptor = (StandardUserDataDescriptor)_userDataService.RegisterType("Barotrauma.Networking.Client"); clientDescriptor.AddMember("ClientList", new PropertyMemberDescriptor(typeof(ModUtils.Client).GetProperty(nameof(ModUtils.Client.ClientList), BindingFlags.NonPublic | BindingFlags.Static), InteropAccessMode.LazyOptimized)); From b8f1642d9d1ad516d972feb6fe7c6c2a8bb995e7 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 28 Mar 2026 13:04:11 -0300 Subject: [PATCH 249/288] Simplify command register --- .../LuaCs/_Services/ConsoleCommandsService.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConsoleCommandsService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConsoleCommandsService.cs index 9aefed1d8..242938a16 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConsoleCommandsService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConsoleCommandsService.cs @@ -11,7 +11,7 @@ namespace Barotrauma.LuaCs; internal class ConsoleCommandsService : IConsoleCommandsService { - private readonly ConcurrentDictionary _registeredCommands = new(); + private readonly List _registeredCommands = new(); public void Dispose() { @@ -20,7 +20,7 @@ internal class ConsoleCommandsService : IConsoleCommandsService return; } - foreach (var cmd in _registeredCommands.Values.ToImmutableArray()) + foreach (var cmd in _registeredCommands.ToImmutableArray()) { DebugConsole.Commands.Remove(cmd); } @@ -38,11 +38,14 @@ internal class ConsoleCommandsService : IConsoleCommandsService public void RegisterCommand(string name, string help, Action onExecute, Func getValidArgs = null, bool isCheat = false) { IService.CheckDisposed(this); - var cmd = new DebugConsole.Command(name, help, onExecute, getValidArgs, isCheat); - if (!_registeredCommands.TryAdd(name, cmd)) + + if (DebugConsole.Commands.Any(cmd => cmd.Names.Contains(name))) { - throw new ArgumentException($"A command with the name '{name}' is already registered."); + LuaCsSetup.Instance.Logger.LogWarning($"Registering console command {name} more than once!"); } + + var cmd = new DebugConsole.Command(name, help, onExecute, getValidArgs, isCheat); + _registeredCommands.Add(cmd); DebugConsole.Commands.Add(cmd); } @@ -77,16 +80,15 @@ internal class ConsoleCommandsService : IConsoleCommandsService public void RemoveCommand(string name) { IService.CheckDisposed(this); - if (_registeredCommands.TryRemove(name, out DebugConsole.Command cmd)) - { - DebugConsole.Commands.Remove(cmd); - } + + _registeredCommands.RemoveAll(cmd => cmd.Names.Contains(name)); + DebugConsole.Commands.RemoveAll(cmd => cmd.Names.Contains(name)); } public void RemoveRegisteredCommands() { IService.CheckDisposed(this); - foreach (var cmd in _registeredCommands.Values.ToImmutableArray()) + foreach (var cmd in _registeredCommands.ToImmutableArray()) { DebugConsole.Commands.Remove(cmd); } From b3588820163eb6098ef860fac43fe35dd87f26aa Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 28 Mar 2026 14:03:28 -0300 Subject: [PATCH 250/288] This should be ItemPrefab, not Item, also add it the source code compat --- .../BarotraumaShared/SharedSource/LuaCs/ModUtils.cs | 10 +++++----- .../LuaCs/_Services/PluginManagementService.cs | 3 ++- .../LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs index 5e8202e20..9e667cf09 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs @@ -26,13 +26,13 @@ namespace Barotrauma public static class ModUtils { - public static class Item + public static class ItemPrefab { - internal static ItemPrefab GetItemPrefab(string itemNameOrId) + internal static Barotrauma.ItemPrefab GetItemPrefab(string itemNameOrId) { - ItemPrefab itemPrefab = - (MapEntityPrefab.Find(itemNameOrId, identifier: null, showErrorMessages: false) ?? - MapEntityPrefab.Find(null, identifier: itemNameOrId, showErrorMessages: false)) as ItemPrefab; + Barotrauma.ItemPrefab itemPrefab = + (Barotrauma.MapEntityPrefab.Find(itemNameOrId, identifier: null, showErrorMessages: false) ?? + Barotrauma.MapEntityPrefab.Find(null, identifier: itemNameOrId, showErrorMessages: false)) as Barotrauma.ItemPrefab; return itemPrefab; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index b38e8b220..b87a1d525 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -639,7 +639,8 @@ public class PluginManagementService : IAssemblyManagementService { return sourceCode .Replace("GameMain.LuaCs", "LuaCsSetup.Instance") - .Replace("Client.ClientList", "ModUtils.Client.ClientList"); + .Replace("Client.ClientList", "ModUtils.Client.ClientList") + .Replace("ItemPrefab.GetItemPrefab", "ModUtils.ItemPrefab.GetItemPrefab"); } private IntPtr OnAssemblyLoaderResolvingUnmanaged(Assembly callerAssembly, string targetAssemblyName) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs index c0fe49393..26a9afbf9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs @@ -183,7 +183,7 @@ public class DefaultLuaRegistrar : IDefaultLuaRegistrar _userDataService.RegisterExtensionType("Barotrauma.XMLExtensions"); var itemPrefabDescriptor = (StandardUserDataDescriptor)_userDataService.RegisterType("Barotrauma.ItemPrefab"); - itemPrefabDescriptor.AddMember("GetItemPrefab", new MethodMemberDescriptor(typeof(ModUtils.Item).GetMethod(nameof(ModUtils.Item.GetItemPrefab), BindingFlags.NonPublic | BindingFlags.Static))); + itemPrefabDescriptor.AddMember("GetItemPrefab", new MethodMemberDescriptor(typeof(ModUtils.ItemPrefab).GetMethod(nameof(ModUtils.ItemPrefab.GetItemPrefab), BindingFlags.NonPublic | BindingFlags.Static))); var clientDescriptor = (StandardUserDataDescriptor)_userDataService.RegisterType("Barotrauma.Networking.Client"); clientDescriptor.AddMember("ClientList", new PropertyMemberDescriptor(typeof(ModUtils.Client).GetProperty(nameof(ModUtils.Client.ClientList), BindingFlags.NonPublic | BindingFlags.Static), InteropAccessMode.LazyOptimized)); From 958335a01c15e12cc19596838dbb1864e48c4554 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 28 Mar 2026 14:14:11 -0300 Subject: [PATCH 251/288] Replicate old Add() method signature structure --- .../SharedSource/LuaCs/Compatibility/ILuaCsHook.cs | 4 ++-- .../SharedSource/LuaCs/_Services/EventService.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsHook.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsHook.cs index 69e57544d..7dfdb8291 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsHook.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsHook.cs @@ -8,9 +8,9 @@ public interface ILuaCsHook : ILuaPatcher, ILuaCsShim { // Event Services [Obsolete("ACsMod is deprecated. Use ILuaEventService.Add() instead.")] - void Add(string eventName, string identifier, LuaCsFunc callback); + 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); + void Add(string eventName, LuaCsFunc callback, object owner = null); // 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/_Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs index 8803ca2c8..0323fad08 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs @@ -103,7 +103,7 @@ public partial class EventService : IEventService #region LuaEventSystem - public void Add(string eventName, string identifier, LuaCsFunc callback) + public void Add(string eventName, string identifier, LuaCsFunc callback, object owner = null) { Guard.IsNotNullOrWhiteSpace(eventName, nameof(eventName)); Guard.IsNotNullOrWhiteSpace(identifier, nameof(identifier)); @@ -123,7 +123,7 @@ public partial class EventService : IEventService } } - public void Add(string eventName, LuaCsFunc callback) + public void Add(string eventName, LuaCsFunc callback, object owner = null) { // random ident, we hope for no conflicts :barodev:. Add(eventName, Random.Shared.NextInt64().ToString() ,callback); From 14c610e6c78c06e82992d004d1ba454584a6f08b Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 28 Mar 2026 21:50:13 -0300 Subject: [PATCH 252/288] Re-added the install_cl_lua command --- .../ClientSource/LuaCs/LuaCsInstaller.cs | 44 ------------------- .../ServerSource/LuaCs/LuaCsInstaller.cs | 10 ++--- 2 files changed, 3 insertions(+), 51 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs index 4a7f9fdea..68fa823c7 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsInstaller.cs @@ -8,51 +8,7 @@ namespace Barotrauma { public static void Uninstall() { - if (!File.Exists("Temp/Original/Barotrauma.dll")) - { - new GUIMessageBox("Error", "Error: Temp/Original/Barotrauma.dll not found, Github version? Use Steam validate files instead."); - return; - } - - var msg = new GUIMessageBox("Confirm", "Are you sure you want to remove Client-Side LuaCs?", new LocalizedString[2] { TextManager.Get("Yes"), TextManager.Get("Cancel") }); - - msg.Buttons[0].OnClicked = (GUIButton button, object obj) => - { - msg.Close(); - - string[] filesToRemove = new string[] - { - "Barotrauma.dll", "Barotrauma.deps.json", "Barotrauma.pdb", "BarotraumaCore.dll", "BarotraumaCore.pdb", - "System.Reflection.Metadata.dll", "System.Collections.Immutable.dll", - "System.Runtime.CompilerServices.Unsafe.dll" - }; - try - { - CreateMissingDirectory(); - - foreach (string file in filesToRemove) - { - File.Move(file, "Temp/ToDelete/" + file, true); - File.Move("Temp/Original/" + file, file, true); - } - } - catch (Exception e) - { - new GUIMessageBox("Error", $"{e} {e.InnerException} \nTry verifying files instead."); - return false; - } - - new GUIMessageBox("Restart", "Restart your game to apply the changes. If the mod continues to stay active after the restart, try verifying games instead."); - - return true; - }; - - msg.Buttons[1].OnClicked = (GUIButton button, object obj) => - { - msg.Close(); - return true; - }; } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsInstaller.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsInstaller.cs index a75ada38e..82081bd8b 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsInstaller.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsInstaller.cs @@ -9,13 +9,11 @@ namespace Barotrauma { public static void Install() { - throw new NotImplementedException(); - // TODO: Refactor the installer to not be dependent on LuaCsSetup. - /*ContentPackage luaPackage = LuaCsSetup.GetPackage(); + ContentPackage luaPackage = LuaCsSetup.GetLuaCsPackage(); if (luaPackage == null) { - GameMain.Server.SendChatMessage("Couldn't find the LuaCs For Barotrauma package.", ChatMessageType.ServerMessageBox); + GameMain.Server.SendChatMessage("Couldn't find the LuaCsForBarotrauma content package.", ChatMessageType.ServerMessageBox); return; } @@ -47,8 +45,6 @@ namespace Barotrauma File.Copy(Path.Combine(path, "Binary", file), file, true); } - File.WriteAllText(LuaCsSetup.VersionFile, luaPackage.ModVersion); - #if WINDOWS File.WriteAllText("LuaCsDedicatedServer.bat", "\"%LocalAppData%/Daedalic Entertainment GmbH/Barotrauma/WorkshopMods/Installed/2559634234/Binary/DedicatedServer.exe\""); #endif @@ -66,7 +62,7 @@ namespace Barotrauma return; } - GameMain.Server.SendChatMessage("Client-Side LuaCs installed, restart your game to apply changes.", ChatMessageType.ServerMessageBox);*/ + GameMain.Server.SendChatMessage("Client-Side LuaCs installed, restart your game to apply changes.", ChatMessageType.ServerMessageBox); } } } From bcec409618e2cd25688622bfd43a79ba135c6273 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 28 Mar 2026 22:20:22 -0300 Subject: [PATCH 253/288] Fix deep-fried main menu text --- .../SharedSource/LuaCs/_Services/MainMenuPatch.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/MainMenuPatch.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/MainMenuPatch.cs index 44172628d..5dfdc794b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/MainMenuPatch.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/MainMenuPatch.cs @@ -12,6 +12,8 @@ internal class MainMenuPatch : ISystem, IEventScreenSelected private readonly IEventService _eventService; + private bool mainMenuUIAdded = false; + public MainMenuPatch(IEventService eventService) { _eventService = eventService; @@ -39,10 +41,14 @@ internal class MainMenuPatch : ISystem, IEventScreenSelected #if CLIENT private void AddToMainMenu(MainMenuScreen screen) { + 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) { IgnoreLayoutGroups = false }; + + mainMenuUIAdded = true; } #endif From f1808d9231ca00d549c2ba301020c0e82c876c0c Mon Sep 17 00:00:00 2001 From: Maplewheels Date: Sun, 29 Mar 2026 00:30:18 -0400 Subject: [PATCH 254/288] Added verbose cs compilation. --- .../LuaCs/_Plugins/AssemblyLoader.cs | 21 ++++++++++++++----- .../LuaCs/_Plugins/IAssemblyLoaderService.cs | 18 ++++++++-------- .../_Services/PluginManagementService.cs | 2 +- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs index bd82efe1f..485690f8e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/AssemblyLoader.cs @@ -9,6 +9,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Loader; +using System.Text; using System.Threading; using Barotrauma.Extensions; using Barotrauma.LuaCs; @@ -255,8 +256,7 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService } } - public FluentResults.Result CompileScriptAssembly( - [NotNull] string assemblyName, + public Result CompileScriptAssembly([NotNull] string assemblyName, bool compileWithInternalAccess, ImmutableArray syntaxTrees, ImmutableArray metadataReferences, @@ -290,7 +290,8 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService outputKind: OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release, concurrentBuild: true, - reportSuppressedDiagnostics: true, + reportSuppressedDiagnostics: false, + warningLevel: 0, allowUnsafe: true); if (!compileWithInternalAccess) @@ -306,12 +307,22 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService using var asmMemoryStream = new MemoryStream(); var result = CSharpCompilation - .Create(compilationAssemblyName, syntaxTrees, metadataReferences, compilationOptions) + .Create(compilationAssemblyName, syntaxTrees, + metadataReferences, compilationOptions) .Emit(asmMemoryStream); if (!result.Success) { + StringBuilder sb = new StringBuilder(); + foreach (var resultDiagnostic in result.Diagnostics) + { + if (resultDiagnostic.Severity != DiagnosticSeverity.Error) + { + continue; + } + sb.AppendLine($">>> {resultDiagnostic.GetMessage()} | Location: {resultDiagnostic.Location.SourceTree?.GetLineSpan(resultDiagnostic.Location.SourceSpan)} "); + } var res = new FluentResults.Result().WithError( - new Error($"Compilation failed for assembly {assemblyName}!") + new Error($"Package Error: {OwnerPackage.Name}: Compilation failed for assembly {assemblyName}! {sb.ToString()}\n") .WithMetadata(MetadataType.ExceptionObject, this) .WithMetadata(MetadataType.RootObject, syntaxTrees)); var failuresDiag = diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs index 00cc5042b..f02f5c89b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Plugins/IAssemblyLoaderService.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; using Barotrauma.LuaCs; +using FluentResults; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -74,20 +75,19 @@ public interface IAssemblyLoaderService : IService /// AssemblyManager class. /// /// [NotNull]Name reference of the assembly. - /// [IMPORTANT] This is used to reference this assembly as the true name will be forced if - /// publicized assemblies are not used (InternalsVisibleTo Attrib). - /// Must be supplied for in-memory assemblies. - /// Must be unique to all other assemblies explicitly loaded using this context. + /// [IMPORTANT] This is used to reference this assembly as the true name will be forced if + /// publicized assemblies are not used (InternalsVisibleTo Attrib). + /// Must be supplied for in-memory assemblies. + /// Must be unique to all other assemblies explicitly loaded using this context. /// Forces the assembly name to and grants access to internal. - /// [IMPORTANT]Cannot be null or empty if is false. /// [NotNull]Syntax trees to compile into the assembly. /// All MetadataReferences to be used for compilation. - /// [IMPORTANT] This method builds metadata from loaded assemblies, only supply your own if you have in-memory - /// images not managed by the AssemblyManager class. + /// [IMPORTANT] This method builds metadata from loaded assemblies, only supply your own if you have in-memory + /// images not managed by the AssemblyManager class. /// [NotNull]CSharp compilation options. This method automatically adds the 'IgnoreAccessChecks' property for compilation. + /// [IMPORTANT]Cannot be null or empty if is false. /// Success state of the operation. - public FluentResults.Result CompileScriptAssembly( - [NotNull] string assemblyName, + public Result CompileScriptAssembly([NotNull] string assemblyName, bool compileWithInternalAccess, ImmutableArray syntaxTrees, ImmutableArray metadataReferences, diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index b87a1d525..a8cf9ec23 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -588,7 +588,7 @@ public class PluginManagementService : IAssemblyManagementService syntaxTreesBuilder.Add(SyntaxFactory.ParseSyntaxTree( text: sourceCode, options: ScriptParseOptions, - path: null, + path: resourcePath.FullPath, encoding: Encoding.Default, cancellationToken: token )); From 4f2da55a8e95cd1482b0c299f20b37d1bce23701 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 31 Mar 2026 10:48:44 -0400 Subject: [PATCH 255/288] - Added legacy LuaCsPerformanceCounter.cs - Added legacy redirects. - Added IConsoleCommandsService injection to plugins. --- .../BarotraumaServer/ServerSource/GameMain.cs | 7 +++- .../Compatibility/LuaCsPerformanceCounter.cs | 41 +++++++++++++++++++ .../SharedSource/LuaCs/LuaCsSetup.cs | 19 +++++++-- .../_Services/PluginManagementService.cs | 6 ++- 4 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/LuaCsPerformanceCounter.cs diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs index 2fc1138e4..bb4c4f628 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs @@ -364,10 +364,15 @@ namespace Barotrauma CoroutineManager.Update(paused: false, (float)Timing.Step); performanceCounterTimer.Stop(); + if (LuaCsSetup.Instance.PerformanceCounterService.EnablePerformanceCounter) + { + LuaCsSetup.Instance.PerformanceCounterService.AddElapsedTicks(new SimplePerformanceData("Update", performanceCounterTimer.ElapsedTicks)); + } if (LuaCsSetup.Instance.PerformanceCounter.EnablePerformanceCounter) { - LuaCsSetup.Instance.PerformanceCounter.AddElapsedTicks(new SimplePerformanceData("Update", performanceCounterTimer.ElapsedTicks)); + LuaCsSetup.Instance.PerformanceCounter.UpdateElapsedTime = (double)performanceCounterTimer.ElapsedTicks / Stopwatch.Frequency; } + performanceCounterTimer.Reset(); Timing.Accumulator -= Timing.Step; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/LuaCsPerformanceCounter.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/LuaCsPerformanceCounter.cs new file mode 100644 index 000000000..807b30ecc --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/LuaCsPerformanceCounter.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + + +namespace Barotrauma +{ + /// + /// [Obsolete] Legacy compatibility only. + /// + [Obsolete("Deprecated.")] + public class LuaCsPerformanceCounter + { + public bool EnablePerformanceCounter = false; + + public double UpdateElapsedTime; + public Dictionary> HookElapsedTime = new Dictionary>(); + + public static float MemoryUsage + { + get + { + Process proc = Process.GetCurrentProcess(); + float memory = MathF.Round(proc.PrivateMemorySize64 / (1024 * 1024), 2); + proc.Dispose(); + + return memory; + } + } + + public void SetHookElapsedTicks(string eventName, string hookName, long ticks) + { + if (!HookElapsedTime.ContainsKey(eventName)) + { + HookElapsedTime[eventName] = new Dictionary(); + } + + HookElapsedTime[eventName][hookName] = (double)ticks / Stopwatch.Frequency; + } + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 0e8dcb1bc..21cf10aa5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -68,8 +68,9 @@ namespace Barotrauma */ private readonly IServicesProvider _servicesProvider; - - public PerformanceCounterService PerformanceCounter => _servicesProvider.GetService(); + + private PerformanceCounterService _performanceCounterService; + public PerformanceCounterService PerformanceCounterService => _performanceCounterService ??= _servicesProvider.GetService(); public ILoggerService Logger => _servicesProvider.GetService(); public IConfigService ConfigService => _servicesProvider.GetService(); public IPackageManagementService PackageManagementService => _servicesProvider.GetService(); @@ -82,8 +83,8 @@ namespace Barotrauma // hotpath performance ref cache private LuaGame _game; public LuaGame Game => _game ??= _servicesProvider.GetService(); - + /// /// Whether C# plugin code is enabled. /// @@ -381,6 +382,17 @@ namespace Barotrauma #region LegacyRedirects + // --- Compatibility + /// + /// [Obsolete] Legacy support only. + /// + [Obsolete] + public LuaCsPerformanceCounter PerformanceCounter { get; private set; } = new LuaCsPerformanceCounter(); + /// + /// [Obsolete] Use instead. + /// + [Obsolete($"Use {nameof(PluginManagementService)} instead.")] + public IPluginManagementService PluginPackageManager => this.PluginManagementService; public ILuaCsHook Hook => this.EventService; public INetworkingService Networking => this.NetworkingService; public ILuaCsTimer Timer => _servicesProvider.GetService(); @@ -413,6 +425,7 @@ namespace Barotrauma _eventService = null; _game = null; + PerformanceCounter = null; _servicesProvider.DisposeAndReset(); } catch (Exception e) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index a8cf9ec23..6c23761dd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -198,6 +198,7 @@ public class PluginManagementService : IAssemblyManagementService private Lazy _luaScriptManagementService; private IEventService _pluginEventService; private Lazy _pluginLuaPatcherService; + private Func _consoleCommandServiceFactory; private readonly ConcurrentDictionary _assemblyLoaders = new(); private readonly ConcurrentDictionary _pluginPackageLookup = new(); private readonly ConcurrentDictionary> _pluginInstances = new(); @@ -213,7 +214,8 @@ public class PluginManagementService : IAssemblyManagementService Lazy eventService, Lazy luaScriptManagementService, Lazy configService, - Lazy pluginLuaPatcherService) + Lazy pluginLuaPatcherService, + Func consoleCommandServiceFactory) { _assemblyLoaderFactory = assemblyLoaderFactory; _storageService = storageService; @@ -222,6 +224,7 @@ public class PluginManagementService : IAssemblyManagementService _luaScriptManagementService = luaScriptManagementService; _configService = configService; _pluginLuaPatcherService = pluginLuaPatcherService; + _consoleCommandServiceFactory = consoleCommandServiceFactory; } private ServiceContainer CreatePluginServiceContainer() @@ -240,6 +243,7 @@ public class PluginManagementService : IAssemblyManagementService container.Register(fac => this); container.Register(fac => _luaScriptManagementService.Value); container.Register(fac => _configService.Value); + container.Register(fac => _consoleCommandServiceFactory?.Invoke()); return container; } From 413cc3ed4cbfbcc8f4502cfb72faec16ca610e69 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 2 Apr 2026 23:29:21 -0400 Subject: [PATCH 256/288] - Added setting to disable lua scripts caching in the storage service for scenarios that use extern editors. --- .../Config/SettingsShared.xml | 1 + .../LuaCsForBarotrauma/Texts/English.xml | 12 ++++++---- .../SharedSource/LuaCs/LuaCsSetup.cs | 11 ++++++++- .../LuaCs/_Services/LuaCsInfoProvider.cs | 1 + .../_Services/LuaScriptManagementService.cs | 23 +++++++++++++++++-- .../_Interfaces/ILuaCsInfoProvider.cs | 5 ++++ .../ILuaScriptManagementService.cs | 6 +++++ .../LuaCs/_Services/_Lua/ILuaScriptLoader.cs | 5 ++++ .../LuaCs/_Services/_Lua/LuaScriptLoader.cs | 14 +++++++++++ 9 files changed, 70 insertions(+), 8 deletions(-) diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml index 6546e5370..35fa892f7 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml @@ -3,5 +3,6 @@ + diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml index 84d297395..2462c80ea 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml @@ -3,14 +3,16 @@ Mod Controls Settings Mod Gameplay Settings - Suppress GUI Popup on Error - General + Are C# Mods Allowed Should unsandboxed scripts and dlls be allowed to run. General + + Use Pre-Caching + Should mod files be preloaded to speed up loading. Should only be turned off if you have mods that have issues with this. + General + Hide Local OS Account Name In Logs + If true, will replace your OS account name with 'USERNAME' in log files' paths. General - Limit Network Message Size - Limits the maximum network message size to avoid buffer size issues. - Networking diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 21cf10aa5..d9bb9f415 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -106,6 +106,12 @@ namespace Barotrauma } private ISettingBase _hideUserNamesInLogs; + public bool UseCaching + { + get => _useCaching?.Value ?? true; + } + private ISettingBase _useCaching; + public static ContentPackage GetLuaCsPackage() { return ContentPackageManager.EnabledPackages.Regular.FirstOrDefault(cp => cp.NameMatches(PackageId), null) @@ -125,6 +131,10 @@ namespace Barotrauma ConfigService.TryGetConfig>(luaCsPackage, "HideUserNamesInLogs", out var val4) ? val4 : null; + _useCaching = + ConfigService.TryGetConfig>(luaCsPackage, "UseCaching", out var val5) + ? val5 + : null; } private IServicesProvider SetupServicesProvider() @@ -329,7 +339,6 @@ namespace Barotrauma Logger.LogResults(PackageManagementService.LoadPackagesInfo(GetEnabledPackagesList())); Logger.LogResults(ConfigService.LoadSavedConfigsValues()); LoadLuaCsConfig(); - } CurrentRunState = RunState.LoadedNoExec; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs index 5225e710b..e6d3df87a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaCsInfoProvider.cs @@ -14,6 +14,7 @@ public sealed class LuaCsInfoProvider : ILuaCsInfoProvider public bool IsDisposed => false; public bool IsCsEnabled => LuaCsSetup.Instance.IsCsEnabled; public bool HideUserNamesInLogs => LuaCsSetup.Instance.HideUserNamesInLogs; + public bool UseCaching => LuaCsSetup.Instance.UseCaching; public RunState CurrentRunState => LuaCsSetup.Instance.CurrentRunState; public ContentPackage LuaCsForBarotraumaPackage { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index af8d61df0..140ed2d87 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -52,6 +52,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, private readonly IPluginManagementService _pluginManagementService; private readonly INetworkingService _networkingService; private readonly IConsoleCommandsService _commandsService; + private readonly ILuaCsInfoProvider _luaCsInfoProvider; //private readonly ILuaCsUtility _luaCsUtility; public LuaScriptManagementService( @@ -67,8 +68,8 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, IEventService eventService, //ILuaCsUtility luaCsUtility, ILuaCsTimer luaCsTimer, - IConsoleCommandsService commandsService - ) + IConsoleCommandsService commandsService, + ILuaCsInfoProvider luaCsInfoProvider) { _luaScriptLoader = loader; _userDataService = userDataService; @@ -82,6 +83,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, _luaGame = luaGame; _eventService = eventService; _commandsService = commandsService; + _luaCsInfoProvider = luaCsInfoProvider; _luaCsTimer = luaCsTimer; RegisterLuaEvents(); @@ -164,11 +166,23 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, public bool IsDisposed { get; private set; } + public void SetCachingPolicy(bool useCaching) + { + _luaScriptLoader?.SetCachingPolicy(useCaching); + } + public async Task LoadScriptResourcesAsync(ImmutableArray resourcesInfo) { + if (!_luaCsInfoProvider.UseCaching) + { + return FluentResults.Result.Ok(); + } + // Do any exception checks you can before acquiring a lock to avoid needlessly holding up resources. if (resourcesInfo.IsDefaultOrEmpty) + { ThrowHelper.ThrowArgumentNullException($"{nameof(LoadScriptResourcesAsync)}: The parameter is empty!"); + } // Acquire a lock: // Reader = Allow parallel operations (try to avoid nesting acquiring the lock when possible) @@ -186,7 +200,9 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, // Aggregate and return results to the caller to deal with. Optionally, log here if you want. // Automatically converted to a Task when 'async' is in the method declaration. if (cacheRes.IsFailed) + { return cacheRes.ToResult(); + } return new FluentResults.Result().WithReasons(cacheRes.Value.SelectMany(cr => cr.Item2.Reasons)); } @@ -325,6 +341,8 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, { _loggerService.LogMessage($"[Lua] {msg}"); }; + SetCachingPolicy(_luaCsInfoProvider.UseCaching); + _script.Options.ScriptLoader = _luaScriptLoader; _script.Options.CheckThreadAccess = false; @@ -486,6 +504,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, } _resourcesInfo.Clear(); + _luaScriptLoader.ClearCaches(); return FluentResults.Result.Ok(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaCsInfoProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaCsInfoProvider.cs index bf5e38c08..57a8a0251 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaCsInfoProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaCsInfoProvider.cs @@ -15,6 +15,11 @@ public interface ILuaCsInfoProvider : IService ///
public bool HideUserNamesInLogs { get; } + /// + /// Whether file system caching is enabled. + /// + public bool UseCaching { get; } + /// /// The current state of the Execution State Machine. /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs index 0c8a938a2..b5ee2cfc5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/ILuaScriptManagementService.cs @@ -21,6 +21,12 @@ public interface ILuaScriptManagementService : IReusableService FluentResults.Result DoString(string code); DynValue? CallFunctionSafe(object luaFunction, params object[] args); + /// + /// Whether to enable/disable the file system caching for lua. + /// + /// + void SetCachingPolicy(bool useCaching); + /// /// Parses and loads script sources (code) into a memory cache without executing it. /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaScriptLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaScriptLoader.cs index 9cb26c3d6..cca1d482d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaScriptLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaScriptLoader.cs @@ -9,5 +9,10 @@ namespace Barotrauma.LuaCs; public interface ILuaScriptLoader : IService, IScriptLoader, ISafeStorageValidation { void ClearCaches(); + /// + /// Whether caching is enabled/disabled. + /// + /// + void SetCachingPolicy(bool useCaching); Task)>>> CacheResourcesAsync(ImmutableArray resourceInfos); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaScriptLoader.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaScriptLoader.cs index e8a3ffc75..d1b751d23 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaScriptLoader.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaScriptLoader.cs @@ -56,6 +56,20 @@ namespace Barotrauma.LuaCs _storageService?.PurgeCache(); } + public void SetCachingPolicy(bool useCaching) + { + if (_storageService is null) + { + return; + } + + if (!useCaching) + { + _storageService.PurgeCache(); + } + _storageService.UseCaching = useCaching; + } + public async Task)>>> CacheResourcesAsync(ImmutableArray resourceInfos) { IService.CheckDisposed(this); From 4167448279f32a9e314e135e016193978d159904 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 4 Apr 2026 11:50:35 -0300 Subject: [PATCH 257/288] Fix Game.Settings being nil --- .../SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs index 39f65c314..3d8492674 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs @@ -273,6 +273,7 @@ namespace Barotrauma.LuaCs public LuaGame(IConsoleCommandsService consoleCommands) { + UserData.RegisterType(typeof(GameSettings)); Settings = UserData.CreateStatic(typeof(GameSettings)); _consoleCommands = consoleCommands; } From f05d4ef129f4f613c85aeeca7004f2e65feb040d Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 4 Apr 2026 12:24:12 -0300 Subject: [PATCH 258/288] Fic CSharp enabling message printing before config got loaded --- .../LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua | 8 ++++++++ .../BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua index eee2287b6..08e139173 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua @@ -27,4 +27,12 @@ dofile(path .. "/Lua/DefaultLib/Utils/String.lua") dofile(path .. "/Lua/DefaultLib/Utils/Util.lua") dofile(path .. "/Lua/DefaultLib/Utils/SteamApi.lua") +if not CSActive then + for k, v in pairs(debug) do + if k ~= "getmetatable" and k ~= "setmetatable" and k ~= "traceback" then + debug[k] = nil + end + end +end + LuaSetup = nil \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index d9bb9f415..7b8525677 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -346,9 +346,6 @@ namespace Barotrauma void RunStateRunning_OnEnter(State currentState) { - string csEnabled = IsCsEnabled ? "enabled" : "disabled"; - Logger.LogMessage($"LuaCs running state entered. Running under commit {AssemblyInfo.GitRevision}, CSharp is {csEnabled}"); - if (!PackageManagementService.IsAnyPackageLoaded()) { foreach (var registrationProvider in _servicesProvider.GetAllServices()) @@ -360,6 +357,9 @@ namespace Barotrauma LoadLuaCsConfig(); } + string csEnabled = IsCsEnabled ? "enabled" : "disabled"; + Logger.LogMessage($"LuaCs running state entered. Running under commit {AssemblyInfo.GitRevision}, CSharp is {csEnabled}"); + if (!PackageManagementService.IsAnyPackageRunning()) { Logger.LogResults(PackageManagementService.ExecuteLoadedPackages(GetEnabledPackagesList(), IsCsEnabled)); From 121f670c8bb5459dae9c5768147408d54a9fa630 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 4 Apr 2026 13:04:34 -0300 Subject: [PATCH 259/288] Fix character.death hook getting called a billion times per second --- .../SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs | 2 +- .../SharedSource/LuaCs/_Services/LuaScriptManagementService.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs index 478ce7434..eb58aebcd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs @@ -205,7 +205,7 @@ internal class HarmonyEventPatchesService : ISystem _eventService.PublishEvent(x => x.OnCharacterCreated(__result)); } - [HarmonyPatch(typeof(Character), nameof(Character.Kill)), HarmonyPostfix] + [HarmonyPatch(typeof(Character), "KillProjSpecific"), HarmonyPostfix] public static void Character_Kill_Post(Character __instance, Affliction causeOfDeathAffliction, CauseOfDeathType causeOfDeath) { _eventService.PublishEvent(x => x.OnCharacterDeath(__instance, causeOfDeathAffliction, causeOfDeath)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 140ed2d87..f35c941b4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -412,6 +412,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, _script.Globals["ExecutionNumber"] = 0; _script.Globals["CSActive"] = !enableSandbox; + ((Table)_script.Globals["debug"])["breakpoint"] = () => { Debugger.Break(); }; _script.Globals["SERVER"] = LuaCsSetup.IsServer; _script.Globals["CLIENT"] = LuaCsSetup.IsClient; From 5e003bcf116338f85b2ccb5377dccc7a122bc6fd Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 4 Apr 2026 22:30:46 -0300 Subject: [PATCH 260/288] Fix missing Game.Commands --- .../SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs index 3d8492674..07f528c9f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/LuaClasses/LuaGame.cs @@ -135,6 +135,8 @@ namespace Barotrauma.LuaCs } } + public List Commands => DebugConsole.Commands; + public bool? ForceVoice = null; public bool? ForceLocalVoice = null; From 9b05c51ae5b78eb39990600c92139732ed19d110 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sat, 4 Apr 2026 22:52:27 -0300 Subject: [PATCH 261/288] Fix calling Hook.Call directly from Lua being broken --- .../SharedSource/LuaCs/_Services/EventService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs index 0323fad08..e973d12f1 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/EventService.cs @@ -134,6 +134,7 @@ public partial class EventService : IEventService return Call(eventName, args); } + [MoonSharpHidden] // Needs to be hidden so Lua doesn't accidentally use this instead of the above public T Call(string eventName, params object[] args) { Guard.IsNotNullOrWhiteSpace(eventName, nameof(eventName)); From 0e14983e8846686ef776366b64a55008ccba6d5f Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 5 Apr 2026 11:20:08 -0300 Subject: [PATCH 262/288] Allow System.Console --- .../SharedSource/LuaCs/_Services/_Lua/SafeLuaUserDataService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/SafeLuaUserDataService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/SafeLuaUserDataService.cs index fe23103fe..a31869e78 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/SafeLuaUserDataService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/SafeLuaUserDataService.cs @@ -81,6 +81,8 @@ public class SafeLuaUserDataService : ISafeLuaUserDataService } if (typeName == "System.Single") { return true; } + + if (typeName == "System.Console") { return true; } if (typeName.StartsWith("System.Collections", StringComparison.Ordinal)) return true; From 70554800157667641c4db3bb595bbb9504c7326b Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Sun, 5 Apr 2026 13:09:18 -0400 Subject: [PATCH 263/288] - Fixed publicized Barotrauma.dll missing error on DedicatedServer. - Fixed non-implemented folder search for ModConfig resources. --- .../LuaCsForBarotrauma/ModConfig.xml | 2 +- .../_Services/ModConfigFileParserService.cs | 30 +++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml index cd06c211b..25afd73df 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/ModConfig.xml @@ -3,6 +3,6 @@ - + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs index 530632e22..980eac69f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigFileParserService.cs @@ -5,6 +5,7 @@ using System.Runtime.CompilerServices; using System.Threading.Tasks; using System.Xml.Linq; using Barotrauma.LuaCs.Data; +using FarseerPhysics.Common; using FluentResults; using Microsoft.Toolkit.Diagnostics; @@ -191,7 +192,14 @@ public sealed partial class ModConfigFileParserService : } else { - res.WithError($"{srcOwner.Name}: The file '{filePath}' is missing!"); + if (srcElement.GetAttributeBool("IsFileRequired", true)) + { + res.WithError($"{srcOwner.Name}: The file '{filePath}' is missing!"); + } + else + { + res.WithSuccess($"Skipped missing not-required file: '{filePath}'"); + } } } @@ -200,10 +208,28 @@ public sealed partial class ModConfigFileParserService : if (_storageService.DirectoryExists(folderPath.FullPath) is { IsSuccess: true, Value: true }) { var files = _storageService.FindFilesInPackage(srcOwner, folderPath.Value, fileExtension, true); + if (files.IsFailed) + { + res.WithError($"{srcOwner.Name}: Failed to load files from {folderPath}!"); + } + else + { + foreach (var file in files.Value) + { + builder.Add(ContentPath.FromRaw(srcOwner, $"%ModDir%/{System.IO.Path.GetRelativePath(System.IO.Path.GetFullPath(srcOwner.Dir), file)}")); + } + } } else { - res.WithError($"{srcOwner.Name}: The folder '{filePath}' is missing!"); + if (srcElement.GetAttributeBool("IsFileRequired", true)) + { + res.WithError($"{srcOwner.Name}: The file '{folderPath}' is missing!"); + } + else + { + res.WithSuccess($"Skipped missing not-required folder: '{folderPath}'"); + } } } From df0a4e62f5adf4f32110f49d2c449b402ae8755f Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 7 Apr 2026 15:43:01 -0400 Subject: [PATCH 264/288] Added logging for additional plugin loading exceptions. --- .../_Services/PluginManagementService.cs | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index 6c23761dd..b3b99ad09 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -364,18 +364,35 @@ public class PluginManagementService : IAssemblyManagementService .Where(al => executionOrder.Contains(al.Key)) .Where(al => !excludeAlreadyRunningPackages || !_pluginInstances.ContainsKey(al.Key)) .SelectMany(al => al.Value.Assemblies.Select(ass => (al.Key, ass))) - .SelectMany(kvp => kvp.ass.GetSafeTypes() - .Where(type => - type is { IsInterface: false, IsAbstract: false, IsGenericType: false } - && type.IsAssignableTo(typeof(IAssemblyPlugin))) - .Select(type => (kvp.Key, type))) + .SelectMany<(ContentPackage Key, Assembly ass), (ContentPackage Key, Type type)>(kvp => + { + try + { + return kvp.ass.GetTypes() + .Where(type => + type is { IsInterface: false, IsAbstract: false, IsGenericType: false } + && type.IsAssignableTo(typeof(IAssemblyPlugin))) + .Select(type => (kvp.Key, type)); + } + catch (ReflectionTypeLoadException re) + { + results.WithError(new Error($"Failed to get types from Package '{kvp.Key.Name}'")); + results.WithError(new ExceptionalError(re)); + } + catch (Exception e) + { + results.WithError(new Error($"Failed to get types from Package '{kvp.Key.Name}'")); + results.WithError(new ExceptionalError(e)); + } + return new List<(ContentPackage Key, Type type)>(); + }) .GroupBy(kvp => kvp.Key, kvp => kvp.type) .OrderBy(exeGrp => executionOrder.IndexOf(exeGrp.Key)) .ToImmutableArray(); if (toLoad.Length == 0) { - return FluentResults.Result.Ok(); + return results; } _logger.LogMessage($"Activating {nameof(IAssemblyPlugin)} instances"); @@ -400,7 +417,7 @@ public class PluginManagementService : IAssemblyManagementService } catch (Exception e) { - results.WithError(new ExceptionalError(e)); + results.WithError(new ExceptionalError($"Failed to instantiate mod: {packageTypes.Key.Name}", e)); continue; } } From 232f7203e2adcd3517655d7a0b45379eefb26732 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 7 Apr 2026 16:07:55 -0400 Subject: [PATCH 265/288] Fixed shared Csharp src files not being included if there weren't also architecture-specific files. --- .../LuaCs/_Services/ModConfigService.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs index f9abe7371..3882a0175 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ModConfigService.cs @@ -320,6 +320,7 @@ public sealed class ModConfigService : IModConfigService foreach (var searchPathways in srcSearchInd) { + // we have architecture dependent files as well if (_storageService.FindFilesInPackage(srcPackage, searchPathways.SubFolder, "*.cs", true) is { IsSuccess: true, Value.IsDefaultOrEmpty: false } result) { @@ -342,6 +343,25 @@ public sealed class ModConfigService : IModConfigService IsReferenceModeOnly = false }); } + // add the shared files by themselves + else if (!sharedFiles.IsDefaultOrEmpty) + { + builder.Add(new AssemblyResourceInfo() + { + OwnerPackage = srcPackage, + InternalName = searchPathways.SubFolder, + SupportedPlatforms = searchPathways.Platforms, + SupportedTargets = searchPathways.Targets, + LoadPriority = 0, + FilePaths = sharedFiles, + FriendlyName = IAssemblyLoaderService.InternalsAwareAssemblyName, + IncompatiblePackages = ImmutableArray.Empty, + RequiredPackages = ImmutableArray.Empty, + UseInternalAccessName = true, + IsScript = true, + IsReferenceModeOnly = false + }); + } } return builder.ToImmutable(); From 4c8e016dea9df8e4e051454178b1f254e13d015f Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 8 Apr 2026 08:09:45 -0400 Subject: [PATCH 266/288] - Added extra source code translation (for CTS). - Added localization to SettingList.cs display dropdown. --- .../SharedSource/LuaCs/Data/SettingList.cs | 9 ++++++++- .../LuaCs/_Services/PluginManagementService.cs | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingList.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingList.cs index 82e7af94f..6e1b19f7e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingList.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingList.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Xml; using System.Xml.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.Toolkit.Diagnostics; @@ -100,10 +101,16 @@ public class SettingList : SettingEntry, ISettingList where T : IEquata #if CLIENT public override void AddDisplayComponent(GUILayoutGroup layoutGroup, Vector2 relativeSize, Action onSerializedValue) { - GUIUtil.Dropdown(layoutGroup, (T val) => val.ToString(), null, Options, Value, (T val) => + GUIUtil.Dropdown(layoutGroup, (T val) => GetLocalizedString(val.ToString(), val.ToString()), null, Options, Value, (T val) => { onSerializedValue?.Invoke(val.ToString()); }, new Vector2(relativeSize.X, 1f)); + + string GetLocalizedString(string identifier, string defaultValue) + { + var lstr = TextManager.Get($"{XmlConvert.EncodeLocalName(OwnerPackage.Name)}.{InternalName}.{identifier}.DisplayName"); + return lstr.IsNullOrWhiteSpace() ? defaultValue : lstr.Value; + } } #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs index b3b99ad09..7a0a52745 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PluginManagementService.cs @@ -660,7 +660,8 @@ public class PluginManagementService : IAssemblyManagementService { return sourceCode .Replace("GameMain.LuaCs", "LuaCsSetup.Instance") - .Replace("Client.ClientList", "ModUtils.Client.ClientList") + .Replace(" Client.ClientList", " ModUtils.Client.ClientList") + .Replace(" Barotrauma.Networking.Client.ClientList", " ModUtils.Client.ClientList") .Replace("ItemPrefab.GetItemPrefab", "ModUtils.ItemPrefab.GetItemPrefab"); } From 1294ba6decd14f522e8d2b9612ff0153c00e672a Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 8 Apr 2026 11:03:52 -0400 Subject: [PATCH 267/288] - Added API to get package by name to PackageManagementService.cs and LuaScriptManagementService.cs - Added ILuaConfigService to Lua API. --- .../SharedSource/LuaCs/LuaCsSetup.cs | 1 + .../_Services/LuaScriptManagementService.cs | 11 ++++++++++- .../LuaCs/_Services/PackageManagementService.cs | 16 ++++++++++++++++ .../_Services/_Interfaces/IConfigService.cs | 3 --- .../_Interfaces/IPackageManagementService.cs | 1 + .../LuaCs/_Services/_Lua/ILuaConfigService.cs | 4 +++- .../LuaCs/_Services/_Lua/ILuaService.cs | 2 +- 7 files changed, 32 insertions(+), 6 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 7b8525677..7e900d231 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -158,6 +158,7 @@ namespace Barotrauma servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); servicesProvider.RegisterServiceType(ServiceLifetime.Transient); servicesProvider.RegisterServiceType(ServiceLifetime.Singleton); + servicesProvider.RegisterServiceResolver(factory => factory.GetInstance() as ILuaConfigService); // Extension/Sub Services servicesProvider.RegisterServiceType(ServiceLifetime.Transient); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index f35c941b4..bcef4842c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -52,7 +52,9 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, private readonly IPluginManagementService _pluginManagementService; private readonly INetworkingService _networkingService; private readonly IConsoleCommandsService _commandsService; + private readonly ILuaConfigService _configService; private readonly ILuaCsInfoProvider _luaCsInfoProvider; + private readonly Lazy _packageManagementService; //private readonly ILuaCsUtility _luaCsUtility; public LuaScriptManagementService( @@ -69,7 +71,9 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, //ILuaCsUtility luaCsUtility, ILuaCsTimer luaCsTimer, IConsoleCommandsService commandsService, - ILuaCsInfoProvider luaCsInfoProvider) + ILuaCsInfoProvider luaCsInfoProvider, + ILuaConfigService configService, + Lazy packageManagementService) { _luaScriptLoader = loader; _userDataService = userDataService; @@ -84,6 +88,8 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, _eventService = eventService; _commandsService = commandsService; _luaCsInfoProvider = luaCsInfoProvider; + _configService = configService; + _packageManagementService = packageManagementService; _luaCsTimer = luaCsTimer; RegisterLuaEvents(); @@ -379,7 +385,10 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, _script.Globals["Hook"] = _eventService; _script.Globals["Timer"] = _luaCsTimer; _script.Globals["File"] = UserData.CreateStatic(); + _script.Globals["ConfigService"] = _configService; _script.Globals["Networking"] = _networkingService; + _script.Globals["trygetpackage"] = (string name, out ContentPackage package) => + _packageManagementService.Value.TryGetLoadedPackageByName(name, out package); //_script.Globals["Steam"] = Steam; if (enableSandbox) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs index 596539c3c..589f5c650 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/PackageManagementService.cs @@ -29,6 +29,7 @@ public sealed class PackageManagementService : IPackageManagementService // state private readonly ConcurrentDictionary _loadedPackages = new(); private readonly ConcurrentDictionary _runningPackages = new(); + private readonly ConcurrentDictionary _packageNameCache = new(); // control /// /// Service Disposal Lock. @@ -141,6 +142,7 @@ public sealed class PackageManagementService : IPackageManagementService #endif _runningPackages.Clear(); _loadedPackages.Clear(); + _packageNameCache.Clear(); return operationResult; } catch (Exception e) @@ -149,6 +151,18 @@ public sealed class PackageManagementService : IPackageManagementService } } + public bool TryGetLoadedPackageByName(string name, out ContentPackage package) + { + package = null; + if (name.IsNullOrWhiteSpace()) + { + return false; + } + + using var _ = _operationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult(); + return _packageNameCache.TryGetValue(name, out package); + } + public FluentResults.Result LoadPackageInfo(ContentPackage package) { Guard.IsNotNull(package, nameof(package)); @@ -232,6 +246,7 @@ public sealed class PackageManagementService : IPackageManagementService } _loadedPackages[package] = config; + _packageNameCache[package.Name] = package; try { var res = new FluentResults.Result(); @@ -435,6 +450,7 @@ public sealed class PackageManagementService : IPackageManagementService result.WithReasons(_uiStylesService.UnloadPackage(package).Reasons); #endif _loadedPackages.TryRemove(package, out _); + _packageNameCache.TryRemove(package.Name, out _); return result; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConfigService.cs index 83f8a3d43..09d571962 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IConfigService.cs @@ -17,11 +17,8 @@ public partial interface IConfigService : IReusableService, ILuaConfigService where T : class, ISettingBase; Task LoadConfigsAsync(ImmutableArray configResources); Task LoadConfigsProfilesAsync(ImmutableArray configProfileResources); - FluentResults.Result LoadSavedValueForConfig(ISettingBase setting); FluentResults.Result LoadSavedConfigsValues(); FluentResults.Result ApplyConfigProfile(ContentPackage package, string internalName); - FluentResults.Result SaveConfigValue(ISettingBase setting); FluentResults.Result DisposePackageData(ContentPackage package); FluentResults.Result DisposeAllPackageData(); - bool TryGetConfig(ContentPackage package, string internalName, out T instance) where T : ISettingBase; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPackageManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPackageManagementService.cs index c59da4ecf..8d050b3e0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPackageManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/IPackageManagementService.cs @@ -12,6 +12,7 @@ namespace Barotrauma.LuaCs; public interface IPackageManagementService : IReusableService { + public bool TryGetLoadedPackageByName(string name, out ContentPackage package); public FluentResults.Result LoadPackageInfo(ContentPackage package); public FluentResults.Result LoadPackagesInfo(ImmutableArray packages); public FluentResults.Result ExecuteLoadedPackages(ImmutableArray executionOrder, bool executeCsAssemblies); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaConfigService.cs index ca60c8318..29b851fc2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaConfigService.cs @@ -6,5 +6,7 @@ namespace Barotrauma.LuaCs; public interface ILuaConfigService : ILuaService { - + FluentResults.Result LoadSavedValueForConfig(ISettingBase setting); + bool TryGetConfig(ContentPackage package, string internalName, out T instance) where T : ISettingBase; + FluentResults.Result SaveConfigValue(ISettingBase setting); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaService.cs index 6db78b287..becb54333 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/ILuaService.cs @@ -1,6 +1,6 @@ namespace Barotrauma.LuaCs; -public interface ILuaService +public interface ILuaService : IService { } From d440ccbce2c5932d1501d6d6c0b1552d26ba6ae3 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:32:31 -0300 Subject: [PATCH 268/288] Register IConfigService and add back some missing APIs --- .../LuaCsForBarotrauma/Lua/LuaSetup.lua | 40 +++++++++++++++++++ .../LuaCs/Compatibility/ILuaCsNetworking.cs | 2 + .../_Services/LuaScriptManagementService.cs | 1 + .../LuaCs/_Services/NetworkingService.cs | 9 +++++ 4 files changed, 52 insertions(+) diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua index 08e139173..f83071229 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua @@ -35,4 +35,44 @@ 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/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs index 9ed27fc32..22b741488 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/ILuaCsNetworking.cs @@ -10,6 +10,8 @@ internal interface ILuaCsNetworking : ILuaCsShim void HttpRequest(string url, LuaCsAction callback, string data = null, string method = "POST", string contentType = "application/json", Dictionary headers = null, string savePath = null); void HttpPost(string url, LuaCsAction callback, string data, string contentType = "application/json", Dictionary headers = null, string savePath = null); void HttpGet(string url, LuaCsAction callback, Dictionary headers = null, string savePath = null); + void RequestGetHTTP(string url, LuaCsAction callback, Dictionary headers = null, string savePath = null); + void RequestPostHTTP(string url, LuaCsAction callback, string data, string contentType = "application/json", Dictionary headers = null, string savePath = null); void Receive(string netId, LuaCsAction action); #if SERVER diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index bcef4842c..78166c831 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -366,6 +366,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, UserData.RegisterType(typeof(IResourceInfo)); UserData.RegisterType(typeof(IUserDataDescriptor)); UserData.RegisterType(typeof(INetworkingService)); + UserData.RegisterType(typeof(IConfigService)); new LuaConverters(this).RegisterLuaConverters(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs index 8482bebc3..8ec528afa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs @@ -348,12 +348,21 @@ internal partial class NetworkingService : INetworkingService HttpRequest(url, callback, data, "POST", contentType, headers, savePath); } + public void RequestPostHTTP(string url, LuaCsAction callback, string data, string contentType = "application/json", Dictionary headers = null, string savePath = null) + { + HttpRequest(url, callback, data, "POST", contentType, headers, savePath); + } public void HttpGet(string url, LuaCsAction callback, Dictionary headers = null, string savePath = null) { HttpRequest(url, callback, null, "GET", null, headers, savePath); } + public void RequestGetHTTP(string url, LuaCsAction callback, Dictionary headers = null, string savePath = null) + { + HttpRequest(url, callback, null, "GET", null, headers, savePath); + } + public void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData) { GameMain.NetworkMember.CreateEntityEvent(entity, extraData); From ebe8ec455fa9336e1bc31a6c657d6c6bda594525 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Tue, 7 Apr 2026 14:20:30 -0400 Subject: [PATCH 269/288] LuaCs CSharp Enabled Rework - New UI for the prompt - Third time's the charm. - Fixed TOCTOU for cs prompt on main menu. - Fixed SubEditor not running lua when don't run is selected for cs. - LuaCs CSharp Enabled Rework --- .../ClientSource/LuaCs/LuaCsSetup.cs | 231 ++++++++++-------- .../Screens/MainMenuScreen/MainMenuScreen.cs | 29 ++- .../ServerSource/LuaCs/LuaCsSetup.cs | 5 + .../Config/SettingsShared.xml | 9 +- .../LuaCsForBarotrauma/Texts/English.xml | 6 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 37 ++- 6 files changed, 186 insertions(+), 131 deletions(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index 8a20415ab..f1fdab03a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -7,6 +7,8 @@ using System.Text; using Barotrauma.CharacterEditor; using Barotrauma.LuaCs; using Barotrauma.LuaCs.Data; +using Barotrauma.Networking; +using Microsoft.Xna.Framework; // ReSharper disable ObjectCreationAsStatement @@ -15,120 +17,147 @@ namespace Barotrauma partial class LuaCsSetup { private bool _isClientPromptActive; - - /// - /// Returns whether execution should continue - /// - public bool CheckReadyToRun() + private bool _isCsEnabledForSession = false; + + public void CheckRunConditionalHostingCsEnabled(Action onReadyToRun) { - // Fast exit if enabled - if (this.IsCsEnabled) + var res = ReadyToRunNoPrompt(); + if (res.ShouldRun) { - return true; + onReadyToRun?.Invoke(); + return; } - StringBuilder sb = new StringBuilder(); - - foreach (ContentPackage cp in PackageManagementService.GetLoadedAssemblyPackages()) + DisplayCsModsPromptClient(res.Item2, (selectedYes) => { - if (cp.NameMatches(PackageId)) + if (selectedYes) { - continue; + onReadyToRun?.Invoke(); } + }); + } - if (cp.UgcId.TryUnwrap(out ContentPackageId id)) - { - sb.AppendLine($"- {cp.Name} ({id})"); - } - else - { - sb.AppendLine($"- {cp.Name} (Not On Workshop)"); - } + private (bool ShouldRun, ImmutableArray PromptPackages) ReadyToRunNoPrompt() + { + if (this.IsCsEnabled) + { + return (true, ImmutableArray.Empty); } - if (string.IsNullOrEmpty(sb.ToString())) + if (!ShouldPromptForCs) { - return true; + return (true, ImmutableArray.Empty); } - if (!_isClientPromptActive) + ImmutableArray contentPackages = PackageManagementService.GetLoadedAssemblyPackages() + .Where(p => p.Name != PackageId) + .ToImmutableArray(); + + return (contentPackages.IsEmpty, contentPackages); + } + + partial void CheckReadyToRun(Action onReadyToRun) + { + var res = ReadyToRunNoPrompt(); + if (res.ShouldRun) { - _isClientPromptActive = true; - if (GameMain.Client == null || GameMain.Client.IsServerOwner) + onReadyToRun?.Invoke(); + return; + } + + if (GameMain.Client?.ClientPeer is P2POwnerPeer) + { + SetCsPolicyAndContinue(true); + return; + } + + DisplayCsModsPromptClient(res.PromptPackages, (selectedYes) => + { + SetCsPolicyAndContinue(selectedYes); + return; + }); + + void SetCsPolicyAndContinue(bool csSessionExecutionPolicy) + { + var prevRunState = this.CurrentRunState; + if (CurrentRunState >= RunState.Running) { - DisplayCsModsPromptServer(sb); + 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"), + relativeSize: new Vector2(0.3f, 0.55f), + minSize: new Point(400, 500), + text: string.Empty, + buttons: []); + + GUILayoutGroup msgBoxLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.75f), messageBox.Content.RectTransform), isHorizontal: false, childAnchor: Anchor.TopCenter) + { + RelativeSpacing = 0.01f, + Stretch = true + }; + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), msgBoxLayout.RectTransform), "The following mods contain CSharp code", + font: GUIStyle.SubHeadingFont, wrap: true, textAlignment: Alignment.Center); + + GUIListBox packageListBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.4f), msgBoxLayout.RectTransform)) + { + CurrentSelectMode = GUIListBox.SelectMode.None + }; + + 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); + } + + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0f), msgBoxLayout.RectTransform), "CSharp 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) + { + 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") + { + TextBlock = { AutoScaleHorizontal = true }, + OnClicked = (btn, userdata) => + { + _isClientPromptActive = false; + onSelection(true); + messageBox.Close(); return true; } - else + }; + + new GUIButton(new RectTransform(new Vector2(0.8f, 0.0f), buttonLayout.RectTransform), "Cancel") + { + OnClicked = (btn, userdata) => { - DisplayCsModsPromptClient(sb); - return false; + _isClientPromptActive = false; + onSelection(false); + messageBox.Close(); + return true; } - } - else - { - return false; - } - - void DisplayCsModsPromptServer(StringBuilder sb) - { - var msg = new GUIMessageBox("", $"You have CSharp mods enabled but don't have the CSharp Scripting enabled, " + - $"those mods might not work, go to the Main Menu, click on LuaCs Settings and check Enable CSharp Scripting.\n\n{sb}"); - foreach (var button in msg.Buttons) - { - var old = button.OnClicked; - button.OnClicked = (btn, obj) => - { - var ret = old?.Invoke(btn, obj); - _isClientPromptActive = false; - return ret ?? true; - }; - } - } - - void DisplayCsModsPromptClient(StringBuilder sb) - { - GUIMessageBox msg = new GUIMessageBox( - "Confirm", - $"This server has the following CSharp mods installed: \n{sb}\nDo you wish to run them? Cs mods are not sandboxed so make sure you trust these mods.", - new LocalizedString[2] { "Run", "Don't Run" }); - - msg.Buttons[0].OnClicked = (GUIButton button, object obj) => - { - try - { - this._isClientPromptActive = false; - CoroutineManager.Invoke(() => - { - SetRunState(RunState.LoadedNoExec); - this.IsCsEnabled = true; - SetRunState(RunState.Running); - }, 0f); - return true; - } - finally - { - msg.Close(); - } - }; - - msg.Buttons[1].OnClicked = (GUIButton button, object obj) => - { - try - { - // avoid a TOCTOU scenario. - this.IsCsEnabled = false; - this._isClientPromptActive = false; - SetRunState(RunState.LoadedNoExec); - SetRunState(RunState.Running); - return true; - } - finally - { - msg.Close(); - } - }; - } + }; } private void SetupServicesProviderClient(IServicesProvider serviceProvider) @@ -172,16 +201,10 @@ namespace Barotrauma case SpriteEditorScreen: case SubEditorScreen: case TestScreen: // notes: TestScreen is a Linux edge case editor screen and is deprecated. - if (!CheckReadyToRun()) + CheckReadyToRun(() => { - if (CurrentRunState >= RunState.Running) - { - SetRunState(RunState.LoadedNoExec); - } - return; - } - - SetRunState(RunState.Running); + SetRunState(RunState.Running); + }); break; default: Logger.LogError( diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs index e287d991b..6704de400 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs @@ -1012,20 +1012,25 @@ namespace Barotrauma private void TryStartServer() { - if (SubmarineInfo.SavedSubmarines.Any(s => s.CalculatingHash)) + LuaCsSetup.Instance.CheckRunConditionalHostingCsEnabled(() => { - 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) => + if (SubmarineInfo.SavedSubmarines.Any(s => s.CalculatingHash)) { - CoroutineManager.StopCoroutines(waitCoroutine); - return true; - }; - } - else - { - StartServer(); - } + 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(); + } + }); + + } private IEnumerable WaitForSubmarineHashCalculations(GUIMessageBox messageBox) diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs index 3e39a4b72..7c33a0f64 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/LuaCsSetup.cs @@ -6,6 +6,11 @@ namespace Barotrauma; partial class LuaCsSetup { + partial void CheckReadyToRun(Action onReadyToRun) + { + onReadyToRun?.Invoke(); + } + /// /// Handles changes in game states tracked by screen changes. /// diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml index 35fa892f7..103091314 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Config/SettingsShared.xml @@ -2,7 +2,14 @@ - + + + + + + + + diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml index 2462c80ea..a0f8ccd72 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Texts/English.xml @@ -4,9 +4,9 @@ Mod Gameplay Settings - Are C# Mods Allowed - Should unsandboxed scripts and dlls be allowed to run. - General + Are C# Mods Allowed + Should unsandboxed scripts and dlls be allowed to run. + General Use Pre-Caching Should mod files be preloaded to speed up loading. Should only be turned off if you have mods that have issues with this. diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 7e900d231..8c6502776 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -84,18 +84,22 @@ namespace Barotrauma private LuaGame _game; public LuaGame Game => _game ??= _servicesProvider.GetService(); - + /// /// Whether C# plugin code is enabled. /// public bool IsCsEnabled { - get => _isCsEnabled?.Value ?? false; - internal set => _isCsEnabled?.TrySetValue(value); +#if CLIENT + 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 ISettingBase _isCsEnabled; - + private ISettingList _csRunPolicy; + private bool ShouldPromptForCs => _csRunPolicy?.Value is "Prompt"; + /// /// Whether usernames are anonymized or show in logs. /// @@ -123,8 +127,8 @@ namespace Barotrauma { var luaCsPackage = GetLuaCsPackage(); - _isCsEnabled = - ConfigService.TryGetConfig>(luaCsPackage, "IsCsEnabled", out var val1) + _csRunPolicy = + ConfigService.TryGetConfig>(luaCsPackage, "CsRunPolicy", out var val1) ? val1 : null; _hideUserNamesInLogs = @@ -380,16 +384,27 @@ 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"); } // ReSharper restore InconsistentNaming } + + + + #endregion + /// + /// Checks for Cs Execution Policy (ie. prompting the user) and then calls the delegate once completed. + /// + /// + partial void CheckReadyToRun(Action onReadyToRun); + #region LegacyRedirects // --- Compatibility @@ -457,7 +472,7 @@ namespace Barotrauma void DisposeLuaCsConfig() { - _isCsEnabled = null; + _csRunPolicy = null; _hideUserNamesInLogs = null; } } From bdd4dcfb9ee714b80b3672fbc5fcfa817ccf4ec3 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 8 Apr 2026 16:27:01 -0400 Subject: [PATCH 270/288] Disabled caching on the ConfigService. --- .../SharedSource/LuaCs/_Services/ConfigService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs index 5685571b4..2ff7199f6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs @@ -162,7 +162,7 @@ public sealed partial class ConfigService : IConfigService _commandsService = commandsService; _infoProvider = infoProvider; - _storageService.UseCaching = true; + _storageService.UseCaching = false; InjectCommands(commandsService); } From 6c32873d4e0824540fd87b30a3adc80e14281d89 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Wed, 8 Apr 2026 18:04:25 -0300 Subject: [PATCH 271/288] Only register ILuaConfigService --- .../SharedSource/LuaCs/_Services/LuaScriptManagementService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 78166c831..d761f0b59 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -366,7 +366,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, UserData.RegisterType(typeof(IResourceInfo)); UserData.RegisterType(typeof(IUserDataDescriptor)); UserData.RegisterType(typeof(INetworkingService)); - UserData.RegisterType(typeof(IConfigService)); + UserData.RegisterType(typeof(ILuaConfigService)); new LuaConverters(this).RegisterLuaConverters(); From c882b0bb45b75f4008b961cdebbb9815d9f43243 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Wed, 8 Apr 2026 18:07:12 -0300 Subject: [PATCH 272/288] Update moonsharp --- Libraries/moonsharp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/moonsharp b/Libraries/moonsharp index 3a71aa309..a06c73f68 160000 --- a/Libraries/moonsharp +++ b/Libraries/moonsharp @@ -1 +1 @@ -Subproject commit 3a71aa3096c8d6f1be5a42c2ab241934a63bc0b3 +Subproject commit a06c73f68853adcd6b6f3ae316d97f2ac3e61bed From eeeb3d9db3095382d415baafe8038decc3af9cee Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Wed, 8 Apr 2026 18:29:07 -0300 Subject: [PATCH 273/288] Expose all settings in Lua --- .../_Services/LuaScriptManagementService.cs | 35 +++++++++++++++++++ .../_Services/_Lua/DefaultLuaRegistrar.cs | 21 ----------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index d761f0b59..d0b97c785 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -368,6 +368,41 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, UserData.RegisterType(typeof(INetworkingService)); UserData.RegisterType(typeof(ILuaConfigService)); + UserData.RegisterType(typeof(ISettingBase)); + + Type[] settingBaseTypes = [ + typeof(ISettingBase), + typeof(ISettingBase), + typeof(ISettingBase), + typeof(ISettingBase), + typeof(ISettingBase), + typeof(ISettingBase), + typeof(ISettingBase), + typeof(ISettingBase), + typeof(ISettingBase), + typeof(ISettingBase), + typeof(ISettingBase), + typeof(ISettingBase), + typeof(ISettingBase), + typeof(ISettingRangeBase) + ]; + + Table settingsTable = new Table(_script); + + foreach (Type type in settingBaseTypes) + { + UserData.RegisterType(type); + + settingsTable[type.GetGenericArguments()[0].Name] = UserData.CreateStatic(type); + } + + _script.Globals["Settings"] = settingsTable; + + UserData.RegisterType(typeof(ISettingRangeBase)); +#if CLIENT + UserData.RegisterType(typeof(ISettingControl)); +#endif + new LuaConverters(this).RegisterLuaConverters(); var luaRequire = new LuaRequire(_script); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs index 26a9afbf9..69107eb60 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Lua/DefaultLuaRegistrar.cs @@ -83,27 +83,6 @@ public class DefaultLuaRegistrar : IDefaultLuaRegistrar _userDataService.RegisterType(type.FullName); } - _userDataService.RegisterType(typeof(IConfigService).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingBase).FullName); - _userDataService.RegisterType(typeof(ISettingRangeBase).FullName); - _userDataService.RegisterType(typeof(ISettingRangeBase).FullName); -#if CLIENT - _userDataService.RegisterType(typeof(ISettingControl).FullName); -#endif - _userDataService.RegisterType("Barotrauma.LuaSByte"); _userDataService.RegisterType("Barotrauma.LuaByte"); _userDataService.RegisterType("Barotrauma.LuaInt16"); From e9673bf7f5e17a6fe143cc1f8a72f3e7f248c67f Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Wed, 8 Apr 2026 18:29:29 -0300 Subject: [PATCH 274/288] Update moonsharp --- Libraries/moonsharp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/moonsharp b/Libraries/moonsharp index a06c73f68..b556e550e 160000 --- a/Libraries/moonsharp +++ b/Libraries/moonsharp @@ -1 +1 @@ -Subproject commit a06c73f68853adcd6b6f3ae316d97f2ac3e61bed +Subproject commit b556e550eb20b950a7db3ef69006104af3f654da From b0b4acf392a148780a23ac160b1c91493dd21407 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 8 Apr 2026 17:29:56 -0400 Subject: [PATCH 275/288] - Made readonly readonly. --- .../LuaCs/Data/SettingsFactoryRegistrationProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs index 883699e8b..8e705c4d6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingsFactoryRegistrationProvider.cs @@ -100,7 +100,7 @@ public class SettingsEntryRegistrar : ISettingsRegistrationProvider || valueChangePredicate.Invoke(newValue); #else // Server has absolute authority. - return true; + return !info.Element.GetAttributeBool("ReadOnly", false); #endif } From 2e656509a816a738f9112585e87c8b2e677446b1 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 8 Apr 2026 17:53:12 -0400 Subject: [PATCH 276/288] - Added null check to get string value. --- .../BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs index 16394b6cd..4e584dbdf 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs @@ -104,7 +104,7 @@ public partial class SettingEntry : SettingBase, ISettingBase, INetworkSyn public override Type GetValueType() => typeof(T); - public override string GetStringValue() => Value.ToString(); + public override string GetStringValue() => Value?.ToString() ?? string.Empty; public override string GetDefaultStringValue() => DefaultValue.ToString(); From e161d921174f1a1835e756f470b88e33326d9041 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Wed, 8 Apr 2026 19:44:16 -0300 Subject: [PATCH 277/288] Fix settings not being properly exposed to Lua --- .../[DebugOnlyTest]TestLuaMod/Lua/init.lua | 16 ++++++- .../_Services/LuaScriptManagementService.cs | 44 +++++++++++++++---- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua index a3252fc85..45348369b 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua @@ -22,4 +22,18 @@ end) Hook.Add("missionsEnded", "test", function() print("missionsEnded") -end) \ No newline at end of file +end) + +local failed, package = trygetpackage("[DebugOnlyTest]TestLuaMod") + +local success, config = ConfigService.TryGetConfig(SettingBase.Single, package, "TestFloat") + +print("config ", success, " ", config) + +local lastTime = 0 +Hook.Add("think", "printconfig", function() + if lastTime > Timer.Time then return end + + lastTime = Timer.Time + 10 + print(config.Value) +end) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index d0b97c785..841511c49 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -1,7 +1,9 @@ #nullable enable -using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs; using Barotrauma.LuaCs.Compatibility; +using Barotrauma.LuaCs.Data; +using Barotrauma.LuaCs.Events; using Barotrauma.Networking; using FluentResults; using Microsoft.CodeAnalysis; @@ -13,17 +15,16 @@ 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.Threading.Tasks; -using Barotrauma.LuaCs; -using Barotrauma.LuaCs.Events; -using System.Diagnostics; using System.Reflection; +using System.Threading.Tasks; namespace Barotrauma.LuaCs; @@ -384,19 +385,44 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, typeof(ISettingBase), typeof(ISettingBase), typeof(ISettingBase), - typeof(ISettingRangeBase) + + typeof(ISettingRangeBase), + typeof(ISettingRangeBase), + + typeof(ISettingList), + typeof(ISettingList), + typeof(ISettingList), + typeof(ISettingList), + typeof(ISettingList), + typeof(ISettingList), + typeof(ISettingList), + typeof(ISettingList), + typeof(ISettingList), + typeof(ISettingList), + typeof(ISettingList), + typeof(ISettingList), ]; - Table settingsTable = new Table(_script); + Dictionary> settingsTable = []; foreach (Type type in settingBaseTypes) { UserData.RegisterType(type); - settingsTable[type.GetGenericArguments()[0].Name] = UserData.CreateStatic(type); + string baseName = type.Name.RemoveFromEnd("`1").Substring(1); + + if (!settingsTable.ContainsKey(baseName)) + { + settingsTable[baseName] = new Dictionary(); + } + + settingsTable[baseName][type.GetGenericArguments()[0].Name] = UserData.CreateStatic(type); } - _script.Globals["Settings"] = settingsTable; + foreach (var keyPair in settingsTable) + { + _script.Globals[keyPair.Key] = keyPair.Value; + } UserData.RegisterType(typeof(ISettingRangeBase)); #if CLIENT From b9b457262ff32d2e4cc226bfcb100b1b2e73037e Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Wed, 8 Apr 2026 19:56:32 -0300 Subject: [PATCH 278/288] Test Lua config mod --- .../LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua | 4 ++++ .../LocalMods/[DebugOnlyTest]TestLuaMod/dummy.xml | 4 ++++ .../LocalMods/[DebugOnlyTest]TestLuaMod/filelist.xml | 1 + 3 files changed, 9 insertions(+) create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/dummy.xml diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua index 45348369b..cac8db7c9 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua @@ -36,4 +36,8 @@ Hook.Add("think", "printconfig", function() lastTime = Timer.Time + 10 print(config.Value) + + if SERVER then + config.TrySetValue(config.Value + 1) + end end) diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/dummy.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/dummy.xml new file mode 100644 index 000000000..ba75a446c --- /dev/null +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/dummy.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/filelist.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/filelist.xml index dfb60968b..1fc37166a 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/filelist.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/filelist.xml @@ -1,4 +1,5 @@  + From 3a5fbfde1efd094ef1c040b84c0fdb4f56e688af Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Wed, 8 Apr 2026 20:12:20 -0300 Subject: [PATCH 279/288] Fix cs enabled for session state not being preserved between reloads --- .../BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs | 2 +- .../BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index f1fdab03a..1c67daf45 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -129,7 +129,7 @@ namespace Barotrauma new GUITextBlock(new RectTransform(new Vector2(1f, 1f), packageFrame.RectTransform), package.Name); } - new GUITextBlock(new RectTransform(new Vector2(1.0f, 0f), msgBoxLayout.RectTransform), "CSharp 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) + 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) { Wrap = true }; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 8c6502776..7eb9fd6a5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -230,8 +230,14 @@ namespace Barotrauma CoroutineManager.Invoke(() => { +#if CLIENT + bool prevCsEnabled = _isCsEnabledForSession; +#endif var state = CurrentRunState; SetRunState(RunState.Unloaded); +#if CLIENT + _isCsEnabledForSession = prevCsEnabled; +#endif SetRunState(state); }); } From 89aa818c0ba70663580a52712ffe31753db74f62 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 8 Apr 2026 19:38:18 -0400 Subject: [PATCH 280/288] Made Config initialization errors more forgiving to noobs. --- .../LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua | 3 +++ .../LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml | 2 +- .../SharedSource/LuaCs/_Services/ConfigService.cs | 10 +++++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua index cac8db7c9..600035344 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua @@ -26,6 +26,9 @@ end) local failed, package = trygetpackage("[DebugOnlyTest]TestLuaMod") +print("packageFailed=", failed) +print("package", package.Name) + local success, config = ConfigService.TryGetConfig(SettingBase.Single, package, "TestFloat") print("config ", success, " ", config) diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml index 8889ae392..1af981a06 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/ModConfig.xml @@ -1,5 +1,5 @@ - + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs index 2ff7199f6..442aa0185 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/ConfigService.cs @@ -325,6 +325,8 @@ public sealed partial class ConfigService : IConfigService { return FluentResults.Result.Ok(); } + + var result = new FluentResults.Result(); var taskBuilder = ImmutableArray.CreateBuilder>>(); var toProcessErrors = new ConcurrentStack(); @@ -362,7 +364,9 @@ public sealed partial class ConfigService : IConfigService { if (!_instanceFactory.TryGetValue(info.DataType, out var factory)) { - return FluentResults.Result.Fail($"{nameof(LoadConfigsAsync)}: Could not retrieve the instance factory for the data type of '{info.DataType}'!"); + result.WithError( + $"{nameof(LoadConfigsAsync)}: Could not retrieve the instance factory for the data type of '{info.DataType}'!"); + continue; } if (_settingsInstances.ContainsKey((info.OwnerPackage, info.InternalName))) { @@ -383,13 +387,13 @@ public sealed partial class ConfigService : IConfigService } catch (Exception e) { - FluentResults.Result.Fail( + result.WithError( $"{nameof(LoadConfigsAsync)}: Error while instancing setting for '{instanceFactoryInfo.configInfo.OwnerPackage}.{instanceFactoryInfo.configInfo.InternalName}': {e.Message}!"); + continue; } } using var settingsLck = await _settingsByPackageLock.AcquireWriterLock(); // block to protect new bag instance creation - var result = new FluentResults.Result(); while (toProcessInstanceQueue.TryDequeue(out var newInstanceData)) { From 53be3f0073a7ed3f23726c2761c2d654689f1c64 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Wed, 8 Apr 2026 19:49:57 -0400 Subject: [PATCH 281/288] Fixed lua attempting to invoke the base interface type. --- .../SharedSource/LuaCs/_Services/LuaScriptManagementService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 841511c49..7a7eed6d5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -369,7 +369,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, UserData.RegisterType(typeof(INetworkingService)); UserData.RegisterType(typeof(ILuaConfigService)); - UserData.RegisterType(typeof(ISettingBase)); + //UserData.RegisterType(typeof(ISettingBase)); Type[] settingBaseTypes = [ typeof(ISettingBase), From 84cb7cfeb7fc42b3571986f1f621c735a009be75 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 9 Apr 2026 01:29:48 -0400 Subject: [PATCH 282/288] server auth var. --- .../LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml index 7a5f97a3e..0cd18babc 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml @@ -2,7 +2,7 @@ - + From ef66d27ffe32a87fba40d04922b7c24310e151d5 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 9 Apr 2026 05:34:50 -0400 Subject: [PATCH 283/288] - Fixed network synchro of vars, needs synctype testing. --- .../{Services => _Services}/ConfigService.cs | 0 .../{Services => _Services}/LoggerService.cs | 0 .../ModConfigStylesFileParserService.cs | 0 .../NetworkingService.cs | 18 ++++- .../UIStylesCollection.cs | 0 .../UIStylesService.cs | 0 .../_Interfaces/IClientLoggerService.cs | 0 .../_Interfaces/IConfigService.cs | 0 .../_Interfaces/ISettingsMenuService.cs | 0 .../_Interfaces/IUIStylesCollection.cs | 0 .../_Interfaces/IUIStylesService.cs | 0 .../_SettingsMenu/ModsControlsSettingsMenu.cs | 0 .../_SettingsMenu/ModsGameplaySettingsMenu.cs | 0 .../_SettingsMenu/ModsSettingsMenuBase.cs | 0 .../_SettingsMenu/SettingsMenuSystem.cs | 0 .../NetworkingService.cs | 0 .../[DebugOnlyTest]TestLuaMod/Settings.xml | 2 + .../SharedSource/LuaCs/Data/SettingEntry.cs | 75 ++++++++++++++----- .../SharedSource/LuaCs/Data/SettingList.cs | 6 +- .../SharedSource/LuaCs/IEvents.cs | 2 + .../LuaCs/_Services/NetworkingService.cs | 34 ++++++++- .../_Interfaces/INetworkingService.cs | 1 + 22 files changed, 115 insertions(+), 23 deletions(-) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/{Services => _Services}/ConfigService.cs (100%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/{Services => _Services}/LoggerService.cs (100%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/{Services => _Services}/ModConfigStylesFileParserService.cs (100%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/{Services => _Services}/NetworkingService.cs (90%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/{Services => _Services}/UIStylesCollection.cs (100%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/{Services => _Services}/UIStylesService.cs (100%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/{Services => _Services}/_Interfaces/IClientLoggerService.cs (100%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/{Services => _Services}/_Interfaces/IConfigService.cs (100%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/{Services => _Services}/_Interfaces/ISettingsMenuService.cs (100%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/{Services => _Services}/_Interfaces/IUIStylesCollection.cs (100%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/{Services => _Services}/_Interfaces/IUIStylesService.cs (100%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/{Services => _Services}/_SettingsMenu/ModsControlsSettingsMenu.cs (100%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/{Services => _Services}/_SettingsMenu/ModsGameplaySettingsMenu.cs (100%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/{Services => _Services}/_SettingsMenu/ModsSettingsMenuBase.cs (100%) rename Barotrauma/BarotraumaClient/ClientSource/LuaCs/{Services => _Services}/_SettingsMenu/SettingsMenuSystem.cs (100%) rename Barotrauma/BarotraumaServer/ServerSource/LuaCs/{Services => _Services}/NetworkingService.cs (100%) diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/ConfigService.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ConfigService.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/ConfigService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/LoggerService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/LoggerService.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/LoggerService.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/LoggerService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModConfigStylesFileParserService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/ModConfigStylesFileParserService.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/ModConfigStylesFileParserService.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/ModConfigStylesFileParserService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/NetworkingService.cs similarity index 90% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/NetworkingService.cs index 0e4a1eb38..4b7c5d771 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/NetworkingService.cs @@ -13,9 +13,25 @@ partial class NetworkingService : INetworkingService, IEventServerConnected, IEv public void OnServerConnected() { + ActivateNetVars(); SendSyncMessage(); } + private void ActivateNetVars() + { + if (GameMain.Client == null) + { + return; + } + + // re-activate net vars + // todo: unregister net vars on client disconnect, currently handled by unloading the state machine. + foreach (var networkSyncVar in netVars.Keys) + { + networkSyncVar.SetNetworkOwner(this); + } + } + public void OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader) { if (serverPacketHeader != ServerHeader) @@ -44,7 +60,7 @@ partial class NetworkingService : INetworkingService, IEventServerConnected, IEv private void SendSyncMessage() { if (GameMain.Client == null) { return; } - + WriteOnlyMessage message = new WriteOnlyMessage(); message.WriteByte((byte)ClientHeader); message.WriteByte((byte)ClientToServer.RequestSync); diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesCollection.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/UIStylesCollection.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesCollection.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/UIStylesCollection.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/UIStylesService.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/UIStylesService.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/UIStylesService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IClientLoggerService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_Interfaces/IClientLoggerService.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IClientLoggerService.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_Interfaces/IClientLoggerService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IConfigService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_Interfaces/IConfigService.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IConfigService.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_Interfaces/IConfigService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/ISettingsMenuService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_Interfaces/ISettingsMenuService.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/ISettingsMenuService.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_Interfaces/ISettingsMenuService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IUIStylesCollection.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_Interfaces/IUIStylesCollection.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IUIStylesCollection.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_Interfaces/IUIStylesCollection.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IUIStylesService.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_Interfaces/IUIStylesService.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_Interfaces/IUIStylesService.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_Interfaces/IUIStylesService.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsControlsSettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/ModsControlsSettingsMenu.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsControlsSettingsMenu.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/ModsControlsSettingsMenu.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/ModsGameplaySettingsMenu.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsGameplaySettingsMenu.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/ModsGameplaySettingsMenu.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsSettingsMenuBase.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/ModsSettingsMenuBase.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/ModsSettingsMenuBase.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/ModsSettingsMenuBase.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/SettingsMenuSystem.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/SettingsMenuSystem.cs similarity index 100% rename from Barotrauma/BarotraumaClient/ClientSource/LuaCs/Services/_SettingsMenu/SettingsMenuSystem.cs rename to Barotrauma/BarotraumaClient/ClientSource/LuaCs/_Services/_SettingsMenu/SettingsMenuSystem.cs diff --git a/Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs b/Barotrauma/BarotraumaServer/ServerSource/LuaCs/_Services/NetworkingService.cs similarity index 100% rename from Barotrauma/BarotraumaServer/ServerSource/LuaCs/Services/NetworkingService.cs rename to Barotrauma/BarotraumaServer/ServerSource/LuaCs/_Services/NetworkingService.cs diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml index 0cd18babc..c05583cf2 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Settings.xml @@ -2,6 +2,8 @@ + + diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs index 4e584dbdf..83879b195 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingEntry.cs @@ -53,9 +53,14 @@ public partial class SettingEntry : SettingBase, ISettingBase, INetworkSyn public virtual bool TrySetValue(T value) { + if (value is null || value.Equals(Value)) + { + return false; + } #if CLIENT if (SyncType is NetSync.ServerAuthority && NetworkingService is not null && GameMain.IsMultiplayer + && GameMain.Client is not null && !GameMain.Client.HasPermission(this.WritePermissions)) { return false; @@ -73,7 +78,7 @@ public partial class SettingEntry : SettingBase, ISettingBase, INetworkSyn NetworkingService?.SendNetVar(this); } #elif SERVER - if (GameMain.IsMultiplayer && SyncType is NetSync.TwoWay or NetSync.ServerAuthority) + if (SyncType is NetSync.TwoWay or NetSync.ServerAuthority) { NetworkingService?.SendNetVar(this); } @@ -97,9 +102,48 @@ public partial class SettingEntry : SettingBase, ISettingBase, INetworkSyn return true; } + /// + /// handles internal networking rules after reading the net message (to avoid synchro issues). + /// + /// + /// + private bool TrySetValueNetwork(T value) + { + if (NetworkingService is null) + { + return false; + } +#if CLIENT + if (SyncType is NetSync.None or NetSync.ClientOneWay) + { + return false; + } +#else + if (SyncType is NetSync.None or NetSync.ServerAuthority) + { + return false; + } +#endif + if (!TrySetValueInternal(value)) + { + return false; + } + +#if SERVER + if (SyncType is NetSync.TwoWay) + { + NetworkingService?.SendNetVar(this); + } +#endif + + OnValueChanged?.Invoke(this); + return true; + } + protected override void OnDispose() { ValueChangePredicate = null; + NetworkingService?.DeregisterNetVar(this); } public override Type GetValueType() => typeof(T); @@ -151,11 +195,6 @@ public partial class SettingEntry : SettingBase, ISettingBase, INetworkSyn public void SetNetworkOwner(IEntityNetworkingService networkingService) { NetworkingService = networkingService; - if (NetworkingService is null) - { - return; - } - NetworkingService.RegisterNetVar(this); } public NetSync SyncType => ConfigInfo?.NetSync ?? NetSync.None; @@ -181,42 +220,42 @@ public partial class SettingEntry : SettingBase, ISettingBase, INetworkSyn switch (typeCode) { case TypeCode.Boolean: - TrySetValueInternal((T)Convert.ChangeType(message.ReadBoolean(), typeCode)); + TrySetValueNetwork((T)Convert.ChangeType(message.ReadBoolean(), typeCode)); return; case TypeCode.Byte: - TrySetValueInternal((T)Convert.ChangeType(message.ReadByte(), typeCode)); + TrySetValueNetwork((T)Convert.ChangeType(message.ReadByte(), typeCode)); return; // SByte not supported by interface case TypeCode.SByte: - TrySetValueInternal((T)Convert.ChangeType(message.ReadInt16(), typeCode)); + TrySetValueNetwork((T)Convert.ChangeType(message.ReadInt16(), typeCode)); return; case TypeCode.Int16: - TrySetValueInternal((T)Convert.ChangeType(message.ReadInt16(), typeCode)); + TrySetValueNetwork((T)Convert.ChangeType(message.ReadInt16(), typeCode)); return; case TypeCode.Char: case TypeCode.UInt16: - TrySetValueInternal((T)Convert.ChangeType(message.ReadUInt16(), typeCode)); + TrySetValueNetwork((T)Convert.ChangeType(message.ReadUInt16(), typeCode)); return; case TypeCode.Int32: - TrySetValueInternal((T)Convert.ChangeType(message.ReadInt32(), typeCode)); + TrySetValueNetwork((T)Convert.ChangeType(message.ReadInt32(), typeCode)); return; case TypeCode.UInt32: - TrySetValueInternal((T)Convert.ChangeType(message.ReadUInt32(), typeCode)); + TrySetValueNetwork((T)Convert.ChangeType(message.ReadUInt32(), typeCode)); return; case TypeCode.Int64: - TrySetValueInternal((T)Convert.ChangeType(message.ReadInt64(), typeCode)); + TrySetValueNetwork((T)Convert.ChangeType(message.ReadInt64(), typeCode)); return; case TypeCode.UInt64: - TrySetValueInternal((T)Convert.ChangeType(message.ReadUInt64(), typeCode)); + TrySetValueNetwork((T)Convert.ChangeType(message.ReadUInt64(), typeCode)); return; case TypeCode.Single: - TrySetValueInternal((T)Convert.ChangeType(message.ReadSingle(), typeCode)); + TrySetValueNetwork((T)Convert.ChangeType(message.ReadSingle(), typeCode)); return; case TypeCode.Double: - TrySetValueInternal((T)Convert.ChangeType(message.ReadDouble(), typeCode)); + TrySetValueNetwork((T)Convert.ChangeType(message.ReadDouble(), typeCode)); return; case TypeCode.String: - TrySetValueInternal((T)Convert.ChangeType(message.ReadString(), typeCode)); + TrySetValueNetwork((T)Convert.ChangeType(message.ReadString(), typeCode)); return; case TypeCode.Decimal: default: diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingList.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingList.cs index 6e1b19f7e..5c080f5ba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingList.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Data/SettingList.cs @@ -91,7 +91,11 @@ public class SettingList : SettingEntry, ISettingList where T : IEquata public bool TrySetValueByIndex(int index) { - throw new NotImplementedException(); + if (_valuesList.Count <= index) + { + return false; + } + return base.TrySetValue(_valuesList[index]); } public IReadOnlyList Options => _valuesList.AsReadOnly(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs index 095ec22bd..d0da76844 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/IEvents.cs @@ -947,6 +947,8 @@ interface IEventInventoryItemSwap : IEvent #region Networking + + #region Networking-Server #if SERVER public interface IEventClientRawNetMessageReceived : IEvent diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs index 8ec528afa..42511ed01 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/NetworkingService.cs @@ -11,10 +11,11 @@ using System.Linq; using System.Net.Http; using System.Security.Cryptography; using System.Text; +using Barotrauma.LuaCs.Data; namespace Barotrauma.LuaCs; -internal partial class NetworkingService : INetworkingService +internal partial class NetworkingService : INetworkingService, IEventSettingInstanceLifetime { public readonly record struct NetId { @@ -112,7 +113,6 @@ internal partial class NetworkingService : INetworkingService #if SERVER IsSynchronized = true; #endif - SubscribeToEvents(); } @@ -189,6 +189,7 @@ internal partial class NetworkingService : INetworkingService private void SubscribeToEvents() { + _eventService.Subscribe(this); #if CLIENT _eventService.Subscribe(this); _eventService.Subscribe(this); @@ -246,7 +247,18 @@ internal partial class NetworkingService : INetworkingService #endif } - public void SendNetVar(INetworkSyncVar netVar) => SendNetVar(netVar); + public void DeregisterNetVar(INetworkSyncVar netVar) + { + if (netVar is null) + { + return; + } + + netVar.SetNetworkOwner(null); + netVars.TryRemove(netVar, out _); + } + + public void SendNetVar(INetworkSyncVar netVar) => SendNetVar(netVar, null); public void SendNetVar(INetworkSyncVar netVar, NetworkConnection connection = null) { @@ -390,4 +402,20 @@ internal partial class NetworkingService : INetworkingService #endif #endregion + + public void OnSettingInstanceCreated(T configInstance) where T : ISettingBase + { + if (configInstance is INetworkSyncVar syncVar) + { + RegisterNetVar(syncVar); + } + } + + public void OnSettingInstanceDisposed(T configInstance) where T : ISettingBase + { + if (configInstance is INetworkSyncVar syncVar) + { + DeregisterNetVar(syncVar); + } + } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs index d72b2c303..007cd6ffe 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/_Interfaces/INetworkingService.cs @@ -33,6 +33,7 @@ public interface IEntityNetworkingService { Guid GetNetworkIdForInstance(INetworkSyncVar var); void RegisterNetVar(INetworkSyncVar netVar); + void DeregisterNetVar(INetworkSyncVar netVar); void SendNetVar(INetworkSyncVar netVar); void SendNetVar(INetworkSyncVar netVar, NetworkConnection connection); } From a4607dffad34c056783e057228e75fc22e3127f6 Mon Sep 17 00:00:00 2001 From: Regalis11 Date: Thu, 9 Apr 2026 15:10:07 +0300 Subject: [PATCH 284/288] v1.12.6.2 (Spring Update 2026) --- .../Characters/AI/EnemyAIController.cs | 9 +- .../Characters/AI/HumanAIController.cs | 16 +- .../Characters/Animation/Ragdoll.cs | 8 +- .../ClientSource/Characters/Character.cs | 4 +- .../Characters/InteractionLabelManager.cs | 16 +- .../ClientSource/Characters/Limb.cs | 11 +- .../CircuitBox/CircuitBoxInputOutputNode.cs | 2 +- .../Events/EventActions/ConversationAction.cs | 13 +- .../Missions/AbandonedOutpostMission.cs | 12 +- .../Events/Missions/CustomMission.cs | 8 + .../Events/Missions/SalvageMission.cs | 2 +- .../ClientSource/GUI/GUIComponent.cs | 11 +- .../ClientSource/GUI/GUIDropDown.cs | 4 +- .../ClientSource/GUI/HRManagerUI.cs | 29 +- .../Items/Components/ItemComponent.cs | 16 +- .../Items/Components/ItemContainer.cs | 4 +- .../Items/Components/LightComponent.cs | 26 +- .../Items/Components/Machines/Controller.cs | 48 +- .../Items/Components/Machines/Fabricator.cs | 54 +- .../Items/Components/Machines/MiniMap.cs | 1 + .../Items/Components/Machines/Sonar.cs | 28 +- .../Items/Components/Repairable.cs | 2 +- .../Items/Components/Signal/CircuitBox.cs | 2 +- .../Components/Signal/ConnectionPanel.cs | 2 +- .../Items/Components/StatusHUD.cs | 30 +- .../ClientSource/Items/Inventory.cs | 5 +- .../ClientSource/Items/Item.cs | 46 +- .../ClientSource/Items/ItemPrefab.cs | 8 + .../BarotraumaClient/ClientSource/Map/Hull.cs | 15 +- .../ClientSource/Map/Lights/LightManager.cs | 10 +- .../ClientSource/Map/Lights/LightSource.cs | 39 +- .../ClientSource/Map/Structure.cs | 7 +- .../Primitives/Peers/LidgrenClientPeer.cs | 7 + .../ClientSource/Screens/GameScreen.cs | 2 +- .../Screens/MainMenuScreen/MainMenuScreen.cs | 37 +- .../ClientSource/Screens/ModDownloadScreen.cs | 2 +- .../ClientSource/Screens/SubEditorScreen.cs | 48 +- .../Serialization/SerializableEntityEditor.cs | 79 +- .../ClientSource/SpamServerFilter.cs | 20 +- .../ClientSource/Steam/Workshop.cs | 26 +- .../Steam/WorkshopMenu/Mutable/ItemList.cs | 4 +- .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../ServerSource/Events/EventManager.cs | 9 + .../Events/Missions/CombatMission.cs | 2 +- .../BarotraumaServer/ServerSource/GameMain.cs | 3 + .../Items/Components/Machines/Controller.cs | 2 +- .../Items/Components/Repairable.cs | 7 +- .../ServerSource/Items/Inventory.cs | 23 +- .../ServerSource/Items/Item.cs | 2 - .../BarotraumaServer/ServerSource/Map/Hull.cs | 3 +- .../ServerSource/Networking/ChatMessage.cs | 3 + .../ServerSource/Networking/KarmaManager.cs | 19 +- .../Peers/Server/LidgrenServerPeer.cs | 7 + .../ServerSource/Networking/RespawnManager.cs | 17 + .../ServerSource/Networking/ServerSettings.cs | 2 +- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../Characters/Crawler/Crawler.xml | 5 + .../Lighting stress (10000 lights).sub | Bin 0 -> 382364 bytes .../filelist.xml | 4 + .../EthanolPowerGenerator.png | Bin 0 -> 45363 bytes .../OxygenDispenserTest.xml | 23 + .../RotationAndFlippingTests.sub | Bin 10094 -> 13456 bytes .../StatusEffectAndLightTest.xml | 36 + .../filelist.xml | 4 +- .../[DebugOnlyTest]TestPathFinding/Events.xml | 159 ++ .../[DebugOnlyTest]TestPathFinding.sub | Bin 0 -> 108613 bytes .../filelist.xml | 8 + Barotrauma/BarotraumaShared/README.txt | 38 - .../SharedSource/AchievementManager.cs | 11 +- .../SharedSource/Characters/AI/AITarget.cs | 8 + .../Characters/AI/EnemyAIController.cs | 225 ++- .../Characters/AI/HumanAIController.cs | 2 +- .../Characters/AI/IndoorsSteeringManager.cs | 239 ++- .../Characters/AI/Objectives/AIObjective.cs | 18 +- .../AI/Objectives/AIObjectiveCleanupItems.cs | 2 +- .../AI/Objectives/AIObjectiveCombat.cs | 18 +- .../Objectives/AIObjectiveDeconstructItem.cs | 6 +- .../Objectives/AIObjectiveDeconstructItems.cs | 6 +- .../Objectives/AIObjectiveExtinguishFire.cs | 2 +- .../Objectives/AIObjectiveExtinguishFires.cs | 2 + .../AI/Objectives/AIObjectiveGetItem.cs | 29 +- .../AI/Objectives/AIObjectiveGoTo.cs | 1 + .../AI/Objectives/AIObjectiveIdle.cs | 5 +- .../AI/Objectives/AIObjectiveInspectNoises.cs | 7 +- .../AI/Objectives/AIObjectiveLoadItems.cs | 2 +- .../AI/Objectives/AIObjectiveOperateItem.cs | 1 + .../AI/Objectives/AIObjectivePumpWater.cs | 2 +- .../AI/Objectives/AIObjectiveRescueAll.cs | 2 +- .../SharedSource/Characters/AICharacter.cs | 4 +- .../Animation/FishAnimController.cs | 2 +- .../Characters/Animation/Ragdoll.cs | 65 +- .../SharedSource/Characters/Character.cs | 173 +- .../Characters/CharacterPrefab.cs | 1 + .../Health/Afflictions/AfflictionPrefab.cs | 2 +- .../Characters/Health/CharacterHealth.cs | 16 +- .../SharedSource/Characters/HumanPrefab.cs | 2 +- .../SharedSource/Characters/Limb.cs | 2 +- .../Characters/Params/CharacterParams.cs | 4 +- .../CircuitBox/CircuitBoxInputOutputNode.cs | 2 +- .../ContentFile/CharacterFile.cs | 5 +- .../ContentPackage/ContentPackage.cs | 11 +- .../SharedSource/DebugConsole.cs | 5 +- .../SharedSource/Decals/Decal.cs | 7 +- .../BarotraumaShared/SharedSource/Enums.cs | 8 + .../SharedSource/Events/Event.cs | 6 +- .../EventActions/CheckConditionalAction.cs | 8 +- .../Events/EventActions/ConversationAction.cs | 71 +- .../Events/EventActions/CountTargetsAction.cs | 1 + .../Events/EventActions/EventAction.cs | 19 +- .../Events/EventActions/ForceSayAction.cs | 62 + .../Events/EventActions/MissionStateAction.cs | 123 +- .../Events/EventActions/NPCFollowAction.cs | 8 +- .../Events/EventActions/SpawnAction.cs | 4 + .../SharedSource/Events/EventManager.cs | 57 +- .../Missions/AbandonedOutpostMission.cs | 2 +- .../Events/Missions/BeaconMission.cs | 2 +- .../Events/Missions/CargoMission.cs | 2 +- .../Events/Missions/CombatMission.cs | 2 +- .../Events/Missions/CustomMission.cs | 18 + .../Missions/EliminateTargetsMission.cs | 4 +- .../Events/Missions/EndMission.cs | 4 +- .../Events/Missions/EscortMission.cs | 2 +- .../Events/Missions/GoToMission.cs | 4 +- .../Events/Missions/MineralMission.cs | 10 +- .../SharedSource/Events/Missions/Mission.cs | 16 +- .../Events/Missions/MissionPrefab.cs | 12 +- .../Events/Missions/MonsterMission.cs | 2 +- .../Events/Missions/NestMission.cs | 2 +- .../Events/Missions/PirateMission.cs | 2 +- .../Events/Missions/SalvageMission.cs | 2 +- .../Events/Missions/ScanMission.cs | 51 +- .../GameAnalytics/GameAnalyticsConsent.cs | 11 +- .../GameSession/GameModes/CampaignMode.cs | 2 +- .../GameSession/GameModes/MissionMode.cs | 3 +- .../SharedSource/GameSession/GameSession.cs | 31 +- .../SharedSource/Items/CharacterInventory.cs | 69 +- .../Items/Components/Holdable/Holdable.cs | 6 + .../Items/Components/Holdable/MeleeWeapon.cs | 15 +- .../Items/Components/ItemComponent.cs | 5 +- .../Items/Components/ItemContainer.cs | 32 +- .../LinkedControllerCharacterComponent.cs | 121 ++ .../Items/Components/Machines/Controller.cs | 482 +++++- .../Components/Machines/Deconstructor.cs | 152 +- .../Items/Components/Projectile.cs | 10 + .../Items/Components/Repairable.cs | 5 +- .../Items/Components/Signal/Connection.cs | 5 + .../Items/Components/Signal/LightComponent.cs | 1 + .../SharedSource/Items/Components/Turret.cs | 42 +- .../SharedSource/Items/Inventory.cs | 15 +- .../SharedSource/Items/Item.cs | 24 +- .../SharedSource/Items/ItemInventory.cs | 10 +- .../SharedSource/Items/ItemPrefab.cs | 4 + .../SharedSource/Items/RelatedItem.cs | 2 +- .../BarotraumaShared/SharedSource/Map/Hull.cs | 20 +- .../SharedSource/Map/Levels/Level.cs | 22 + .../SharedSource/Map/Levels/LevelData.cs | 3 +- .../SharedSource/Map/Structure.cs | 2 +- .../SharedSource/Map/WayPoint.cs | 6 +- .../SharedSource/Networking/NetConfig.cs | 6 + .../SharedSource/Networking/NetIdUtils.cs | 6 - .../SteamAuthTicketForEosHostAuthenticator.cs | 11 +- .../SharedSource/Networking/ServerSettings.cs | 2 +- .../SharedSource/Physics/PhysicsBody.cs | 4 +- .../SharedSource/Prefabs/PrefabCollection.cs | 2 +- .../Serialization/XMLExtensions.cs | 21 + .../SharedSource/Settings/GameSettings.cs | 12 + .../StatusEffects/StatusEffect.cs | 49 +- .../Text/LocalizedString/TrimLString.cs | 8 +- .../SharedSource/Text/TextPack.cs | 5 +- .../SharedSource/Utils/RestFactory.cs | 35 + Barotrauma/BarotraumaShared/changelog.txt | 90 + .../BarotraumaCore/BarotraumaCore.csproj | 56 +- .../BarotraumaCore/Utils/ToolBoxCore.cs | 16 +- .../EosInterface/EosInterface.csproj | 52 +- Libraries/Concentus/.gitignore | 478 +++--- .../Concentus/Concentus.NetStandard.csproj | 40 +- .../Farseer.NetStandard.csproj | 80 +- .../GA_SDK_NETSTANDARD.csproj | 70 +- .../SharpFont/SharpFont.NetStandard.csproj | 90 +- Libraries/XNATypes/XNATypes.csproj | 20 +- .../opus/win32/VS2015/common.props | 162 +- .../opus/win32/VS2015/opus.vcxproj | 796 ++++----- .../opus/win32/VS2015/opus.vcxproj.filters | 1486 ++++++++--------- .../opus/win32/VS2015/opus_demo.vcxproj | 340 ++-- .../win32/VS2015/opus_demo.vcxproj.filters | 42 +- .../opus/win32/VS2015/test_opus_api.vcxproj | 340 ++-- .../VS2015/test_opus_api.vcxproj.filters | 26 +- .../win32/VS2015/test_opus_decode.vcxproj | 340 ++-- .../VS2015/test_opus_decode.vcxproj.filters | 26 +- .../win32/VS2015/test_opus_encode.vcxproj | 342 ++-- .../VS2015/test_opus_encode.vcxproj.filters | 32 +- .../opus/win32/genversion.bat | 74 +- .../webm-mem-playback.vcxproj | 294 ++-- 197 files changed, 5586 insertions(+), 3461 deletions(-) create mode 100644 Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CustomMission.cs create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Lighting stress (10000 lights)/Lighting stress (10000 lights).sub create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Lighting stress (10000 lights)/filelist.xml create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/EthanolPowerGenerator.png create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/OxygenDispenserTest.xml create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/StatusEffectAndLightTest.xml create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestPathFinding/Events.xml create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestPathFinding/[DebugOnlyTest]TestPathFinding.sub create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestPathFinding/filelist.xml delete mode 100644 Barotrauma/BarotraumaShared/README.txt create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ForceSayAction.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CustomMission.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/Components/LinkedControllerCharacterComponent.cs create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Utils/RestFactory.cs diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs index 49c4b4309..1e7554604 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/EnemyAIController.cs @@ -29,7 +29,7 @@ namespace Barotrauma } } } - else if (SelectedAiTarget?.Entity != null) + else if (SelectedAiTarget?.Entity != null && AttackLimb != null) { Vector2 targetPos = SelectedAiTarget.Entity.DrawPosition; if (State == AIState.Attack) @@ -37,15 +37,16 @@ namespace Barotrauma targetPos = attackWorldPos; } targetPos.Y = -targetPos.Y; - - GUI.DrawLine(spriteBatch, pos, targetPos, GUIStyle.Red * 0.5f, 0, 4); + Vector2 attackLimbPos = AttackLimb.DrawPosition; + attackLimbPos.Y = -attackLimbPos.Y; + GUI.DrawLine(spriteBatch, attackLimbPos, targetPos, GUIStyle.Red * 0.75f, 0, 4); if (wallTarget != null && !IsCoolDownRunning) { Vector2 wallTargetPos = wallTarget.Position; if (wallTarget.Structure.Submarine != null) { wallTargetPos += wallTarget.Structure.Submarine.DrawPosition; } wallTargetPos.Y = -wallTargetPos.Y; GUI.DrawRectangle(spriteBatch, wallTargetPos - new Vector2(10.0f, 10.0f), new Vector2(20.0f, 20.0f), Color.Orange, false); - GUI.DrawLine(spriteBatch, pos, wallTargetPos, Color.Orange * 0.5f, 0, 5); + GUI.DrawLine(spriteBatch, attackLimbPos, wallTargetPos, Color.Orange * 0.75f, 0, 5); } GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 60.0f, $"{SelectedAiTarget.Entity}", GUIStyle.Red, Color.Black); GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 40.0f, $"{targetValue.FormatZeroDecimal()} (M: {CurrentTargetMemory?.Priority.FormatZeroDecimal()}, P: {CurrentTargetingParams?.Priority.FormatZeroDecimal()})", GUIStyle.Red, Color.Black); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs index 4e628af8b..9e3ac4669 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/AI/HumanAIController.cs @@ -23,6 +23,8 @@ namespace Barotrauma //GUI.DrawString(spriteBatch, pos + textOffset, $"AI TARGET: {SelectedAiTarget.Entity.ToString()}", Color.White, Color.Black); } + Vector2 spacing = new Vector2(0, GUIStyle.Font.MeasureChar('T').Y); + Vector2 stringDrawPos = pos + textOffset; GUI.DrawString(spriteBatch, stringDrawPos, Character.Name, Color.White, Color.Black); @@ -33,14 +35,14 @@ namespace Barotrauma currentOrders.Sort((x, y) => y.ManualPriority.CompareTo(x.ManualPriority)); for (int i = 0; i < currentOrders.Count; i++) { - stringDrawPos += new Vector2(0, 20); + stringDrawPos += spacing; var order = currentOrders[i]; GUI.DrawString(spriteBatch, stringDrawPos, $"ORDER {i + 1}: {order.Objective.DebugTag} ({order.Objective.Priority.FormatZeroDecimal()})", Color.White, Color.Black); } } else if (ObjectiveManager.WaitTimer > 0) { - stringDrawPos += new Vector2(0, 20); + stringDrawPos += spacing; GUI.DrawString(spriteBatch, stringDrawPos - textOffset, $"Waiting... {ObjectiveManager.WaitTimer.FormatZeroDecimal()}", Color.White, Color.Black); } var currentObjective = ObjectiveManager.CurrentObjective; @@ -49,19 +51,19 @@ namespace Barotrauma int offset = currentOrder != null ? 20 + ((ObjectiveManager.CurrentOrders.Count - 1) * 20) : 0; if (currentOrder == null || currentOrder.Priority <= 0) { - stringDrawPos += new Vector2(0, 20); + stringDrawPos += spacing; GUI.DrawString(spriteBatch, stringDrawPos, $"MAIN OBJECTIVE: {currentObjective.DebugTag} ({currentObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black); } var subObjective = currentObjective.CurrentSubObjective; if (subObjective != null) { - stringDrawPos += new Vector2(0, 20); + stringDrawPos += spacing; GUI.DrawString(spriteBatch, stringDrawPos, $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black); } var activeObjective = ObjectiveManager.GetActiveObjective(); if (activeObjective != null) { - stringDrawPos += new Vector2(0, 20); + stringDrawPos += spacing; GUI.DrawString(spriteBatch, stringDrawPos, $"ACTIVE OBJECTIVE: {activeObjective.DebugTag} ({activeObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black); } if (currentObjective is AIObjectiveCombat @@ -85,12 +87,12 @@ namespace Barotrauma } } - Vector2 objectiveStringDrawPos = stringDrawPos + new Vector2(120, 40); + Vector2 objectiveStringDrawPos = stringDrawPos + new Vector2(120, spacing.Y * 2); for (int i = 0; i < ObjectiveManager.Objectives.Count; i++) { var objective = ObjectiveManager.Objectives[i]; GUI.DrawString(spriteBatch, objectiveStringDrawPos, $"{objective.DebugTag} ({objective.Priority.FormatZeroDecimal()})", Color.White, Color.Black * 0.5f); - objectiveStringDrawPos += new Vector2(0, 18); + objectiveStringDrawPos += spacing * 0.8f; } if (steeringManager is IndoorsSteeringManager pathSteering) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs index 6e2ac141b..ce45568d2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs @@ -547,7 +547,7 @@ namespace Barotrauma } } - public void Draw(SpriteBatch spriteBatch, Camera cam) + public void Draw(SpriteBatch spriteBatch, Camera cam, bool onlyDrawSeveredLimbs) { if (simplePhysicsEnabled) { return; } @@ -573,8 +573,12 @@ namespace Barotrauma { foreach (Limb limb in limbs) { limb.ActiveSprite.Depth += depthOffset; } } - for (int i = 0; i < limbs.Length; i++) + for (int i = 0; i < inversedLimbDrawOrder.Length; i++) { + if (onlyDrawSeveredLimbs && !inversedLimbDrawOrder[i].IsSevered) + { + continue; + } inversedLimbDrawOrder[i].Draw(spriteBatch, cam, color); } if (!MathUtils.NearlyEqual(depthOffset, 0.0f)) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs index 0180efb8c..c38878268 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs @@ -938,8 +938,8 @@ namespace Barotrauma public void Draw(SpriteBatch spriteBatch, Camera cam) { - if (!Enabled || InvisibleTimer > 0.0f) { return; } - AnimController.Draw(spriteBatch, cam); + if (!Enabled) { return; } + AnimController.Draw(spriteBatch, cam, onlyDrawSeveredLimbs: InvisibleTimer > 0.0f); } public void DrawHUD(SpriteBatch spriteBatch, Camera cam, bool drawHealth = true) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/InteractionLabelManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/InteractionLabelManager.cs index 897637950..cd9ba6820 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/InteractionLabelManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/InteractionLabelManager.cs @@ -4,6 +4,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System.Collections.Generic; using Barotrauma.Items.Components; +using System.Linq; namespace Barotrauma; @@ -106,12 +107,17 @@ public static class InteractionLabelManager } RectangleF textRect = GetLabelRect(interactableInRange, cam); - - if (labels.None(l => l.Item == interactableInRange)) + var existingLabel = labels.FirstOrDefault(l => l.Item == interactableInRange); + if (existingLabel == null) { var labelData = new LabelData(interactableInRange, textRect, RichString.Rich(interactableInRange.Prefab.Name), cam); labels.Add(labelData); } + //size of the label doesn't match - can happen when we're using a CJK font which we asynchronously render new symbols for + else if (existingLabel.TextRect.Size != textRect.Size) + { + existingLabel.TextRect = textRect; + } } PreventInteractionLabelOverlap(centerPos: character.Position); @@ -127,7 +133,11 @@ public static class InteractionLabelManager private static RectangleF GetLabelRect(Item item, Camera cam) { // create rectangle for overlap prevention - Vector2 itemTextSizeScreen = GUIStyle.SubHeadingFont.MeasureString(RichString.Rich(item.Prefab.Name).SanitizedValue) * LabelScale; + + string nameText = RichString.Rich(item.Prefab.Name).SanitizedValue; + + var font = GUIStyle.SubHeadingFont.GetFontForStr(nameText)!; + Vector2 itemTextSizeScreen = font.MeasureString(nameText) * LabelScale; Vector2 interactablePosScreen = cam.WorldToScreen(item.Position); RectangleF textRect = new RectangleF(interactablePosScreen.X, interactablePosScreen.Y, itemTextSizeScreen.X, itemTextSizeScreen.Y); // center the rectangle on the item diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs index 47e90a6cb..0d9c45be1 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Limb.cs @@ -340,10 +340,6 @@ namespace Barotrauma break; case "randomcolor": randomColor = subElement.GetAttributeColorArray("colors", null)?.GetRandomUnsynced(); - if (randomColor.HasValue) - { - Params.GetSprite().Color = randomColor.Value; - } break; case "lightsource": LightSource = new LightSource(subElement, GetConditionalTarget()) @@ -631,6 +627,8 @@ namespace Barotrauma SoundPlayer.PlayDamageSound(damageSoundType, Math.Max(damage, bleedingDamage), WorldPosition); } + if (character.InvisibleTimer > 0.0f) { return; } + // spawn damage particles float damageParticleAmount = damage < 1 ? 0 : Math.Min(damage / 5, 1.0f) * damageMultiplier; if (damageParticleAmount > 0.001f) @@ -734,7 +732,8 @@ namespace Barotrauma if (spriteParams == null || Alpha <= 0) { return; } float burn = spriteParams.IgnoreTint ? 0 : burnOverLayStrength; float brightness = Math.Max(1.0f - burn, 0.2f); - Color tintedColor = spriteParams.Color; + Color baseColor = randomColor ?? spriteParams.Color; + Color tintedColor = baseColor; if (!spriteParams.IgnoreTint) { tintedColor = tintedColor.Multiply(ragdoll.RagdollParams.Color); @@ -752,7 +751,7 @@ namespace Barotrauma } } Color color = new Color(tintedColor.Multiply(brightness), tintedColor.A); - Color colorWithoutTint = new Color(spriteParams.Color.Multiply(brightness), spriteParams.Color.A); + Color colorWithoutTint = new Color(baseColor.Multiply(brightness), baseColor.A); Color blankColor = new Color(brightness, brightness, brightness, 1); if (deadTimer > 0) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxInputOutputNode.cs b/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxInputOutputNode.cs index d9658086f..7e0a3b547 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxInputOutputNode.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/CircuitBox/CircuitBoxInputOutputNode.cs @@ -31,7 +31,7 @@ namespace Barotrauma GUILayoutGroup connLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.12f), labelList.Content.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft); new GUITextBlock(new RectTransform(new Vector2(0.4f, 1f), connLayout.RectTransform), text: conn.Connection.DisplayName, font: GUIStyle.SubHeadingFont); - GUITextBox box = GUI.CreateTextBoxWithPlaceholder(new RectTransform(new Vector2(0.6f, 1f), connLayout.RectTransform), text: found ? labelOverride : string.Empty, conn.Connection.DisplayName.Value); + GUITextBox box = GUI.CreateTextBoxWithPlaceholder(new RectTransform(new Vector2(0.6f, 1f), connLayout.RectTransform), text: found ? labelOverride : string.Empty, conn.Connection.DefaultDisplayName.Value); box.MaxTextLength = MaxConnectionLabelLength; textBoxes.Add(conn.Name, box); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs index 3a7988c21..e7538dc03 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/EventActions/ConversationAction.cs @@ -277,6 +277,14 @@ namespace Barotrauma int selectedOption = (userdata as int?) ?? 0; if (actionInstance != null) { + var option = actionInstance.Options[selectedOption]; + if (GameMain.Client == null && option.ForceSay) + { + Character.Controlled.ForceSay( + option.ForceSayText.IsNullOrEmpty() ? TextManager.Get(option.Text).Fallback(option.Text) : TextManager.Get(option.ForceSayText).Fallback(option.ForceSayText), + option.ForceSayInRadio, + option.ForceSayRemoveQuotes); + } actionInstance.selectedOption = selectedOption; DisableButtons(optionButtons, btn); btn.ExternalHighlight = true; @@ -340,7 +348,8 @@ namespace Barotrauma if (speaker?.Info != null && drawChathead) { // chathead - new GUICustomComponent(new RectTransform(new Vector2(0.15f, 0.8f), content.RectTransform), onDraw: (sb, customComponent) => + int chatHeadWidth = (int)(content.RectTransform.Rect.Width * 0.15f); + new GUICustomComponent(new RectTransform(new Point(chatHeadWidth, chatHeadWidth), content.RectTransform, isFixedSize: true), onDraw: (sb, customComponent) => { speaker.Info.DrawIcon(sb, customComponent.Rect.Center.ToVector2(), customComponent.Rect.Size.ToVector2()); }); @@ -382,7 +391,7 @@ namespace Barotrauma } textContent.RectTransform.MinSize = new Point(0, textContent.Children.Sum(c => c.Rect.Height + textContent.AbsoluteSpacing) + GUI.IntScale(16)); - content.RectTransform.MinSize = new Point(0, content.Children.Sum(c => c.Rect.Height)); + content.RectTransform.MinSize = textContent.RectTransform.MinSize; // Recalculate the text size as it is scaled up and no longer matching the text height due to the textContent's minSize increasing textBlock.CalculateHeightFromText(); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs index e3a607d75..d8644061b 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/AbandonedOutpostMission.cs @@ -61,17 +61,9 @@ namespace Barotrauma { Item.ReadSpawnData(msg); } - if (character.Submarine != null && character.AIController is EnemyAIController enemyAi) + if (character.AIController is EnemyAIController enemyAi && character.Submarine is Submarine ownSub) { - enemyAi.UnattackableSubmarines.Add(character.Submarine); - if (Submarine.MainSub != null) - { - enemyAi.UnattackableSubmarines.Add(Submarine.MainSub); - foreach (Submarine sub in Submarine.MainSub.DockedTo) - { - enemyAi.UnattackableSubmarines.Add(sub); - } - } + enemyAi.SetUnattackableSubmarines(ownSub); } } if (characters.Contains(null)) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CustomMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CustomMission.cs new file mode 100644 index 000000000..601d4ec09 --- /dev/null +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/CustomMission.cs @@ -0,0 +1,8 @@ +#nullable enable +namespace Barotrauma; + +internal sealed partial class CustomMission : Mission +{ + public override bool DisplayAsCompleted => State == SuccessState; + public override bool DisplayAsFailed => State == FailureState; +} diff --git a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs index 8b9d8c492..fbf3e7a16 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Events/Missions/SalvageMission.cs @@ -14,7 +14,7 @@ namespace Barotrauma private void TryShowRetrievedMessage() { - if (DetermineCompleted()) + if (DetermineCompleted(CampaignMode.TransitionType.None)) { HandleMessage(ref allRetrievedMessage); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs index 918b67d16..af674c696 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIComponent.cs @@ -1435,8 +1435,15 @@ namespace Barotrauma Uri baseAddress = new Uri(url); Uri remoteDirectory = new Uri(baseAddress, "."); string remoteFileName = Path.GetFileName(baseAddress.LocalPath); - IRestClient client = new RestClient(remoteDirectory); - var response = client.Execute(new RestRequest(remoteFileName, Method.GET)); + var client = RestFactory.CreateClient(remoteDirectory.ToString()); + var request = RestFactory.CreateRequest(remoteFileName); + var response = client.Execute(request); + if (response.ErrorException != null) + { + DebugConsole.AddWarning($"Connection error: Failed to load remote sprite from {url} " + + $"({response.ErrorException.Message})."); + return null; + } if (response.ResponseStatus != ResponseStatus.Completed) { return null; } if (response.StatusCode != HttpStatusCode.OK) { return null; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs index 3116cfd81..2b0041c13 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUIDropDown.cs @@ -26,7 +26,9 @@ namespace Barotrauma public OnSelectedHandler OnDropped; - private readonly GUIButton button; + private readonly GUIButton button; + public GUIButton Button => button; + private readonly GUIImage icon; private readonly GUIListBox listBox; diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/HRManagerUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/HRManagerUI.cs index 928ae3519..ea17ff7b5 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/GUI/HRManagerUI.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/HRManagerUI.cs @@ -710,19 +710,24 @@ namespace Barotrauma if (listBox == pendingList || listBox == crewList) { - nameBlock.RectTransform.Resize(new Point(nameBlock.Rect.Width - nameBlock.Rect.Height, nameBlock.Rect.Height)); - nameBlock.Text = ToolBox.LimitString(characterName, nameBlock.Font, nameBlock.Rect.Width); - nameBlock.RectTransform.Resize(new Point((int)(nameBlock.Padding.X + nameBlock.TextSize.X + nameBlock.Padding.Z), nameBlock.Rect.Height)); - Point size = new Point((int)(0.7f * nameBlock.Rect.Height)); - new GUIImage(new RectTransform(size, nameGroup.RectTransform), "EditIcon") { CanBeFocused = false }; - size = new Point(3 * mainGroup.AbsoluteSpacing + icon.Rect.Width + nameAndJobGroup.Rect.Width, mainGroup.Rect.Height); - new GUIButton(new RectTransform(size, frame.RectTransform) { RelativeOffset = new Vector2(0.025f) }, style: null) + //if the character is already in the crew, only check permissions - reputation doesn't matter for renaming an already-hired bot + bool canRename = listBox == crewList ? HasPermissionToHire : CanHire(characterInfo); + if (canRename) { - Enabled = CanHire(characterInfo), - ToolTip = TextManager.GetWithVariable("campaigncrew.givenicknametooltip", "[mouseprimary]", PlayerInput.PrimaryMouseLabel), - UserData = characterInfo, - OnClicked = CreateRenamingComponent - }; + nameBlock.RectTransform.Resize(new Point(nameBlock.Rect.Width - nameBlock.Rect.Height, nameBlock.Rect.Height)); + nameBlock.Text = ToolBox.LimitString(characterName, nameBlock.Font, nameBlock.Rect.Width); + nameBlock.RectTransform.Resize(new Point((int)(nameBlock.Padding.X + nameBlock.TextSize.X + nameBlock.Padding.Z), nameBlock.Rect.Height)); + Point iconSize = new Point((int)(0.7f * nameBlock.Rect.Height)); + new GUIImage(new RectTransform(iconSize, nameGroup.RectTransform), "EditIcon") { CanBeFocused = false }; + Point buttonSize = new Point(3 * mainGroup.AbsoluteSpacing + icon.Rect.Width + nameAndJobGroup.Rect.Width + (int)(iconSize.X * 1.5f), mainGroup.Rect.Height); + new GUIButton(new RectTransform(buttonSize, frame.RectTransform) { RelativeOffset = new Vector2(0.025f) }, style: null) + { + ClampMouseRectToParent = false, + ToolTip = TextManager.GetWithVariable("campaigncrew.givenicknametooltip", "[mouseprimary]", PlayerInput.PrimaryMouseLabel), + UserData = characterInfo, + OnClicked = CreateRenamingComponent + }; + } } //recalculate everything and truncate texts if needed diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs index 0452c45e7..e33660954 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemComponent.cs @@ -487,7 +487,21 @@ namespace Barotrauma.Items.Components return 0.0f; } - public virtual bool ShouldDrawHUD(Character character) + public bool ShouldDrawHUD(Character character) + { + if (Character.Controlled?.SelectedItem != null) + { + Controller controller = item.GetComponent(); + if (controller != null && controller.User == Character.Controlled && controller.HideAllItemComponentHUDs) + { + return false; + } + } + + return ShouldDrawHUDComponentSpecific(character); + } + + protected virtual bool ShouldDrawHUDComponentSpecific(Character character) { return true; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs index c0a6ece27..31af8751a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ItemContainer.cs @@ -552,9 +552,9 @@ namespace Barotrauma.Items.Components if (flippedY) { origin.Y = contained.Item.Sprite.SourceRect.Height - origin.Y; } float containedSpriteDepth = ContainedSpriteDepth < 0.0f ? contained.Item.Sprite.Depth : ContainedSpriteDepth; - if (i < containedSpriteDepths.Length) + if (targetSlotIndex < containedSpriteDepths.Length) { - containedSpriteDepth = containedSpriteDepths[i]; + containedSpriteDepth = containedSpriteDepths[targetSlotIndex]; } containedSpriteDepth = itemDepth + (containedSpriteDepth - (item.Sprite?.Depth ?? item.SpriteDepth)) / 10000.0f; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs index 4e637d1dc..82183702e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/LightComponent.cs @@ -52,10 +52,12 @@ namespace Barotrauma.Items.Components partial void SetLightSourceTransformProjSpecific() { - Vector2 offset = Vector2.Zero; - if (LightOffset != Vector2.Zero) + Vector2 offset = LightOffset * item.Scale; + if (offset != Vector2.Zero) { - offset = Vector2.Transform(LightOffset, Matrix.CreateRotationZ(item.FlippedY ? -item.RotationRad - MathHelper.Pi : -item.RotationRad)) * item.Scale; + if (item.FlippedX) { offset.X *= -1; } + if (item.FlippedY) { offset.Y *= -1; } + offset = Vector2.Transform(offset, Matrix.CreateRotationZ(-item.RotationRad)); } if (ParentBody != null) @@ -101,7 +103,10 @@ namespace Barotrauma.Items.Components if (Light?.LightSprite == null) { return; } if ((item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn && Light.Enabled) { - Vector2 offset = Vector2.Transform(LightOffset, Matrix.CreateRotationZ(item.FlippedY ? -item.RotationRad - MathHelper.Pi : -item.RotationRad)) * item.Scale; + Vector2 offset = LightOffset * item.Scale; + if (item.FlippedX) { offset.X *= -1; } + if (item.FlippedY) { offset.Y *= -1; } + offset = Vector2.Transform(offset, Matrix.CreateRotationZ(-item.RotationRad)); Vector2 origin = Light.LightSprite.Origin; if ((Light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = Light.LightSprite.SourceRect.Width - origin.X; } @@ -114,6 +119,7 @@ namespace Barotrauma.Items.Components { color = new Color(lightColor, Light.OverrideLightSpriteAlpha.Value); } + Light.LightSprite.Draw(spriteBatch, new Vector2(drawPos.X, -drawPos.Y), color * lightBrightness, @@ -128,8 +134,16 @@ namespace Barotrauma.Items.Components { if (Light?.LightSprite != null && item.Prefab.CanSpriteFlipX) { - Light.LightSpriteEffect = Light.LightSpriteEffect == SpriteEffects.None ? - SpriteEffects.FlipHorizontally : SpriteEffects.None; + Light.LightSpriteEffect ^= SpriteEffects.FlipHorizontally; + } + SetLightSourceTransformProjSpecific(); + } + + public override void FlipY(bool relativeToSub) + { + if (Light?.LightSprite != null && item.Prefab.CanSpriteFlipY) + { + Light.LightSpriteEffect ^= SpriteEffects.FlipVertically; } SetLightSourceTransformProjSpecific(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Controller.cs index 710dcb9f6..bee454c79 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Controller.cs @@ -8,6 +8,30 @@ namespace Barotrauma.Items.Components { private bool isHUDsHidden; + public void UpdateMsg() + { + if (Character.Controlled == null) { return; } + + if (!string.IsNullOrEmpty(KickOutCharacterMsg) && + SelectingKicksCharacterOut && + User != null && !User.Removed) + { + DisplayMsg = TextManager.ParseInputTypes(TextManager.Get(KickOutCharacterMsg)); + } + else if (!string.IsNullOrEmpty(PutOtherCharacterMsg) && + AllowPuttingInOtherCharacters && + CanPutSelectedCharacter(Character.Controlled.SelectedCharacter)) + { + DisplayMsg = TextManager.ParseInputTypes(TextManager.Get(PutOtherCharacterMsg)); + } + else + { + DisplayMsg = TextManager.ParseInputTypes(TextManager.Get(Msg)); + } + + CharacterHUD.RecreateHudTextsIfControlling(Character.Controlled); + } + public override void DrawHUD(SpriteBatch spriteBatch, Character character) { base.DrawHUD(spriteBatch, character); @@ -69,21 +93,33 @@ namespace Barotrauma.Items.Components ushort userID = msg.ReadUInt16(); if (userID == 0) { - if (user != null) + if (User != null) { IsActive = false; - CancelUsing(user); - user = null; + CancelUsing(User); + User = null; } } else { Character newUser = Entity.FindEntityByID(userID) as Character; - if (newUser != user) + if (newUser != User) { - CancelUsing(user); + CancelUsing(User); } - user = newUser; + User = newUser; + + // If the server assigned a user to this controller but the character is not selecting the item + // on the client-side, force the selection to prevent desync. This is required for force attaching, + // since the character placed into the controller may be unconscious, and in that state + // the server no longer syncs the current SelectedItem to clients. + if (ForceUserToStayAttached && + user != null && + !user.IsAnySelectedItem(Item)) + { + user.SelectedItem = Item; + } + IsActive = true; } } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs index b791fcf87..5dca29d13 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs @@ -434,18 +434,13 @@ namespace Barotrauma.Items.Components foreach (FabricationRecipe fi in fabricationRecipes.Values) { - RichString recipeTooltip = - fi.RequiresRecipe ? - RichString.Rich(fi.TargetItem.Description + "\n\n" + $"‖color:{XMLExtensions.ToStringHex(GUIStyle.Red)}‖{TextManager.Get("fabricatorrequiresrecipe")}‖color:end‖") : - RichString.Rich(fi.TargetItem.Description); - var frame = new GUIFrame(new RectTransform(new Point(itemList.Content.Rect.Width, (int)(40 * GUI.yScale)), itemList.Content.RectTransform), style: null) { UserData = fi, HoverColor = Color.Gold * 0.2f, SelectedColor = Color.Gold * 0.5f, - ToolTip = recipeTooltip }; + SetRecipeTooltip(frame, fi); var container = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true) { RelativeSpacing = 0.02f }; @@ -457,7 +452,7 @@ namespace Barotrauma.Items.Components itemIcon, scaleToFit: true) { Color = itemIcon == fi.TargetItem.Sprite ? fi.TargetItem.SpriteColor : fi.TargetItem.InventoryIconColor, - ToolTip = recipeTooltip + CanBeFocused = false }; } @@ -466,7 +461,7 @@ namespace Barotrauma.Items.Components { Padding = Vector4.Zero, AutoScaleVertical = true, - ToolTip = recipeTooltip + CanBeFocused = false }; new GUITextBlock(new RectTransform(new Vector2(0.85f, 1f), frame.RectTransform, Anchor.BottomRight), @@ -478,6 +473,20 @@ namespace Barotrauma.Items.Components } } + private void SetRecipeTooltip(GUIComponent component, FabricationRecipe recipe) + { + if (!recipe.RequiresRecipe) + { + component.ToolTip = RichString.Rich(recipe.TargetItem.Description); + } + else + { + component.ToolTip = AnyOneHasRecipeForItem(Character.Controlled, recipe.TargetItem) ? + RichString.Rich(recipe.TargetItem.Description + "\n\n" + $"‖color:{XMLExtensions.ToStringHex(GUIStyle.Green)}‖{TextManager.Get("unlockedrecipe.true")}‖color:end‖") : + RichString.Rich(recipe.TargetItem.Description + "\n\n" + $"‖color:{XMLExtensions.ToStringHex(GUIStyle.Red)}‖{TextManager.Get("fabricatorrequiresrecipe")}‖color:end‖"); + } + } + private void InitInventoryUIs() { if (inputInventoryHolder != null) @@ -927,16 +936,24 @@ namespace Barotrauma.Items.Components } } - if (recipe.RequiresRecipe && recipe.HideIfNoRecipe) + if (recipe.RequiresRecipe) { - if (Character.Controlled != null) + if (recipe.HideIfNoRecipe) { - if (!AnyOneHasRecipeForItem(Character.Controlled, recipe.TargetItem)) + bool anyOneHasRecipe = AnyOneHasRecipeForItem(Character.Controlled, recipe.TargetItem); + if (Character.Controlled != null) { - child.Visible = false; - continue; + if (!anyOneHasRecipe) + { + child.Visible = false; + continue; + } } } + else + { + SetRecipeTooltip(child, recipe); + } } child.Visible = @@ -1147,7 +1164,16 @@ namespace Barotrauma.Items.Components var lines = description.WrappedText.Split('\n'); if (lines.Count <= 1) { break; } string newString = string.Join('\n', lines.Take(lines.Count - 1)); - description.Text = newString.Substring(0, newString.Length - 4) + "..."; + + if (newString.Length > 4) + { + description.Text = newString.Substring(0, newString.Length - 4) + "..."; + } + else + { + description.Text = newString + "..."; + } + description.CalculateHeightFromText(); description.ToolTip = richDescription; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs index 3c08f7fe7..f76b9506a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs @@ -443,6 +443,7 @@ namespace Barotrauma.Items.Components var wire = targetItem.GetComponent(); if (wire != null && wire.Connections.Any(c => c != null)) { return false; } + if (targetItem.Container is { NonInteractable: true }) { return false; } if (targetItem.Container?.GetComponent() is { DrawInventory: false } or { AllowAccess: false }) { return false; } if (targetItem.HasTag(Tags.TraitorMissionItem)) { return false; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs index 872b9be0c..adb44aa8e 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs @@ -575,6 +575,22 @@ namespace Barotrauma.Items.Components pos /= c.Resources.Count; MineralClusters.Add((center: pos, resources: c.Resources)); } + + if (GameMain.GameSession != null) + { + foreach (var mission in GameMain.GameSession.Missions) + { + if (mission is MineralMission mineralMission) + { + foreach (var minerals in mineralMission.SpawnedResources) + { + MineralClusters.Add(( + center: new Vector2(minerals.Average(m => m.WorldPosition.X), minerals.Average(m => m.WorldPosition.Y)), + resources: minerals)); + } + } + } + } } else { @@ -823,18 +839,20 @@ namespace Barotrauma.Items.Components if (t.Entity is Character c && !c.IsUnconscious && c.Params.HideInSonar) { continue; } if (t.SoundRange <= 0.0f || float.IsNaN(t.SoundRange) || float.IsInfinity(t.SoundRange)) { continue; } + float sonarSoundRange = t.SoundRange * t.SoundRangeOnSonarMultiplier; + float distSqr = Vector2.DistanceSquared(t.WorldPosition, transducerCenter); - if (distSqr > t.SoundRange * t.SoundRange * 2) { continue; } + if (distSqr > sonarSoundRange * sonarSoundRange * 2) { continue; } float dist = (float)Math.Sqrt(distSqr); if (dist > prevPassivePingRadius * Range && dist <= passivePingRadius * Range && Rand.Int(sonarBlips.Count) < 500) { Ping(t.WorldPosition, transducerCenter, - t.SoundRange * DisplayScale, 0, DisplayScale, range, + sonarSoundRange * DisplayScale, 0, DisplayScale, range, passive: true, pingStrength: 0.5f, needsToBeInSector: t); if (t.IsWithinSector(transducerCenter)) { - sonarBlips.Add(new SonarBlip(t.WorldPosition, fadeTimer: 1.0f, scale: MathHelper.Clamp(t.SoundRange / 2000, 1.0f, 5.0f))); + sonarBlips.Add(new SonarBlip(t.WorldPosition, fadeTimer: 1.0f, scale: MathHelper.Clamp(sonarSoundRange / 2000, 1.0f, 5.0f))); } } } @@ -977,7 +995,9 @@ namespace Barotrauma.Items.Components if (aiTarget.InDetectable) { continue; } if (aiTarget.SonarLabel.IsNullOrEmpty() || aiTarget.SoundRange <= 0.0f) { continue; } - if (Vector2.DistanceSquared(aiTarget.WorldPosition, transducerCenter) < aiTarget.SoundRange * aiTarget.SoundRange) + float sonarSoundRange = aiTarget.SoundRange * aiTarget.SoundRangeOnSonarMultiplier; + + if (Vector2.DistanceSquared(aiTarget.WorldPosition, transducerCenter) < sonarSoundRange * sonarSoundRange) { DrawMarker(spriteBatch, aiTarget.SonarLabel.Value, diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs index e86923ef7..ad7305e89 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Repairable.cs @@ -58,7 +58,7 @@ namespace Barotrauma.Items.Components get { return Vector2.Zero; } } - public override bool ShouldDrawHUD(Character character) + protected override bool ShouldDrawHUDComponentSpecific(Character character) { if (item.IsHidden) { return false; } if (!HasRequiredItems(character, false) || character.SelectedItem != item) { return false; } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CircuitBox.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CircuitBox.cs index 9d8ba4214..cf4f06813 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CircuitBox.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/CircuitBox.cs @@ -78,7 +78,7 @@ namespace Barotrauma.Items.Components } } - public override bool ShouldDrawHUD(Character character) + protected override bool ShouldDrawHUDComponentSpecific(Character character) => character == Character.Controlled && (character.SelectedItem == item || character.SelectedSecondaryItem == item); public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs index 50ee85985..d7d63d74d 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Signal/ConnectionPanel.cs @@ -97,7 +97,7 @@ namespace Barotrauma.Items.Components MoveConnectedWires(amount); } - public override bool ShouldDrawHUD(Character character) + protected override bool ShouldDrawHUDComponentSpecific(Character character) { return character == Character.Controlled && character == user && (character.SelectedItem == item || character.SelectedSecondaryItem == item); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs index 9b999dbae..c6a7231fb 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/StatusHUD.cs @@ -287,20 +287,24 @@ namespace Barotrauma.Items.Components texts.Add(target.CustomInteractHUDText); textColors.Add(GUIStyle.Green); } - if (!target.IsIncapacitated && target.IsPet) + if (equipper?.FocusedCharacter == target) { - texts.Add(CharacterHUD.GetCachedHudText("PlayHint", InputType.Use)); - textColors.Add(GUIStyle.Green); - } - if (equipper?.FocusedCharacter == target && target.CanBeHealedBy(equipper, checkFriendlyTeam: false)) - { - texts.Add(CharacterHUD.GetCachedHudText("HealHint", InputType.Health)); - textColors.Add(GUIStyle.Green); - } - if (target.CanBeDraggedBy(Character.Controlled)) - { - texts.Add(CharacterHUD.GetCachedHudText("GrabHint", InputType.Grab)); - textColors.Add(GUIStyle.Green); + if (!target.IsIncapacitated && target.IsPet && + target.AIController is EnemyAIController enemyAI && enemyAI.PetBehavior.CanPlayWith(Character.Controlled)) + { + texts.Add(CharacterHUD.GetCachedHudText("PlayHint", InputType.Use)); + textColors.Add(GUIStyle.Green); + } + if (target.CanBeHealedBy(equipper, checkFriendlyTeam: false)) + { + texts.Add(CharacterHUD.GetCachedHudText("HealHint", InputType.Health)); + textColors.Add(GUIStyle.Green); + } + if (target.CanBeDraggedBy(Character.Controlled)) + { + texts.Add(CharacterHUD.GetCachedHudText("GrabHint", InputType.Grab)); + textColors.Add(GUIStyle.Green); + } } if (target.IsUnconscious) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs index 21f681e5e..3f7bfdfcd 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Inventory.cs @@ -1597,7 +1597,8 @@ namespace Barotrauma { if (DraggingSlot == null || (!DraggingSlot.MouseOn())) { - Sprite sprite = DraggingItems.First().Prefab.InventoryIcon ?? DraggingItems.First().Sprite; + Item firstDraggingItem = DraggingItems.First(); + Sprite sprite = firstDraggingItem.OverrideInventorySprite ?? firstDraggingItem.Prefab.InventoryIcon ?? firstDraggingItem.Sprite; int iconSize = (int)(64 * GUI.Scale); float scale = Math.Min(Math.Min(iconSize / sprite.size.X, iconSize / sprite.size.Y), 1.5f); @@ -1854,7 +1855,7 @@ namespace Barotrauma if (item != null && drawItem) { - Sprite sprite = item.Prefab.InventoryIcon ?? item.Sprite; + Sprite sprite = item.OverrideInventorySprite ?? item.Prefab.InventoryIcon ?? item.Sprite; float scale = Math.Min(Math.Min((rect.Width - 10) / sprite.size.X, (rect.Height - 10) / sprite.size.Y), 2.0f); Vector2 itemPos = rect.Center.ToVector2(); if (itemPos.Y > GameMain.GraphicsHeight) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs index eb371af3f..78be7c33a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs @@ -419,7 +419,7 @@ namespace Barotrauma if (fadeInBrokenSprite != null) { - float d = Math.Min(depth + (fadeInBrokenSprite.Sprite.Depth - activeSprite.Depth - 0.000001f), 0.999f); + float d = MathHelper.Clamp(depth + (fadeInBrokenSprite.Sprite.Depth - activeSprite.Depth - 0.000001f), 0.0f, 0.999f); fadeInBrokenSprite.Sprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)) + fadeInBrokenSprite.Offset.ToVector2() * Scale, size, color: color * fadeInBrokenSpriteAlpha, textureScale: Vector2.One * Scale, depth: d); @@ -435,7 +435,7 @@ namespace Barotrauma activeSprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, origin, RotationRad, Scale, activeSprite.effects, depth); if (fadeInBrokenSprite != null) { - float d = Math.Min(depth + (fadeInBrokenSprite.Sprite.Depth - activeSprite.Depth - 0.000001f), 0.999f); + float d = MathHelper.Clamp(depth + (fadeInBrokenSprite.Sprite.Depth - activeSprite.Depth - 0.000001f), 0.0f, 0.999f); fadeInBrokenSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + fadeInBrokenSprite.Offset.ToVector2() * Scale, color * fadeInBrokenSpriteAlpha, origin, RotationRad, Scale, activeSprite.effects, d); } } @@ -885,7 +885,12 @@ namespace Barotrauma Spacing = (int)(25 * GUI.Scale) }; - var itemEditor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, titleFont: GUIStyle.LargeFont) { UserData = this }; + var itemEditor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, + titleFont: GUIStyle.LargeFont, + dimOutDefaultValues: false) + { + UserData = this + }; activeEditors.Add(itemEditor); itemEditor.Children.First().Color = Color.Black * 0.7f; if (!inGame) @@ -1045,7 +1050,12 @@ namespace Barotrauma new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), listBox.Content.RectTransform), style: "HorizontalLine"); - var componentEditor = new SerializableEntityEditor(listBox.Content.RectTransform, ic, inGame, showName: !inGame, titleFont: GUIStyle.SubHeadingFont) { UserData = ic }; + var componentEditor = new SerializableEntityEditor(listBox.Content.RectTransform, ic, inGame, showName: !inGame, + titleFont: GUIStyle.SubHeadingFont, + dimOutDefaultValues: false) + { + UserData = ic + }; componentEditor.Children.First().Color = Color.Black * 0.7f; activeEditors.Add(componentEditor); @@ -1064,7 +1074,12 @@ namespace Barotrauma requiredItems.Add(relatedItem); } } - requiredItems.AddRange(ic.DisabledRequiredItems); + //if we have some actual requirements, no need to keep the empty requirement + //as a "placeholder" for the user to add requirements in the sub editor + if (ic.RequiredItems.None()) + { + requiredItems.AddRange(ic.DisabledRequiredItems); + } foreach (RelatedItem relatedItem in requiredItems) { @@ -1626,12 +1641,16 @@ namespace Barotrauma activeComponents.Clear(); activeComponents.AddRange(components); - foreach (MapEntity entity in linkedTo) + Controller controller = GetComponent(); + if (controller == null || controller.User != Character.Controlled || !controller.HideAllItemComponentHUDs) { - if (Prefab.IsLinkAllowed(entity.Prefab) && entity is Item i) + foreach (MapEntity entity in linkedTo) { - if (!i.DisplaySideBySideWhenLinked) { continue; } - activeComponents.AddRange(i.components); + if (Prefab.IsLinkAllowed(entity.Prefab) && entity is Item i) + { + if (!i.DisplaySideBySideWhenLinked) { continue; } + activeComponents.AddRange(i.components); + } } } @@ -1701,7 +1720,9 @@ namespace Barotrauma foreach (Character otherCharacter in Character.CharacterList) { if (otherCharacter != character && - otherCharacter.SelectedItem == this) + otherCharacter.SelectedItem == this && + // Prevent the in use message from being shown if a character is, for example, inside the deconstructor + !otherCharacter.IsAttachedToController()) { ItemInUseWarning.Visible = true; if (mergedHUDRect.Width > GameMain.GraphicsWidth / 2) { mergedHUDRect.Inflate(-GameMain.GraphicsWidth / 4, 0); } @@ -1751,6 +1772,11 @@ namespace Barotrauma } } + public void ClearActiveHUDs() + { + activeHUDs.Clear(); + } + readonly List texts = new(); public List GetHUDTexts(Character character, bool recreateHudTexts = true) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs index 7a7701b9e..6f5041c4f 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs @@ -166,6 +166,14 @@ namespace Barotrauma subElement.GetAttributeBool("fadein", false), subElement.GetAttributePoint("offset", Point.Zero)); + if (brokenSprite.FadeIn && brokenSprite.MaxConditionPercentage <= 0.0f) + { + DebugConsole.AddWarning( + $"Potential error in item {Identifier}: a broken sprite that's set to fade in despite the max condition being 0."+ + " The sprite cannot fade in if it's set to only appear when the item is fully broken.", + ContentPackage); + } + int spriteIndex = 0; for (int i = 0; i < brokenSprites.Count && brokenSprites[i].MaxConditionPercentage < brokenSprite.MaxConditionPercentage; i++) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs index 129ef0958..49ab79cca 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Hull.cs @@ -16,15 +16,17 @@ namespace Barotrauma { public readonly UInt32 DecalId; public readonly int SpriteIndex; - public Vector2 NormalizedPos; + public readonly Vector2 NormalizedPos; public readonly float Scale; + public readonly float DecalAlpha; - public RemoteDecal(UInt32 decalId, int spriteIndex, Vector2 normalizedPos, float scale) + public RemoteDecal(UInt32 decalId, int spriteIndex, Vector2 normalizedPos, float scale, float decalAlpha) { DecalId = decalId; SpriteIndex = spriteIndex; NormalizedPos = normalizedPos; Scale = scale; + DecalAlpha = decalAlpha; } } @@ -696,7 +698,7 @@ namespace Barotrauma var decal = decalEventData.Decal; int decalIndex = decals.IndexOf(decal); msg.WriteByte((byte)(decalIndex < 0 ? 255 : decalIndex)); - msg.WriteRangedSingle(decal.BaseAlpha, 0.0f, 1.0f, 8); + msg.WriteRangedSingle(decal.BaseAlpha, 0f, 1f, 8); break; default: throw new Exception($"Malformed hull event: did not expect {eventData.GetType().Name}"); @@ -752,7 +754,9 @@ namespace Barotrauma float normalizedXPos = msg.ReadRangedSingle(0.0f, 1.0f, 8); float normalizedYPos = msg.ReadRangedSingle(0.0f, 1.0f, 8); float decalScale = msg.ReadRangedSingle(0.0f, 2.0f, 12); - remoteDecals.Add(new RemoteDecal(decalId, spriteIndex, new Vector2(normalizedXPos, normalizedYPos), decalScale)); + float decalAlpha = msg.ReadRangedSingle(0f, 1f, 8); + + remoteDecals.Add(new RemoteDecal(decalId, spriteIndex, new Vector2(normalizedXPos, normalizedYPos), decalScale, decalAlpha)); } break; case EventType.BallastFlora: @@ -804,7 +808,8 @@ namespace Barotrauma decalPosX += Submarine.Position.X; decalPosY += Submarine.Position.Y; } - AddDecal(remoteDecal.DecalId, new Vector2(decalPosX, decalPosY), remoteDecal.Scale, isNetworkEvent: true, spriteIndex: remoteDecal.SpriteIndex); + Decal decal = AddDecal(remoteDecal.DecalId, new Vector2(decalPosX, decalPosY), remoteDecal.Scale, isNetworkEvent: true, spriteIndex: remoteDecal.SpriteIndex); + decal.BaseAlpha = remoteDecal.DecalAlpha; } remoteDecals.Clear(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs index 8202240aa..9c145eca0 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs @@ -296,13 +296,9 @@ namespace Barotrauma.Lights light.Priority = lightPriority(range, light); - int i = 0; - while (i < activeLights.Count && light.Priority < activeLights[i].Priority) - { - i++; - } - activeLights.Insert(i, light); + activeLights.Add(light); } + activeLights.Sort(static (a, b) => b.Priority.CompareTo(a.Priority)); ActiveLightCount = activeLights.Count; float lightPriority(float range, LightSource light) @@ -332,7 +328,7 @@ namespace Barotrauma.Lights activeLights.Remove(activeShadowCastingLights[i]); } } - activeLights.Sort((l1, l2) => l1.LastRecalculationTime.CompareTo(l2.LastRecalculationTime)); + activeLights.Sort(static (l1, l2) => l1.LastRecalculationTime.CompareTo(l2.LastRecalculationTime)); //draw light sprites attached to characters //render into a separate rendertarget using alpha blending (instead of on top of everything else with alpha blending) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs index e54f37f21..2912d88f2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs @@ -50,8 +50,14 @@ namespace Barotrauma.Lights [Serialize("0, 0", IsPropertySaveable.Yes), Editable(ValueStep = 1, DecimalCount = 1, MinValueFloat = -1000f, MaxValueFloat = 1000f)] public Vector2 Offset { get; set; } + public float RotationRad { get; private set; } + [Serialize(0f, IsPropertySaveable.Yes), Editable(MinValueFloat = -360, MaxValueFloat = 360, ValueStep = 1, DecimalCount = 0)] - public float Rotation { get; set; } + public float Rotation + { + get => MathHelper.ToDegrees(RotationRad); + set => RotationRad = MathHelper.ToRadians(value); + } [Serialize(false, IsPropertySaveable.Yes, "Directional lights only shine in \"one direction\", meaning no shadows are cast behind them."+ " Note that this does not affect how the light texture is drawn: if you want something like a conical spotlight, you should use an appropriate texture for that.")] @@ -314,6 +320,10 @@ namespace Barotrauma.Lights private float prevCalculatedRotation; private float rotation; + + /// + /// Current rotation in radians. Note that LightSourceParams.RotationRad also affects the final rotation of the light. + /// public float Rotation { get { return rotation; } @@ -322,7 +332,7 @@ namespace Barotrauma.Lights if (Math.Abs(value - rotation) < 0.001f) { return; } rotation = value; - dir = new Vector2(MathF.Cos(rotation), -MathF.Sin(rotation)); + RefreshDirection(); if (Math.Abs(rotation - prevCalculatedRotation) < RotationRecalculationThreshold && vertices != null) { @@ -486,6 +496,9 @@ namespace Barotrauma.Lights break; } } + //make sure the rotation defined in the parameters is taken into account + RefreshDirection(); + NeedsRecalculation = true; } public LightSource(LightSourceParams lightSourceParams) @@ -497,6 +510,9 @@ namespace Barotrauma.Lights { DeformableLightSprite = new DeformableSprite(lightSourceParams.DeformableLightSpriteElement, invert: true); } + //make sure the rotation defined in the parameters is taken into account + RefreshDirection(); + NeedsRecalculation = true; } public LightSource(Vector2 position, float range, Color color, Submarine submarine, bool addLight=true) @@ -511,6 +527,14 @@ namespace Barotrauma.Lights if (addLight) { GameMain.LightManager.AddLight(this); } } + /// + /// Refresh the direction vector of the light (which is used for calculating shadows) based on the rotation and + /// + private void RefreshDirection() + { + dir = new Vector2(MathF.Cos(rotation - LightSourceParams.RotationRad), -MathF.Sin(rotation - LightSourceParams.RotationRad)); + } + public void Update(float time) { float brightness = 1.0f; @@ -773,9 +797,6 @@ namespace Barotrauma.Lights float boundsExtended = TextureRange; if (OverrideLightTexture != null) { - float cosAngle = (float)Math.Cos(rotation); - float sinAngle = -(float)Math.Sin(rotation); - var overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height); Vector2 origin = OverrideLightTextureOrigin; @@ -790,8 +811,11 @@ namespace Barotrauma.Lights origin *= TextureRange; - drawOffset.X = -origin.X * cosAngle - origin.Y * sinAngle; - drawOffset.Y = origin.X * sinAngle + origin.Y * cosAngle; + //rotate the origin based on the direction + float cos = dir.X; + float sin = dir.Y; + drawOffset.X = -origin.X * cos - origin.Y * sin; + drawOffset.Y = origin.X * sin + origin.Y * cos; } //add a square-shaped boundary to make sure we've got something to construct the triangles from @@ -1536,7 +1560,6 @@ namespace Barotrauma.Lights Vector2 offset = ParentSub == null ? Vector2.Zero : ParentSub.DrawPosition; lightEffect.World = Matrix.CreateTranslation(-new Vector3(position, 0.0f)) * - Matrix.CreateRotationZ(MathHelper.ToRadians(LightSourceParams.Rotation)) * Matrix.CreateTranslation(new Vector3(position + offset + translateVertices, 0.0f)) * transform; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs index e15d5bd48..4db180177 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs @@ -193,7 +193,12 @@ namespace Barotrauma { CanTakeKeyBoardFocus = false }; - var editor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, titleFont: GUIStyle.LargeFont) { UserData = this }; + var editor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, + titleFont: GUIStyle.LargeFont, + dimOutDefaultValues: false) + { + UserData = this + }; if (editor.Fields.TryGetValue(nameof(Scale).ToIdentifier(), out GUIComponent[] scaleFields) && scaleFields.FirstOrDefault() is GUINumberInput scaleInput) diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs index c0656420b..96f56708a 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/Primitives/Peers/LidgrenClientPeer.cs @@ -30,6 +30,13 @@ namespace Barotrauma.Networking { DualStack = GameSettings.CurrentConfig.UseDualModeSockets }; + if (NetConfig.UseLenientHandshake) + { + // More lenient timeouts for local testing, so the server would start even without perfect conditions + netPeerConfiguration.ConnectionTimeout = 60.0f; + netPeerConfiguration.ResendHandshakeInterval = 5.0f; + netPeerConfiguration.MaximumHandshakeAttempts = 20; + } if (endpoint.NetEndpoint.Address.AddressFamily == AddressFamily.InterNetworkV6) { netPeerConfiguration.LocalAddress = System.Net.IPAddress.IPv6Any; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs index 77c3a4005..13f701023 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/GameScreen.cs @@ -351,7 +351,7 @@ namespace Barotrauma { Level.Loaded.DrawBack(graphics, spriteBatch, cam); } - else if (GameMain.GameSession.GameMode is TestGameMode testMode) + else if (GameMain.GameSession?.GameMode is TestGameMode testMode) { graphics.Clear(testMode.BackgroundParams.BackgroundColor); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs index 61f536f38..e42d09894 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/MainMenuScreen/MainMenuScreen.cs @@ -49,6 +49,9 @@ namespace Barotrauma private GUITextBox serverNameBox, passwordBox, maxPlayersBox; private GUITickBox isPublicBox, wrongPasswordBanBox, karmaBox; private GUIDropDown languageDropdown, serverExecutableDropdown; +#if DEBUG + private GUITickBox lenientHandshakeBox; +#endif private readonly GUIButton joinServerButton, hostServerButton; private readonly GUIFrame modsButtonContainer; @@ -1075,7 +1078,7 @@ namespace Barotrauma "-public", isPublicBox.Selected.ToString(), "-playstyle", ((PlayStyle)playstyleBanner.UserData).ToString(), "-banafterwrongpassword", wrongPasswordBanBox.Selected.ToString(), - "-karmaenabled", (!karmaBox.Selected).ToString(), + "-karmaenabled", (karmaBox.Selected).ToString(), "-maxplayers", maxPlayersBox.Text, "-language", languageDropdown.SelectedData.ToString() }; @@ -1114,6 +1117,13 @@ namespace Barotrauma int ownerKey = Math.Max(CryptoRandom.Instance.Next(), 1); arguments.Add("-ownerkey"); arguments.Add(ownerKey.ToString()); +#if DEBUG + if (lenientHandshakeBox.Selected) + { + arguments.Add("-lenienthandshake"); + NetConfig.UseLenientHandshake = true; + } +#endif var processInfo = new ProcessStartInfo { @@ -1368,7 +1378,7 @@ namespace Barotrauma } int maxPlayers = Math.Clamp(maxPlayersElement, min: 1, max: NetConfig.MaxPlayers); - var karmaEnabled = serverSettings.GetAttributeBool("karmaenabled", true); + var karmaEnabled = serverSettings.GetAttributeBool("karmaenabled", false); var selectedPlayStyle = serverSettings.GetAttributeEnum("playstyle", PlayStyle.Casual); Vector2 textLabelSize = new Vector2(1.0f, 0.05f); @@ -1579,10 +1589,18 @@ namespace Barotrauma karmaBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickboxAreaLower.RectTransform), TextManager.Get("HostServerKarmaSetting")) { - Selected = !karmaEnabled, + Selected = karmaEnabled, ToolTip = TextManager.Get("hostserverkarmasettingtooltip") }; +#if DEBUG + lenientHandshakeBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickboxAreaLower.RectTransform), "DEBUG: Lenient server startup timeouts") + { + Selected = true, + ToolTip = "Start with more lenient Lidgren handshake timeouts. The server is more likely to start even when running multiple instances on the same machine under heavy load." + }; +#endif + tickboxAreaLower.RectTransform.IsFixedSize = true; //spacing @@ -1671,8 +1689,8 @@ namespace Barotrauma if (string.IsNullOrEmpty(remoteContentUrl)) { return; } try { - var client = new RestClient(remoteContentUrl); - var request = new RestRequest("MenuContent.xml", Method.GET); + var client = RestFactory.CreateClient(remoteContentUrl); + var request = RestFactory.CreateRequest("MenuContent.xml"); TaskPool.Add("RequestMainMenuRemoteContent", client.ExecuteAsync(request), RemoteContentReceived); } @@ -1693,12 +1711,17 @@ namespace Barotrauma try { if (!t.TryGetResult(out IRestResponse remoteContentResponse)) { throw new Exception("Task did not return a valid result"); } + if (remoteContentResponse.ErrorException != null) + { + DebugConsole.AddWarning($"Connection error: Failed to fetch remote main menu content " + + $"({remoteContentResponse.ErrorException.Message})."); + return; + } if (remoteContentResponse.StatusCode != HttpStatusCode.OK) { DebugConsole.AddWarning( "Failed to receive remote main menu content. " + - "There may be an issue with your internet connection, or the master server might be temporarily unavailable " + - $"(error code: {remoteContentResponse.StatusCode})"); + $"The master server might be temporarily unavailable (HTTP error: {remoteContentResponse.StatusCode})"); return; } string xml = remoteContentResponse.Content; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs index 1e3393406..2aa54d20c 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/ModDownloadScreen.cs @@ -398,7 +398,7 @@ namespace Barotrauma string dir = path.RemoveFromEnd(ModReceiver.Extension, StringComparison.OrdinalIgnoreCase); SaveUtil.DecompressToDirectory(path, dir); - var result = ContentPackage.TryLoad(Path.Combine(dir, ContentPackage.FileListFileName)); + var result = ContentPackage.TryLoad(Path.Combine(dir, ContentPackage.FileListFileName).CleanUpPathCrossPlatform()); if (!result.TryUnwrapSuccess(out var newPackage)) { diff --git a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs index a42a29457..206009ddc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Screens/SubEditorScreen.cs @@ -733,6 +733,8 @@ namespace Barotrauma AutoHideScrollBar = false, OnSelected = (component, userdata) => { + //if we're clicking on a checkbox (toggle visibility) on the list, don't select the entry on the list + if (GUI.MouseOn is GUITickBox) { return false; } //toggling selection is not how listboxes normally work, need to do that manually here SoundPlayer.PlayUISound(GUISoundType.Select); if (layerList.SelectedData == userdata) @@ -3253,6 +3255,20 @@ namespace Barotrauma = new GUITextBox(new RectTransform((1.0f, 0.15f), saveInPackageLayout.RectTransform), createClearButton: true); + packToSaveInFilter.OnTextChanged += (GUITextBox textBox, string text) => + { + + foreach (GUIComponent child in packageToSaveInList.Content.Children) + { + child.Visible = + // Get the pkgText from below + !(child.GetChild()?.GetChild() is GUITextBlock textBlock && + !textBlock.Text.Contains(packToSaveInFilter.Text, StringComparison.OrdinalIgnoreCase)); + } + + return true; + }; + GUILayoutGroup addItemToPackageToSaveList(LocalizedString itemText, ContentPackage p) { var listItem = new GUIFrame(new RectTransform((1.0f, 0.15f), packageToSaveInList.Content.RectTransform), @@ -3273,28 +3289,26 @@ namespace Barotrauma return retVal; } + ContentPackage ownerPkg = null; + #if DEBUG //this is a debug-only option so I won't bother submitting it for localization var modifyVanillaListItem = addItemToPackageToSaveList("Modify Vanilla content package", ContentPackageManager.VanillaCorePackage); var modifyVanillaListIcon = modifyVanillaListItem.GetChild(); GUIStyle.Apply(modifyVanillaListIcon, "WorkshopMenu.EditButton"); + + if (MainSub?.Info != null && IsVanillaSub(MainSub.Info)) + { + ownerPkg = ContentPackageManager.VanillaCorePackage; + } #endif var newPackageListItem = addItemToPackageToSaveList(TextManager.Get("CreateNewLocalPackage"), null); var newPackageListIcon = newPackageListItem.GetChild(); var newPackageListText = newPackageListItem.GetChild(); GUIStyle.Apply(newPackageListIcon, "NewContentPackageIcon"); - new GUICustomComponent(new RectTransform(Vector2.Zero, saveInPackageLayout.RectTransform), - onUpdate: (f, component) => - { - foreach (GUIComponent contentChild in packageToSaveInList.Content.Children) - { - contentChild.Visible &= !(contentChild.GetChild()?.GetChild() is GUITextBlock tb && - !tb.Text.Contains(packToSaveInFilter.Text, StringComparison.OrdinalIgnoreCase)); - } - }); - ContentPackage ownerPkg = null; - if (MainSub?.Info != null) { ownerPkg = GetLocalPackageThatOwnsSub(MainSub.Info); } + + if (ownerPkg == null && MainSub?.Info != null) { ownerPkg = GetLocalPackageThatOwnsSub(MainSub.Info); } foreach (var p in ContentPackageManager.LocalPackages) { var packageListItem = addItemToPackageToSaveList(p.Name, p); @@ -3849,6 +3863,10 @@ namespace Barotrauma return true; }; + new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), deleteButtonHolder.RectTransform), TextManager.Get("DragAndDropSubmarineTip").Fallback(LocalizedString.EmptyString), textAlignment: Alignment.Center, font: GUIStyle.Font) + { + Wrap = true + }; if (AutoSaveInfo?.Root != null) { @@ -4486,6 +4504,7 @@ namespace Barotrauma public void ReconstructLayers() { + Dictionary previousLayers = Layers.ToDictionary(); ClearLayers(); foreach (MapEntity entity in MapEntity.MapEntityList) { @@ -4494,6 +4513,13 @@ namespace Barotrauma Layers.TryAdd(entity.Layer, new LayerData(!entity.IsLayerHidden)); } } + foreach ((string layerName, LayerData data) in previousLayers) + { + if (Layers.ContainsKey(layerName)) + { + Layers[layerName] = data; + } + } UpdateLayerPanel(); } diff --git a/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs b/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs index 668b1bde8..22b885e50 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Serialization/SerializableEntityEditor.cs @@ -26,6 +26,8 @@ namespace Barotrauma public static DateTime NextCommandPush; public static Tuple CommandBuffer; + private bool dimOutDefaultValues; + private bool isReadonly; public bool Readonly { @@ -316,16 +318,17 @@ namespace Barotrauma } } - public SerializableEntityEditor(RectTransform parent, ISerializableEntity entity, bool inGame, bool showName, string style = "", int elementHeight = 24, GUIFont titleFont = null) + public SerializableEntityEditor(RectTransform parent, ISerializableEntity entity, bool inGame, bool showName, string style = "", int elementHeight = 24, GUIFont titleFont = null, bool dimOutDefaultValues = true) : this(parent, entity, inGame ? SerializableProperty.GetProperties(entity).Union(SerializableProperty.GetProperties(entity).Where(p => p.GetAttribute()?.IsEditable(entity) ?? false)) - : SerializableProperty.GetProperties(entity).Where(p => p.GetAttribute()?.IsEditable(entity) ?? true), showName, style, elementHeight, titleFont) + : SerializableProperty.GetProperties(entity).Where(p => p.GetAttribute()?.IsEditable(entity) ?? true), showName, style, elementHeight, titleFont, dimOutDefaultValues) { } - public SerializableEntityEditor(RectTransform parent, ISerializableEntity entity, IEnumerable properties, bool showName, string style = "", int elementHeight = 24, GUIFont titleFont = null) + public SerializableEntityEditor(RectTransform parent, ISerializableEntity entity, IEnumerable properties, bool showName, string style = "", int elementHeight = 24, GUIFont titleFont = null, bool dimOutDefaultValues = true) : base(style, new RectTransform(Vector2.One, parent)) { + this.dimOutDefaultValues = dimOutDefaultValues; elementHeight = (int)(elementHeight * GUI.Scale); var tickBoxStyle = GUIStyle.GetComponentStyle("GUITickBox"); var textBoxStyle = GUIStyle.GetComponentStyle("GUITextBox"); @@ -523,9 +526,67 @@ namespace Barotrauma { propertyField = CreateStringField(entity, property, value.ToString(), displayName, toolTip); } + if (propertyField != null && dimOutDefaultValues) + { + UpdateTextColors(property, entity, propertyField); + } return propertyField; } + + private void UpdateTextColors(SerializableProperty property, object parentObject, GUIComponent parentElement) + { + if (!dimOutDefaultValues) { return; } + + bool isSetToDefaultValue = false; + object currentValue = property.GetValue(parentObject); + foreach (var attribute in property.Attributes.OfType()) + { + if (XMLExtensions.DefaultValueEquals(attribute.DefaultValue, currentValue) || + //treat null and empty strings as identical, because there's no way to differentiate between those in the editor + (currentValue == null && attribute.DefaultValue is string defaultValueStr && defaultValueStr.IsNullOrEmpty())) + { + isSetToDefaultValue = true; + break; + } + } + foreach (var component in parentElement.GetAllChildren()) + { + UpdateTextColors(component, isSetToDefaultValue); + } + } + + private void UpdateTextColors(GUIComponent component, bool isSetToDefaultValue) + { + if (!dimOutDefaultValues) { return; } + + if (component is GUINumberInput numberInput) + { + SetTextColor(numberInput.TextBox.TextBlock); + } + else if (component is GUIDropDown dropDown) + { + SetTextColor(dropDown.Button.TextBlock); + } + else if (component is GUITextBox textBox) + { + SetTextColor(textBox.TextBlock); + } + else if (component is GUITextBlock textBlock) + { + SetTextColor(textBlock); + } + else if (component is GUITickBox tickBox) + { + SetTextColor(tickBox.TextBlock); + } + + void SetTextColor(GUITextBlock textBlock) + { + textBlock.TextColor = new Color(textBlock.TextColor, alpha: isSetToDefaultValue ? 0.5f : 1.0f); + } + } + public GUIComponent CreateBoolField(ISerializableEntity entity, SerializableProperty property, bool value, LocalizedString displayName, LocalizedString toolTip) { var editableAttribute = property.GetAttribute(); @@ -564,6 +625,7 @@ namespace Barotrauma tickBox.Selected = propertyValue; tickBox.Flash(Color.Red); } + UpdateTextColors(property, entity, tickBox); return true; } }; @@ -611,6 +673,7 @@ namespace Barotrauma { TrySendNetworkUpdate(entity, property); } + UpdateTextColors(property, entity, frame); }; refresh += () => { @@ -654,6 +717,7 @@ namespace Barotrauma { TrySendNetworkUpdate(entity, property); } + UpdateTextColors(property, entity, frame); }; HandleSetterValueTampering(numberInput, () => property.GetFloatValue(entity)); @@ -711,6 +775,7 @@ namespace Barotrauma { TrySendNetworkUpdate(entity, property); } + UpdateTextColors(property, entity, frame); return true; }; refresh += () => @@ -829,6 +894,7 @@ namespace Barotrauma TrySendNetworkUpdate(entity, property); textBox.Text = StripPrefabTags(property.GetValue(entity).ToString()); textBox.Flash(GUIStyle.Green, flashDuration: 1f); + UpdateTextColors(property, entity, frame); } //restore the entities that were selected before applying MapEntity.SelectedList.Clear(); @@ -973,6 +1039,7 @@ namespace Barotrauma { TrySendNetworkUpdate(entity, property); } + UpdateTextColors(property, entity, frame); }; fields[i] = numberInput; } @@ -1046,6 +1113,7 @@ namespace Barotrauma { TrySendNetworkUpdate(entity, property); } + UpdateTextColors(property, entity, frame); }; HandleSetterValueTampering(numberInput, () => { @@ -1126,6 +1194,7 @@ namespace Barotrauma { TrySendNetworkUpdate(entity, property); } + UpdateTextColors(property, entity, frame); }; fields[i] = numberInput; } @@ -1206,6 +1275,7 @@ namespace Barotrauma { TrySendNetworkUpdate(entity, property); } + UpdateTextColors(property, entity, frame); }; fields[i] = numberInput; } @@ -1299,6 +1369,7 @@ namespace Barotrauma TrySendNetworkUpdate(entity, property); colorBox.Color = colorBox.HoverColor = colorBox.PressedColor = colorBox.SelectedTextColor = newVal; } + UpdateTextColors(property, entity, frame); }; colorBox.Color = colorBox.HoverColor = colorBox.PressedColor = colorBox.SelectedTextColor = (Color)property.GetValue(entity); fields[i] = numberInput; @@ -1373,6 +1444,7 @@ namespace Barotrauma { TrySendNetworkUpdate(entity, property); } + UpdateTextColors(property, entity, frame); }; fields[i] = numberInput; } @@ -1437,6 +1509,7 @@ namespace Barotrauma TrySendNetworkUpdate(entity, property); textBox.Flash(color: GUIStyle.Green, flashDuration: 1f); } + UpdateTextColors(property, entity, frame); } else { diff --git a/Barotrauma/BarotraumaClient/ClientSource/SpamServerFilter.cs b/Barotrauma/BarotraumaClient/ClientSource/SpamServerFilter.cs index 2214848d4..47d3d1b58 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/SpamServerFilter.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/SpamServerFilter.cs @@ -387,13 +387,11 @@ These will hide all servers that have a discord.gg link in their name or descrip try { - var client = new RestClient($"{remoteContentUrl}spamfilter") - { - CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore) - }; + var client = RestFactory.CreateClient($"{remoteContentUrl}spamfilter"); + client.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore); client.AddDefaultHeader("Cache-Control", "no-cache"); client.AddDefaultHeader("Pragma", "no-cache"); - var request = new RestRequest("serve_spamlist.php", Method.GET); + var request = RestFactory.CreateRequest("serve_spamlist.php"); TaskPool.Add("RequestGlobalSpamFilter", client.ExecuteAsync(request), RemoteContentReceived); } catch (Exception e) @@ -410,12 +408,18 @@ These will hide all servers that have a discord.gg link in their name or descrip try { if (!t.TryGetResult(out IRestResponse? remoteContentResponse)) { throw new Exception("Task did not return a valid result"); } + if (remoteContentResponse.ErrorException != null) + { + DebugConsole.AddWarning( + "Connection error: Failed to receive global spam filter " + + $"({remoteContentResponse.ErrorException.Message})."); + return; + } if (remoteContentResponse.StatusCode != HttpStatusCode.OK) { DebugConsole.AddWarning( - "Failed to receive global spam filter." + - "There may be an issue with your internet connection, or the master server might be temporarily unavailable " + - $"(error code: {remoteContentResponse.StatusCode})"); + "Failed to receive global spam filter. " + + $"The master server might be temporarily unavailable, HTTP status: {remoteContentResponse.StatusCode}"); return; } string data = remoteContentResponse.Content; diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/Workshop.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/Workshop.cs index cc1ef9c79..5f33df3bc 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Steam/Workshop.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/Workshop.cs @@ -23,13 +23,27 @@ namespace Barotrauma.Steam "submarine", "item", "monster", - "art", "mission", + "outpost", + "beaconstation", + "wreck", + "ruin", + "weapons", + "medical", + "equipment", + "art", "event set", "total conversion", + "gamemode", + "gameplaymechanics", "environment", "item assembly", "language", + "qol", + "clientside", + "serverside", + "outdated", + "library" }.ToIdentifiers().ToImmutableArray(); public class ItemThumbnail : IDisposable @@ -113,10 +127,14 @@ namespace Barotrauma.Steam string? thumbnailUrl = item.PreviewImageUrl; if (thumbnailUrl.IsNullOrWhiteSpace()) { return null; } - var client = new RestClient(thumbnailUrl); - var request = new RestRequest(".", Method.GET); + var client = RestFactory.CreateClient(thumbnailUrl); + var request = RestFactory.CreateRequest("."); IRestResponse response = await client.ExecuteAsync(request, cancellationToken); - if (response is { StatusCode: System.Net.HttpStatusCode.OK, ResponseStatus: ResponseStatus.Completed }) + if (response.ErrorException != null) + { + DebugConsole.NewMessage($"Connection error: Failed to load workshop item thumbnail for {item.Id} ({response.ErrorException.Message})."); + } + else if (response is { StatusCode: System.Net.HttpStatusCode.OK, ResponseStatus: ResponseStatus.Completed }) { using var dataStream = new System.IO.MemoryStream(); await dataStream.WriteAsync(response.RawBytes, cancellationToken); diff --git a/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/ItemList.cs b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/ItemList.cs index 91038dab5..3c7ec45b2 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/ItemList.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/Steam/WorkshopMenu/Mutable/ItemList.cs @@ -535,9 +535,9 @@ namespace Barotrauma.Steam = new GUIListBox(rectT, style: null, isHorizontal: false) { UseGridLayout = true, - ScrollBarEnabled = false, + ScrollBarEnabled = true, ScrollBarVisible = false, - HideChildrenOutsideFrame = false, + HideChildrenOutsideFrame = true, Spacing = GUI.IntScale(4) }; tagsList.Content.ClampMouseRectToParent = false; diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj index 89eec0609..0fd9b5f60 100644 --- a/Barotrauma/BarotraumaClient/LinuxClient.csproj +++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.11.5.0 + 1.12.6.2 Copyright © FakeFish 2018-2024 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj index 4dcfb80d3..ced8f9692 100644 --- a/Barotrauma/BarotraumaClient/MacClient.csproj +++ b/Barotrauma/BarotraumaClient/MacClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.11.5.0 + 1.12.6.2 Copyright © FakeFish 2018-2024 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj index 086a023fa..a62c8335f 100644 --- a/Barotrauma/BarotraumaClient/WindowsClient.csproj +++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma - 1.11.5.0 + 1.12.6.2 Copyright © FakeFish 2018-2024 AnyCPU;x64 Barotrauma diff --git a/Barotrauma/BarotraumaServer/LinuxServer.csproj b/Barotrauma/BarotraumaServer/LinuxServer.csproj index 1cc47a6fc..050b41dce 100644 --- a/Barotrauma/BarotraumaServer/LinuxServer.csproj +++ b/Barotrauma/BarotraumaServer/LinuxServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.11.5.0 + 1.12.6.2 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/MacServer.csproj b/Barotrauma/BarotraumaServer/MacServer.csproj index 11fed9443..b255712af 100644 --- a/Barotrauma/BarotraumaServer/MacServer.csproj +++ b/Barotrauma/BarotraumaServer/MacServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.11.5.0 + 1.12.6.2 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs index 6850a5174..50e04783c 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/EventManager.cs @@ -79,6 +79,15 @@ namespace Barotrauma convAction.SelectedOption = selectedOption; if (convAction.Options.Any() && !convAction.GetEndingOptions().Contains(selectedOption)) { + var option = convAction.Options[selectedOption]; + if (option.ForceSay && sender.Character != null) + { + sender.Character.ForceSay( + option.ForceSayText.IsNullOrEmpty() ? TextManager.Get(option.Text).Fallback(option.Text) : TextManager.Get(option.ForceSayText).Fallback(option.ForceSayText), + option.ForceSayInRadio, + option.ForceSayRemoveQuotes); + } + foreach (Client c in convAction.TargetClients) { if (c == sender) { continue; } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs index 736c98542..836dc9f1c 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Events/Missions/CombatMission.cs @@ -133,7 +133,7 @@ namespace Barotrauma switch (winCondition) { case WinCondition.LastManStanding: - if (crews[0].Count == 0 || crews[1].Count == 0) + if (crews[0].Count == 0 && crews[1].Count == 0) { //if there are no characters in either crew, end the round teamDead[0] = teamDead[1] = true; diff --git a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs index ecada43df..2d50a32ea 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/GameMain.cs @@ -230,6 +230,9 @@ namespace Barotrauma //handled in TryStartChildServerRelay i += 2; break; + case "-lenienthandshake": + NetConfig.UseLenientHandshake = true; + break; } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Controller.cs index 8d8fe2933..5a6f1708c 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/Controller.cs @@ -7,7 +7,7 @@ namespace Barotrauma.Items.Components public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) { msg.WriteBoolean(State); - msg.WriteUInt16(user == null ? (ushort)0 : user.ID); + msg.WriteUInt16(User == null || User.Removed ? (ushort)0 : User.ID); } } } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs index 089fc03df..f37295790 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Repairable.cs @@ -16,8 +16,11 @@ namespace Barotrauma.Items.Components public void ServerEventRead(IReadMessage msg, Client c) { if (c.Character == null) { return; } - var requestedFixAction = (FixActions)msg.ReadRangedInteger(0, 2); - var QTESuccess = msg.ReadBoolean(); + FixActions requestedFixAction = (FixActions)msg.ReadRangedInteger(0, 2); + bool QTESuccess = msg.ReadBoolean(); + + if (!item.CanClientAccess(c) || !HasRequiredItems(c.Character, addMessage: false)) { return; } + if (requestedFixAction != FixActions.None) { if (!c.Character.IsTraitor && requestedFixAction == FixActions.Sabotage) diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs index 3d85e08ad..38e09b975 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs @@ -121,9 +121,9 @@ namespace Barotrauma if (shouldBeRemoved) { bool itemAccessDenied = prevItems.Contains(item) && // if the item was in the inventory before - !itemAccessibility[item] && // and the sender is not allowed to access it - (item.PreviousParentInventory == null || // and either the item has no previous inventory - !sender.Character.CanAccessInventory(item.PreviousParentInventory)); // or the sender can't access the previous inventory + !itemAccessibility[item] && // and the sender is not allowed to access it + (item.PreviousParentInventory == null || // and either the item has no previous inventory + !sender.Character.CanAccessInventory(item.PreviousParentInventory)); // or the sender can't access the previous inventory if (itemAccessDenied) { @@ -136,7 +136,7 @@ namespace Barotrauma Item droppedItem = item; Entity prevOwner = Owner; Inventory previousInventory = droppedItem.ParentInventory; - droppedItem.Drop(null); + droppedItem.Drop(sender.Character); droppedItem.PreviousParentInventory = previousInventory; var previousCharacterInventory = prevOwner switch @@ -188,9 +188,18 @@ namespace Barotrauma if (holdable != null && !holdable.CanBeDeattached()) { continue; } - bool itemAccessDenied = !prevItems.Contains(item) && !itemAccessibility[item] && - (sender.Character == null || item.PreviousParentInventory == null || !sender.Character.CanAccessInventory(item.PreviousParentInventory)); - + bool itemAccessDenied = !prevItems.Contains(item) && + !itemAccessibility[item] && + (item.PreviousParentInventory == null || + !sender.Character.CanAccessInventory(item.PreviousParentInventory)); + + // Prevent modified clients from being able to steal items from characters by item swapping with an existing item + // due to drag and drop being enabled + if (!sender.Character.CanAccessInventory(this, CharacterInventory.AccessLevel.AllowBotsAndPets) && GetItemAt(slotIndex) != null) + { + itemAccessDenied = true; + } + //more restricted "adding" of handcuffs: we can't allow putting handcuffs on a player just because dragging and dropping is allowed if (item.HasTag(Tags.HandLockerItem) && !itemAccessDenied) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs index 0a66bbd08..216aeafa8 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Item.cs @@ -12,8 +12,6 @@ namespace Barotrauma { private CoroutineHandle logPropertyChangeCoroutine; - public Inventory PreviousParentInventory; - public override Sprite Sprite { get { return base.Prefab?.Sprite; } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs b/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs index cfb0592cd..705c06fa1 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Map/Hull.cs @@ -112,6 +112,7 @@ namespace Barotrauma msg.WriteRangedSingle(normalizedXPos, 0.0f, 1.0f, 8); msg.WriteRangedSingle(normalizedYPos, 0.0f, 1.0f, 8); msg.WriteRangedSingle(decal.Scale, 0f, 2f, 12); + msg.WriteRangedSingle(decal.BaseAlpha, 0f, 1f, 8); } break; case BallastFloraEventData ballastFloraEventData: @@ -251,7 +252,7 @@ namespace Barotrauma break; case EventType.Decal: byte decalIndex = msg.ReadByte(); - float decalAlpha = msg.ReadRangedSingle(0.0f, 1.0f, 255); + float decalAlpha = msg.ReadRangedSingle(0f, 1f, 8); if (decalIndex < 0 || decalIndex >= decals.Count) { return; } if (c.Character != null && c.Character.AllowInput && c.Character.HeldItems.Any(it => it.GetComponent() != null)) { diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs index 8ffc5a751..06ed826ff 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ChatMessage.cs @@ -66,6 +66,9 @@ namespace Barotrauma.Networking txt = msg.ReadString() ?? ""; } + // Sanitize incoming text message from client so they can't use RichString features + txt = txt.Replace('‖', ' '); + if (!NetIdUtils.IdMoreRecent(ID, c.LastSentChatMsgID)) { return; } c.LastSentChatMsgID = ID; diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/KarmaManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/KarmaManager.cs index 6bab9e4ec..eb0442453 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/KarmaManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/KarmaManager.cs @@ -244,6 +244,8 @@ namespace Barotrauma Character targetCharacter = inventory.Owner as Character; if (yoinker == null || item == null || thiefCharacter == null || targetCharacter == null || thiefCharacter == targetCharacter) { return; } + + if (thiefCharacter.TeamID != targetCharacter.TeamID) { return; } if (targetClient == null && (!DangerousItemStealBots || targetCharacter.AIController == null)) { return; } @@ -261,7 +263,7 @@ namespace Barotrauma } Item foundItem = null; - if (isValid(item)) + if (IsValid(item)) { foundItem = item; } @@ -269,7 +271,7 @@ namespace Barotrauma { foreach (Item containedItem in item.ContainedItems) { - if (isValid(containedItem)) + if (IsValid(containedItem)) { foundItem = containedItem; break; @@ -277,16 +279,19 @@ namespace Barotrauma } } - static bool isValid(Item item) + static bool IsValid(Item item) { - return item.GetComponent() != null || item.GetComponent() != null || item.GetComponent() != null; + return item.GetComponent() != null || IsWeapon(item); + } + static bool IsWeapon(Item item) + { + //a threshold of 10 excludes things like tools, all "proper weapons" seem to have a priority higher than that + return item.Components.Max(c => c.CombatPriority) > 10.0f || item.HasTag(Tags.Weapon); } if (foundItem == null) { return; } bool isIdCard = foundItem.GetComponent() != null; - bool isWeapon = foundItem.GetComponent() != null || foundItem.GetComponent() != null; - if (isIdCard) { string name = string.Empty; @@ -325,7 +330,7 @@ namespace Barotrauma JobPrefab clientJob = yoinker.CharacterInfo?.Job?.Prefab; // security officers receive less karma penalty - if (clientJob != null && clientJob.Identifier == "securityofficer" && isWeapon) + if (clientJob != null && clientJob.Identifier == "securityofficer" && IsWeapon(foundItem)) { karmaDecrease *= 0.5f; } diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs index 79bfedd93..b320a8d6d 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/Primitives/Peers/Server/LidgrenServerPeer.cs @@ -32,6 +32,13 @@ namespace Barotrauma.Networking Port = serverSettings.Port, DualStack = GameSettings.CurrentConfig.UseDualModeSockets }; + if (NetConfig.UseLenientHandshake) + { + // More lenient timeouts for local testing, so the server would start even without perfect conditions + netPeerConfiguration.ConnectionTimeout = 60.0f; + netPeerConfiguration.ResendHandshakeInterval = 5.0f; + netPeerConfiguration.MaximumHandshakeAttempts = 20; + } netPeerConfiguration.DisableMessageType( NetIncomingMessageType.DebugMessage diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs index e1e2a6c78..7c78adc98 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/RespawnManager.cs @@ -582,6 +582,23 @@ namespace Barotrauma.Networking teamSpecificState.RespawnItems.AddRange(AutoItemPlacer.RegenerateLoot(respawnShuttle, respawnContainer)); } } + else if (character.InWater) + { + if (divingSuitPrefab != null) + { + var divingSuit = new Item(divingSuitPrefab, character.Position, respawnSub); + Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(divingSuit)); + character.Inventory.TryPutItem(divingSuit, user: null, allowedSlots: divingSuit.AllowedSlots); + teamSpecificState.RespawnItems.Add(divingSuit); + if (oxyPrefab != null && divingSuit.GetComponent() != null) + { + var oxyTank = new Item(oxyPrefab, character.Position, respawnSub); + Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(oxyTank)); + divingSuit.Combine(oxyTank, user: null); + teamSpecificState.RespawnItems.Add(oxyTank); + } + } + } var characterData = campaign?.GetClientCharacterData(clients[i]); // NOTE: This was where Reaper's tax got applied diff --git a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs index 8e07d5c89..3e65ec3d6 100644 --- a/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaServer/ServerSource/Networking/ServerSettings.cs @@ -46,7 +46,7 @@ namespace Barotrauma.Networking .Aggregate(NetFlags.None, (f1, f2) => f1 | f2); private bool IsFlagRequired(Client c, NetFlags flag) - => NetIdUtils.IdMoreRecent(LastUpdateIdForFlag[flag], c.LastRecvLobbyUpdate); + => NetIdUtils.IdMoreRecent(LastUpdateIdForFlag[flag], c.LastRecvLobbyUpdate) || !c.InitialLobbyUpdateSent; public NetFlags GetRequiredFlags(Client c) => LastUpdateIdForFlag.Keys diff --git a/Barotrauma/BarotraumaServer/WindowsServer.csproj b/Barotrauma/BarotraumaServer/WindowsServer.csproj index ebbd2c594..828830e30 100644 --- a/Barotrauma/BarotraumaServer/WindowsServer.csproj +++ b/Barotrauma/BarotraumaServer/WindowsServer.csproj @@ -6,7 +6,7 @@ Barotrauma FakeFish, Undertow Games Barotrauma Dedicated Server - 1.11.5.0 + 1.12.6.2 Copyright © FakeFish 2018-2023 AnyCPU;x64 DedicatedServer diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Character override and variant tests/Characters/Crawler/Crawler.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Character override and variant tests/Characters/Crawler/Crawler.xml index f90685787..bb24f0573 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Character override and variant tests/Characters/Crawler/Crawler.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Character override and variant tests/Characters/Crawler/Crawler.xml @@ -45,6 +45,11 @@ + + + + + diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Lighting stress (10000 lights)/Lighting stress (10000 lights).sub b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]Lighting stress (10000 lights)/Lighting stress (10000 lights).sub new file mode 100644 index 0000000000000000000000000000000000000000..282a30b37525495d476c5d7c311c2beeb173cf76 GIT binary patch literal 382364 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�jG&4ED9bvUT9i9IdaQ{gOy3k7 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 qN(Nt%*IY7g-&Fui1l7ug + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/EthanolPowerGenerator.png b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/EthanolPowerGenerator.png new file mode 100644 index 0000000000000000000000000000000000000000..76d1aaf546e24de9c06a26965c0edd0530d5c75e GIT binary patch literal 45363 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

n&?ZIV~XJzS=Do)0IG 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 Zv8 + + + + + + + + + + + + + + + diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/RotationAndFlippingTests.sub b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/RotationAndFlippingTests.sub index 0a09be029d8074bc3d4f3ad8b1c1af6f12864f26..7eafd5c1c5bcc44e349a1aa1f7ce44430c4078cb 100644 GIT binary patch literal 13456 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 QDgMv@2L_340fiz107_$lRR910 diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/StatusEffectAndLightTest.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/StatusEffectAndLightTest.xml new file mode 100644 index 000000000..4b58a87c8 --- /dev/null +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/StatusEffectAndLightTest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/filelist.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/filelist.xml index fb1fd016e..be951b04d 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/filelist.xml +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]RotationAndFlippingTests/filelist.xml @@ -1,4 +1,6 @@  - + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestPathFinding/Events.xml b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestPathFinding/Events.xml new file mode 100644 index 000000000..fecc60223 --- /dev/null +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestPathFinding/Events.xml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestPathFinding/[DebugOnlyTest]TestPathFinding.sub b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestPathFinding/[DebugOnlyTest]TestPathFinding.sub new file mode 100644 index 0000000000000000000000000000000000000000..b1620c41984769ed9c3e40653838413b3e4d52f8 GIT binary patch literal 108613 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%0D~hdyW31&a#L+1c<04B4MEA& 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 + + + + + + + \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/README.txt b/Barotrauma/BarotraumaShared/README.txt deleted file mode 100644 index 6ba44b3f5..000000000 --- a/Barotrauma/BarotraumaShared/README.txt +++ /dev/null @@ -1,38 +0,0 @@ -BAROTRAUMA - -http://www.barotraumagame.com - -© 2017-2024 FakeFish Ltd. All rights reserved. -© 2019-2024 Daedalic Entertainment GmbH. The Daedalic logo is a trademark of Daedalic Entertainment GmbH, Germany. All rights reserved. -Privacy policy: http://privacypolicy.daedalic.com - -See the wiki for more detailed info and instructions: -http://barotraumagame.com/wiki - ------------------------------------------------------------------------- - -Port forwarding: -You may try to forward ports on your router using UPnP (Universal Plug and -Play) port forwarding by selecting "Attempt UPnP port forwarding" in the -"Host Server" menu. - -However, UPnP isn't supported by all routers, so you may need to setup port -forwards manually. The exact steps for forwarding a port depend on your -router's model, but you may be able to find a port forwarding guide for -your particular router/application on portforward.com or by practicing -your google-fu skills. - -These are the values that you should use when forwarding a port to your -Barotrauma server: - -Game port (used to communicate with clients) - Service/Application: barotrauma - External Port: The port you have selected for your server (27015 by default) - Internal Port: The port you have selected for your server (27015 by default) - Protocol: UDP - -Query port (used to communicate with Steam) - Service/Application: barotrauma - External Port: The port you have selected for your server (27016 by default) - Internal Port: The port you have selected for your server (27016 by default) - Protocol: UDP \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/AchievementManager.cs b/Barotrauma/BarotraumaShared/SharedSource/AchievementManager.cs index a4feacedd..2e0365317 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/AchievementManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/AchievementManager.cs @@ -551,9 +551,14 @@ namespace Barotrauma private static void UnlockKillAchievement(Character killer, Character target, Identifier identifier) { - if (killer != null && - target.Params.UnlockKillAchievementForWholeCrew && - GameSession.GetSessionCrewCharacters(CharacterType.Player).Contains(killer)) + bool alwaysUnlockForWholeCrew = false; +#if CLIENT + alwaysUnlockForWholeCrew = GameMain.GameSession?.Campaign is SinglePlayerCampaign; +#endif + + if (killer != null && + (alwaysUnlockForWholeCrew || target.Params.UnlockKillAchievementForWholeCrew) && + GameSession.GetSessionCrewCharacters(CharacterType.Both).Contains(killer)) { UnlockAchievement(identifier, unlockClients: true, characterConditions: c => c != null); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs index 6501c1333..8e005243b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/AITarget.cs @@ -49,6 +49,13 @@ namespace Barotrauma } } + + ///

+ /// A multiplier for the sound range for the purposes of displaying the target on sonar. + /// E.g. a value of 10 would mean the sonar can detect the target from x10 further than monsters. + /// + public float SoundRangeOnSonarMultiplier { get; private set; } = 1.0f; + public float SightRange { get { return sightRange; } @@ -206,6 +213,7 @@ namespace Barotrauma MinSoundRange = element.GetAttributeFloat("minsoundrange", 0f); MaxSightRange = element.GetAttributeFloat("maxsightrange", SightRange); MaxSoundRange = element.GetAttributeFloat("maxsoundrange", SoundRange); + SoundRangeOnSonarMultiplier = element.GetAttributeFloat(nameof(SoundRangeOnSonarMultiplier), 1.0f); FadeOutTime = element.GetAttributeFloat("fadeouttime", FadeOutTime); Static = element.GetAttributeBool("static", Static); StaticSight = element.GetAttributeBool("staticsight", StaticSight); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs index 49389c008..ce74caefa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/EnemyAIController.cs @@ -244,14 +244,39 @@ namespace Barotrauma /// /// The monster won't try to damage these submarines /// - public HashSet UnattackableSubmarines + private readonly HashSet unattackableSubmarines = new HashSet(); + + public void SetUnattackableSubmarines(Submarine submarine, bool includeOwnSub = true, bool includeConnectedSubs = true, bool clearExisting = true) { - get; - private set; - } = new HashSet(); + if (clearExisting) + { + unattackableSubmarines.Clear(); + } + if (submarine != null) + { + AddSubs(submarine); + } + if (includeOwnSub && Character.Submarine is Submarine ownSub && ownSub != submarine) + { + AddSubs(ownSub); + } + + void AddSubs(Submarine sub) + { + unattackableSubmarines.Add(sub); + if (includeConnectedSubs) + { + foreach (Submarine connectedSub in sub.DockedTo) + { + unattackableSubmarines.Add(connectedSub); + } + } + } + } public static bool IsTargetBeingChasedBy(Character target, Character character) => character?.AIController is EnemyAIController enemyAI && enemyAI.SelectedAiTarget?.Entity == target && enemyAI.State is AIState.Attack or AIState.Aggressive; + public bool IsBeingChasedBy(Character c) => IsTargetBeingChasedBy(Character, c); private bool IsBeingChased => IsBeingChasedBy(SelectedAiTarget?.Entity as Character); @@ -539,26 +564,7 @@ namespace Barotrauma //doesn't do anything usually, but events may sometimes change monsters' (or pets' that use enemy AI) teams Character.UpdateTeam(); - bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X)); - if (steeringManager == insideSteering) - { - var currPath = PathSteering.CurrentPath; - if (currPath != null && currPath.CurrentNode != null) - { - if (currPath.CurrentNode.SimPosition.Y < Character.AnimController.GetColliderBottom().Y) - { - // Don't allow to jump from too high. - float allowedJumpHeight = Character.AnimController.ImpactTolerance / 2; - float height = Math.Abs(currPath.CurrentNode.SimPosition.Y - Character.SimPosition.Y); - ignorePlatforms = height < allowedJumpHeight; - } - } - if (Character.IsClimbing && PathSteering.IsNextLadderSameAsCurrent) - { - Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y)); - } - } - Character.AnimController.IgnorePlatforms = ignorePlatforms; + HandleLaddersAndPlatforms(deltaTime); if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer || Character.Controlled == Character)) @@ -986,6 +992,69 @@ namespace Barotrauma } } + //how often the character can try ragdolling to drop down + private const float MaxDroppingInterval = 5.0f; + + //last time the character tried ragdolling to drop down + private double lastDroppingTime; + + //how long the character can stay ragdolled to drop down + private const float MaxDroppingTime = 1.0f; + + //timer for the duration of the ragdolling + private float droppingTimer; + + private void HandleLaddersAndPlatforms(float deltaTime) + { + bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X)); + if (steeringManager == insideSteering) + { + var currPath = PathSteering.CurrentPath; + if (currPath is { CurrentNode: WayPoint currentNode }) + { + Vector2 colliderBottom = Character.AnimController.GetColliderBottom(); + if (Character.Submarine != currentNode.Submarine) + { + colliderBottom = Submarine.GetRelativeSimPosition(colliderBottom, currentNode.Submarine, Character.Submarine); + } + if (currentNode.SimPosition.Y < colliderBottom.Y) + { + // Don't allow to jump from too high. + float allowedJumpHeight = Character.AnimController.ImpactTolerance / 2; + Vector2 diff = currentNode.WorldPosition - Character.WorldPosition; + float height = ConvertUnits.ToSimUnits(Math.Abs(diff.Y)); + ignorePlatforms = height < allowedJumpHeight; + + //trying to head down ladders, but can't climb -> periodically try ragdolling to get down + //(may be required by large monsters like mudraptors to fit through hatches) + if (ignorePlatforms && !Character.CanClimb && PathSteering.IsCurrentNodeLadder && + ConvertUnits.ToSimUnits(Math.Abs(diff.X)) < Character.AnimController.Collider.GetMaxExtent()) + { + if (lastDroppingTime < Timing.TotalTime - MaxDroppingInterval) + { + Character.IsRagdolled = true; + Character.SetInput(InputType.Ragdoll, hit: false, held: true); + droppingTimer += deltaTime; + if (droppingTimer > MaxDroppingTime) + { + lastDroppingTime = Timing.TotalTime; + } + } + else + { + droppingTimer = 0.0f; + } + } + } + } + if (Character.IsClimbing && PathSteering.IsNextLadderSameAsCurrent) + { + Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y)); + } + } + Character.AnimController.IgnorePlatforms = ignorePlatforms; + } + #region Idle private void UpdateIdle(float deltaTime, bool followLastTarget = true) @@ -1229,6 +1298,8 @@ namespace Barotrauma return; } + if (Character.IsAttachedToController()) { return; } + attackWorldPos = SelectedAiTarget.WorldPosition; attackSimPos = SelectedAiTarget.SimPosition; @@ -1751,6 +1822,7 @@ namespace Barotrauma { SelectTarget(door.Item.AiTarget, currentTargetMemory.Priority); State = AIState.Attack; + AttackLimb = null; return; } } @@ -1761,12 +1833,20 @@ namespace Barotrauma float margin = AttackLimb != null ? Math.Min(AttackLimb.attack.Range * 0.9f, max) : max; if ((!canAttack || distance > margin) && !IsTryingToSteerThroughGap) { + bool useManualSteering = false; // Steer towards the target if in the same room and swimming // Ruins have walls/pillars inside hulls and therefore we should navigate around them using the path steering. if (Character.CurrentHull != null && Character.Submarine != null && !Character.Submarine.Info.IsRuin && (Character.AnimController.InWater || pursue || !Character.AnimController.CanWalk) && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull)) + { + if (CanSeeTarget(targetCharacter)) + { + useManualSteering = true; + } + } + if (useManualSteering) { Vector2 myPos = Character.AnimController.SimplePhysicsEnabled ? Character.SimPosition : steeringLimb.SimPosition; SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - myPos)); @@ -2311,18 +2391,49 @@ namespace Barotrauma { float prio = 1 + limb.attack.Priority; if (Character.AnimController.SimplePhysicsEnabled) { return prio; } - float dist = Vector2.Distance(limb.WorldPosition, attackPos); - float distanceFactor = 1; + float distance = Vector2.Distance(limb.WorldPosition, attackPos); + float maxDistance = Math.Max(limb.attack.Range * 3, 1000); + if (distance > maxDistance) + { + // Far enough to ignore the attack. + return 0; + } + // Not in range, but relatively close. Let's use the distance factor as a multiplier. + float distanceFactor; if (limb.attack.Ranged) { float min = 100; - distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(min, Math.Max(limb.attack.Range / 2, min), dist)); + if (distance < min) + { + // Too close -> smoothly but steeply reduce the preference (and prefer other attacks, like melee instead) + float t = MathUtils.InverseLerp(0, min, distance); + distanceFactor = MathHelper.Lerp(0.01f, 1, t * t); + } + else + { + distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(min, maxDistance, distance)); + } } else { - // The limb is ignored if the target is not close. Prevents character going in reverse if very far away from it. - // We also need a max value that is more than the actual range. - distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, limb.attack.Range * 3, dist)); + if (distance <= limb.attack.Range) + { + // In range. + if (!Character.InWater) + { + // On dry land vertical distance works a bit differently, as we can't necessarily reach the target above/below us. + float verticalDistance = Math.Abs(limb.WorldPosition.Y - attackPos.Y); + if (verticalDistance > limb.attack.DamageRange) + { + // Most likely can't reach. + return 0; + } + } + // Highly prefer attacks which we can use to hit immediately. + return prio * 10; + } + float min = limb.attack.Range; + distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(min, maxDistance, distance)); } return prio * distanceFactor; } @@ -2521,6 +2632,7 @@ namespace Barotrauma { SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget, addIfNotFound: true).Priority); State = AIState.Attack; + AttackLimb = null; return true; } } @@ -2555,14 +2667,10 @@ namespace Barotrauma return true; } } - if (damageTarget != null) - { - Character.SetInput(item.IsShootable ? InputType.Shoot : InputType.Use, false, true); - item.Use(deltaTime, user: Character); - } + Character.SetInput(item.IsShootable ? InputType.Shoot : InputType.Use, false, true); + item.Use(deltaTime, user: Character); } } - if (damageTarget == null) { return true; } //simulate attack input to get the character to attack client-side Character.SetInput(InputType.Attack, true, true); if (!ActiveAttack.IsRunning) @@ -2609,10 +2717,24 @@ namespace Barotrauma } return true; } + + private const float VisibilityCheckStep = 0.2f; + private double lastVisibilityCheckTime; + private bool canSeeTarget; + /// + /// This method uses and caches the results. + /// + private bool CanSeeTarget(ISpatialEntity target) + { + if (Timing.TotalTime > lastVisibilityCheckTime + VisibilityCheckStep) + { + canSeeTarget = Character.CanSeeTarget(target); + lastVisibilityCheckTime = Timing.TotalTime; + } + return canSeeTarget; + } private float aimTimer; - private float visibilityCheckTimer; - private bool canSeeTarget; private float sinTime; private bool Aim(float deltaTime, ISpatialEntity target, Item weapon) { @@ -2630,13 +2752,7 @@ namespace Barotrauma { Character.CursorPosition -= Character.Submarine.Position; } - visibilityCheckTimer -= deltaTime; - if (visibilityCheckTimer <= 0.0f) - { - canSeeTarget = Character.CanSeeTarget(target); - visibilityCheckTimer = 0.2f; - } - if (!canSeeTarget) + if (!CanSeeTarget(target)) { SetAimTimer(); return false; @@ -2817,7 +2933,10 @@ namespace Barotrauma } } steeringManager.SteeringManual(deltaTime, Vector2.Normalize(limbDiff) * 3); - Character.AnimController.Collider.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f, mouthPos); + if (Character.AnimController.OnGround || Character.InWater) + { + Character.AnimController.Collider.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f, maxVelocity: 10.0f); + } } } else @@ -2961,7 +3080,7 @@ namespace Barotrauma { if (aiTarget.Entity.Submarine.Info.IsWreck || aiTarget.Entity.Submarine.Info.IsBeacon || - UnattackableSubmarines.Contains(aiTarget.Entity.Submarine)) + unattackableSubmarines.Contains(aiTarget.Entity.Submarine)) { continue; } @@ -3509,13 +3628,16 @@ namespace Barotrauma { if (targetCharacter.Submarine != null) { - // Target is inside -> reduce the priority - valueModifier *= 0.5f; - if (Character.Submarine != null) + if (Character.Submarine != null && !targetCharacter.Submarine.IsConnectedTo(Character.Submarine)) { - // Both inside different submarines -> can ignore safely + // Both inside different, unconnected submarines -> can ignore safely continue; } + else + { + // Target is inside a submarine that we are not -> reduce the priority + valueModifier *= 0.5f; + } } else if (Character.CurrentHull != null) { @@ -4402,6 +4524,7 @@ namespace Barotrauma { SelectTarget(doorAiTarget, CurrentTargetMemory.Priority); State = AIState.Attack; + AttackLimb = null; return false; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs index e84df22f7..320db7e27 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs @@ -1380,7 +1380,7 @@ namespace Barotrauma } else { - isAttackerInfected = attacker.CharacterHealth.GetAfflictionStrengthByType(AfflictionPrefab.AlienInfectedType) > 0; + isAttackerInfected = attacker.CharacterHealth.GetAfflictionStrengthByType(AfflictionPrefab.AlienInfectionType) > 0; // Inform other NPCs if (isAttackerInfected || cumulativeDamage > minorDamageThreshold || totalDamage > minorDamageThreshold) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs index 60ad0799d..5c7447555 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs @@ -4,6 +4,7 @@ using Microsoft.Xna.Framework; using System; using System.Linq; using FarseerPhysics; +using System.Diagnostics; namespace Barotrauma { @@ -50,7 +51,7 @@ namespace Barotrauma } /// - /// Returns true if any node in the path is in stairs + /// Returns true if any node in the path is on stairs /// public bool PathHasStairs => currentPath != null && currentPath.Nodes.Any(n => n.Stairs != null); @@ -285,14 +286,17 @@ namespace Barotrauma } } - Vector2 diff = DiffToCurrentNode(); + Vector2 diff = GetDiffAndAdvance(); if (diff == Vector2.Zero) { return Vector2.Zero; } return Vector2.Normalize(diff) * weight; } protected override Vector2 DoSteeringSeek(Vector2 target, float weight) => CalculateSteeringSeek(target, weight); - - private Vector2 DiffToCurrentNode() + + /// + /// Decides whether and when we should skip to the next node. Returns the difference to the current node (after skipping). + /// + private Vector2 GetDiffAndAdvance() { if (currentPath == null || currentPath.Unreachable) { @@ -320,26 +324,37 @@ namespace Barotrauma Reset(); return Vector2.Zero; } - Vector2 pos = host.WorldPosition; - Vector2 diff = currentPath.CurrentNode.WorldPosition - pos; + WayPoint currentNode = currentPath.CurrentNode; + WayPoint nextNode = currentPath.NextNode; + Vector2 diff = currentNode.WorldPosition - host.WorldPosition; + float horizontalDistance = Math.Abs(diff.X); + float verticalDistance = Math.Abs(diff.Y); bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater; bool canClimb = character.CanClimb; Ladder currentLadder = GetCurrentLadder(); Ladder nextLadder = GetNextLadder(); - var ladders = currentLadder ?? nextLadder; + Ladder ladders = currentLadder ?? nextLadder; bool useLadders = canClimb && ladders != null; var collider = character.AnimController.Collider; - Vector2 colliderSize = collider.GetSize(); + Vector2 colliderSize = ConvertUnits.ToDisplayUnits(collider.GetSize()); + float colliderHeight = colliderSize.Y; + if (character.AnimController.CurrentAnimationParams is FishGroundedParams fishGrounded) + { + // On monsters, the main collider might be rotated, so we need to take that into account here. + float standAngle = fishGrounded.ColliderStandAngleInRadians * character.AnimController.Dir; + Vector2 transformedColliderSize = PhysicsBody.RotateVector(colliderSize, standAngle); + colliderHeight = Math.Abs(transformedColliderSize.Y); + } if (useLadders) { - if (character.IsClimbing && Math.Abs(diff.X) - ConvertUnits.ToDisplayUnits(colliderSize.X) > Math.Abs(diff.Y)) + if (character.IsClimbing && Math.Abs(diff.X) - colliderSize.X > Math.Abs(diff.Y)) { // If the current node is horizontally farther from us than vertically, we don't want to keep climbing the ladders. useLadders = false; } - else if (!character.IsClimbing && currentPath.NextNode != null && nextLadder == null) + else if (!character.IsClimbing && nextNode != null && nextLadder == null) { - Vector2 diffToNextNode = currentPath.NextNode.WorldPosition - pos; + Vector2 diffToNextNode = nextNode.WorldPosition - host.WorldPosition; if (Math.Abs(diffToNextNode.X) > Math.Abs(diffToNextNode.Y)) { // If the next node is horizontally farther from us than vertically, we don't want to start climbing. @@ -356,7 +371,7 @@ namespace Barotrauma { if (currentPath.IsAtEndNode && canClimb && ladders != null) { - // Don't release the ladders when ending a path in ladders. + // Don't release the ladders when ending a path on ladders. useLadders = true; } else @@ -388,20 +403,18 @@ namespace Barotrauma if (currentLadder == null && nextLadder != null && character.SelectedSecondaryItem == nextLadder.Item) { // Climbing a ladder but the path is still on the node next to the ladder -> Skip the node. - NextNode(!doorsChecked); + return NextNode(!doorsChecked); } else { bool nextLadderSameAsCurrent = currentLadder == nextLadder; - float colliderHeight = collider.Height / 2 + collider.Radius; - float heightDiff = currentPath.CurrentNode.SimPosition.Y - collider.SimPosition.Y; - float distanceMargin = ConvertUnits.ToDisplayUnits(colliderSize.X); + float distanceMargin = colliderSize.X; if (currentLadder != null && nextLadder != null) { //climbing ladders -> don't move horizontally diff.X = 0.0f; } - if (Math.Abs(heightDiff) < colliderHeight * 1.25f) + if (verticalDistance < colliderHeight / 2 * 1.25f) { if (nextLadder != null && !nextLadderSameAsCurrent) { @@ -410,7 +423,7 @@ namespace Barotrauma { if (nextLadder.Item.TryInteract(character, forceSelectKey: true)) { - NextNode(!doorsChecked); + return NextNode(!doorsChecked); } } } @@ -432,9 +445,9 @@ namespace Barotrauma } if (isAboveFloor) { - if (Math.Abs(diff.Y) < distanceMargin) + if (verticalDistance < distanceMargin) { - NextNode(!doorsChecked); + return NextNode(!doorsChecked); } else if (!currentPath.IsAtEndNode && (nextLadder == null || (currentLadder != null && Math.Abs(currentLadder.Item.WorldPosition.X - nextLadder.Item.WorldPosition.X) > distanceMargin))) { @@ -443,14 +456,21 @@ namespace Barotrauma } } } - else if (currentLadder != null && currentPath.NextNode != null) + else if (currentLadder != null && nextNode != null) { - if (Math.Sign(currentPath.CurrentNode.WorldPosition.Y - character.WorldPosition.Y) != Math.Sign(currentPath.NextNode.WorldPosition.Y - character.WorldPosition.Y)) + if (Math.Sign(currentNode.WorldPosition.Y - character.WorldPosition.Y) != Math.Sign(nextNode.WorldPosition.Y - character.WorldPosition.Y)) { //if the current node is below the character and the next one is above (or vice versa) //and both are on ladders, we can skip directly to the next one //e.g. no point in going down to reach the starting point of a path when we could go directly to the one above - NextNode(!doorsChecked); + return NextNode(!doorsChecked); + } + //heading towards a ladder waypoint below the character, but the next waypoint is above it on the same ladder + // -> allow skipping to that waypoint. + // Otherwise the character may get stuck trying to move to a waypoint near the floor at the bottom of the ladder, failing to get close enough because they can't move any lower. + else if (nextLadderSameAsCurrent && diff.Y < 0 && nextNode.WorldPosition.Y > currentNode.WorldPosition.Y) + { + return NextNode(!doorsChecked); } } } @@ -458,21 +478,20 @@ namespace Barotrauma else if (character.AnimController.InWater) { // Swimming - var door = currentPath.CurrentNode.ConnectedDoor; + var door = currentNode.ConnectedDoor; if (door == null || door.CanBeTraversed) { - float margin = MathHelper.Lerp(1, 5, MathHelper.Clamp(collider.LinearVelocity.Length() / 10, 0, 1)); - float targetDistance = Math.Max(Math.Max(colliderSize.X, colliderSize.Y) / 2 * margin, 0.5f); - float horizontalDistance = Math.Abs(character.WorldPosition.X - currentPath.CurrentNode.WorldPosition.X); - float verticalDistance = Math.Abs(character.WorldPosition.Y - currentPath.CurrentNode.WorldPosition.Y); - if (character.CurrentHull != currentPath.CurrentNode.CurrentHull) + float distanceMultiplier = MathHelper.Lerp(1, 5, MathHelper.Clamp(collider.LinearVelocity.Length() / 10, 0, 1)); + float targetDistance = Math.Max(Math.Max(colliderSize.X, colliderSize.Y) / 2 * distanceMultiplier, 0.5f); + float modifiedVerticalDist = verticalDistance; + if (character.CurrentHull != currentNode.CurrentHull) { - verticalDistance *= 2; + modifiedVerticalDist *= 2; } - float distance = horizontalDistance + verticalDistance; - if (ConvertUnits.ToSimUnits(distance) < targetDistance) + float distance = horizontalDistance + modifiedVerticalDist; + if (distance < targetDistance) { - NextNode(!doorsChecked); + return NextNode(!doorsChecked); } } } @@ -480,6 +499,10 @@ namespace Barotrauma { // Walking horizontally Vector2 colliderBottom = character.AnimController.GetColliderBottom(); + if (character.Submarine != currentNode.Submarine) + { + colliderBottom = Submarine.GetRelativeSimPosition(colliderBottom, currentNode.Submarine, character.Submarine); + } Vector2 velocity = collider.LinearVelocity; // If the character is very short, it would fail to use the waypoint nodes because they are always too high. // If the character is very thin, it would often fail to reach the waypoints, because the horizontal distance is too small. @@ -487,60 +510,113 @@ namespace Barotrauma float minHeight = 1.6125001f; float minWidth = 0.3225f; // Cannot use the head position, because not all characters have head or it can be below the total height of the character - float characterHeight = Math.Max(colliderSize.Y + character.AnimController.ColliderHeightFromFloor, minHeight); - float horizontalDistance = Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X); - bool isTargetTooHigh = currentPath.CurrentNode.SimPosition.Y > colliderBottom.Y + characterHeight; - bool isTargetTooLow = currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y; - var door = currentPath.CurrentNode.ConnectedDoor; - float margin = MathHelper.Lerp(1, 10, MathHelper.Clamp(Math.Abs(velocity.X) / 5, 0, 1)); - float colliderHeight = collider.Height / 2 + collider.Radius; - if (currentPath.CurrentNode.Stairs == null) + float characterHeight = Math.Max(ConvertUnits.ToSimUnits(colliderHeight) + character.AnimController.ColliderHeightFromFloor, minHeight); + bool isTargetTooHigh = currentNode.SimPosition.Y > colliderBottom.Y + characterHeight; + bool isTargetTooLow = currentNode.SimPosition.Y < colliderBottom.Y; + var door = currentNode.ConnectedDoor; + float targetDistanceMultiplier = MathHelper.Lerp(1, 10, MathHelper.Clamp(Math.Abs(velocity.X) / 5, 0, 1)); + if (currentNode.Stairs == null) { - float heightDiff = currentPath.CurrentNode.SimPosition.Y - collider.SimPosition.Y; - if (heightDiff < colliderHeight) + // Only attempt dropping if the node is below the collider bottom. + // Using the next node position here, because the current node might be on the top of the ladder, which can be at the same level with the character or even above it. + bool isBelowEnough = (nextNode ?? currentNode).WorldPosition.Y < character.WorldPosition.Y - colliderHeight / 2; + bool drop = false; + if (isBelowEnough) { - // Original comment: - //the waypoint is between the top and bottom of the collider, no need to move vertically. - // Note that the waypoint can be below collider too! This might be incorrect. + if (!canClimb) + { + // Can't climb -> check if we should drop. + Door nextDoor = door ?? nextNode?.ConnectedDoor; + if (nextDoor is Door { IsHorizontal: true, CanBeTraversed: true } openHatch) + { + bool isHatchBelowCharacter = openHatch.LinkedGap.WorldPosition.Y < character.WorldPosition.Y; + if (isHatchBelowCharacter) + { + // Trying to go through an open hatch below us -> drop. + drop = true; + } + } + else if (currentLadder != null && !isTargetTooLow && nextDoor == null) + { + // On ladders -> drop. + drop = true; + } + } + } + if (drop) + { + return NextNode(!doorsChecked); + } + else if (verticalDistance < colliderHeight / 2) + { + // The waypoint is between the top and bottom of the collider, and we don't intend to drop -> no need to move vertically. diff.Y = 0.0f; } } else { - // In stairs - bool isNextNodeInSameStairs = currentPath.NextNode?.Stairs == currentPath.CurrentNode.Stairs; + // On stairs + bool isNextNodeInSameStairs = nextNode?.Stairs == currentNode.Stairs; if (!isNextNodeInSameStairs) { - margin = 1; - if (currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y + character.AnimController.ColliderHeightFromFloor * 0.25f) + targetDistanceMultiplier = 1; + if (currentNode.SimPosition.Y < colliderBottom.Y + character.AnimController.ColliderHeightFromFloor * 0.25f) { isTargetTooLow = true; } + Structure nextStairs = nextNode?.Stairs; + if (character.AnimController.Stairs != null && nextStairs != null) + { + //currently on stairs, and the next node is not in the same stairs + // -> we must get off the current stairs first before we can skip to the next node, otherwise the character + // would attempt to get "through the stairs" to the next ones + if (character.AnimController.Stairs.StairDirection == Direction.Right) + { + //the direction in which the bot should keep moving depends on the direction of the stairs and whether we're going up or down + diff = nextStairs.WorldPosition.Y > character.AnimController.Stairs.WorldPosition.Y ? Vector2.UnitX : -Vector2.UnitX; + } + else + { + diff = nextStairs.WorldPosition.Y > character.AnimController.Stairs.WorldPosition.Y ? -Vector2.UnitX : Vector2.UnitX; + } + } } } - float targetDistance = Math.Max(colliderSize.X / 2 * margin, minWidth / 2); - if (horizontalDistance < targetDistance && !isTargetTooHigh && !isTargetTooLow) + // Walking horizontally, check whether we are close enough to the current node. + float targetDistance = Math.Max(colliderSize.X / 2 * targetDistanceMultiplier, ConvertUnits.ToDisplayUnits(minWidth / 2)); + Debug.Assert(targetDistance < 500, "Target distance too large (a character is trying to skip on their path to a waypoint far away), something is probably off here."); + if (!isTargetTooHigh && !isTargetTooLow && horizontalDistance < targetDistance) { - if (door is not { CanBeTraversed: false } && (currentLadder == null || nextLadder == null)) + bool isBlockedByDoor = door is { CanBeTraversed: false }; + // If both the current ladder and the next ladder are not null, we are in the middle of ladders and should let the code above handle advancing the nodes. + // However, if either one is null, and we get here, we are probably walking to or from ladders. + bool notOnLadders = currentLadder == null || nextLadder == null; + if (!isBlockedByDoor && notOnLadders) { - NextNode(!doorsChecked); + return NextNode(!doorsChecked); } } } - if (currentPath.CurrentNode == null) + return ReturnDiff(); + + Vector2 NextNode(bool checkDoors) { - return Vector2.Zero; + if (checkDoors) + { + CheckDoorsInPath(); + } + currentPath.SkipToNextNode(); + return ReturnDiff(); } - return ConvertUnits.ToSimUnits(diff); - } - - private void NextNode(bool checkDoors) - { - if (checkDoors) + + Vector2 ReturnDiff() { - CheckDoorsInPath(); + if (currentPath.CurrentNode == null) + { + return Vector2.Zero; + } + return ConvertUnits.ToSimUnits(diff); } - currentPath.SkipToNextNode(); } public bool CanAccessDoor(Door door, Func buttonFilter = null) @@ -600,8 +676,6 @@ namespace Barotrauma } } - private Vector2 GetColliderSize() => ConvertUnits.ToDisplayUnits(character.AnimController.Collider.GetSize()); - private float GetColliderLength() { Vector2 colliderSize = character.AnimController.Collider.GetSize(); @@ -676,7 +750,7 @@ namespace Barotrauma if (door.LinkedGap.IsHorizontal) { int dir = Math.Sign(nextWaypoint.WorldPosition.X - door.Item.WorldPosition.X); - float size = character.AnimController.InWater ? colliderLength : GetColliderSize().X; + float size = character.AnimController.InWater ? colliderLength : ConvertUnits.ToDisplayUnits(character.AnimController.Collider.GetSize()).X; shouldBeOpen = (door.Item.WorldPosition.X - character.WorldPosition.X) * dir > -size; } else @@ -794,12 +868,17 @@ namespace Barotrauma if (character == null) { return 0.0f; } float? penalty = GetSingleNodePenalty(nextNode); if (penalty == null) { return null; } + Vector2 nextNodePosition = nextNode.Position; + if (nextNode.Waypoint.Submarine != node.Waypoint.Submarine) + { + nextNodePosition = Submarine.GetRelativeSimPosition(nextNodePosition, node.Waypoint.Submarine, nextNode.Waypoint.Submarine); + } bool nextNodeAboveWaterLevel = nextNode.Waypoint.CurrentHull != null && nextNode.Waypoint.CurrentHull.Surface < nextNode.Waypoint.Position.Y; if (!character.CanClimb && node.Waypoint.Stairs == null && nextNode.Waypoint.Stairs == null) { if (node.Waypoint.Ladders != null && nextNode.Waypoint.Ladders != null && (!nextNode.Waypoint.Ladders.Item.IsInteractable(character) || character.LockHands) || - (nextNode.Position.Y - node.Position.Y > 1.0f && //more than one sim unit to climb up - nextNodeAboveWaterLevel)) //upper node not underwater + (nextNodePosition.Y - node.Position.Y > 1.0f && //more than one sim unit to climb up + nextNodeAboveWaterLevel)) //upper node not underwater { return null; } @@ -830,7 +909,7 @@ namespace Barotrauma } } - float yDist = Math.Abs(node.Position.Y - nextNode.Position.Y); + float yDist = Math.Abs(node.Position.Y - nextNodePosition.Y); if (nextNodeAboveWaterLevel && node.Waypoint.Ladders == null && nextNode.Waypoint.Ladders == null && node.Waypoint.Stairs == null && nextNode.Waypoint.Stairs == null) { penalty += yDist * 10.0f; @@ -898,18 +977,14 @@ namespace Barotrauma //steer away from edges of the hull bool wander = false; bool inWater = character.AnimController.InWater; - Hull currentHull = character.CurrentHull; - // TODO: disabled for now, because seems to cause bots to walk towards walls/doors in some places. In some places it's because how the hulls are defined, but there is probably something else too, is it seems to happen also elsewhere. - // if (!inWater) - // { - // Vector2 colliderBottomPos = ConvertUnits.ToDisplayUnits(character.AnimController.GetColliderBottom()); - // if (Hull.FindHull(colliderBottomPos, guess: currentHull, useWorldCoordinates: false) is Hull lowestHull) - // { - // // Use the hull found at the collider bottom, if found. - // // Makes difference in some rooms that have multiple hulls, of which the lowest hull where the feet are might not be the same as where the center position of the main collider is. - // currentHull = lowestHull; - // } - // } + + //use the hull the legs are in (if one is found), so the character won't walk against the wall when their torso is in a different hull where there'd be room to walk further + //(e.g. if the character is in a shallow pool-type room, like in ResearchModule_01_Colony) + Hull currentHull = + character.AnimController.GetLimb(LimbType.RightLeg)?.Hull ?? + character.AnimController.GetLimb(LimbType.LeftLeg)?.Hull ?? + character.CurrentHull; + if (currentHull != null && !inWater) { float roomWidth = currentHull.Rect.Width; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs index e27bf7a04..664813c08 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjective.cs @@ -103,9 +103,19 @@ namespace Barotrauma } } - // For temporarily forcing walking. Will reset after each priority calculation, so it will need to be kept alive by something. - // The intention of this boolean to allow walking even when the priority is higher than AIObjectiveManager.RunPriority. - public bool ForceWalk { get; set; } + /// + /// For temporarily forcing walking. Will reset after each priority calculation, so it will need to be kept alive by something. + /// The intention of this boolean to allow walking even when the priority is higher than AIObjectiveManager.RunPriority. + /// + public bool ForceWalkTemporarily { get; set; } + + /// + /// Forces the character to walk when executing this objective, even if the priority is above . + /// Unlike , this value is not automatically reset. + /// + public bool ForceWalkPermanently { get; set; } + + public bool ForceWalk => ForceWalkTemporarily || ForceWalkPermanently; public bool IgnoreAtOutpost { get; set; } @@ -313,7 +323,7 @@ namespace Barotrauma ///
public float CalculatePriority() { - ForceWalk = false; + ForceWalkTemporarily = false; Priority = GetPriority(); ForceHighestPriority = false; return Priority; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs index b5304e13c..728645fa6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCleanupItems.cs @@ -40,7 +40,7 @@ namespace Barotrauma if (subObjectives.All(so => so.SubObjectives.None())) { // If none of the subobjectives have subobjectives, no valid container was found. Don't allow running. - ForceWalk = true; + ForceWalkTemporarily = true; } return prio; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs index 42e35c1b2..093cf34a2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveCombat.cs @@ -258,13 +258,14 @@ namespace Barotrauma protected override bool CheckObjectiveState() { - if (character.Submarine is { TeamID: CharacterTeamType.FriendlyNPC } && character.Submarine == Enemy.Submarine) + // In a friendly outpost, and the target is still in the outpost + if (character.Submarine is { Info.IsOutpost: true } && character.IsOnFriendlyTeam(character.Submarine.TeamID) && + character.Submarine == Enemy.Submarine) { - // Target still in the outpost + // Outpost guards shouldn't lose the target in friendly outposts, + // However, if we are not a guard, let's ensure that we allow the cooldown. if (character.TeamID == CharacterTeamType.FriendlyNPC && !character.IsSecurity) { - // Outpost guards shouldn't lose the target in friendly outposts, - // However, if we are not a guard, let's ensure that we allow the cooldown. allowCooldown = true; } } @@ -286,7 +287,8 @@ namespace Barotrauma { allowCooldown = true; // Target not in the outpost anymore. - if (character.CanSeeTarget(Enemy)) + if (character.Submarine.IsConnectedTo(Enemy.Submarine) && + character.CanSeeTarget(Enemy)) { allowCooldown = false; coolDownTimer = DefaultCoolDown; @@ -389,7 +391,7 @@ namespace Barotrauma HumanAIController.AutoFaceMovement = false; if (!gotoObjective.ShouldRun(true)) { - ForceWalk = true; + ForceWalkTemporarily = true; } } } @@ -468,7 +470,7 @@ namespace Barotrauma isMoving = true; if (!IsEnemyClose(MeleeDistance)) { - ForceWalk = true; + ForceWalkTemporarily = true; } HumanAIController.FaceTarget(Enemy); HumanAIController.AutoFaceMovement = false; @@ -1234,7 +1236,7 @@ namespace Barotrauma } if (isAimBlocked) { - ForceWalk = true; + ForceWalkTemporarily = true; } if (!followTargetObjective.IsCloseEnough) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDeconstructItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDeconstructItem.cs index 2f0f8ec71..c3fd16668 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDeconstructItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDeconstructItem.cs @@ -95,7 +95,11 @@ namespace Barotrauma if (potentialDeconstructor?.InputContainer == null) { continue; } if (!potentialDeconstructor.InputContainer.Inventory.CanBePut(Item)) { continue; } if (!potentialDeconstructor.Item.HasAccess(character)) { continue; } - if (Item.Prefab.DeconstructItems.None(it => it.IsValidDeconstructor(otherItem))) { continue; } + if (Item.Prefab.DeconstructItems.Any() && + Item.Prefab.DeconstructItems.None(it => it.IsValidDeconstructor(otherItem))) + { + continue; + } float distFactor = GetDistanceFactor(Item.WorldPosition, potentialDeconstructor.Item.WorldPosition, factorAtMaxDistance: 0.2f); if (distFactor > bestDistFactor) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDeconstructItems.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDeconstructItems.cs index 781ed4746..391bbc4dd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDeconstructItems.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveDeconstructItems.cs @@ -64,7 +64,11 @@ namespace Barotrauma if (target == null || target.Removed) { return false; } //bots can't handle deconstructing items that require another item to deconstruct, let's not try to do that //in the vanilla game, this means unidentified genetic materials, which we don't want to "deconstruct" anyway - if (target.Prefab.DeconstructItems.All(d => d.RequiredOtherItem.Length > 0)) { return false; } + if (target.Prefab.DeconstructItems.Any() && + target.Prefab.DeconstructItems.All(d => d.RequiredOtherItem.Length > 0)) + { + return false; + } // If the target was selected as a valid target, we'll have to accept it so that the objective can be completed. // The validity changes when a character picks the item up. if (!IsValidTarget(target, character, checkInventory: true)) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs index 53ec38880..0a57628a2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs @@ -148,7 +148,7 @@ namespace Barotrauma character.Speak(TextManager.GetWithVariable("DialogPutOutFire", "[roomname]", targetHull.DisplayName, FormatCapitals.Yes).Value, null, 0, "putoutfire".ToIdentifier(), 10.0f); } // Prevents running into the flames. - objectiveManager.CurrentObjective.ForceWalk = true; + objectiveManager.CurrentObjective.ForceWalkTemporarily = true; } if (moveCloser) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs index af532273c..c5790c08d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveExtinguishFires.cs @@ -11,6 +11,8 @@ namespace Barotrauma public override Identifier Identifier { get; set; } = "extinguish fires".ToIdentifier(); public override bool ForceRun => true; protected override bool AllowInAnySub => true; + // Periodically clear the ignore list so that fires abandoned when fumbling with finding an extinguisher, navigating etc get reconsidered + protected override float IgnoreListClearInterval => 30; public AIObjectiveExtinguishFires(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs index 94a876ca7..b2f0ae419 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs @@ -49,6 +49,13 @@ namespace Barotrauma public const float DefaultReach = 100; public const float MaxReach = 150; + /// + /// How long it takes for the objective to be abandoned if no suitable item is found. + /// Intended to be an optimization: if the bots are constantly trying to find some item (like a diving suit), + /// it can easily lead to performance issues when e.g. AIObjectiveFindDivingGear constantly starts up new GetItem objectives. + /// + private float abandonDelayIfItemNotFound = 5.0f; + /// /// Is the goal of this objective to get diving gear (i.e. has it been created by )? /// If so, the objective won't attempt to create another objective if the path requires diving gear @@ -213,7 +220,7 @@ namespace Barotrauma { if (isDoneSeeking) { - HandlePotentialItems(); + HandlePotentialItems(deltaTime); } if (objectiveManager.CurrentOrder is not AIObjectiveGoTo) { @@ -389,6 +396,8 @@ namespace Barotrauma // If the root container changes, the item is no longer where it was (taken by someone -> need to find another item) AbortCondition = obj => targetItem == null || (targetItem.GetRootInventoryOwner() is Entity owner && owner != moveToTarget && owner != character), SpeakIfFails = false, + ForceWalkTemporarily = this.ForceWalkTemporarily, + ForceWalkPermanently = this.ForceWalkPermanently, endNodeFilter = CreateEndNodeFilter(moveToTarget) }; }, @@ -598,7 +607,7 @@ namespace Barotrauma } } - private void HandlePotentialItems() + private void HandlePotentialItems(float deltaTime) { Debug.Assert(isDoneSeeking); if (itemCandidates.Any()) @@ -652,10 +661,14 @@ namespace Barotrauma } else { -#if DEBUG - DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", IdentifiersOrTags)}", Color.Yellow); -#endif - Abandon = true; + abandonDelayIfItemNotFound -= deltaTime; + if (abandonDelayIfItemNotFound <= 0.0f) + { + #if DEBUG + DebugConsole.NewMessage($"{character.Name}: Cannot find an item with the following identifier(s) or tag(s): {string.Join(", ", IdentifiersOrTags)}", Color.Yellow); + #endif + Abandon = true; + } } } } @@ -718,13 +731,15 @@ namespace Barotrauma private bool CheckItem(Item item) { + bool matchesIdentifiersOrTags = item.HasIdentifierOrTags(IdentifiersOrTags) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && IdentifiersOrTags.Contains(item.Prefab.VariantOf)); + if (!matchesIdentifiersOrTags) { return false; } if (!item.HasAccess(character)) { return false; } if (ignoredItems.Contains(item)) { return false; }; if (ignoredIdentifiersOrTags != null && item.HasIdentifierOrTags(ignoredIdentifiersOrTags)) { return false; } if (item.Condition < TargetCondition) { return false; } if (ItemFilter != null && !ItemFilter(item)) { return false; } if (RequireNonEmpty && item.Components.Any(i => i.IsEmpty(character))) { return false; } - return item.HasIdentifierOrTags(IdentifiersOrTags) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && IdentifiersOrTags.Contains(item.Prefab.VariantOf)); + return true; } public override void Reset() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs index fd5ade494..79786bcea 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs @@ -958,6 +958,7 @@ namespace Barotrauma public bool ShouldRun(bool run) { + if (ForceWalk) { return false; } if (run && objectiveManager.ForcedOrder == this && IsWaitOrder && !character.IsOnPlayerTeam) { // NPCs with a wait order don't run. diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs index efa495d6c..1a2d14048 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs @@ -267,7 +267,10 @@ namespace Barotrauma if (node.Waypoint.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(node.Waypoint.CurrentHull)) { return false; } return true; //don't stop at ladders when idling - }, endNodeFilter: node => node.Waypoint.Stairs == null && node.Waypoint.Ladders == null && (!isCurrentHullAllowed || !IsForbidden(node.Waypoint.CurrentHull))); + }, endNodeFilter: node => + node.Waypoint.Stairs == null && node.Waypoint.CurrentHull == currentTarget && node.Waypoint.Ladders == null && + (!isCurrentHullAllowed || !IsForbidden(node.Waypoint.CurrentHull))); + if (path.Unreachable) { //can't go to this room, remove it from the list and try another room diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveInspectNoises.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveInspectNoises.cs index b8639dd08..6439708bc 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveInspectNoises.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveInspectNoises.cs @@ -78,9 +78,10 @@ namespace Barotrauma if (item.GetRootInventoryOwner() is Character targetCharacter && AIObjectiveFightIntruders.IsValidTarget(targetCharacter, character, targetCharactersInOtherSubs: false)) { - float dist = character.CurrentHull.GetApproximateDistance(character.Position, targetCharacter.Position, targetCharacter.CurrentHull, aiTarget.SoundRange, distanceMultiplierPerClosedDoor: 2); - if (dist * HumanAIController.Hearing > aiTarget.SoundRange) { continue; } - + float range = aiTarget.SoundRange * HumanAIController.Hearing; + float dist = character.CurrentHull.GetApproximateDistance(character.Position, targetCharacter.Position, targetCharacter.CurrentHull, range, distanceMultiplierPerClosedDoor: 2); + if (dist > range) { continue; } + character.Speak(TextManager.Get("dialogheardenemy").Value, identifier: "heardenemy".ToIdentifier(), minDurationBetweenSimilar: 30.0f); if (inspectNoiseObjective != null && subObjectives.Contains(inspectNoiseObjective)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoadItems.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoadItems.cs index 894f27e60..65d0e110a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoadItems.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveLoadItems.cs @@ -112,7 +112,7 @@ namespace Barotrauma float prio = objectiveManager.GetOrderPriority(this); if (subObjectives.All(so => so.SubObjectives.None() || so.Priority <= 0)) { - ForceWalk = true; + ForceWalkTemporarily = true; } return prio; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs index ad38f8cee..04a9bafa4 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveOperateItem.cs @@ -257,6 +257,7 @@ namespace Barotrauma { DialogueIdentifier = AIObjectiveGoTo.DialogCannotReachTarget, TargetName = target.Item.Name, + ForceWalkPermanently = ForceWalk, endNodeFilter = EndNodeFilter ?? AIObjectiveGetItem.CreateEndNodeFilter(target.Item) }, onAbandon: () => Abandon = true, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectivePumpWater.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectivePumpWater.cs index 6c1a7a37b..7aa3d1e87 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectivePumpWater.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectivePumpWater.cs @@ -29,7 +29,7 @@ namespace Barotrauma { if (pump?.Item == null || pump.Item.Removed) { return false; } if (pump.Item.IgnoreByAI(character)) { return false; } - if (!pump.Item.IsInteractable(character)) { return false; } + if (!pump.Item.IsInteractable(character) || !pump.CanBeSelected) { return false; } if (pump.IsAutoControlled) { return false; } if (pump.Item.ConditionPercentage <= 0) { return false; } if (pump.Item.CurrentHull == null) { return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs index 42cbd4de6..8963eeb34 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveRescueAll.cs @@ -136,7 +136,7 @@ namespace Barotrauma public static bool IsValidTarget(Character target, Character character, out bool ignoredAsMinorWounds) { ignoredAsMinorWounds = false; - if (target == null || target.IsDead || target.Removed) { return false; } + if (target == null || target.IsDead || target.Removed || target.InvisibleTimer > 0.0f) { return false; } if (target.IsInstigator) { return false; } if (target.IsPet) { return false; } if (!HumanAIController.IsFriendly(character, target, onlySameTeam: true)) { return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/AICharacter.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/AICharacter.cs index 879c4197f..669bc3a0a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/AICharacter.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/AICharacter.cs @@ -42,7 +42,9 @@ namespace Barotrauma { enemyAi.PetBehavior?.Update(deltaTime); } - if (IsDead || IsUnconscious || Stun > 0.0f || IsIncapacitated) + if (IsDead || IsUnconscious || IsIncapacitated || + //only check "real" stuns here, ignoring ragdolling, so the AI can run and decide whether to ragdoll or unragdoll + CharacterHealth.Stun > 0.0f) { //don't enable simple physics on dead/incapacitated characters //the ragdoll controls the movement of incapacitated characters instead of the collider, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs index 316d3db5d..db9322c49 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/FishAnimController.cs @@ -685,7 +685,7 @@ namespace Barotrauma { movement = MathUtils.SmoothStep(movement, TargetMovement, 0.2f); - if (Collider.BodyType == BodyType.Dynamic) + if (Collider.BodyType == BodyType.Dynamic && onGround) { Collider.LinearVelocity = new Vector2( movement.X, diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs index ed100e587..8f8a4f87b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Animation/Ragdoll.cs @@ -30,6 +30,7 @@ namespace Barotrauma { public Fixture F1, F2; public Vector2 LocalNormal; + public Vector2 WorldNormal; public Vector2 Velocity; public Vector2 ImpactPos; @@ -39,7 +40,7 @@ namespace Barotrauma F2 = f2; Velocity = velocity; LocalNormal = contact.Manifold.LocalNormal; - contact.GetWorldManifold(out _, out FarseerPhysics.Common.FixedArray2 points); + contact.GetWorldManifold(out WorldNormal, out FarseerPhysics.Common.FixedArray2 points); ImpactPos = points[0]; } } @@ -826,7 +827,7 @@ namespace Barotrauma return true; } - private void ApplyImpact(Fixture f1, Fixture f2, Vector2 localNormal, Vector2 impactPos, Vector2 velocity) + private void ApplyImpact(Fixture f1, Fixture f2, Vector2 worldNormal, Vector2 impactPos, Vector2 velocity) { if (character.DisableImpactDamageTimer > 0.0f) { return; } @@ -838,7 +839,7 @@ namespace Barotrauma return; } - Vector2 normal = localNormal; + Vector2 normal = worldNormal; float impact = Vector2.Dot(velocity, -normal); if (f1.Body == Collider.FarseerBody || !Collider.Enabled) { @@ -1069,9 +1070,12 @@ namespace Barotrauma } Hull newHull = Hull.FindHull(findPos, currentHull); - if (setInWater && newHull == null) + if (setInWater) { - inWater = true; + if (newHull == null || findPos.Y < newHull.WorldSurface) + { + inWater = true; + } } if (newHull == currentHull) { return; } @@ -1114,7 +1118,10 @@ namespace Barotrauma { //don't teleport out yet if the character is going through a gap if (Gap.FindAdjacent(Gap.GapList.Where(g => g.Submarine == currentHull.Submarine), findPos, 150.0f, allowRoomToRoom: true) != null) { return; } - if (Limbs.Any(l => Gap.FindAdjacent(currentHull.ConnectedGaps, l.WorldPosition, ConvertUnits.ToDisplayUnits(l.body.GetSize().Combine()), allowRoomToRoom: true) != null)) { return; } + if (Limbs.Any(l => !l.IsSevered && Gap.FindAdjacent(currentHull.ConnectedGaps, l.WorldPosition, ConvertUnits.ToDisplayUnits(l.body.GetSize().Combine()), allowRoomToRoom: true) != null)) + { + return; + } character.MemLocalState?.Clear(); Teleport(ConvertUnits.ToSimUnits(currentHull.Submarine.Position), currentHull.Submarine.Velocity); } @@ -1246,6 +1253,9 @@ namespace Barotrauma private float BodyInRestDelay = 1.0f; + /// + /// Controls the sleeping state of this character + /// public bool BodyInRest { get { return bodyInRestTimer > BodyInRestDelay; } @@ -1269,7 +1279,7 @@ namespace Barotrauma while (impactQueue.Count > 0) { var impact = impactQueue.Dequeue(); - ApplyImpact(impact.F1, impact.F2, impact.LocalNormal, impact.ImpactPos, impact.Velocity); + ApplyImpact(impact.F1, impact.F2, impact.WorldNormal, impact.ImpactPos, impact.Velocity); } CheckValidity(); @@ -1312,9 +1322,18 @@ namespace Barotrauma } float MaxVel = NetConfig.MaxPhysicsBodyVelocity; - Collider.LinearVelocity = new Vector2( - NetConfig.Quantize(Collider.LinearVelocity.X, -MaxVel, MaxVel, 12), - NetConfig.Quantize(Collider.LinearVelocity.Y, -MaxVel, MaxVel, 12)); + if (GameMain.NetworkMember != null) + { + Collider.LinearVelocity = new Vector2( + NetConfig.Quantize(Collider.LinearVelocity.X, -MaxVel, MaxVel, 12), + NetConfig.Quantize(Collider.LinearVelocity.Y, -MaxVel, MaxVel, 12)); + } + else + { + Collider.LinearVelocity = new Vector2( + MathHelper.Clamp(Collider.LinearVelocity.X, -MaxVel, MaxVel), + MathHelper.Clamp(Collider.LinearVelocity.Y, -MaxVel, MaxVel)); + } if (forceStanding) { @@ -1368,9 +1387,19 @@ namespace Barotrauma UpdateHullFlowForces(deltaTime); - if (currentHull == null || + bool applyWaterForces = + currentHull == null || currentHull.WaterVolume > currentHull.Volume * 0.95f || - ConvertUnits.ToSimUnits(currentHull.Surface) > Collider.SimPosition.Y) + ConvertUnits.ToSimUnits(currentHull.Surface) > Collider.SimPosition.Y; +#if CLIENT + if (Screen.Selected is CharacterEditor.CharacterEditorScreen && + this is AnimController animController) + { + applyWaterForces = animController.CurrentAnimationParams is SwimParams; + } +#endif + + if (applyWaterForces) { Collider.ApplyWaterForces(); } @@ -1460,10 +1489,10 @@ namespace Barotrauma else { // Falling -> ragdoll briefly if we are not moving at all, because we are probably stuck. - if (Collider.LinearVelocity == Vector2.Zero && !character.IsRemotePlayer) + if (Collider.LinearVelocity == Vector2.Zero && GameMain.NetworkMember is not { IsClient: true }) { character.IsRagdolled = true; - if (character.IsBot) + if (!character.IsPlayer) { // Seems to work without this on player controlled characters -> not sure if we should call it always or just for the bots. character.SetInput(InputType.Ragdoll, hit: false, held: true); @@ -1823,7 +1852,13 @@ namespace Barotrauma { floorFixture = standOnFloorFixture; standOnFloorY = rayStart.Y + (rayEnd.Y - rayStart.Y) * standOnFloorFraction; - if (rayStart.Y - standOnFloorY < Collider.Height * 0.5f + Collider.Radius + ColliderHeightFromFloor * 1.2f) + + //allow the floor to be just a bit below the bottom of the collider for the character to be "on ground" + //there is some inaccuracy in the physics simulation (and floats), the collider isn't usually precisely ColliderHeightFromFloor above the floor + const float Tolerance = 0.1f; + float standHeight = Collider.Height * 0.5f + Collider.Radius + ColliderHeightFromFloor; + + if (rayStart.Y - standOnFloorY <= standHeight + Tolerance) { onGround = true; if (standOnFloorFixture.CollisionCategories == Physics.CollisionStairs) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs index 6fcd7a33b..8201439ba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs @@ -187,6 +187,11 @@ namespace Barotrauma set => Params.Health.DoesBleed = value; } + /// + /// Can this character be contained inside a controller? + /// + public bool IsContainable { get; set; } + public readonly Dictionary Properties; public Dictionary SerializableProperties { @@ -683,6 +688,11 @@ namespace Barotrauma get { return AnimController.Mass; } } + /// + /// The position the character was at when we previously set the transforms of the items in the character's inventory. + /// + private Vector2 lastInventoryItemSetTransformPosition; + public CharacterInventory Inventory { get; private set; } /// @@ -788,7 +798,24 @@ namespace Barotrauma set { if (value == selectedCharacter) { return; } - if (selectedCharacter != null) { selectedCharacter.selectedBy = null; } + //deselect the currently selected character + if (selectedCharacter != null) + { + selectedCharacter.selectedBy = null; + //check if some other character has selected the currently selected character too, + //and set selectedBy to that other character (otherwise the currently selected character would be unaware they're still being dragged by someone) + foreach (var otherCharacter in CharacterList) + { + if (otherCharacter != this && otherCharacter.selectedCharacter == selectedCharacter) + { + selectedCharacter.selectedBy = otherCharacter; + break; + } + } + } + + CharacterHUD.RecreateHudTextsIfControlling(this); + selectedCharacter = value; if (selectedCharacter != null) { selectedCharacter.selectedBy = this; } #if CLIENT @@ -1642,8 +1669,10 @@ namespace Barotrauma AnimController.FindHull(setInWater: true); if (AnimController.CurrentHull != null) { Submarine = AnimController.CurrentHull.Submarine; } + IsContainable = prefab.ConfigElement.GetAttributeBool(nameof(IsContainable), def: Mass <= 30.0f); + CharacterList.Add(this); - + Enabled = GameMain.NetworkMember == null; if (info != null) @@ -2268,6 +2297,12 @@ namespace Barotrauma } } + // Try to detach from the controller if we are currently attached to something that is dangerous for our character + if (aiControlled && Stun <= 0f && !IsKnockedDownOrRagdolled && !LockHands && ShouldAvoidStayingAttachedToController()) + { + SelectedItem = null; + } + if (GameMain.NetworkMember != null) { if (GameMain.NetworkMember.IsServer) @@ -2316,7 +2351,7 @@ namespace Barotrauma { attackCoolDown -= deltaTime; } - else if (IsKeyDown(InputType.Attack)) + else if (IsKeyDown(InputType.Attack) && !IsAttachedToController()) { //normally the attack target, where to aim the attack and such is handled by EnemyAIController, //but in the case of player-controlled monsters, we handle it here @@ -2843,14 +2878,14 @@ namespace Barotrauma #if CLIENT if (Screen.Selected == GameMain.SubEditorScreen) { hidden = false; } #endif - if (!CanInteract || hidden || !item.IsInteractable(this)) { return false; } - Controller controller = item.GetComponent(); if (controller != null && IsAnySelectedItem(item) && controller.IsAttachedUser(this)) { return true; } + if (!CanInteract || hidden || !item.IsInteractable(this)) { return false; } + if (item.ParentInventory != null) { return CanAccessInventory(item.ParentInventory); @@ -2972,7 +3007,9 @@ namespace Barotrauma } } - if (!item.Prefab.InteractThroughWalls && Screen.Selected != GameMain.SubEditorScreen && !insideTrigger) + //note that the distance to item should be set to 0 above if the character is within the item's bounding box + bool closeEnoughToIgnoreVisibilityCheck = distanceToItem <= 0.1f; + if (!item.Prefab.InteractThroughWalls && Screen.Selected != GameMain.SubEditorScreen && !insideTrigger && !closeEnoughToIgnoreVisibilityCheck) { var body = Submarine.CheckVisibility(SimPosition, itemPosition, ignoreLevel: true); bool itemCenterVisible = CheckBody(body, item); @@ -3001,7 +3038,6 @@ namespace Barotrauma { return itemCenterVisible; } - } return true; @@ -3091,7 +3127,11 @@ namespace Barotrauma if (!CanInteract) { - SelectedItem = SelectedSecondaryItem = null; + if (!IsAttachedToController()) + { + SelectedItem = null; + } + SelectedSecondaryItem = null; focusedItem = null; if (!AllowInput) { @@ -3110,8 +3150,16 @@ namespace Barotrauma { if (!PlayerInput.PrimaryMouseButtonHeld() || Barotrauma.Inventory.DraggingItemToWorld) { - FocusedCharacter = CanInteract || CanEat ? FindCharacterAtPosition(mouseSimPos) : null; - if (FocusedCharacter != null && !CanSeeTarget(FocusedCharacter)) { FocusedCharacter = null; } + //don't allow focusing on anyone when the health window is open (avoids accidentally selecting someone when closing the window) + if (CharacterHealth.OpenHealthWindow != null) + { + FocusedCharacter = null; + } + else + { + FocusedCharacter = CanInteract || CanEat ? FindCharacterAtPosition(mouseSimPos) : null; + if (FocusedCharacter != null && !CanSeeTarget(FocusedCharacter)) { FocusedCharacter = null; } + } float aimAssist = GameSettings.CurrentConfig.AimAssistAmount * (AnimController.InWater ? 1.5f : 1.0f); if (HeldItems.Any(it => it?.GetComponent()?.IsActive ?? false)) { @@ -3435,7 +3483,7 @@ namespace Barotrauma obstructVisionAmount = Math.Max(obstructVisionAmount - deltaTime, 0.0f); - if (Inventory != null) + if (Inventory != null && Vector2.DistanceSquared(lastInventoryItemSetTransformPosition, Position) > 0.1f) { //do not check for duplicates: this is code is called very frequently, and duplicates don't matter here, //so it's better just to avoid the relatively expensive duplicate check @@ -3444,6 +3492,7 @@ namespace Barotrauma if (item.body == null || item.body.Enabled) { continue; } item.SetTransform(SimPosition, 0.0f, forceSubmarine: Submarine); } + lastInventoryItemSetTransformPosition = Position; } HideFace = false; @@ -3570,7 +3619,7 @@ namespace Barotrauma { wasRagdolled = IsRagdolled; IsRagdolled = IsKeyDown(InputType.Ragdoll); - if (IsRagdolled && IsBot && GameMain.NetworkMember is not { IsClient: true }) + if (IsRagdolled && !IsPlayer && GameMain.NetworkMember is not { IsClient: true }) { ClearInput(InputType.Ragdoll); } @@ -3622,7 +3671,19 @@ namespace Barotrauma AnimController.IgnorePlatforms = true; } AnimController.ResetPullJoints(); - SelectedItem = SelectedSecondaryItem = null; + + // Prevent us from detaching from the controller if we are attached to it OR detach if we + // manually ragdoll, in this case it should be similar to us deselecting the controller + if (!IsAttachedToController() || + (IsKeyDown(InputType.Ragdoll) + // Let only the server do this check since the Ragdoll input for other clients is set to be held + // for stunned characters even if a character isn't manually ragdolling + && (GameMain.NetworkMember == null || GameMain.NetworkMember is { IsServer: true } ))) + { + SelectedItem = null; + } + + SelectedSecondaryItem = null; SelectedCharacter = null; return; } @@ -3651,6 +3712,13 @@ namespace Barotrauma bool MustDeselect(Item item) { if (item == null) { return false; } + + // Prevent creatures from deselecting the controller if they are attached to it + if (IsAIControlled && !CanInteract && IsAttachedToController()) + { + return false; + } + if (!CanInteractWith(item)) { return true; } bool hasSelectableComponent = false; foreach (var component in item.Components) @@ -4376,6 +4444,41 @@ namespace Barotrauma } } + public void ForceSay(LocalizedString messageToSay, bool sayInRadio, bool removeQuotes = false, float delay = 0.0f) + { + if (messageToSay.IsNullOrEmpty() || SpeechImpediment >= 100.0f || IsDead) + { + return; + } + + if (removeQuotes) + { + messageToSay = new TrimLString(messageToSay, + TrimLString.Mode.Both, ['"', '”', '“', ' ']); + } + + ChatMessageType messageType = ChatMessageType.Default; + bool canUseRadio = ChatMessage.CanUseRadio(this, out WifiComponent radio); + if (canUseRadio && sayInRadio) + { + messageType = ChatMessageType.Radio; + } + + CoroutineManager.Invoke(() => + { +#if SERVER + GameMain.Server?.SendChatMessage(messageToSay.Value, messageType, senderClient: null, this); +#elif CLIENT + // no need to create the message when playing as a client, the server will send it to us + if (GameMain.Client == null) + { + AIChatMessage message = new AIChatMessage(messageToSay.Value, messageType); + SendSinglePlayerMessage(message, canUseRadio, radio); + } +#endif + }, delay); + } + public void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount) { CharacterHealth.SetAllDamage(damageAmount, bleedingDamageAmount, burnDamageAmount); @@ -4760,6 +4863,10 @@ namespace Barotrauma { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && !isNetworkMessage) { return; } if (Screen.Selected != GameMain.GameScreen) { return; } + //don't allow stunning for less than one frame + //fixes monsters/enemies that take some minuscule amount of stun from a weapon still being noticeable affected by the stun, + //because even a one-frame stun briefly disables the animations and makes the character stop + if (newStun < Timing.Step && Stun <= 0.0f) { return; } if (GodMode) { CharacterHealth.Stun = 0; @@ -4787,7 +4894,12 @@ namespace Barotrauma CharacterHealth.Stun = newStun; if (newStun > 0.0f) { - SelectedItem = SelectedSecondaryItem = null; + if (!IsAttachedToController()) + { + SelectedItem = null; + } + + SelectedSecondaryItem = null; if (SelectedCharacter != null) { DeselectCharacter(); } } HealthUpdateInterval = 0.0f; @@ -4976,6 +5088,37 @@ namespace Barotrauma } } + public bool IsAttachedToController() + { + if (SelectedItem == null) { return false; } + + var controller = SelectedItem.GetComponent(); + if (controller == null) { return false; } + + return controller.IsAttachedUser(this); + } + + public bool ShouldAvoidStayingAttachedToController() + { + if (!IsAttachedToController()) { return false; } + + var deconstructor = SelectedItem.GetComponent(); + if (deconstructor != null) + { + return true; + } + + // Character is being carried by an enemy! + if (IsHuman && + SelectedItem.GetRootInventoryOwner() is Character carryingCharacter && + TeamID != carryingCharacter.TeamID) + { + return true; + } + + return false; + } + public void Kill(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool isNetworkMessage = false, bool log = true) { if (IsDead || CharacterHealth.Unkillable || GodMode || Removed) { return; } @@ -5113,7 +5256,7 @@ namespace Barotrauma AnimController.movement = Vector2.Zero; AnimController.TargetMovement = Vector2.Zero; - if (!LockHands) + if (!LockHands && causeOfDeath != CauseOfDeathType.Disconnected) { foreach (Item heldItem in HeldItems.ToList()) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterPrefab.cs index 93e4b1bf6..68110740b 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/CharacterPrefab.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Xml.Linq; using Microsoft.Xna.Framework; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs index cb36b1196..8008179fb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs @@ -629,7 +629,7 @@ namespace Barotrauma public static readonly Identifier StunType = "stun".ToIdentifier(); public static readonly Identifier EMPType = "emp".ToIdentifier(); public static readonly Identifier SpaceHerpesType = "spaceherpes".ToIdentifier(); - public static readonly Identifier AlienInfectedType = "alieninfected".ToIdentifier(); + public static readonly Identifier AlienInfectionType = "alieninfection".ToIdentifier(); public static readonly Identifier InvertControlsType = "invertcontrols".ToIdentifier(); public static readonly Identifier DisguisedAsHuskType = "disguiseashusk".ToIdentifier(); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs index f72b31c43..ed5415fe6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs @@ -827,9 +827,21 @@ namespace Barotrauma } } + float modifiedStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab, limbType)); + if (newAffliction.Prefab.AfflictionType == AfflictionPrefab.StunType) + { + //don't allow stunning for less than one frame + //fixes monsters/enemies that take some minuscule amount of stun from a weapon still being noticeable affected by the stun, + //because even a one-frame stun briefly disables the animations and makes the character stop + if (modifiedStrength < Timing.Step && Stun <= 0.0f) + { + return; + } + } + if (existingAffliction != null) { - float newStrength = newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(existingAffliction.Prefab, limbType)); + float newStrength = modifiedStrength; if (allowStacking) { // Add the existing strength @@ -851,7 +863,7 @@ namespace Barotrauma //create a new instance of the affliction to make sure we don't use the same instance for multiple characters //or modify the affliction instance of an Attack or a StatusEffect var copyAffliction = newAffliction.Prefab.Instantiate( - Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab, limbType))), + Math.Min(newAffliction.Prefab.MaxStrength, modifiedStrength), newAffliction.Source); afflictions.Add(copyAffliction, limbHealth); AchievementManager.OnAfflictionReceived(copyAffliction, Character); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs index 67b0141e4..a8ce33b63 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/HumanPrefab.cs @@ -190,7 +190,7 @@ namespace Barotrauma idleObjective.PreferredOutpostModuleTypes.Add(moduleType); } } - humanAI.ReportRange = Hearing; + humanAI.Hearing = Hearing; humanAI.ReportRange = ReportRange; humanAI.FindWeaponsRange = FindWeaponsRange; humanAI.AimSpeed = AimSpeed; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs index d9226ce75..e9f1d2f0a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Limb.cs @@ -1293,7 +1293,7 @@ namespace Barotrauma if (!statusEffects.TryGetValue(actionType, out var statusEffectList)) { return; } foreach (StatusEffect statusEffect in statusEffectList) { - if (statusEffect.ShouldWaitForInterval(character, deltaTime)) { return; } + if (statusEffect.ShouldWaitForInterval(character, deltaTime)) { continue; } statusEffect.sourceBody = body; if (statusEffect.type == ActionType.OnDamaged) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs index 14c8e83c8..675faa777 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs @@ -728,7 +728,9 @@ namespace Barotrauma [Serialize(true, IsPropertySaveable.Yes, description: "Should the character target or ignore walls when it's outside the submarine."), Editable] public bool TargetOuterWalls { get; private set; } - [Serialize(false, IsPropertySaveable.Yes, description: "If enabled, the character chooses randomly from the available attacks. The priority is used as a weight for weighted random."), Editable] + [Serialize(false, IsPropertySaveable.Yes, description: "If disabled (default), the character selects the limb based on a formula where the parameters are a) the priority of the attack b) the distance to the target, and c) the range of the attack" + + "If enabled, the character chooses randomly from the available attacks. The priority is used as a weight for weighted random. The distance to the target is in this case ignored." + ), Editable] public bool RandomAttack { get; private set; } [Serialize(false, IsPropertySaveable.Yes, description:"Does the creature know how to open doors (still requires a proper ID card). Humans can always open doors (They don't use this AI definition)."), Editable] diff --git a/Barotrauma/BarotraumaShared/SharedSource/CircuitBox/CircuitBoxInputOutputNode.cs b/Barotrauma/BarotraumaShared/SharedSource/CircuitBox/CircuitBoxInputOutputNode.cs index e589401bc..99b619886 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/CircuitBox/CircuitBoxInputOutputNode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/CircuitBox/CircuitBoxInputOutputNode.cs @@ -77,8 +77,8 @@ namespace Barotrauma } else { - conn.SetLabel(conn.Connection.DisplayName, this); conn.Connection.DisplayNameOverride = null; + conn.SetLabel(conn.Connection.DisplayName, this); } } #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CharacterFile.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CharacterFile.cs index e608d06ba..0e67a57ef 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CharacterFile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentFile/CharacterFile.cs @@ -106,9 +106,10 @@ namespace Barotrauma void AddTexturePath(string path) { if (string.IsNullOrEmpty(path)) { return; } + var contentPath = ContentPath.FromRaw(characterPrefab.ContentPackage, ragdollParams.Texture); //if the path contains a gender variable, we can't load it yet because we don't know which gender we need - if (path.Contains("[GENDER]")) { return; } - texturePaths.Add(ContentPath.FromRaw(characterPrefab.ContentPackage, ragdollParams.Texture)); + if (contentPath.FullPath.Contains("[GENDER]")) { return; } + texturePaths.Add(contentPath); } } #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackage/ContentPackage.cs b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackage/ContentPackage.cs index 2bee7b3c1..12967f125 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackage/ContentPackage.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackage/ContentPackage.cs @@ -199,9 +199,16 @@ namespace Barotrauma try { - return success(doc.Root.GetAttributeBool("corepackage", false) + ContentPackage contentPackage = doc.Root.GetAttributeBool("corepackage", false) ? new CorePackage(doc, path) - : new RegularPackage(doc, path)); + : new RegularPackage(doc, path); + + if (System.IO.Path.GetFileNameWithoutExtension(path)?.Any(char.IsUpper) is true) + { + DebugConsole.ThrowError($"Invalid filename casing. Please rename \"filelist.xml\" so it is entirely lowercase.", contentPackage: contentPackage); + } + + return success(contentPackage); } catch (Exception e) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs index 8dc9bb60d..c70f3a767 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/DebugConsole.cs @@ -3019,7 +3019,10 @@ namespace Barotrauma switch (args[argIndex].ToLowerInvariant()) { case "inside": - spawnPoint = WayPoint.GetRandom(SpawnType.Human, job, Submarine.MainSub); + spawnPoint = + WayPoint.GetRandom(SpawnType.Human, job, Submarine.MainSub) ?? + //try a non-job-specific spawnpoint if a job-specific one can't be found + WayPoint.GetRandom(SpawnType.Human, assignedJob: null, Submarine.MainSub); break; case "outside": spawnPoint = WayPoint.GetRandom(SpawnType.Enemy); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Decals/Decal.cs b/Barotrauma/BarotraumaShared/SharedSource/Decals/Decal.cs index b42f1131c..281022315 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Decals/Decal.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Decals/Decal.cs @@ -34,11 +34,12 @@ namespace Barotrauma get { return Prefab.LifeTime; } } + private float baseAlpha = 1.0f; public float BaseAlpha { - get; - set; - } = 1.0f; + get => baseAlpha; + set => baseAlpha = MathHelper.Clamp(value, 0f, 1f); + } public Color Color { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs index 79a25913a..3f2cb74c6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Enums.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Enums.cs @@ -131,6 +131,14 @@ namespace Barotrauma /// OnRemoved = 25, /// + /// Executes continuously while the item/character is being deconstructed. + /// + OnDeconstructing = 26, + /// + /// Executed once when the item/character is deconstructed. + /// + OnDeconstructed = 27, + /// /// Executes when the character dies. Only valid for characters. /// OnDeath = OnBroken diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Event.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Event.cs index 08abda9ed..1c2cc3a87 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Event.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Event.cs @@ -12,7 +12,11 @@ namespace Barotrauma public readonly int RandomSeed; protected readonly EventPrefab prefab; - + +#nullable enable + public Mission? TriggeringMission; +#nullable restore + public EventPrefab Prefab => prefab; public EventSet ParentSet { get; private set; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConditionalAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConditionalAction.cs index 04d517c2f..b1391bd64 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConditionalAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CheckConditionalAction.cs @@ -29,6 +29,9 @@ namespace Barotrauma [Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the target (or all targets if there's multiple) when the check succeeds.")] public Identifier ApplyTagToTarget { get; set; } + [Serialize(true, IsPropertySaveable.Yes, description: "Should the check fail if no targets matching the specified tag are found?")] + public bool FailIfTargetNotFound { get; set; } + public CheckConditionalAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { if (TargetTag.IsEmpty) @@ -79,11 +82,10 @@ namespace Barotrauma if (targets.None()) { - DebugConsole.LogError($"{nameof(CheckConditionalAction)} error: {GetEventDebugName()} uses a {nameof(CheckConditionalAction)} but no valid target was found for tag \"{TargetTag}\"! This will cause the check to automatically succeed.", - contentPackage: ParentEvent.Prefab.ContentPackage); + return !FailIfTargetNotFound; } - if (targets.None() || Conditionals.None()) + if (Conditionals.None()) { foreach (var target in targets) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs index f6ff09a03..bc40eab16 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ConversationAction.cs @@ -14,6 +14,33 @@ namespace Barotrauma /// partial class ConversationAction : EventAction { + public class OptionActionGroup : SubactionGroup + { + [Serialize("", IsPropertySaveable.Yes, description: "The text to display in the option.")] + public string Text { get; set; } + + [Serialize(false, IsPropertySaveable.Yes, description: "Should this option end the conversation (closing the conversation prompt?). " + + "By default, options that don't have any actions inside them, or that only have a GoTo action, end the conversation. " + + "But if there are other actions inside the option, the game assumes there may be some kind of a follow-up coming to the conversation, " + + "and by default leaves it open.")] + public bool EndConversation { get; set; } + + [Serialize(false, IsPropertySaveable.Yes, description: $"If enabled, the player will send the {nameof(Text)} in chat when selecting the option, or if {nameof(ForceSayText)} is not empty, will send that instead.")] + public bool ForceSay { get; set; } + + [Serialize(false, IsPropertySaveable.Yes, description: "If enabled, the message sent in chat will be sent in radio chat instead.")] + public bool ForceSayInRadio { get; set; } + + [Serialize("", IsPropertySaveable.Yes, description: $"Message sent in chat, if empty, {nameof(Text)} is used instead.")] + public string ForceSayText { get; set; } + + [Serialize(true, IsPropertySaveable.Yes, description: "Should the chat message be stripped of any quotation mark characters?")] + public bool ForceSayRemoveQuotes { get; set; } + + public OptionActionGroup(ScriptedEvent scriptedEvent, ContentXElement element) : base(scriptedEvent, element) + { + } + } public enum DialogTypes { @@ -33,6 +60,18 @@ namespace Barotrauma [Serialize("", IsPropertySaveable.Yes, description: "The text to display in the prompt. Can be the text as-is, or a tag referring to a line in a text file.")] public string Text { get; set; } + [Serialize(false, IsPropertySaveable.Yes, description: $"If enabled, the speaker will send the {nameof(Text)} in chat, or if {nameof(ForceSayText)} is not empty, will send that instead. Note: requires a valid SpeakerTag to be defined.")] + public bool ForceSay { get; set; } + + [Serialize(false, IsPropertySaveable.Yes, description: "If enabled, the message sent in chat by the speaker will be sent in radio chat instead.")] + public bool ForceSayInRadio { get; set; } + + [Serialize("", IsPropertySaveable.Yes, description: $"Message sent in chat by the speaker, if empty, {nameof(Text)} is used instead.")] + public string ForceSayText { get; set; } + + [Serialize(true, IsPropertySaveable.Yes, description: "Should the chat message be stripped of any quotation mark characters?")] + public bool ForceSayRemoveQuotes { get; set; } + [Serialize("", IsPropertySaveable.Yes, description: "Tag of the character who's speaking. Makes a speech bubble icon appear above the character to indicate you can speak with them, and stops the character in place when the conversation triggers. Also allows the conversation to be interrupted if the speaker dies or becomes incapacitated mid-conversation.")] public Identifier SpeakerTag { get; set; } @@ -75,7 +114,7 @@ namespace Barotrauma private AIObjective prevIdleObjective, prevGotoObjective; private AIObjective npcWaitObjective; - public List Options { get; private set; } + public List Options { get; private set; } public SubactionGroup Interrupted { get; private set; } @@ -99,12 +138,12 @@ namespace Barotrauma { actionCount++; Identifier = actionCount; - Options = new List(); + Options = new List(); foreach (var elem in element.Elements()) { if (elem.Name.LocalName.Equals("option", StringComparison.OrdinalIgnoreCase)) { - Options.Add(new SubactionGroup(ParentEvent, elem)); + Options.Add(new OptionActionGroup(ParentEvent, elem)); } else if (elem.Name.LocalName.Equals("interrupt", StringComparison.OrdinalIgnoreCase)) { @@ -215,6 +254,10 @@ namespace Barotrauma interrupt = false; dialogOpened = false; Speaker = null; +#if CLIENT + dialogBox?.Close(); + dialogBox = null; +#endif } /// @@ -292,6 +335,7 @@ namespace Barotrauma if (dialogOpened) { lastActiveTime = Timing.TotalTime; + #if CLIENT if (GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "ConversationAction")) { @@ -350,7 +394,7 @@ namespace Barotrauma } else { - TryStartConversation(null); + TryStartConversation(Speaker); } } else @@ -467,11 +511,26 @@ namespace Barotrauma ParentEvent.AddTarget(InvokerTag, targetCharacter); } - ShowDialog(speaker, targetCharacter); + if (ForceSay) + { + speaker?.ForceSay( + ForceSayText.IsNullOrEmpty() ? TextManager.Get(Text).Fallback(Text) : TextManager.Get(ForceSayText).Fallback(ForceSayText), + ForceSayInRadio, + ForceSayRemoveQuotes, + // Small delay so the speaking character doesn't talk at the same time as the player + delay: 0.7f); + } + + ShowDialog(Speaker, targetCharacter); dialogOpened = true; - if (speaker != null) + if (Speaker != null) { + Speaker = speaker; + + // Set the Speaker of the child conversation actions so they know which character is speaking + Options.SelectMany(static op => op.Actions).OfType().ForEach(action => action.Speaker = speaker); + speaker.CampaignInteractionType = CampaignMode.InteractionType.None; speaker.SetCustomInteract(null, null); #if SERVER diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CountTargetsAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CountTargetsAction.cs index 13d3b6859..ba10a1f7e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CountTargetsAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/CountTargetsAction.cs @@ -99,6 +99,7 @@ namespace Barotrauma else { int compareToTargetCount = ParentEvent.GetTargets(CompareToTarget).Count(); + if (compareToTargetCount == 0) { return false; } float percentage = MathUtils.Percentage(targetCount, compareToTargetCount); if (MinPercentageRelativeToTarget > -1 && percentage < MinPercentageRelativeToTarget) { return false; } if (MaxPercentageRelativeToTarget > -1 && percentage > MaxPercentageRelativeToTarget) { return false; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventAction.cs index f1a54e742..bf96cb286 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/EventAction.cs @@ -9,14 +9,7 @@ namespace Barotrauma { public class SubactionGroup { - public string Text; public List Actions; - /// - /// Should this option end the conversation (closing the conversation prompt?). By default, options that don't have any actions inside them, or that only have a GoTo action, end the conversation. - /// But if there are other actions inside the option, the game assumes there may be some kind of a follow-up coming to the conversation, and by default leaves it open. - /// - public bool EndConversation; - private int currentSubAction = 0; public EventAction CurrentSubAction @@ -31,17 +24,17 @@ namespace Barotrauma } } - public SubactionGroup(ScriptedEvent scriptedEvent, ContentXElement elem) + public SubactionGroup(ScriptedEvent scriptedEvent, ContentXElement element) { - Text = elem.GetAttribute("text")?.Value ?? ""; + SerializableProperty.DeserializeProperties(this, element); + Actions = new List(); - EndConversation = elem.GetAttributeBool("endconversation", false); - foreach (var e in elem.Elements()) + foreach (var e in element.Elements()) { if (e.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase)) { - DebugConsole.ThrowError($"Error in event prefab \"{scriptedEvent.Prefab.Identifier}\". Status effect configured as a sub action (text: \"{Text}\"). Please configure status effects as child elements of a StatusEffectAction.", - contentPackage: elem.ContentPackage); + DebugConsole.ThrowError($"Error in event prefab \"{scriptedEvent.Prefab.Identifier}\". Status effect configured as a sub action. Please configure status effects as child elements of a StatusEffectAction.", + contentPackage: element.ContentPackage); continue; } var action = Instantiate(scriptedEvent, e); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ForceSayAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ForceSayAction.cs new file mode 100644 index 000000000..1e5b933a2 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/ForceSayAction.cs @@ -0,0 +1,62 @@ +using Barotrauma.Items.Components; +using Barotrauma.Networking; +using System.Linq; + +namespace Barotrauma +{ + /// + /// Forces a specific character to say a message in chat. + /// + class ForceSayAction : EventAction + { + [Serialize("", IsPropertySaveable.Yes, description: "Tag of the character that should say the message.")] + public Identifier TargetTag { get; set; } + + [Serialize("", IsPropertySaveable.Yes, description: "The message that the character should say. Can be the text as-is, or a tag referring to a line in a text file.")] + public string Message { get; set; } + + [Serialize(false, IsPropertySaveable.Yes, description: "Should the message that the character says be sent in radio?")] + public bool SayInRadio { get; set; } + + [Serialize(true, IsPropertySaveable.Yes, description: "Should the message be stripped of any quotation mark characters?")] + public bool RemoveQuotes { get; set; } + + public ForceSayAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { } + + private bool isFinished = false; + + public override bool IsFinished(ref string goTo) + { + return isFinished; + } + + public override void Reset() + { + isFinished = false; + } + + public override void Update(float deltaTime) + { + if (isFinished) { return; } + + var targets = ParentEvent.GetTargets(TargetTag); + + LocalizedString messageToSay = TextManager.Get(Message).Fallback(Message); + foreach (var target in targets) + { + if (target != null && target is Character character) + { + character.ForceSay(messageToSay, SayInRadio, RemoveQuotes); + } + } + + isFinished = true; + } + + public override string ToDebugString() + { + return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(ForceSayAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " + + $"Message: {Message})"; + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionStateAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionStateAction.cs index a6dd612be..92ddc1ee3 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionStateAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionStateAction.cs @@ -1,84 +1,77 @@ -namespace Barotrauma +#nullable enable +namespace Barotrauma; + +/// Changes the state of missions. The way the states are used depends on the type of mission. +internal sealed class MissionStateAction : EventAction { - - /// - /// Changes the state of a specific active mission. The way the states are used depends on the type of mission. - /// - class MissionStateAction : EventAction + /// The operation to perform on missions' states. + public enum OperationType { - [Serialize("", IsPropertySaveable.Yes, description: "Identifier of the mission whose state to change.")] - public Identifier MissionIdentifier { get; set; } + /// Sets the missions' states to . + Set, + /// Adds to the missions' states. + Add + } - public enum OperationType + [Serialize("", IsPropertySaveable.Yes, "Identifiers of the missions whose states to change. Leave blank to only set the state of the mission that triggered the parent event.")] + public Identifier MissionIdentifier { get; set; } + + [Serialize(OperationType.Set, IsPropertySaveable.Yes, "The operation to perform on missions' states.")] + public OperationType Operation { get; set; } + + [Serialize(0, IsPropertySaveable.Yes, "The value to apply to missions' states.")] + public int State { get; set; } + + [Serialize(false, IsPropertySaveable.Yes, "If set to true, missions are forced to fail without a chance of retrying them.")] + public bool ForceFailure { get; set; } + + public MissionStateAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) + { + State = element.GetAttributeInt("value", State); + if (Operation == OperationType.Add && State == 0 && !ForceFailure) { - Set, - Add + DebugConsole.AddWarning($"Potential error in event \"{parentEvent.Prefab.Identifier}\": {nameof(MissionStateAction)} is set to only add 0 to the mission state, which will do nothing.", + contentPackage: element.ContentPackage); } + } - [Serialize(OperationType.Set, IsPropertySaveable.Yes, description: "Should the value be added to the state of the mission, or should the state be set to the specified value.")] - public OperationType Operation { get; set; } + private bool isFinished; + public override bool IsFinished(ref string goTo) => isFinished; + public override void Reset() => isFinished = false; - [Serialize(0, IsPropertySaveable.Yes, description: "The state to set the mission to, or how much to add to the state of the mission.")] - public int State { get; set; } + public override void Update(float deltaTime) + { + if (isFinished) { return; } - [Serialize(false, IsPropertySaveable.Yes, description: "If set to true, the mission is forced to fail without a chance of retrying it.")] - public bool ForceFailure { get; set; } - - private bool isFinished; - - public MissionStateAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) + if (!MissionIdentifier.IsEmpty) { - State = element.GetAttributeInt("value", State); - if (MissionIdentifier.IsEmpty) - { - DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": MissionIdentifier has not been configured.", - contentPackage: element.ContentPackage); - } - if (Operation == OperationType.Add && State == 0 && !ForceFailure) - { - DebugConsole.AddWarning($"Potential error in event \"{parentEvent.Prefab.Identifier}\": {nameof(MissionStateAction)} is set to add 0 to the mission state, which will do nothing.", - contentPackage: element.ContentPackage); - } - } - - public override bool IsFinished(ref string goTo) - { - return isFinished; - } - public override void Reset() - { - isFinished = false; - } - - public override void Update(float deltaTime) - { - if (isFinished) { return; } - foreach (Mission mission in GameMain.GameSession.Missions) { if (mission.Prefab.Identifier != MissionIdentifier) { continue; } - if (ForceFailure) - { - mission.ForceFailure = true; - } - - switch (Operation) - { - case OperationType.Set: - mission.State = State; - break; - case OperationType.Add: - mission.State += State; - break; - } + SetMissionState(mission); } - - isFinished = true; + } + else if (ParentEvent.TriggeringMission != null) + { + SetMissionState(ParentEvent.TriggeringMission); } - public override string ToDebugString() + isFinished = true; + } + + private void SetMissionState(Mission mission) + { + if (ForceFailure) { mission.ForceFailure = true; } + switch (Operation) { - return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(MissionStateAction)} -> ({(Operation == OperationType.Set ? State : '+' + State)})"; + case OperationType.Set: + mission.State = State; + break; + case OperationType.Add: + mission.State += State; + break; } } + + public override string ToDebugString() => $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(MissionStateAction)} -> ({(Operation == OperationType.Set ? State : '+' + State)})"; } \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCFollowAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCFollowAction.cs index 968e8c988..ed8fa1590 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCFollowAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCFollowAction.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; @@ -18,6 +18,9 @@ namespace Barotrauma [Serialize(true, IsPropertySaveable.Yes, description: "Should the NPC start or stop following the target?")] public bool Follow { get; set; } + [Serialize(false, IsPropertySaveable.Yes, description: "Should the NPC be forced to walk towards the target?")] + public bool ForceWalk { get; set; } + [Serialize(-1, IsPropertySaveable.Yes, description: "Maximum number of NPCs to target (e.g. you could choose to only make a specific number of security officers follow the player.)")] public int MaxTargets { get; set; } @@ -65,7 +68,8 @@ namespace Barotrauma var newObjective = new AIObjectiveGoTo(target, npc, humanAiController.ObjectiveManager, repeat: true) { OverridePriority = Priority, - IsFollowOrder = true + IsFollowOrder = true, + ForceWalkPermanently = ForceWalk }; humanAiController.ObjectiveManager.AddObjective(newObjective); humanAiController.ObjectiveManager.WaitTimer = 0.0f; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs index 3b28cd186..6accd385e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/SpawnAction.cs @@ -271,6 +271,10 @@ namespace Barotrauma ParentEvent.AddTarget(TargetTag, newCharacter); } spawnedEntity = newCharacter; + if (newCharacter is { AIController: EnemyAIController enemyAi, Submarine: Submarine ownSub }) + { + enemyAi.SetUnattackableSubmarines(ownSub); + } }); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs index 01009f1db..195ff9fe6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs @@ -240,42 +240,45 @@ namespace Barotrauma CreateEvents(eventSet); } - if (level?.LevelData != null) + bool isOutpostLevel = level?.LevelData is { Type: LevelData.LevelType.Outpost } || + (GameMain.GameSession?.GameMode is TestGameMode && Submarine.MainSub?.Info?.Type == SubmarineType.Outpost); + if (isOutpostLevel) { - if (level.LevelData.Type == LevelData.LevelType.Outpost) + //if the outpost is connected to a locked connection, create an event to unlock it + if (level?.StartLocation?.Connections.Any(c => c.Locked && level.StartLocation.MapPosition.X < c.OtherLocation(level.StartLocation).MapPosition.X) ?? false) { - //if the outpost is connected to a locked connection, create an event to unlock it - if (level.StartLocation?.Connections.Any(c => c.Locked && level.StartLocation.MapPosition.X < c.OtherLocation(level.StartLocation).MapPosition.X) ?? false) + var unlockPathEventPrefab = EventPrefab.GetUnlockPathEvent(level.LevelData.Biome.Identifier, level.StartLocation.Faction); + if (unlockPathEventPrefab != null) { - var unlockPathEventPrefab = EventPrefab.GetUnlockPathEvent(level.LevelData.Biome.Identifier, level.StartLocation.Faction); - if (unlockPathEventPrefab != null) + var newEvent = unlockPathEventPrefab.CreateInstance(RandomSeed); + activeEvents.Add(newEvent); + } + else + { + //if no event that unlocks the path can be found, unlock it automatically + level.StartLocation.Connections.ForEach(c => c.Locked = false); + } + } + Submarine outpost = level?.StartOutpost ?? Submarine.MainSub; + if (GameMain.NetworkMember is not { IsClient: true } && outpost != null) + { + foreach (var eventTag in outpost.Info.TriggerOutpostMissionEvents) + { + EventPrefab eventPrefab = EventPrefab.FindEventPrefab(identifier: Identifier.Empty, tag: eventTag, outpost.ContentPackage); + if (eventPrefab == null) { - var newEvent = unlockPathEventPrefab.CreateInstance(RandomSeed); - activeEvents.Add(newEvent); + DebugConsole.ThrowError($"Outpost {outpost.Info.DisplayName} failed to trigger an event (tag: {eventTag}).", contentPackage: outpost.ContentPackage); } else { - //if no event that unlocks the path can be found, unlock it automatically - level.StartLocation.Connections.ForEach(c => c.Locked = false); + var newEvent = eventPrefab.CreateInstance(RandomSeed); + ActivateEvent(newEvent); } } - if (GameMain.NetworkMember is not { IsClient: true } && level.StartOutpost != null) - { - foreach (var eventTag in level.StartOutpost.Info.TriggerOutpostMissionEvents) - { - EventPrefab eventPrefab = EventPrefab.FindEventPrefab(identifier: Identifier.Empty, tag: eventTag, level.StartOutpost.ContentPackage); - if (eventPrefab == null) - { - DebugConsole.ThrowError($"Outpost {level.StartOutpost.Info.DisplayName} failed to trigger an event (tag: {eventTag}).", contentPackage: level.StartOutpost.ContentPackage); - } - else - { - var newEvent = eventPrefab.CreateInstance(RandomSeed); - ActivateEvent(newEvent); - } - } - } - } + } + } + if (level?.LevelData != null) + { RegisterNonRepeatableChildEvents(initialEventSet); void RegisterNonRepeatableChildEvents(EventSet eventSet) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs index 09703cf05..16d635b69 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/AbandonedOutpostMission.cs @@ -233,7 +233,7 @@ namespace Barotrauma } - protected override bool DetermineCompleted() + protected override bool DetermineCompleted(CampaignMode.TransitionType transitionType) { return State > 0 && State != HostagesKilledState; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs index f9b102a87..70fc86b00 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs @@ -171,7 +171,7 @@ namespace Barotrauma #endif } - protected override bool DetermineCompleted() + protected override bool DetermineCompleted(CampaignMode.TransitionType transitionType) { return level.CheckBeaconActive(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs index f62efa2ef..4768696f5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CargoMission.cs @@ -331,7 +331,7 @@ namespace Barotrauma } } - protected override bool DetermineCompleted() + protected override bool DetermineCompleted(CampaignMode.TransitionType transitionType) { if (Submarine.MainSub != null && Submarine.MainSub.AtEndExit) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs index 64bf201c7..6b45f0ef9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CombatMission.cs @@ -204,7 +204,7 @@ namespace Barotrauma } } - protected override bool DetermineCompleted() + protected override bool DetermineCompleted(CampaignMode.TransitionType transitionType) { return Winner != CharacterTeamType.None; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CustomMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CustomMission.cs new file mode 100644 index 000000000..d18349dbd --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/CustomMission.cs @@ -0,0 +1,18 @@ +#nullable enable +namespace Barotrauma; + +/// +/// Defines a mission where the success and failure are determined solely by its state. +/// Intended to be used alongside . +/// +internal sealed partial class CustomMission(MissionPrefab prefab, Location[] locations, Submarine sub) : Mission(prefab, locations, sub) +{ + public readonly int SuccessState = prefab.ConfigElement.GetAttributeInt(nameof(SuccessState), +1); + public readonly int FailureState = prefab.ConfigElement.GetAttributeInt(nameof(FailureState), -1); + + public bool RequireDestinationReached = prefab.ConfigElement.GetAttributeBool(nameof(RequireDestinationReached), false); + + protected override bool DetermineCompleted(CampaignMode.TransitionType transitionType) => + State == SuccessState && + (!RequireDestinationReached || transitionType is CampaignMode.TransitionType.ProgressToNextLocation or CampaignMode.TransitionType.ProgressToNextEmptyLocation); +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EliminateTargetsMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EliminateTargetsMission.cs index cc7855701..1bfeaea1f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EliminateTargetsMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EliminateTargetsMission.cs @@ -1,4 +1,4 @@ -using System; +using System; using Barotrauma.Extensions; using Barotrauma.RuinGeneration; using Microsoft.Xna.Framework; @@ -199,7 +199,7 @@ namespace Barotrauma private static bool IsEnemyDefeated(Character enemy) => enemy == null ||enemy.Removed || enemy.IsDead; - protected override bool DetermineCompleted() + protected override bool DetermineCompleted(CampaignMode.TransitionType transitionType) { bool exitingLevel = GameMain.GameSession?.GameMode is CampaignMode campaign ? campaign.GetAvailableTransition() != CampaignMode.TransitionType.None : diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EndMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EndMission.cs index 1ffc09b93..e68af0349 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EndMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EndMission.cs @@ -1,4 +1,4 @@ -using Barotrauma.Extensions; +using Barotrauma.Extensions; using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System; @@ -301,7 +301,7 @@ namespace Barotrauma partial void OnStateChangedProjSpecific(); - protected override bool DetermineCompleted() + protected override bool DetermineCompleted(CampaignMode.TransitionType transitionType) { return Phase == MissionPhase.BossKilled; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs index 473006b60..3a770fffa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/EscortMission.cs @@ -343,7 +343,7 @@ namespace Barotrauma return character != null && !character.Removed && !character.IsDead; } - protected override bool DetermineCompleted() + protected override bool DetermineCompleted(CampaignMode.TransitionType transitionType) { if (Submarine.MainSub != null && Submarine.MainSub.AtEndExit) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/GoToMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/GoToMission.cs index a1924db58..c35e7b789 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/GoToMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/GoToMission.cs @@ -17,7 +17,7 @@ namespace Barotrauma } } - protected override bool DetermineCompleted() + protected override bool DetermineCompleted(CampaignMode.TransitionType transitionType) { if (Level.Loaded?.Type == LevelData.LevelType.Outpost) { @@ -25,7 +25,7 @@ namespace Barotrauma } else { - return Submarine.MainSub is { AtEndExit: true }; + return transitionType == CampaignMode.TransitionType.ProgressToNextLocation; } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs index 10d174170..d75e61fa6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MineralMission.cs @@ -1,4 +1,4 @@ -using Barotrauma.Extensions; +using Barotrauma.Extensions; using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System; @@ -47,6 +47,12 @@ namespace Barotrauma } } + /// + /// Minerals spawned by the mission. Note that minerals that were already present in the level may have also been used as targets. + /// Each list of items represents a separate cluster of minerals. + /// + public IEnumerable> SpawnedResources => spawnedResources.Values; + public override LocalizedString SuccessMessage => ModifyMessage(base.SuccessMessage); public override LocalizedString FailureMessage => ModifyMessage(base.FailureMessage); public override LocalizedString Description => ModifyMessage(description); @@ -169,7 +175,7 @@ namespace Barotrauma } } - protected override bool DetermineCompleted() + protected override bool DetermineCompleted(CampaignMode.TransitionType transitionType) { return EnoughHaveBeenCollected(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs index e6629022b..6dcfd314f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs @@ -401,14 +401,9 @@ namespace Barotrauma { characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true)); } - if (submarine != null && spawnedCharacter.AIController is EnemyAIController enemyAi) + if (spawnedCharacter.AIController is EnemyAIController enemyAi && submarine != null) { - enemyAi.UnattackableSubmarines.Add(submarine); - enemyAi.UnattackableSubmarines.Add(Submarine.MainSub); - foreach (Submarine sub in Submarine.MainSub.DockedTo) - { - enemyAi.UnattackableSubmarines.Add(sub); - } + enemyAi.SetUnattackableSubmarines(submarine); } InitCharacter(spawnedCharacter, element); return spawnedCharacter; @@ -532,6 +527,7 @@ namespace Barotrauma if (GameMain.GameSession?.EventManager != null) { var newEvent = eventPrefab.CreateInstance(GameMain.GameSession.EventManager.RandomSeed); + newEvent.TriggeringMission = this; GameMain.GameSession.EventManager.ActivateEvent(newEvent); } } @@ -539,13 +535,13 @@ namespace Barotrauma /// /// End the mission and give a reward if it was completed successfully /// - public void End() + public void End(CampaignMode.TransitionType transitionType) { if (GameMain.NetworkMember is not { IsClient: true }) { completed = !ForceFailure && - DetermineCompleted() && + DetermineCompleted(transitionType) && (completeCheckDataAction == null || completeCheckDataAction.GetSuccess()); } if (completed) @@ -578,7 +574,7 @@ namespace Barotrauma } } - protected abstract bool DetermineCompleted(); + protected abstract bool DetermineCompleted(CampaignMode.TransitionType transitionType); protected virtual void EndMissionSpecific(bool completed) { } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs index cd32310eb..28c5e4a26 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MissionPrefab.cs @@ -30,7 +30,8 @@ namespace Barotrauma { "GoTo".ToIdentifier(), typeof(GoToMission) }, { "ScanAlienRuins".ToIdentifier(), typeof(ScanMission) }, { "EliminateTargets".ToIdentifier(), typeof(EliminateTargetsMission) }, - { "End".ToIdentifier(), typeof(EndMission) } + { "End".ToIdentifier(), typeof(EndMission) }, + { "Custom".ToIdentifier(), typeof(CustomMission) } }; /// @@ -64,6 +65,7 @@ namespace Barotrauma public Type MissionClass { get; private set; } + public bool CampaignOnly { get; private set; } public bool MultiplayerOnly { get; private set; } public bool SingleplayerOnly { get; private set; } @@ -319,8 +321,9 @@ namespace Barotrauma SonarIconIdentifier = ConfigElement.GetAttributeIdentifier("sonaricon", ""); - MultiplayerOnly = ConfigElement.GetAttributeBool("multiplayeronly", false); - SingleplayerOnly = ConfigElement.GetAttributeBool("singleplayeronly", false); + CampaignOnly = ConfigElement.GetAttributeBool(nameof(CampaignOnly), false); + MultiplayerOnly = ConfigElement.GetAttributeBool(nameof(MultiplayerOnly), false); + SingleplayerOnly = ConfigElement.GetAttributeBool(nameof(SingleplayerOnly), false); AchievementIdentifier = ConfigElement.GetAttributeIdentifier("achievementidentifier", ""); @@ -543,7 +546,7 @@ namespace Barotrauma } /// - /// Returns all mission types that can be selected e.g. in the server lobby, excluding any special, hidden ones like EndMission + /// Returns all mission types that can be selected in the server lobby, excluding any special, hidden ones like EndMission /// (the mission at the end of the campaign) /// public static IEnumerable GetAllMultiplayerSelectableMissionTypes() @@ -552,6 +555,7 @@ namespace Barotrauma foreach (var missionPrefab in Prefabs) { if (missionPrefab.Commonness <= 0.0f) { continue; } + if (missionPrefab.CampaignOnly) { continue; } if (missionPrefab.SingleplayerOnly) { continue; } if (HiddenMissionTypes.Contains(missionPrefab.Type)) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs index 941b7c4dd..656764b14 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/MonsterMission.cs @@ -242,7 +242,7 @@ namespace Barotrauma } } - protected override bool DetermineCompleted() + protected override bool DetermineCompleted(CampaignMode.TransitionType transitionType) { return state > 0; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs index 3ad3effe1..41dbb356a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs @@ -337,7 +337,7 @@ namespace Barotrauma return true; } - protected override bool DetermineCompleted() + protected override bool DetermineCompleted(CampaignMode.TransitionType transitionType) { return AllItemsDestroyedOrRetrieved(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs index 6331311af..6d7f895ff 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs @@ -547,7 +547,7 @@ namespace Barotrauma return character == null || character.Removed || character.Submarine == null || (character.LockHands && character.Submarine == Submarine.MainSub) || character.IsIncapacitated; } - protected override bool DetermineCompleted() + protected override bool DetermineCompleted(CampaignMode.TransitionType transitionType) { return state == 2; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs index 602e4dd0d..e0a7aa4af 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs @@ -715,7 +715,7 @@ namespace Barotrauma } } - protected override bool DetermineCompleted() + protected override bool DetermineCompleted(CampaignMode.TransitionType transitionType) { if (requiredDeliveryAmount < 1.0f) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs index e388b2908..a16c556fd 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/ScanMission.cs @@ -1,4 +1,4 @@ -using System; +using System; using Barotrauma.Extensions; using Barotrauma.Items.Components; using Barotrauma.RuinGeneration; @@ -17,7 +17,7 @@ namespace Barotrauma private readonly Dictionary parentInventoryIDs = new Dictionary(); private readonly Dictionary inventorySlotIndices = new Dictionary(); private readonly Dictionary parentItemContainerIndices = new Dictionary(); - private readonly int targetsToScan; + private readonly int totalTargetsToScan; private readonly Dictionary scanTargets = new Dictionary(); private readonly HashSet newTargetsScanned = new HashSet(); private readonly float minTargetDistance; @@ -44,7 +44,7 @@ namespace Barotrauma public ScanMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub) { itemConfig = prefab.ConfigElement.GetChildElement("Items"); - targetsToScan = prefab.ConfigElement.GetAttributeInt("targets", 1); + totalTargetsToScan = prefab.ConfigElement.GetAttributeInt("targets", 1); minTargetDistance = prefab.ConfigElement.GetAttributeFloat("mintargetdistance", 0.0f); } @@ -77,57 +77,60 @@ namespace Barotrauma var ruinWaypoints = TargetRuin.Submarine.GetWaypoints(false); ruinWaypoints.RemoveAll(wp => wp.CurrentHull == null); - if (ruinWaypoints.Count < targetsToScan) + if (ruinWaypoints.Count < totalTargetsToScan) { - DebugConsole.ThrowError($"Failed to initialize a Scan mission: target ruin has less waypoints than required as scan targets ({ruinWaypoints.Count} < {targetsToScan})", + DebugConsole.ThrowError($"Failed to initialize a Scan mission: target ruin has less waypoints than required as scan targets ({ruinWaypoints.Count} < {totalTargetsToScan})", contentPackage: Prefab.ContentPackage); return; } + + //the distance we'll use if we otherwise fail to place the targets far enough from each other + //(smallest extent should be large enough to fit the targets and one extra to be safe) + float guaranteedDistance = Math.Min(TargetRuin.Area.Width, TargetRuin.Area.Height) / (totalTargetsToScan + 1); + var availableWaypoints = new List(); - float minTargetDistanceSquared = minTargetDistance * minTargetDistance; - for (int tries = 0; tries < 15; tries++) + const int MaxTries = 15; + for (int tries = 0; tries < MaxTries; tries++) { + float triesNormalized = tries / (float)(MaxTries - 1); // 0.0 -> 1.0 + float desperationFactor = MathF.Pow(triesNormalized, 2); + //try placing the targets the desired minimum distance apart, gradually lowering the distance requirement on each try + float currentMinDistance = MathHelper.Lerp(minTargetDistance, guaranteedDistance, desperationFactor); + float currentMinDistanceSquared = currentMinDistance * currentMinDistance; + scanTargets.Clear(); availableWaypoints.Clear(); availableWaypoints.AddRange(ruinWaypoints); - for (int i = 0; i < targetsToScan; i++) + for (int i = 0; i < totalTargetsToScan; i++) { var selectedWaypoint = availableWaypoints.GetRandom(randSync: Rand.RandSync.ServerAndClient); scanTargets.Add(selectedWaypoint, false); availableWaypoints.Remove(selectedWaypoint); - if (i < (targetsToScan - 1)) + if (i < (totalTargetsToScan - 1)) { availableWaypoints.RemoveAll(wp => wp.CurrentHull == selectedWaypoint.CurrentHull); - availableWaypoints.RemoveAll(wp => Vector2.DistanceSquared(wp.WorldPosition, selectedWaypoint.WorldPosition) < minTargetDistanceSquared); + availableWaypoints.RemoveAll(wp => Vector2.DistanceSquared(wp.WorldPosition, selectedWaypoint.WorldPosition) < currentMinDistanceSquared); if (availableWaypoints.None()) { #if DEBUG - DebugConsole.ThrowError($"Error initializing a Scan mission: not enough targets available on try #{tries + 1} to reach the required scan target count (current targets: {scanTargets.Count}, required targets: {targetsToScan})", + DebugConsole.ThrowError($"Error initializing a Scan mission: not enough targets available on try #{tries + 1} to reach the required scan target count (current targets: {scanTargets.Count}, required targets: {totalTargetsToScan})", contentPackage: Prefab.ContentPackage); #endif break; } } } - if (scanTargets.Count >= targetsToScan) + if (scanTargets.Count >= totalTargetsToScan) { #if DEBUG DebugConsole.NewMessage($"Successfully initialized a Scan mission: targets set on try #{tries + 1}", Color.Green); #endif break; } - if ((tries + 1) % 5 == 0) - { - float reducedMinTargetDistance = (1.0f - (((tries + 1) / 5) * 0.1f)) * minTargetDistance; - minTargetDistanceSquared = reducedMinTargetDistance * reducedMinTargetDistance; -#if DEBUG - DebugConsole.NewMessage($"Reducing minimum distance between Scan mission targets (new min: {reducedMinTargetDistance}) to reach the required target count", Color.Yellow); -#endif - } } - if (scanTargets.Count < targetsToScan) + if (scanTargets.Count < totalTargetsToScan) { - DebugConsole.ThrowError($"Error initializing a Scan mission: not enough targets (current targets: {scanTargets.Count}, required targets: {targetsToScan})", + DebugConsole.ThrowError($"Error initializing a Scan mission: not enough targets (current targets: {scanTargets.Count}, required targets: {totalTargetsToScan})", contentPackage: Prefab.ContentPackage); } } @@ -241,9 +244,9 @@ namespace Barotrauma State = Math.Max(State, scanTargets.Count(kvp => kvp.Value)); } - private bool AllTargetsScanned() => State >= targetsToScan; + private bool AllTargetsScanned() => State >= totalTargetsToScan; - protected override bool DetermineCompleted() => AllTargetsScanned(); + protected override bool DetermineCompleted(CampaignMode.TransitionType transitionType) => AllTargetsScanned(); protected override void EndMissionSpecific(bool completed) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameAnalytics/GameAnalyticsConsent.cs b/Barotrauma/BarotraumaShared/SharedSource/GameAnalytics/GameAnalyticsConsent.cs index 129fa3d25..6edea3673 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameAnalytics/GameAnalyticsConsent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameAnalytics/GameAnalyticsConsent.cs @@ -104,7 +104,7 @@ namespace Barotrauma return authTicket.TryUnwrap(out var ticketUnwrapped) && ticketUnwrapped.Data is { Length: > 0 } ? new AuthTicket(ToolBoxCore.ByteArrayToHexString(ticketUnwrapped.Data), Platform.Steam) //convert byte array to hex - : throw new Exception("Could not retrieve Steamworks authentication ticket for GameAnalytics"); + : throw new Exception("Could not retrieve Steam authentication ticket, possibly due to connection issues. GameAnalytics logging will be disabled."); } private static async Task GetEOSAuthTicket() @@ -215,9 +215,8 @@ namespace Barotrauma IRestResponse response; try { - var client = new RestClient(consentServerUrl); - - var request = new RestRequest(consentServerFile, Method.GET); + var client = RestFactory.CreateClient(consentServerUrl); + var request = RestFactory.CreateRequest(consentServerFile); request.AddParameter("authticket", authTicket.Token); if (consent == Consent.Ask) { @@ -321,7 +320,7 @@ namespace Barotrauma RestClient client; try { - client = new RestClient(consentServerUrl); + client = RestFactory.CreateClient(consentServerUrl); } catch (Exception e) { @@ -329,7 +328,7 @@ namespace Barotrauma return Consent.Error; } - var request = new RestRequest(consentServerFile, Method.GET); + var request = RestFactory.CreateRequest(consentServerFile); request.AddParameter("authticket", authTicket.Token); request.AddParameter("action", "getconsent"); request.AddParameter("request_version", RemoteRequestVersion); diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs index daa20659a..ae18a5a21 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs @@ -1017,7 +1017,7 @@ namespace Barotrauma UpdateStoreStock(); } - GameMain.GameSession.EndMissions(); + GameMain.GameSession.EndMissions(TransitionType.None); GameMain.GameSession.EventManager?.StoreEventDataAtRoundEnd(registerFinishedOnly: true); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MissionMode.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MissionMode.cs index e00bf540b..d19250e5e 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MissionMode.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/MissionMode.cs @@ -53,6 +53,7 @@ namespace Barotrauma { foreach (MissionPrefab missionPrefab in missionPrefabs) { + if (missionPrefab.CampaignOnly) { continue; } if (!missionClasses.ContainsValue(missionPrefab.MissionClass)) { throw new InvalidOperationException($"Cannot start gamemode with a {missionPrefab.MissionClass} mission."); @@ -68,7 +69,7 @@ namespace Barotrauma { return missionTypes.Where(type => MissionPrefab.Prefabs.OrderBy(missionPrefab => missionPrefab.UintIdentifier) - .Any(missionPrefab => missionPrefab.Type == type && missionClasses.ContainsValue(missionPrefab.MissionClass))); + .Any(missionPrefab => missionPrefab.Type == type && !missionPrefab.CampaignOnly && missionClasses.ContainsValue(missionPrefab.MissionClass))); } } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs index 79e38a2f7..5174ef002 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs @@ -1,16 +1,17 @@ #nullable enable +using Barotrauma.Extensions; using Barotrauma.IO; using Barotrauma.Items.Components; +using Barotrauma.Networking; +using Barotrauma.PerkBehaviors; +using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Xml.Linq; -using Barotrauma.Networking; -using Barotrauma.Extensions; -using Barotrauma.PerkBehaviors; namespace Barotrauma { @@ -952,14 +953,6 @@ namespace Barotrauma sub.SetPosition(spawnPos); myPort.Dock(outPostPort); myPort.Lock(isNetworkMessage: true, applyEffects: false); - foreach (var item in sub.GetItems(alsoFromConnectedSubs: true)) - { - //need to refresh position to maintain since the sub was moved to the docking port - if (item.GetComponent() is { MaintainPos: true } steering) - { - steering.RefreshPosToMaintain(); - } - } } else { @@ -982,6 +975,16 @@ namespace Barotrauma sub.EnableMaintainPosition(); } + foreach (var item in sub.GetItems(alsoFromConnectedSubs: true)) + { + // Refresh pos to maintain in all steering components maintaining + // position, including ones in shuttles, since the submarines moved + if (item.GetComponent() is { MaintainPos: true } steering) + { + steering.RefreshPosToMaintain(); + } + } + // Make sure that linked subs which are NOT docked to the main sub // (but still close enough to NOT be considered as 'left behind') // are also moved to keep their relative position to the main sub @@ -1094,7 +1097,7 @@ namespace Barotrauma ImmutableHashSet crewCharacters = GetSessionCrewCharacters(CharacterType.Both); int prevMoney = GetAmountOfMoney(crewCharacters); - EndMissions(); + EndMissions(transitionType); foreach (Character character in crewCharacters) { @@ -1197,12 +1200,12 @@ namespace Barotrauma } } - public void EndMissions() + public void EndMissions(CampaignMode.TransitionType transitionType) { ImmutableHashSet crewCharacters = GetSessionCrewCharacters(CharacterType.Both); foreach (Mission mission in missions) { - mission.End(); + mission.End(transitionType); } if (missions.Any()) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs index 98b9bd60a..146f6b603 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/CharacterInventory.cs @@ -43,8 +43,12 @@ namespace Barotrauma public InvSlotType[] SlotTypes { get; - private set; } + + /// + /// Optimization for fast access of by . + /// + private readonly Dictionary> slotsByType = []; public static readonly List AnySlot = new List { InvSlotType.Any }; public static readonly List BagSlot = new List { InvSlotType.Bag }; @@ -106,9 +110,20 @@ namespace Barotrauma case InvSlotType.RightHand: slots[i].HideIfEmpty = true; break; - } + } } - + + for (int i = 0; i < capacity; i++) + { + InvSlotType slotType = SlotTypes[i]; + if (!slotsByType.TryGetValue(slotType, out List slotList)) + { + slotList = []; + slotsByType[SlotTypes[i]] = slotList; + } + slotList.Add(slots[i]); + } + InitProjSpecific(element); var itemElements = element.Elements().Where(e => e.Name.ToString().Equals("item", StringComparison.OrdinalIgnoreCase)); @@ -198,39 +213,55 @@ namespace Barotrauma public Item GetItemInLimbSlot(InvSlotType limbSlot) { - for (int i = 0; i < slots.Length; i++) + if (slotsByType.TryGetValue(limbSlot, out List slotList)) { - if (SlotTypes[i] == limbSlot) { return slots[i].FirstOrDefault(); } + return slotList.First().FirstOrDefault(); } return null; } + public IEnumerable GetItemsInLimbSlot(InvSlotType limbSlot) + { + if (slotsByType.TryGetValue(limbSlot, out List slotList)) + { + foreach (var slot in slotList) + { + foreach (Item item in slot.Items) + { + yield return item; + } + } + } + } public bool IsInLimbSlot(Item item, InvSlotType limbSlot) { if (limbSlot == (InvSlotType.LeftHand | InvSlotType.RightHand)) { - int rightHandSlot = FindLimbSlot(InvSlotType.RightHand); - int leftHandSlot = FindLimbSlot(InvSlotType.LeftHand); - if (rightHandSlot > -1 && slots[rightHandSlot].Contains(item) && - leftHandSlot > -1 && slots[leftHandSlot].Contains(item)) + if (GetItemsInLimbSlot(InvSlotType.RightHand).Contains(item) && + GetItemsInLimbSlot(InvSlotType.LeftHand).Contains(item)) { return true; } } - - for (int i = 0; i < slots.Length; i++) + else if (slotsByType.TryGetValue(limbSlot, out List slotList)) { - if (SlotTypes[i] == limbSlot && slots[i].Contains(item)) { return true; } + foreach (ItemSlot slot in slotList) + { + if (slot.Contains(item)) { return true; } + } } return false; } public bool IsSlotEmpty(InvSlotType limbSlot) { - for (int i = 0; i < slots.Length; i++) + if (slotsByType.TryGetValue(limbSlot, out List slotList)) { - if (SlotTypes[i] == limbSlot && slots[i].Empty()) { return true; } + foreach (ItemSlot slot in slotList) + { + if (slot.Empty()) { return true; } + } } return false; } @@ -370,7 +401,7 @@ namespace Barotrauma /// /// If there is room, puts the item in the inventory and returns true, otherwise returns false /// - public override bool TryPutItem(Item item, Character user, IEnumerable allowedSlots = null, bool createNetworkEvent = true, bool ignoreCondition = false) + public override bool TryPutItem(Item item, Character user, IEnumerable allowedSlots = null, bool createNetworkEvent = true, bool ignoreCondition = false, bool triggerOnInsertedEffects = true) { if (allowedSlots == null || !allowedSlots.Any()) { return false; } if (item == null) @@ -494,8 +525,6 @@ namespace Barotrauma return placedInSlot > -1; } - - public bool IsAnySlotAvailable(Item item) => GetFreeAnySlot(item, inWrongSlot: false) > -1; private int GetFreeAnySlot(Item item, bool inWrongSlot) @@ -542,7 +571,7 @@ namespace Barotrauma return -1; } - public override bool TryPutItem(Item item, int index, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true, bool ignoreCondition = false) + public override bool TryPutItem(Item item, int index, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true, bool ignoreCondition = false, bool triggerOnInsertedEffects = true) { if (index < 0 || index >= slots.Length) { @@ -590,9 +619,9 @@ namespace Barotrauma return TryPutItem(item, user, new List() { placeToSlots }, createNetworkEvent, ignoreCondition); } - protected override void PutItem(Item item, int i, Character user, bool removeItem = true, bool createNetworkEvent = true) + protected override void PutItem(Item item, int i, Character user, bool removeItem = true, bool createNetworkEvent = true, bool triggerOnInsertedEffects = true) { - base.PutItem(item, i, user, removeItem, createNetworkEvent); + base.PutItem(item, i, user, removeItem, createNetworkEvent, triggerOnInsertedEffects); #if CLIENT CreateSlots(); if (character == Character.Controlled) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs index dacf4103a..75155b00c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/Holdable.cs @@ -739,6 +739,12 @@ namespace Barotrauma.Items.Components { picker.Inventory.FlashAllowedSlots(item, Color.Red); } + else + { + //normally this would be done in the base.OnPicked method, but clients don't call it, + //but instead rely on the server telling them to put the item in the inventory + SoundPlayer.PlayUISound(GUISoundType.PickItem); + } return false; } #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs index d27ca9cd6..a94b6f7ef 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/MeleeWeapon.cs @@ -347,18 +347,9 @@ namespace Barotrauma.Items.Components } else if (f2.Body.UserData is Character targetCharacter) { - if (targetCharacter == picker || targetCharacter == User) { return false; } - if (targetCharacter.IgnoreMeleeWeapons) { return false; } - if (HitFriendlyTarget(targetCharacter)) { return false; } - if (AllowHitMultiple) - { - if (hitTargets.Contains(targetCharacter)) { return false; } - } - else - { - if (hitTargets.Any(t => t is Character)) { return false; } - } - hitTargets.Add(targetCharacter); + //only allow hitting limbs, not the main collider + //otherwise it's difficult to make certain parts of the ragdoll not take hits by making them ignore collisions or melee weapons + return false; } else if (!HitOnlyCharacters) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs index 58d4ddc3c..75976e215 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemComponent.cs @@ -378,7 +378,7 @@ namespace Barotrauma.Items.Components break; case "requireditem": case "requireditems": - SetRequiredItems(subElement); + SetRequiredItems(subElement, allowEmpty: true); break; case "requiredskill": case "requiredskills": @@ -1100,6 +1100,9 @@ namespace Barotrauma.Items.Components foreach (RelatedItem ri in DisabledRequiredItems) { XElement newElement = new XElement("requireditem"); + //if we have some actual requirements, no need to keep the empty requirement + //as a "placeholder" for the user to add requirements in the sub editor + if (ri.Identifiers.IsEmpty && RequiredItems.Any()) { continue; } ri.Save(newElement); componentElement.Add(newElement); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs index ae7016391..561fa6716 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 (offset from the upper left corner of the sprite in pixels).")] + [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.")] 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).")] @@ -425,7 +425,7 @@ namespace Barotrauma.Items.Components partial void InitProjSpecific(ContentXElement element); - public void OnItemContained(Item containedItem) + public void OnItemContained(Item containedItem, bool triggerOnInsertedEffects = true) { int index = Inventory.FindIndex(containedItem); RelatedItem relatedItem = null; @@ -444,14 +444,20 @@ namespace Barotrauma.Items.Components ActiveContainedItem activeContainedItem = new(containedItem, effect, containableItem.ExcludeBroken, containableItem.ExcludeFullCondition, containableItem.BlameEquipperForDeath); activeContainedItems.Add(activeContainedItem); - if (!ShouldApplyEffects(activeContainedItem) || item.Submarine is { Loading: true} || initializingLoadedItems || - containedItem.OnInsertedEffectsApplied) - { - continue; + if (triggerOnInsertedEffects) + { + if (!ShouldApplyEffects(activeContainedItem) || item.Submarine is { Loading: true} || initializingLoadedItems || + containedItem.OnInsertedEffectsApplied) + { + continue; + } + activeContainedItem.StatusEffect.Apply(ActionType.OnInserted, deltaTime: 1, item, targets); } - activeContainedItem.StatusEffect.Apply(ActionType.OnInserted, deltaTime: 1, item, targets); } - containedItem.OnInsertedEffectsApplied = true; + if (triggerOnInsertedEffects) + { + containedItem.OnInsertedEffectsApplied = true; + } } } } @@ -1087,21 +1093,25 @@ 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; - transformedItemPos.X += item.Rect.Width; + if (!useCenterAsOrigin) { transformedItemPos.X += item.Rect.Width; } transformedItemInterval.X = -transformedItemInterval.X; transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X; } if (flippedY) { transformedItemPos.Y = -transformedItemPos.Y; - transformedItemPos.Y -= item.Rect.Height; + if (!useCenterAsOrigin) { transformedItemPos.Y -= item.Rect.Height; } transformedItemInterval.Y = -transformedItemInterval.Y; transformedItemIntervalVertical.Y = -transformedItemIntervalVertical.Y; } - transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y); + transformedItemPos += useCenterAsOrigin ? item.Position : new Vector2(item.Rect.X, item.Rect.Y); if (drawPosition) { if (item.Submarine != null) { transformedItemPos += item.Submarine.DrawPosition; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/LinkedControllerCharacterComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/LinkedControllerCharacterComponent.cs new file mode 100644 index 000000000..6faa6db36 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/LinkedControllerCharacterComponent.cs @@ -0,0 +1,121 @@ +#nullable enable + +using Barotrauma.Networking; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Xml.Linq; + +namespace Barotrauma.Items.Components +{ + /// + /// Item component used by for keeping a reference to the character that is currently + /// selecting the controller. Also provides functionality for changing the inventory sprite of the item based on the linked character. + /// + partial class LinkedControllerCharacterComponent : ItemComponent, IServerSerializable + { +#if CLIENT + private class SpriteOverride + { + public readonly Sprite? Sprite; + public readonly Identifier SpeciesName; + public readonly Identifier SpeciesGroup; + public SpriteOverride(ContentXElement element) + { + if (element.GetChildElement("Sprite") is ContentXElement spriteElement) + { + Sprite = new Sprite(spriteElement); + } + SpeciesName = element.GetAttributeIdentifier("speciesname", Identifier.Empty); + SpeciesGroup = element.GetAttributeIdentifier("speciesgroup", Identifier.Empty); + } + } + + private readonly ImmutableArray spriteOverrides; +#endif + + [Serialize(0.5f, IsPropertySaveable.No, description: $"Maximum value which {nameof(DeconstructTimeMultiplier)} can be.")] + public float MaxDeconstructTimeMultiplier + { + get; + set; + } + + public Character? Character { get; private set; } + + public bool DoesBleed => Character?.DoesBleed == true; + + public float DeconstructTimeMultiplier { get; private set; } = 1f; + + public LinkedControllerCharacterComponent(Item item, ContentXElement element) : base(item, element) + { +#if CLIENT + spriteOverrides = element.Elements() + .Where(static e => e.Name.LocalName.ToLowerInvariant() == "spriteoverride") + .Select(static e => new SpriteOverride(e)) + .ToImmutableArray(); +#endif + } + + public void UpdateLinkedCharacter(Character? character) + { + Character = character; + + if (character != null) + { + var animController = character.AnimController; + float totalLimbs = animController.Limbs.Length; + float nonSeveredLimbs = animController.Limbs.Count(static l => !l.IsSevered); + + // Decrease deconstruction time if the character is missing some limbs + DeconstructTimeMultiplier *= MathF.Max(MaxDeconstructTimeMultiplier, nonSeveredLimbs / totalLimbs); + } + +#if CLIENT + if (character != null) + { + SpriteOverride? spriteOverride = + spriteOverrides.Where(s => s.SpeciesName == character.SpeciesName).FirstOrDefault() + ?? spriteOverrides.Where(s => s.SpeciesGroup == character.Group).FirstOrDefault(); + + if (spriteOverride != null) + { + item.OverrideInventorySprite = spriteOverride.Sprite; + } + } + else + { + item.OverrideInventorySprite = null; + } +#elif SERVER + Item.CreateServerEvent(this); +#endif + } + + public void ClientEventRead(IReadMessage msg, float sendingTime) + { + UInt16 characterId = msg.ReadUInt16(); + if (characterId == Entity.NullEntityID) + { + UpdateLinkedCharacter(null); + } + else if (Entity.FindEntityByID(characterId) is Character character) + { + UpdateLinkedCharacter(character); + } + } + + public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData? extraData = null) + { + if (Character != null) + { + msg.WriteUInt16(Character.ID); + } + else + { + msg.WriteUInt16(Entity.NullEntityID); + } + } + } +} \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs index 899258e4f..b9c3222ba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Controller.cs @@ -1,9 +1,12 @@ -using FarseerPhysics; -using Barotrauma.Networking; +using Barotrauma.Networking; +using FarseerPhysics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.ComponentModel; using System.Globalization; +using System.Linq; using System.Xml.Linq; namespace Barotrauma.Items.Components @@ -45,6 +48,39 @@ namespace Barotrauma.Items.Components private Camera cam; private Character user; + public Character User + { + get { return user; } + private set + { + if (user == value) + { + return; + } + + user = value; + + if (user != null) + { + teleportTransition = 0f; + teleportStartPosition = user.WorldPosition; + } +#if SERVER + item.CreateServerEvent(this); +#endif + +#if CLIENT + UpdateMsg(); + + if (HideAllItemComponentHUDs && Character.Controlled == user) + { + // Prevents any UIs in this item from briefly showing up when you select this controller, since + // activeHUDs would take a single frame to be updated to not contain any other item component HUD + Item.ClearActiveHUDs(); + } +#endif + } + } private Item focusTarget; private float targetRotation; @@ -55,11 +91,6 @@ namespace Barotrauma.Items.Components set { userPos = value; } } - public Character User - { - get { return user; } - } - public IEnumerable LimbPositions { get { return limbPositions; } } [Editable, Serialize(false, IsPropertySaveable.No, description: "When enabled, the item will continuously send out a signal and interacting with it will flip the signal (making the item behave like a switch). When disabled, the item will simply send out a signal when interacted with.", alwaysUseInstanceValues: true)] @@ -123,6 +154,13 @@ namespace Barotrauma.Items.Components set; } + [Serialize(false, IsPropertySaveable.No, description: "Should the HUDs of all item components in this item be hidden when a character is using this controller.")] + public bool HideAllItemComponentHUDs + { + get; + set; + } + public enum UseEnvironment { Air, Water, Both @@ -152,6 +190,49 @@ namespace Barotrauma.Items.Components set; } + [Serialize(false, IsPropertySaveable.No, description: "Can a character put another character into this controller by dragging them and selecting this controller?")] + public bool AllowPuttingInOtherCharacters + { + get; + set; + } + + [Serialize(true, IsPropertySaveable.No, description: "Can a character select this controller by themselves?")] + public bool CanBeSelectedByCharacters + { + get; + set; + } + + [Serialize(false, IsPropertySaveable.No, description: "If a character selects this controller, but another character already has it selected, should it be kicked out?")] + public bool SelectingKicksCharacterOut + { + get; + set; + } + + [Serialize("", IsPropertySaveable.No, description: "Message displayed when there's a character inside this controller.")] + public string KickOutCharacterMsg + { + get; + set; + } + + [Serialize("", IsPropertySaveable.No, description: "Message displayed when you are putting a character into the controller.")] + public string PutOtherCharacterMsg + { + get; + set; + } + + + [Serialize("", IsPropertySaveable.No, description: "Spawns this item in the first available item container slot when a character selects this controller, if the item container is full, the character will not be able to select the controller.")] + public Identifier SpawnItemOnSelected + { + get; + private set; + } + public bool ControlCharacterPose { get { return limbPositions.Count > 0; } @@ -204,6 +285,23 @@ namespace Barotrauma.Items.Components set; } + /// + /// Used to determine how fast the character is teleported + /// to the item when they first select the controller. + /// Only relevant for + /// + private const float TeleportTransitionSpeed = 8f; + private float teleportTransition = 0f; + private Vector2 teleportStartPosition; + + private readonly ItemPrefab spawnItemOnSelectedPrefab; + private readonly ItemContainer containerToSpawnOnSelectedItem; + + /// + /// Item spawned by + /// + private Item spawnedItemOnSelected = null; + public Controller(Item item, ContentXElement element) : base(item, element) { @@ -211,6 +309,18 @@ namespace Barotrauma.Items.Components Enum.TryParse(element.GetAttributeString("direction", "None"), out dir); LoadLimbPositions(element); IsActive = true; + + containerToSpawnOnSelectedItem = item.GetComponent(); + + if (!SpawnItemOnSelected.IsEmpty && !ItemPrefab.Prefabs.TryGet(SpawnItemOnSelected, out spawnItemOnSelectedPrefab)) + { + DebugConsole.ThrowError($"Failed to find item prefab \"{SpawnItemOnSelected}\""); + } + + if (containerToSpawnOnSelectedItem == null && !SpawnItemOnSelected.IsEmpty) + { + DebugConsole.ThrowError($"Error - Controller has a {nameof(SpawnItemOnSelected)} but no ItemContainer defined"); + } } /// @@ -236,58 +346,77 @@ namespace Barotrauma.Items.Components item.SendSignal(signal, "trigger_out"); } - if (forceSelectNextFrame && user != null) + if (forceSelectNextFrame && User != null) { - user.SelectedItem = item; + User.SelectedItem = item; } forceSelectNextFrame = false; userCanInteractCheckTimer -= deltaTime; - if (user == null - || user.Removed - || !user.IsAnySelectedItem(item) - || (item.ParentInventory != null && !IsAttachedUser(user)) - || (UsableIn == UseEnvironment.Water && !user.AnimController.InWater) - || (UsableIn == UseEnvironment.Air && user.AnimController.InWater) - || !CheckUserCanInteract()) + if (User == null + || User.Removed + || (((User.Stun <= 0f && !User.IsKnockedDownOrRagdolled && !User.LockHands) || !ForceUserToStayAttached) && (!User.IsAnySelectedItem(item) || !CheckUserCanInteract())) + || (item.ParentInventory != null && !IsAttachedUser(User)) + || (UsableIn == UseEnvironment.Water && !User.AnimController.InWater) + || (UsableIn == UseEnvironment.Air && User.AnimController.InWater) + || !CheckSpawnItem() + ) { - if (user != null) + if (User != null) { - CancelUsing(user); - user = null; + CancelUsing(User); + User = null; } if (item.Connections == null || !IsToggle || string.IsNullOrEmpty(signal)) { IsActive = false; } return; } - if (ForceUserToStayAttached && Vector2.DistanceSquared(item.WorldPosition, user.WorldPosition) > 0.1f) + if (ForceUserToStayAttached) { - user.TeleportTo(item.WorldPosition); - user.AnimController.Collider.ResetDynamics(); - foreach (var limb in user.AnimController.Limbs) + teleportTransition = MathF.Min(teleportTransition + deltaTime * TeleportTransitionSpeed, 1f); + + if (teleportTransition >= 1f) { - if (limb.Removed || limb.IsSevered) { continue; } - limb.body?.ResetDynamics(); + // Transition is complete, if someone was holding this character, force them to deselect + // so they aren't holding the character that is now attached to the controller + if (User.SelectedBy != null) + { + User.SelectedBy.SelectedCharacter = null; + } + } + + if (User == Character.Controlled + || teleportTransition < 1f + || Vector2.DistanceSquared(item.WorldPosition, User.WorldPosition) > 0.1f) + { + var targetPosition = Vector2.Lerp(teleportStartPosition, item.WorldPosition, teleportTransition); + User.TeleportTo(targetPosition); + User.AnimController.Collider.ResetDynamics(); + foreach (var limb in User.AnimController.Limbs) + { + if (limb.Removed || limb.IsSevered) { continue; } + limb.body?.ResetDynamics(); + } } } - user.AnimController.StartUsingItem(); + User.AnimController.StartUsingItem(); if (userPos != Vector2.Zero) { - Vector2 diff = (item.WorldPosition + userPos) - user.WorldPosition; + Vector2 diff = (item.WorldPosition + userPos) - User.WorldPosition; - if (user.AnimController.InWater) + if (User.AnimController.InWater) { if (diff.LengthSquared() > 30.0f * 30.0f) { - user.AnimController.TargetMovement = Vector2.Clamp(diff * 0.01f, -Vector2.One, Vector2.One); - user.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left; + User.AnimController.TargetMovement = Vector2.Clamp(diff * 0.01f, -Vector2.One, Vector2.One); + User.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left; } else { - user.AnimController.TargetMovement = Vector2.Zero; + User.AnimController.TargetMovement = Vector2.Zero; UserInCorrectPosition = true; } } @@ -295,10 +424,10 @@ namespace Barotrauma.Items.Components { // Secondary items (like ladders or chairs) will control the character position over primary items // Only control the character position if the character doesn't have another secondary item already controlling it - if (!user.HasSelectedAnotherSecondaryItem(Item)) + if (!User.HasSelectedAnotherSecondaryItem(Item)) { diff.Y = 0.0f; - if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && user != Character.Controlled) + if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && User != Character.Controlled) { if (Math.Abs(diff.X) > 20.0f) { @@ -308,48 +437,48 @@ namespace Barotrauma.Items.Components else if (Math.Abs(diff.X) > 0.1f) { //aim to keep the collider at the correct position once close enough - user.AnimController.Collider.LinearVelocity = new Vector2( + User.AnimController.Collider.LinearVelocity = new Vector2( diff.X * 0.1f, - user.AnimController.Collider.LinearVelocity.Y); + User.AnimController.Collider.LinearVelocity.Y); } } else if (Math.Abs(diff.X) > 10.0f) { - user.AnimController.TargetMovement = Vector2.Normalize(diff); - user.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left; + User.AnimController.TargetMovement = Vector2.Normalize(diff); + User.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left; return; } - user.AnimController.TargetMovement = Vector2.Zero; + User.AnimController.TargetMovement = Vector2.Zero; } UserInCorrectPosition = true; } } - ApplyStatusEffects(ActionType.OnActive, deltaTime, user); + ApplyStatusEffects(ActionType.OnActive, deltaTime, User); if (limbPositions.Count == 0) { return; } - user.AnimController.StartUsingItem(); + User.AnimController.StartUsingItem(); - if (user.SelectedItem != null) + if (User.SelectedItem != null) { - user.AnimController.ResetPullJoints(l => l.IsLowerBody); + User.AnimController.ResetPullJoints(l => l.IsLowerBody); } else { - user.AnimController.ResetPullJoints(); + User.AnimController.ResetPullJoints(); } - if (dir != 0) { user.AnimController.TargetDir = dir; } + if (dir != 0) { User.AnimController.TargetDir = dir; } foreach (LimbPos lb in limbPositions) { - Limb limb = user.AnimController.GetLimb(lb.LimbType); + Limb limb = User.AnimController.GetLimb(lb.LimbType); if (limb == null || !limb.body.Enabled) { continue; } // Don't move lower body limbs if there's another selected secondary item that should control them - if (limb.IsLowerBody && user.HasSelectedAnotherSecondaryItem(Item)) { continue; } + if (limb.IsLowerBody && User.HasSelectedAnotherSecondaryItem(Item)) { continue; } // Don't move hands if there's a selected primary item that should control them - if (limb.IsArm && Item == user.SelectedSecondaryItem && user.SelectedItem != null) { continue; } + if (limb.IsArm && Item == User.SelectedSecondaryItem && User.SelectedItem != null) { continue; } if (lb.AllowUsingLimb) { switch (lb.LimbType) @@ -357,12 +486,12 @@ namespace Barotrauma.Items.Components case LimbType.RightHand: case LimbType.RightForearm: case LimbType.RightArm: - if (user.Inventory.GetItemInLimbSlot(InvSlotType.RightHand) != null) { continue; } + if (User.Inventory.GetItemInLimbSlot(InvSlotType.RightHand) != null) { continue; } break; case LimbType.LeftHand: case LimbType.LeftForearm: case LimbType.LeftArm: - if (user.Inventory.GetItemInLimbSlot(InvSlotType.LeftHand) != null) { continue; } + if (User.Inventory.GetItemInLimbSlot(InvSlotType.LeftHand) != null) { continue; } break; } } @@ -374,15 +503,77 @@ namespace Barotrauma.Items.Components } } + private bool IsSpawnContainerFull() + { + if (spawnItemOnSelectedPrefab == null || containerToSpawnOnSelectedItem == null) + { + return false; + } + + if (containerToSpawnOnSelectedItem.Inventory.IsFull()) + { + return true; + } + + return false; + } + + private bool CheckSpawnItem() + { + if (spawnItemOnSelectedPrefab == null || containerToSpawnOnSelectedItem == null) + { + return true; + } + + if (containerToSpawnOnSelectedItem.Inventory.AllItems.Any(x => x.Prefab == spawnItemOnSelectedPrefab)) + { + return true; + } + + if (spawnedItemOnSelected != null && !spawnedItemOnSelected.Removed) + { + if (spawnedItemOnSelected.ParentInventory != containerToSpawnOnSelectedItem.Inventory) + { + // Item was moved or dropped, force the user in this controller out + return false; + } + else + { + return true; + } + } + + if (IsSpawnContainerFull()) + { + return false; + } + + if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer) + { + Entity.Spawner.AddItemToSpawnQueue(spawnItemOnSelectedPrefab, containerToSpawnOnSelectedItem.Inventory, onSpawned: spawnedItem => + { + spawnedItemOnSelected = spawnedItem; + + var linkedCharacterComponent = spawnedItem.GetComponent(); + if (linkedCharacterComponent != null) + { + linkedCharacterComponent.UpdateLinkedCharacter(User); + } + }); + } + + return true; + } + private bool CheckUserCanInteract() { //optimization: CanInteractWith is relatively heavy (can involve visibility checks for example), let's not do it every frame - if (user != null) + if (User != null) { if (userCanInteractCheckTimer <= 0.0f) { userCanInteractCheckTimer = UserCanInteractCheckInterval; - return user.CanInteractWith(item); + return User.CanInteractWith(item); } } //we only do the actual check every UserCanInteractCheckInterval seconds @@ -394,13 +585,13 @@ namespace Barotrauma.Items.Components public override bool Use(float deltaTime, Character activator = null) { - if (activator != user) + if (activator != User) { return false; } - if (user == null || user.Removed || !user.IsAnySelectedItem(item) || !user.CanInteractWith(item)) + if (User == null || User.Removed || !User.IsAnySelectedItem(item) || !User.CanInteractWith(item)) { - user = null; + User = null; return false; } @@ -419,7 +610,7 @@ namespace Barotrauma.Items.Components } else if (!string.IsNullOrEmpty(output)) { - item.SendSignal(new Signal(output, sender: user), "trigger_out"); + item.SendSignal(new Signal(output, sender: User), "trigger_out"); } lastUsed = Timing.TotalTime; @@ -428,13 +619,13 @@ namespace Barotrauma.Items.Components public override bool SecondaryUse(float deltaTime, Character character = null) { - if (user != character) + if (User != character) { return false; } - if (user == null || character.Removed || !user.IsAnySelectedItem(item) || !character.CanInteractWith(item)) + if (User == null || character.Removed || !User.IsAnySelectedItem(item) || !character.CanInteractWith(item)) { - user = null; + User = null; return false; } if (character == null) @@ -495,7 +686,7 @@ namespace Barotrauma.Items.Components if (IsOutOfPower()) { return null; } - item.SendSignal(new Signal(MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), sender: user), positionOut); + item.SendSignal(new Signal(MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), sender: User), positionOut); for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--) { @@ -547,8 +738,20 @@ namespace Barotrauma.Items.Components private void CancelUsing(Character character) { + if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) + { + if (spawnedItemOnSelected != null) + { + Entity.Spawner.AddEntityToRemoveQueue(spawnedItemOnSelected); + spawnedItemOnSelected = null; + } + } + if (character == null || character.Removed) { return; } + // Wake character's colliders so they don't just float in the air when taken out of the controller + character.AnimController.BodyInRest = false; + foreach (LimbPos lb in limbPositions) { Limb limb = character.AnimController.GetLimb(lb.LimbType); @@ -588,31 +791,84 @@ namespace Barotrauma.Items.Components return false; } - //someone already using the item - if (user != null && !user.Removed) + // Someone already using the item + if (User != null && !User.Removed) { - if (user == activator) + // Let the server handle the logic here + if (GameMain.NetworkMember is { IsClient: true }) { - IsActive = false; - CancelUsing(user); - user = null; return false; } - else if (user.IsBot && !activator.IsBot) + + // Prevent user from kicking character out if they are holding another character + if (AllowPuttingInOtherCharacters && CanPutSelectedCharacter(activator.SelectedCharacter)) + { + return false; + } + + if (User == activator || SelectingKicksCharacterOut) + { +#if SERVER + if (User != activator) + { + GameServer.Log($"{GameServer.CharacterLogName(activator)} removed {GameServer.CharacterLogName(User)} from {item.Name}", + ServerLog.MessageType.Attack); + } +#endif + + IsActive = false; + CancelUsing(User); + User = null; + return false; + } + else if (User.IsBot && !activator.IsBot) { if (AllowSelectingWhenSelectedByBot) { - CancelUsing(user); - user = activator; + CancelUsing(User); + User = activator; IsActive = true; return true; } } return AllowSelectingWhenSelectedByOther; } - else + else if (AllowPuttingInOtherCharacters && CanPutSelectedCharacter(activator.SelectedCharacter)) { - user = activator; + // Stun pets longer so they don't immediately get out of the controller + if (activator.SelectedCharacter.IsPet) + { + activator.SelectedCharacter.SetStun(MathF.Max(activator.SelectedCharacter.Stun, 4f), isNetworkMessage: true); + } + else + { + // Small amount of stun since non-ragdolled characters behave weirdly when syncing the periodic teleportation in multiplayer + activator.SelectedCharacter.SetStun(MathF.Max(activator.SelectedCharacter.Stun, 1f), isNetworkMessage: true); + } + +#if SERVER + GameServer.Log($"{GameServer.CharacterLogName(activator)} forced {GameServer.CharacterLogName(activator.SelectedCharacter)} into {item.Name}", + ServerLog.MessageType.Attack); +#endif + + User = activator.SelectedCharacter; + User.SelectedItem = this.Item; + IsActive = true; + if (ForceUserToStayAttached && item.Container != null) + { + forceSelectNextFrame = true; + } + return false; + } + else if (CanBeSelectedByCharacters) + { +#if SERVER + GameServer.Log($"{GameServer.CharacterLogName(activator)} entered {item.Name}", ServerLog.MessageType.ItemInteraction); +#endif + + activator.DeselectCharacter(); + + User = activator; IsActive = true; if (ForceUserToStayAttached && item.Container != null) { @@ -621,15 +877,12 @@ namespace Barotrauma.Items.Components } } - //allow the selection logic above to run when out of power, but allow sending signals + //allow the selection logic above to run when out of power, but disallow sending signals if (IsOutOfPower()) { return false; } - -#if SERVER - item.CreateServerEvent(this); -#endif + if (!string.IsNullOrEmpty(output)) { - item.SendSignal(new Signal(output, sender: user), "signal_out"); + item.SendSignal(new Signal(output, sender: User), "signal_out"); } return true; } @@ -639,7 +892,7 @@ namespace Barotrauma.Items.Components /// public bool IsAttachedUser(Character character) { - return character != null && character == user && ForceUserToStayAttached; + return character != null && character == User && ForceUserToStayAttached; } public override void FlipX(bool relativeToSub) @@ -668,12 +921,87 @@ namespace Barotrauma.Items.Components } } + public override bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg = null) + { +#if CLIENT + UpdateMsg(); +#endif + + bool canPutCharacter = AllowPuttingInOtherCharacters && CanPutSelectedCharacter(character.SelectedCharacter, addMessage); + bool canKickCharacter = SelectingKicksCharacterOut && User != null && !User.Removed; + bool canUseController = CanBeSelectedByCharacters; + + // Prevent kicking a character out when the user is holding another character to put into the controller. + // This avoids accidentally taking out a character (e.g. from a deconstructor). + if (canPutCharacter && canKickCharacter) + { +#if CLIENT + if (addMessage) + { + GUI.AddMessage(TextManager.Get("ItemMsgAlreadyHasCharacterFail"), Color.Red, playSound: false); + SoundPlayer.PlayUISound(GUISoundType.PickItemFail); + } +#endif + + return false; + } + + if (!canKickCharacter && !canPutCharacter && !canUseController) + { + return false; + } + + if (IsSpawnContainerFull()) + { +#if CLIENT + if (addMessage) + { + GUI.AddMessage(TextManager.Get("ItemMsgNotEnoughSpaceCharacterFail"), Color.Red, playSound: false); + SoundPlayer.PlayUISound(GUISoundType.PickItemFail); + } +#endif + + return false; + } + + return base.HasRequiredItems(character, addMessage, msg); + } + public override bool HasAccess(Character character) { if (!item.IsInteractable(character)) { return false; } return base.HasAccess(character); } + private bool CanPutSelectedCharacter(Character character, bool showMessage = false) + { + if (character == null) + { + return false; + } + + if (!character.IsContainable) + { +#if CLIENT + if (showMessage) + { + GUI.AddMessage(TextManager.Get("ItemMsgPutCharacterFail"), Color.Red); + } +#endif + + return false; + } + + if (character.IsKnockedDownOrRagdolled) { return true; } + if (character.LockHands) { return true; } + if (character.IsPet) + { + return true; + } + + return false; + } + partial void HideHUDs(bool value); public override XElement Save(XElement parentElement) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs index 2ab676500..c6cb73978 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/Deconstructor.cs @@ -114,7 +114,19 @@ namespace Barotrauma.Items.Components float deconstructTime = 0.0f; foreach (Item targetItem in inputContainer.Inventory.AllItems) { - deconstructTime += targetItem.Prefab.DeconstructTime / (deconstructionSpeed * deconstructionSpeedModifier); + float itemDeconstructTime = item.Submarine is { Info.Type: SubmarineType.Outpost } + ? targetItem.Prefab.DeconstructTimeInOutposts : targetItem.Prefab.DeconstructTime; + float targetDeconstructTime = itemDeconstructTime / (deconstructionSpeed * deconstructionSpeedModifier); + + var linkedCharacter = targetItem.GetComponent(); + if (linkedCharacter != null) + { + targetDeconstructTime *= linkedCharacter.DeconstructTimeMultiplier; + } + + deconstructTime += targetDeconstructTime; + + ApplyDeconstructionStatusEffects(targetItem, ActionType.OnDeconstructing, deltaTime); } progressState = Math.Min(progressTimer / deconstructTime, 1.0f); @@ -143,8 +155,21 @@ namespace Barotrauma.Items.Components var targetItem = inputContainer.Inventory.LastOrDefault(); if (targetItem == null) { return; } + ApplyDeconstructionStatusEffects(targetItem, ActionType.OnDeconstructing, deltaTime); + var validDeconstructItems = targetItem.Prefab.DeconstructItems.Where(it => it.IsValidDeconstructor(item)).ToList(); - float deconstructTime = validDeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / (deconstructionSpeed * deconstructionSpeedModifier) : 1.0f; + + float itemDeconstructTime = item.Submarine is { Info.Type: SubmarineType.Outpost } + ? targetItem.Prefab.DeconstructTimeInOutposts : targetItem.Prefab.DeconstructTime; + + float deconstructTime = !targetItem.Prefab.DeconstructItems.Any() || validDeconstructItems.Any() + ? itemDeconstructTime / (deconstructionSpeed * deconstructionSpeedModifier) : 1.0f; + + var linkedCharacter = targetItem.GetComponent(); + if (linkedCharacter != null) + { + deconstructTime *= linkedCharacter.DeconstructTimeMultiplier; + } progressState = Math.Min(progressTimer / deconstructTime, 1.0f); if (progressTimer > deconstructTime) @@ -183,6 +208,8 @@ namespace Barotrauma.Items.Components amountMultiplier = (int)itemCreationMultiplier.Value; } + ApplyDeconstructionStatusEffects(targetItem, ActionType.OnDeconstructed, 1f); + if (targetItem.Prefab.RandomDeconstructionOutput) { int amount = targetItem.Prefab.RandomDeconstructionOutputAmount; @@ -297,30 +324,8 @@ namespace Barotrauma.Items.Components { humanAi.HandleRelocation(spawnedItem); } - for (int i = 0; i < outputContainer.Capacity; i++) - { - var containedItem = outputContainer.Inventory.GetItemAt(i); - bool combined = false; - if (containedItem?.OwnInventory != null) - { - foreach (Item subItem in containedItem.ContainedItems.ToList()) - { - if (subItem.Combine(spawnedItem, null)) - { - combined = true; - break; - } - } - } - if (!combined) - { - if (containedItem?.Combine(spawnedItem, null) ?? false) - { - break; - } - } - } - PutItemsToLinkedContainer(); + + TryMoveItemToOutputContainers(spawnedItem); }); } } @@ -347,6 +352,7 @@ namespace Barotrauma.Items.Components } } } + inputContainer.Inventory.RemoveItem(targetItem); Entity.Spawner.AddItemToRemoveQueue(targetItem); MoveInputQueue(); @@ -381,6 +387,34 @@ namespace Barotrauma.Items.Components } } + private void TryMoveItemToOutputContainers(Item spawnedItem) + { + for (int i = 0; i < outputContainer.Capacity; i++) + { + var containedItem = outputContainer.Inventory.GetItemAt(i); + bool combined = false; + if (containedItem?.OwnInventory != null) + { + foreach (Item subItem in containedItem.ContainedItems.ToList()) + { + if (subItem.Combine(spawnedItem, null)) + { + combined = true; + break; + } + } + } + if (!combined) + { + if (containedItem?.Combine(spawnedItem, null) ?? false) + { + break; + } + } + } + PutItemsToLinkedContainer(); + } + private void PutItemsToLinkedContainer() { if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; } @@ -399,6 +433,72 @@ namespace Barotrauma.Items.Components } } + private void ApplyDeconstructionStatusEffects(Item targetItem, ActionType type, float deltaTime) + { + var linkedCharacterComponent = targetItem.GetComponent(); + Character character = null; + if (linkedCharacterComponent is { Character.Removed: false }) + { + character = linkedCharacterComponent.Character; + } + + Limb limb = character?.AnimController.Limbs.GetRandomUnsynced(); + + if (user != null) + { + item.GetStatusEffectsOfType(type).ForEach(statusEffect => statusEffect.SetUser(user)); + targetItem.GetStatusEffectsOfType(type).ForEach(statusEffect => statusEffect.SetUser(user)); + } + + // Apply OnDeconstruct/OnDeconstructing to the Deconstructor/item being deconstructed + item.ApplyStatusEffects(type, deltaTime, character, limb, useTarget: targetItem); + targetItem.ApplyStatusEffects(type, deltaTime, character, limb); + + if (character != null) + { + if (type == ActionType.OnDeconstructed) + { + // Move whatever was on the character inventory to free up space for status effects that spawn items + MoveItemsFromCharacterToOutput(); + } + + character.ApplyStatusEffects(type, deltaTime); + + if (type == ActionType.OnDeconstructed) + { + // This needs to run next frame because the status effect might enqueue items to be spawned next frame + CoroutineManager.Invoke(() => + { + if (character.Removed) { return; } + + // Move items again since the status effect could have spawned additional items in the character inventory + MoveItemsFromCharacterToOutput(); + + Entity.Spawner?.AddEntityToRemoveQueue(character); + }, 0.1f); + } + + void MoveItemsFromCharacterToOutput() + { + if (character.Inventory != null) + { + foreach (var item in character.Inventory.AllItemsMod) + { + if (item.Removed) { continue; } + if (!item.IsPlayerTeamInteractable) { continue; } + + if (!outputContainer.Inventory.TryPutItem(item, user: null)) + { + item.Drop(dropper: null); + } + + TryMoveItemToOutputContainers(item); + } + } + } + } + } + /// /// Move items towards the last slot in the inventory if there's free slots /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs index 52df29696..6676545ae 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs @@ -388,6 +388,13 @@ namespace Barotrauma.Items.Components { Attack.DamageMultiplier = damageMultiplier; } + foreach (var statusEffect in Item.GetStatusEffectsOfType(ActionType.OnImpact)) + { + foreach (var explosion in statusEffect.Explosions) + { + explosion.Attack.DamageMultiplier = damageMultiplier; + } + } // Set user for hitscan projectiles to work properly. User = user; // Need to set null for non-characterusable items. @@ -460,6 +467,7 @@ namespace Barotrauma.Items.Components { initialRotation -= MathHelper.Pi; } + Submarine initialSubmarine = item.Submarine; for (int i = 0; i < HitScanCount; i++) { float launchAngle; @@ -476,6 +484,8 @@ namespace Barotrauma.Items.Components Vector2 launchDir = new Vector2((float)Math.Cos(launchAngle), (float)Math.Sin(launchAngle)); Vector2 prevSimpos = item.SimPosition; item.body.SetTransformIgnoreContacts(item.body.SimPosition, launchAngle); + //when launching multiple projectiles, ensure each raycast starts from the same sub + item.Submarine = initialSubmarine; if (Hitscan) { DoHitscan(launchDir); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs index 1aebf8187..6ec4fd5a2 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Repairable.cs @@ -448,9 +448,10 @@ namespace Barotrauma.Items.Components UpdateProjSpecific(deltaTime); IsTinkering = false; - if (prevSentConditionValue != (int)item.ConditionPercentage || conditionSignal == null) + int condition = (int)(item.Condition / (item.MaxCondition / item.MaxRepairConditionMultiplier) * 100f); + if (prevSentConditionValue != condition || conditionSignal == null) { - prevSentConditionValue = (int)item.ConditionPercentage; + prevSentConditionValue = condition; conditionSignal = prevSentConditionValue.ToString(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs index e0475bbd5..c9a3cab15 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/Connection.cs @@ -27,6 +27,11 @@ namespace Barotrauma.Items.Components private init => _displayName = value; } + /// + /// Display name ignoring + /// + public LocalizedString DefaultDisplayName => _displayName; + public LocalizedString DisplayNameOverride; private readonly HashSet wires; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs index e0104a213..bccb65f42 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs @@ -296,6 +296,7 @@ namespace Barotrauma.Items.Components } #endif CheckIfNeedsUpdate(); + SetLightSourceTransformProjSpecific(); } public void CheckIfNeedsUpdate() diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs index d5d7678c5..9da0bcfba 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs @@ -42,6 +42,10 @@ namespace Barotrauma.Items.Components private float currentChargeTime; private bool tryingToCharge; + private const float LineOfSightCheckInterval = 0.5f; + private (Body WorldTarget, Body TransformedTarget, double Time) lastLineOfSightCheck; + private (Character Target, bool CanSee, double Time) lastCanSeeTargetCheck; + private enum ChargingState { Inactive, @@ -1088,6 +1092,7 @@ namespace Barotrauma.Items.Components foreach (Submarine sub in Submarine.Loaded) { if (sub == Item.Submarine) { continue; } + if (sub.IsRespawnShuttle) { continue; } if (item.Submarine != null) { if (Character.IsOnFriendlyTeam(item.Submarine.TeamID, sub.TeamID)) { continue; } @@ -1164,15 +1169,28 @@ namespace Barotrauma.Items.Components } Vector2 start = ConvertUnits.ToSimUnits(item.WorldPosition); Vector2 end = ConvertUnits.ToSimUnits(target.WorldPosition); + + bool doLineOfSightCheck = lastLineOfSightCheck.Time < Timing.TotalTimeUnpaused - LineOfSightCheckInterval; + if (doLineOfSightCheck) + { + lastLineOfSightCheck.WorldTarget = CheckLineOfSight(start, end); + lastLineOfSightCheck.Time = Timing.TotalTime; + } + // Check that there's not other entities that shouldn't be targeted (like a friendly sub) between us and the target. - Body worldTarget = CheckLineOfSight(start, end); + Body worldTarget = lastLineOfSightCheck.WorldTarget; bool shoot; if (target.Submarine != null) { - start -= target.Submarine.SimPosition; - end -= target.Submarine.SimPosition; - Body transformedTarget = CheckLineOfSight(start, end); - shoot = CanShoot(transformedTarget, user: null, friendlyTag, TargetSubmarines) && (worldTarget == null || CanShoot(worldTarget, user: null, friendlyTag, TargetSubmarines)); + if (doLineOfSightCheck) + { + start -= target.Submarine.SimPosition; + end -= target.Submarine.SimPosition; + lastLineOfSightCheck.TransformedTarget = CheckLineOfSight(start, end); + } + shoot = + (worldTarget == null || CanShoot(worldTarget, user: null, friendlyTag, TargetSubmarines)) && + CanShoot(lastLineOfSightCheck.TransformedTarget, user: null, friendlyTag, TargetSubmarines); } else { @@ -1437,8 +1455,20 @@ namespace Barotrauma.Items.Components // Adjust the target character position (limb or submarine) if (currentTarget is Character targetCharacter) { + bool enemyInAnotherSub = targetCharacter.Submarine != null && targetCharacter.CurrentHull != null && targetCharacter.Submarine != item.Submarine; + bool canSeeTarget = true; + if (enemyInAnotherSub) + { + if (lastCanSeeTargetCheck.Time < Timing.TotalTime - LineOfSightCheckInterval || + targetCharacter != lastCanSeeTargetCheck.Target) + { + canSeeTarget = targetCharacter.CanSeeTarget(Item); + lastCanSeeTargetCheck = (targetCharacter, canSeeTarget, Timing.TotalTime); + } + } + //if the enemy is inside another sub, aim at the room they're in to make it less obvious that the enemy "knows" exactly where the target is - if (targetCharacter.Submarine != null && targetCharacter.CurrentHull != null && targetCharacter.Submarine != item.Submarine && !targetCharacter.CanSeeTarget(Item)) + if (enemyInAnotherSub && !canSeeTarget) { targetPos = targetCharacter.CurrentHull.WorldPosition; if (closestDistance > maxDistance * maxDistance) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs index ce4da3bda..54c9a5f90 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Inventory.cs @@ -569,16 +569,16 @@ namespace Barotrauma /// /// If there is room, puts the item in the inventory and returns true, otherwise returns false /// - public virtual bool TryPutItem(Item item, Character user, IEnumerable allowedSlots = null, bool createNetworkEvent = true, bool ignoreCondition = false) + public virtual bool TryPutItem(Item item, Character user, IEnumerable allowedSlots = null, bool createNetworkEvent = true, bool ignoreCondition = false, bool triggerOnInsertedEffects = true) { int slot = FindAllowedSlot(item, ignoreCondition); if (slot < 0) { return false; } - PutItem(item, slot, user, true, createNetworkEvent); + PutItem(item, slot, user, true, createNetworkEvent, triggerOnInsertedEffects); return true; } - public virtual bool TryPutItem(Item item, int i, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true, bool ignoreCondition = false) + public virtual bool TryPutItem(Item item, int i, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true, bool ignoreCondition = false, bool triggerOnInsertedEffects = true) { if (!IsIndexInRange(i)) { @@ -609,14 +609,14 @@ namespace Barotrauma //item in the slot removed as a result of combining -> put this item in the now free slot if (!slots[i].Any()) { - return TryPutItem(item, i, allowSwapping, allowCombine, user, createNetworkEvent, ignoreCondition); + return TryPutItem(item, i, allowSwapping, allowCombine, user, createNetworkEvent, ignoreCondition, triggerOnInsertedEffects); } return true; } } if (CanBePutInSlot(item, i, ignoreCondition)) { - PutItem(item, i, user, true, createNetworkEvent); + PutItem(item, i, user, true, createNetworkEvent, triggerOnInsertedEffects); return true; } else if (slots[i].Any() && item.ParentInventory != null && allowSwapping) @@ -642,7 +642,7 @@ namespace Barotrauma } } - protected virtual void PutItem(Item item, int i, Character user, bool removeItem = true, bool createNetworkEvent = true) + protected virtual void PutItem(Item item, int i, Character user, bool removeItem = true, bool createNetworkEvent = true, bool triggerOnInsertedEffects = true) { if (!IsIndexInRange(i)) { @@ -941,7 +941,8 @@ namespace Barotrauma { foreach (var item in items) { - if (!inventory.TryPutItem(item, slotIndex, false, false, user, createNetworkEvent, ignoreCondition: true) && + //don't trigger OnInserted effects: we're not really "inserting" but just putting it back where it was because swapping failed + if (!inventory.TryPutItem(item, slotIndex, false, false, user, createNetworkEvent, ignoreCondition: true, triggerOnInsertedEffects: false) && !inventory.GetItemsAt(slotIndex).Contains(item)) { inventory.ForceToSlot(item, slotIndex); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs index 53d7e0faf..41f69d22f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs @@ -262,6 +262,8 @@ namespace Barotrauma /// public Character Equipper; + public Inventory PreviousParentInventory { get; set; } + //the inventory in which the item is contained in public Inventory ParentInventory { @@ -277,9 +279,7 @@ namespace Barotrauma Container = parentInventory.Owner as Item; RemoveFromDroppedStack(allowClientExecute: false); } -#if SERVER PreviousParentInventory = value; -#endif } } @@ -560,6 +560,8 @@ namespace Barotrauma set; } = float.PositiveInfinity; + public Sprite OverrideInventorySprite { get; set; } + protected Color spriteColor; [Editable, Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes)] public Color SpriteColor @@ -1102,7 +1104,7 @@ namespace Barotrauma public override string ToString() { - return Name + " (ID: " + ID + ")"; + return (Name.IsNullOrEmpty() ? Prefab.Identifier : Name) + " (ID: " + ID + ")"; } private readonly List allPropertyObjects = new List(); @@ -1790,7 +1792,8 @@ namespace Barotrauma ic.Move(amount, ignoreContacts); } - if (body != null && (Submarine == null || !Submarine.Loading) || Screen.Selected is { IsEditor: true }) { FindHull(); } + // Refresh items without a body in editors so vents (or other static items that do something with hulls) know which hull they are in after being moved + if ((body != null || Screen.Selected is { IsEditor: true }) && Submarine is not { Loading: true }) { FindHull(); } } public Rectangle TransformTrigger(Rectangle trigger, bool world = false) @@ -2070,6 +2073,12 @@ namespace Barotrauma } } + public IEnumerable GetStatusEffectsOfType(ActionType type) + { + if (!hasStatusEffectsOfType[(int)type]) { return Enumerable.Empty(); } + return statusEffectLists[type]; + } + /// /// Executes all StatusEffects of the specified type. Note that condition checks are ignored here: that should be handled by the code calling the method. /// @@ -3255,7 +3264,9 @@ namespace Barotrauma bool showUiMsg = false; #if CLIENT if (!ic.HasRequiredSkills(user, out Skill tempRequiredSkill)) { hasRequiredSkills = false; skillMultiplier = ic.GetSkillMultiplier(); } - showUiMsg = user == Character.Controlled && Screen.Selected != GameMain.SubEditorScreen; + showUiMsg = user == Character.Controlled && Screen.Selected != GameMain.SubEditorScreen && + // Only show the UI message of the component that we actually want to interact with + (pickHit && ic.CanBePicked || selectHit && ic.CanBeSelected); #endif if (!ignoreRequiredItems && !ic.HasRequiredItems(user, showUiMsg)) { continue; } if ((ic.CanBePicked && pickHit && ic.Pick(user)) || @@ -4354,6 +4365,9 @@ namespace Barotrauma Rotation = Rotation }; + if (FlippedX) { newItem.FlipX(relativeToSub: false); } + if (FlippedY) { newItem.FlipY(relativeToSub: false); } + float scaleRelativeToPrefab = Scale / Prefab.Scale; newItem.Scale *= scaleRelativeToPrefab; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs index 1a8cf3cc1..29b914960 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemInventory.cs @@ -88,9 +88,9 @@ namespace Barotrauma return true; } - public override bool TryPutItem(Item item, Character user, IEnumerable allowedSlots = null, bool createNetworkEvent = true, bool ignoreCondition = false) + public override bool TryPutItem(Item item, Character user, IEnumerable allowedSlots = null, bool createNetworkEvent = true, bool ignoreCondition = false, bool triggerOnInsertedEffects = true) { - bool wasPut = base.TryPutItem(item, user, allowedSlots, createNetworkEvent, ignoreCondition); + bool wasPut = base.TryPutItem(item, user, allowedSlots, createNetworkEvent, ignoreCondition, triggerOnInsertedEffects); if (wasPut) { @@ -111,9 +111,9 @@ namespace Barotrauma return wasPut; } - public override bool TryPutItem(Item item, int i, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true, bool ignoreCondition = false) + public override bool TryPutItem(Item item, int i, bool allowSwapping, bool allowCombine, Character user, bool createNetworkEvent = true, bool ignoreCondition = false, bool triggerOnInsertedEffects = true) { - bool wasPut = base.TryPutItem(item, i, allowSwapping, allowCombine, user, createNetworkEvent, ignoreCondition); + bool wasPut = base.TryPutItem(item, i, allowSwapping, allowCombine, user, createNetworkEvent, ignoreCondition, triggerOnInsertedEffects); if (wasPut && item.ParentInventory == this) { foreach (Character c in Character.CharacterList) @@ -124,7 +124,7 @@ namespace Barotrauma } container.IsActive = true; - container.OnItemContained(item); + container.OnItemContained(item, triggerOnInsertedEffects); #if SERVER GameMain.Server?.KarmaManager?.OnItemContained(item, container.Item, user); #endif diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs index c340efab6..a52d1d94c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs @@ -548,6 +548,8 @@ namespace Barotrauma public float DeconstructTime { get; private set; } + public float DeconstructTimeInOutposts { get; private set; } + public bool AllowDeconstruct { get; private set; } //Containers (by identifiers or tags) that this item should be placed in. These are preferences, which are not enforced. @@ -1074,6 +1076,7 @@ namespace Barotrauma var storePrices = new Dictionary(); var preferredContainers = new List(); DeconstructTime = 1.0f; + DeconstructTimeInOutposts = DeconstructTime; if (ConfigElement.GetAttribute("allowasextracargo") != null) { @@ -1174,6 +1177,7 @@ namespace Barotrauma break; case "deconstruct": DeconstructTime = subElement.GetAttributeFloat("time", 1.0f); + DeconstructTimeInOutposts = subElement.GetAttributeFloat("timeinoutposts", DeconstructTime); AllowDeconstruct = true; RandomDeconstructionOutput = subElement.GetAttributeBool("chooserandom", false); RandomDeconstructionOutputAmount = subElement.GetAttributeInt("amount", 1); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs index 2bb9cb587..cad423406 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Items/RelatedItem.cs @@ -398,7 +398,7 @@ namespace Barotrauma } foreach (Item contained in container.Inventory.AllItems) { - if (TargetSlot > -1 && parentItem.OwnInventory.FindIndex(contained) != TargetSlot) { continue; } + if (TargetSlot > -1 && container.Inventory.FindIndex(contained) != TargetSlot) { continue; } if ((!ExcludeBroken || contained.Condition > 0.0f) && (!ExcludeFullCondition || !contained.IsFullCondition) && MatchesItem(contained)) { return true; } if (CheckContained(contained)) { return true; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs index 1c193d983..7c4391e1f 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Hull.cs @@ -123,6 +123,8 @@ namespace Barotrauma public const float OxygenDeteriorationSpeed = 0.3f; public const float OxygenConsumptionSpeed = 700.0f; + private const float DecalAlphaRemoveThreshold = 0.001f; + public const int WaveWidth = 32; public static float WaveStiffness = 0.01f; public static float WaveSpread = 0.02f; @@ -913,7 +915,7 @@ namespace Barotrauma for (int i = decals.Count - 1; i >= 0; i--) { var decal = decals[i]; - if (decal.FadeTimer >= decal.LifeTime || decal.BaseAlpha <= 0.001f) + if (decal.FadeTimer >= decal.LifeTime || decal.BaseAlpha <= DecalAlphaRemoveThreshold) { decals.RemoveAt(i); #if SERVER @@ -1159,7 +1161,10 @@ namespace Barotrauma Hull currentHull = current.hull; Vector2 currentPos = current.pos; - if (currentDist > maxDistance) { return float.MaxValue; } + if (currentDist > maxDistance) + { + return float.MaxValue; + } // If we've reached the target, add the final segment from hull to endPos if (currentHull == targetHull) @@ -1167,7 +1172,7 @@ namespace Barotrauma return currentDist + Vector2.Distance(currentPos, endPos); } - foreach (Gap g in ConnectedGaps) + foreach (Gap g in currentHull.ConnectedGaps) { float distanceMultiplier = 1; if (g.ConnectedDoor != null && !g.ConnectedDoor.IsBroken) @@ -1643,9 +1648,18 @@ namespace Barotrauma bool decalsCleaned = false; foreach (Decal decal in decals) { + // Don't attempt to clean the decal if it's already below the remove threshold, since the server + // is already gonna remove the decal for us, sending another decal update event would result in + // us potentially modifying a different decal since the indices can briefly desync. + if (decal.BaseAlpha <= DecalAlphaRemoveThreshold) + { + continue; + } + if (decal.AffectsSection(section)) { decal.Clean(cleanVal); + decalsCleaned = true; #if SERVER decalUpdatePending = true; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs index c5f0d161f..71f4b0752 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs @@ -7,6 +7,7 @@ using FarseerPhysics.Dynamics; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Globalization; using System.Linq; @@ -327,6 +328,7 @@ namespace Barotrauma public Submarine BeaconStation { get; private set; } private Sonar beaconSonar; + private ImmutableArray beaconTransducers = ImmutableArray.Empty; /// /// Special wall chunks that aren't part of the normal level geometry: includes things like the ocean floor, floating ice chunks and ice spires. @@ -4398,6 +4400,13 @@ namespace Barotrauma attempts++; } } + + foreach (var wreck in Wrecks) + { + wreck.SetCrushDepth(wreck.RealWorldDepth + 1000); + SetLinkedSubCrushDepth(wreck); + } + totalSW.Stop(); Debug.WriteLine($"{Wrecks.Count} wrecks created in { totalSW.ElapsedMilliseconds} (ms)"); } @@ -4782,6 +4791,7 @@ namespace Barotrauma return; } beaconSonar = sonarItem.GetComponent(); + beaconTransducers = sonarItem.GetConnectedComponents().ToImmutableArray(); } public void PrepareBeaconStation() @@ -4908,9 +4918,20 @@ namespace Barotrauma public bool CheckBeaconActive() { if (beaconSonar == null) { return false; } + if (beaconSonar.UseTransducers) + { + var connectedTransducers = beaconSonar.Item.GetConnectedComponents(); + foreach (var beaconTransducer in beaconTransducers) + { + if (!beaconTransducer.HasPower || !connectedTransducers.Contains(beaconTransducer)) { return false; } + } + } return beaconSonar.HasPower && beaconSonar.CurrentMode == Sonar.Mode.Active; } + /// + /// Set the crush depths of the connected subs to match the crush depth of the parent sub. + /// private void SetLinkedSubCrushDepth(Submarine parentSub) { foreach (var connectedSub in parentSub.GetConnectedSubs()) @@ -5149,6 +5170,7 @@ namespace Barotrauma BeaconStation = null; beaconSonar = null; + beaconTransducers = ImmutableArray.Empty; StartOutpost = null; EndOutpost = null; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs index 03e37859c..b7460e61c 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Levels/LevelData.cs @@ -413,8 +413,7 @@ namespace Barotrauma new XAttribute("difficulty", Difficulty.ToString("G", CultureInfo.InvariantCulture)), new XAttribute("size", XMLExtensions.PointToString(Size)), new XAttribute("generationparams", GenerationParams.Identifier), - new XAttribute("initialdepth", InitialDepth), - new XAttribute("exhaustedeventsets", allEventsExhausted)); + new XAttribute("initialdepth", InitialDepth)); newElement.Add( new XAttribute(nameof(exhaustedEventSets), string.Join(',', exhaustedEventSets.Select(e => e.Value)))); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs index 8339e6c65..b96b7f1a7 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/Structure.cs @@ -651,7 +651,7 @@ namespace Barotrauma newBody.Friction = 0.8f; newBody.UserData = this; - newBody.Position = ConvertUnits.ToSimUnits(stairPos) + BodyOffset * Scale; + newBody.Position = ConvertUnits.ToSimUnits(stairPos) + ConvertUnits.ToSimUnits(BodyOffset) * Scale; bodyDimensions.Add(newBody, new Vector2(bodyWidth, bodyHeight)); diff --git a/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs b/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs index 1dc9d991c..10c1728ae 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Map/WayPoint.cs @@ -583,7 +583,11 @@ namespace Barotrauma { for (int dir = -1; dir <= 1; dir += 2) { - WayPoint closest = stairPoints[i].FindClosest(dir, horizontalSearch: true, new Vector2(minDist * 1.5f, minDist / 2)); + //connect to the closest waypoint, preferring non-stair waypoyints + //(it's easier for characters to fully get off stairs before moving on to the next set of stairs, than to move directly from one set of stairs to another) + WayPoint closest = + stairPoints[i].FindClosest(dir, horizontalSearch: true, new Vector2(minDist * 1.5f, minDist / 2), filter: wp => wp.Stairs == null) ?? + stairPoints[i].FindClosest(dir, horizontalSearch: true, new Vector2(minDist * 1.5f, minDist / 2)); if (closest == null) { continue; } stairPoints[i].ConnectTo(closest); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetConfig.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetConfig.cs index 38e335927..efb3d7916 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetConfig.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetConfig.cs @@ -39,6 +39,12 @@ namespace Barotrauma.Networking public const int MaxEventPacketsPerUpdate = 4; + /// + /// When enabled, uses more lenient Lidgren handshake timeouts (longer connection timeout, more retry attempts). + /// Useful for local testing when running multiple instances on the same machine under heavy load. + /// + public static bool UseLenientHandshake; + /// /// Interpolates the positional error of a physics body towards zero. /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetIdUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetIdUtils.cs index e75202086..54f671521 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/NetIdUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/NetIdUtils.cs @@ -33,13 +33,7 @@ namespace Barotrauma.Networking /// regarding its relation to values other than the input. /// public static ushort GetIdOlderThan(ushort id) -#if DEBUG - // Debug implementation has some RNG to discourage bad assumptions about the return value - => unchecked((ushort)(id - 1 - Rand.Int(500, sync: Rand.RandSync.Unsynced))); -#else - // Release implementation favors performance => unchecked((ushort)(id - 1)); -#endif public static ushort Difference(ushort id1, ushort id2) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Auth/SteamAuthTicketForEosHostAuthenticator.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Auth/SteamAuthTicketForEosHostAuthenticator.cs index ff2788158..aa2d46143 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Auth/SteamAuthTicketForEosHostAuthenticator.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/Primitives/Auth/SteamAuthTicketForEosHostAuthenticator.cs @@ -15,13 +15,18 @@ sealed class SteamAuthTicketForEosHostAuthenticator : Authenticator { string ticketData = ToolBoxCore.ByteArrayToHexString(ticket.Data); - var client = new RestClient(ServerUrl); - - var request = new RestRequest(ServerFile, Method.GET); + var client = RestFactory.CreateClient(ServerUrl); + var request = RestFactory.CreateRequest(ServerFile); request.AddParameter("authticket", ticketData); request.AddParameter("request_version", RemoteRequestVersion); var response = await client.ExecuteAsync(request, Method.GET); + if (response.ErrorException != null) + { + DebugConsole.AddWarning($"Connection error: Failed to verify Steam auth ticket for EOS host " + + $"({response.ErrorException.Message})."); + return AccountInfo.None; + } if (!response.IsSuccessful) { return AccountInfo.None; } try diff --git a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs index 89a84fbfe..696b70a0d 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs @@ -454,7 +454,7 @@ namespace Barotrauma.Networking private set; } - [Serialize(300.0f, IsPropertySaveable.Yes)] + [Serialize(30.0f, IsPropertySaveable.Yes)] public float RespawnInterval { get; diff --git a/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs b/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs index 58f0f5280..5434b25a5 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Physics/PhysicsBody.cs @@ -534,8 +534,10 @@ namespace Barotrauma default: throw new NotImplementedException(); } - return spritesheetRotation.HasValue ? Vector2.Transform(pos, Matrix.CreateRotationZ(-spritesheetRotation.Value)) : pos; + return spritesheetRotation.HasValue ? RotateVector(pos, spritesheetRotation.Value) : pos; } + + public static Vector2 RotateVector(Vector2 v, float rotation) => Vector2.Transform(v, Matrix.CreateRotationZ(-rotation)); public float GetMaxExtent() { diff --git a/Barotrauma/BarotraumaShared/SharedSource/Prefabs/PrefabCollection.cs b/Barotrauma/BarotraumaShared/SharedSource/Prefabs/PrefabCollection.cs index 47b575be5..f3a0a4842 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Prefabs/PrefabCollection.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Prefabs/PrefabCollection.cs @@ -410,7 +410,7 @@ namespace Barotrauma && otherPrefab.UintIdentifier == prefabWithUintIdentifier.UintIdentifier); for (T? collision = findCollision(); collision != null; collision = findCollision()) { - DebugConsole.ThrowError($"Hashing collision when generating uint identifiers for {typeof(T).Name}: {prefab.Identifier} has the same UintIdentifier as {collision.Identifier} ({prefabWithUintIdentifier.UintIdentifier})"); + DebugConsole.AddWarning($"Hashing collision when generating uint identifiers for {typeof(T).Name}: {prefab.Identifier} has the same UintIdentifier as {collision.Identifier} ({prefabWithUintIdentifier.UintIdentifier})"); prefabWithUintIdentifier.UintIdentifier++; } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs index b04914057..a3a19cddb 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Serialization/XMLExtensions.cs @@ -36,6 +36,27 @@ namespace Barotrauma { typeof(Rectangle), (str, defVal) => ParseRect(str, true) } }.ToImmutableDictionary(); + /// + /// Check if the given value equals to the default value of the property. + /// Takes into account that certain default values (e.g. Vectors and other values that aren't compile-time constants) are defined as strings. + /// + public static bool DefaultValueEquals(object defaultValue, object value) + { + //if the value is given as a string, check if there's a converter for the type of the default value and attempt converting + if (defaultValue != null && value is string valueAsString && + Converters.TryGetKey(defaultValue.GetType(), out Type type)) + { + return Equals(Converters[type].Invoke(valueAsString, defaultValue), defaultValue); + } + //other way around: default values is given as a string, check if there's a converter for the type of the value + else if (value != null && defaultValue is string defaultValueAsString && + Converters.TryGetKey(value.GetType(), out Type type2)) + { + return Equals(Converters[type2].Invoke(defaultValueAsString, value), value); + } + return Equals(value, defaultValue); + } + public static string ParseContentPathFromUri(this XObject element) => !string.IsNullOrWhiteSpace(element.BaseUri) ? System.IO.Path.GetRelativePath(Environment.CurrentDirectory, element.BaseUri.CleanUpPath()) diff --git a/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs b/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs index eb44f9d6b..8f22a4da0 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs @@ -72,6 +72,7 @@ namespace Barotrauma EnableSplashScreen = true, PauseOnFocusLost = true, RemoteMainMenuContentUrl = "https://www.barotraumagame.com/gamedata/", + RemoteContentTimeoutSeconds = 15f, AimAssistAmount = DefaultAimAssist, ShowEnemyHealthBars = EnemyHealthBarMode.ShowAll, ChatSpeechBubbles = true, @@ -167,6 +168,17 @@ namespace Barotrauma public bool EnableSubmarineAutoSave; public Identifier QuickStartSub; public string RemoteMainMenuContentUrl; + + /// + /// Timeout in seconds for HTTP requests to remote content servers. + /// + public float RemoteContentTimeoutSeconds; + + /// + /// Returns converted to milliseconds needed by eg. RestSharp. + /// + public readonly int RemoteContentTimeoutMs => (int)(RemoteContentTimeoutSeconds * 1000); + #if CLIENT public Eos.EosSteamPrimaryLogin.CrossplayChoice CrossplayChoice; public XElement SavedCampaignSettings; diff --git a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs index bdd8bfac5..2fc581c16 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs @@ -841,6 +841,12 @@ namespace Barotrauma /// public Vector2 Offset { get; private set; } + /// + /// Should be rotated, flipped and scaled based on the entity that this effect is executed by? + /// Currently only supports status effects in items. + /// + public bool OffsetCopiesEntityTransform { get; private set; } + /// /// An random offset (in a random direction) added to the position of the effect is executed at. Only relevant if the effect does something where position matters, /// for example emitting particles or explosions, spawning something or playing sounds. @@ -901,6 +907,7 @@ namespace Barotrauma Range = element.GetAttributeFloat("range", 0.0f); Offset = element.GetAttributeVector2("offset", Vector2.Zero); + OffsetCopiesEntityTransform = element.GetAttributeBool(nameof(OffsetCopiesEntityTransform), false); RandomOffset = element.GetAttributeFloat("randomoffset", 0.0f); string[] targetLimbNames = element.GetAttributeStringArray("targetlimb", null) ?? element.GetAttributeStringArray("targetlimbs", null); if (targetLimbNames != null) @@ -1724,6 +1731,7 @@ namespace Barotrauma protected Vector2 GetPosition(Entity entity, IReadOnlyList targets, Vector2? worldPosition = null) { Vector2 position = worldPosition ?? (entity == null || entity.Removed ? Vector2.Zero : entity.WorldPosition); + if (worldPosition == null) { if (entity is Character character && !character.Removed && targetLimbs != null) @@ -1760,9 +1768,22 @@ namespace Barotrauma } } } - } - position += Offset; + + Vector2 offset = Offset; + + if (OffsetCopiesEntityTransform) + { + if (entity is Item item) + { + offset *= item.Scale; + if (item.FlippedX) { offset.X *= -1; } + if (item.FlippedY) { offset.Y *= -1; } + offset = Vector2.Transform(offset, Matrix.CreateRotationZ(-item.RotationRad)); + } + } + + position += offset; position += Rand.Vector(Rand.Range(0.0f, RandomOffset)); return position; } @@ -2060,24 +2081,9 @@ namespace Barotrauma { LocalizedString messageToSay = TextManager.Get(forceSayIdentifier).Fallback(forceSayIdentifier.Value); - if (!messageToSay.IsNullOrEmpty() && target is Character targetCharacter && targetCharacter.SpeechImpediment < 100.0f && !targetCharacter.IsDead) + if (!messageToSay.IsNullOrEmpty() && target is Character targetCharacter) { - ChatMessageType messageType = ChatMessageType.Default; - bool canUseRadio = ChatMessage.CanUseRadio(targetCharacter, out WifiComponent radio); - if (canUseRadio && forceSayInRadio) - { - messageType = ChatMessageType.Radio; - } -#if SERVER - GameMain.Server?.SendChatMessage(messageToSay.Value, messageType, senderClient: null, targetCharacter); -#elif CLIENT - //no need to create the message when playing as a client, the server will send it to us - if (isNotClient) - { - AIChatMessage message = new AIChatMessage(messageToSay.Value, messageType); - targetCharacter.SendSinglePlayerMessage(message, canUseRadio, radio); - } -#endif + targetCharacter.ForceSay(messageToSay, forceSayInRadio); } } @@ -2229,7 +2235,10 @@ namespace Barotrauma inheritedTeam = entity switch { Character c => c.TeamID, - Item it => it.GetRootInventoryOwner() is Character owner ? owner.TeamID : GetTeamFromSubmarine(it), + Item it => + (it.GetRootInventoryOwner() as Character ?? it.PreviousParentInventory?.Owner as Character) is { } owner ? + owner.TeamID : + GetTeamFromSubmarine(it), MapEntity e => GetTeamFromSubmarine(e), _ => null // Default to Team1, when we can't deduce the team (for example when spawning outside the sub AND character inventory). diff --git a/Barotrauma/BarotraumaShared/SharedSource/Text/LocalizedString/TrimLString.cs b/Barotrauma/BarotraumaShared/SharedSource/Text/LocalizedString/TrimLString.cs index 92c987b9e..4eca59969 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Text/LocalizedString/TrimLString.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Text/LocalizedString/TrimLString.cs @@ -9,19 +9,21 @@ namespace Barotrauma public enum Mode { Start = 0x1, End = 0x2, Both=0x3 } private readonly LocalizedString nestedStr; private readonly Mode mode; + private readonly char[]? trimCharacters; - public TrimLString(LocalizedString nestedStr, Mode mode) + public TrimLString(LocalizedString nestedStr, Mode mode, char[]? trimCharacters = null) { this.nestedStr = nestedStr; this.mode = mode; + this.trimCharacters = trimCharacters; } public override bool Loaded => nestedStr.Loaded; public override void RetrieveValue() { cachedValue = nestedStr.Value; - if (mode.HasFlag(Mode.Start)) { cachedValue = cachedValue.TrimStart(); } - if (mode.HasFlag(Mode.End)) { cachedValue = cachedValue.TrimEnd(); } + if (mode.HasFlag(Mode.Start)) { cachedValue = cachedValue.TrimStart(trimCharacters); } + if (mode.HasFlag(Mode.End)) { cachedValue = cachedValue.TrimEnd(trimCharacters); } UpdateLanguage(); } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Text/TextPack.cs b/Barotrauma/BarotraumaShared/SharedSource/Text/TextPack.cs index 19ee41a2f..5ba27c155 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/Text/TextPack.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/Text/TextPack.cs @@ -242,7 +242,10 @@ namespace Barotrauma } - Barotrauma.IO.File.WriteAllText($"csv_{Language.ToString().ToLower()}_{index}.csv", sb.ToString()); + string fileName = $"csv_{Language.ToString().ToLower()}_{index}.csv"; + Barotrauma.IO.File.WriteAllText(fileName, sb.ToString()); + + DebugConsole.NewMessage($"Wrote \"{ContentFile.Path}\" to \"{fileName}\""); } #endif } diff --git a/Barotrauma/BarotraumaShared/SharedSource/Utils/RestFactory.cs b/Barotrauma/BarotraumaShared/SharedSource/Utils/RestFactory.cs new file mode 100644 index 000000000..1c3c38f97 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/Utils/RestFactory.cs @@ -0,0 +1,35 @@ +using RestSharp; + +namespace Barotrauma +{ + /// + /// Factory methods for creating RestSharp clients and requests with default timeout + /// settings, to avoid unforeseen connectivity issues hanging the game. + /// The timeout needs to be added to both the client and the request, due to known + /// issues with RestSharp 106.x that we use: https://github.com/restsharp/RestSharp/issues/1900 + /// + public static class RestFactory + { + /// + /// Creates a RestClient with applied. + /// + public static RestClient CreateClient(string baseUrl) + { + return new RestClient(baseUrl) + { + Timeout = GameSettings.CurrentConfig.RemoteContentTimeoutMs + }; + } + + /// + /// Creates a RestRequest with applied. + /// + public static RestRequest CreateRequest(string resource, Method method = Method.GET) + { + return new RestRequest(resource, method) + { + Timeout = GameSettings.CurrentConfig.RemoteContentTimeoutMs + }; + } + } +} diff --git a/Barotrauma/BarotraumaShared/changelog.txt b/Barotrauma/BarotraumaShared/changelog.txt index fcd1b5730..3c6b19da0 100644 --- a/Barotrauma/BarotraumaShared/changelog.txt +++ b/Barotrauma/BarotraumaShared/changelog.txt @@ -1,4 +1,94 @@ ------------------------------------------------------------------------------------------------------------------------------------------------- +v1.12.6.2 +------------------------------------------------------------------------------------------------------------------------------------------------- + +- Updated localization files. + +------------------------------------------------------------------------------------------------------------------------------------------------- +v1.12.6.1 +------------------------------------------------------------------------------------------------------------------------------------------------- + +- Fixed some conversation prompts (such as the one with Artie Dolittle) being misaligned, causing parts the text to be cropped. More specifically, ones that start with some special event sprite but also show the speaker's face in the prompt. +- Fixed pets having trouble moving due to some of the navigation changes in the previous build. Also caused huskified containers to get launched off with enormous speed when they tried to eat something. +- Fixed navigation terminals in shuttles having their maintain position get messed up between level transitions. + +------------------------------------------------------------------------------------------------------------------------------------------------- +v1.12.6.0 (Spring Update 2026) +------------------------------------------------------------------------------------------------------------------------------------------------- + +Submarine reworks: +- Tier 1 submarines (Barsuk, Dugong, Orca and Camel) have all received a visual polish as well as gameplay polishing. +- Camel and Orca now uses the pipe weakpoints and valves system. +- Various improvements and additions to the vanilla item assemblies. + +Balance: +- Increase health of flare, alienflare and glowstick. Now doesn't get destroyed as quickly by monsters, allowing it be a more useful distraction again. Glowsticks don't aggro monsters from as far as flares. +- Sonar Beacon's sound range is reduced, and when dropped can now be destroyed by monsters (to avoid making flares redundant due to being a better and invulnerable version of monster distraction). + +Changes: +- Characters can now be "deconstructed" by dragging them into a deconstructor, producing small amounts of raw materials. Also a handy way to get rid of monster corpses on the submarine, and perhaps problematic crew mates as well. +- Stationary batteries can charge the battery cells inside them even when the output is disabled. +- A handful of missions in which you can earn a reward for getting through the level fast enough (which also serve as an example of the new custom mission functionality, see the Modding section for more information). +- Minor lighting optimizations. + +Multiplayer: +- Reduced the default respawn interval from 300 seconds to 30. +- Fixed an issue that sometimes caused the list of hidden subs to get out of sync in multiplayer, preventing some subs from being purchased. +- Fixed pickup sound not playing when picking up an item in multiplayer. +- Fixed karma system considering bandages and other medical items "dangerous" and giving a penalty for taking them from other players. +- Characters no longer drop items when the player disconnects (meaning you won't lose the items you were holding). + +Fixes: +- Another attempt to fix reported freezes at 80% in the loading screen, which seems to have been caused by Steam's servers or our master server refusing connection attempts from certain kinds of IPs, causing the game to hang waiting for the connection. +- Fixed conversation/event prompts sometimes getting stuck when you went rapidly pressing E. In particular, this happened with events that allow you to retrigger the same event by interacting with the NPC again. +- Fixed certain monsters (e.g. mudraptors) having trouble dropping through hatches inside the sub. +- Fixed monsters often failing to follow targets from sub to another (e.g. from Remora's drone to the main sub). +- Fixes to pathfinding bugs that sometimes caused bots to get stuck on stairs. +- Fixed closing the health interface while your cursor is on another character opening that character's health interface. +- Fixed an AI bug that often prevented outpost NPCs from putting out fires. +- Fixed projectiles that fire more than one raycast per shot (e.g. shotgun shells) only registering one hit if you're firing from inside to outside. +- Fixed implacable sometimes not triggering in time, causing a 5-second stun when vitality dropped below zero. +- Fixed radio jammer not having the traitormissionitem tag (unlike every other traitor mission item). +- Fixed ruin scan missions sometimes failing to choose all 3 positions to scan, making the mission impossible to complete. Happened with very small ruins in particular. +- Fixed Engineering_G4 module sometimes spawning with a ladder leading nowhere. +- Hide items inside non-interactable containers (e.g. decorative items not accessible to the player) showing up on the item finder. +- The achievement for killing a monster is also awarded if the monster is killed by an bot in single player. +- Fixed some items sometimes teleporting to the origin when saving and loading in the submarine editor. +- Fixed a broken waypoint near Berilia's engine which made bots sometimes get stuck there. +- Fixed shuttles/drones/elevators or other parts of a wreck getting crush depth damage in deep levels. +- Characters that respawn in a flooded hull (in either a submarine or an outpost) now spawn with diving gear. +- Fixed fabrication tooltip being unclear (previously showed "requires recipe to fabricate" in red even when the recipe is already learnt, now shows in green that is has been learned) +- Fixed characters sometimes not taking fall damage if they fall on a mirrored structure. +- Fixed portable pumps getting damaged by explosions, despite not being repairable. +- Fixed pet raptors getting assigned an incorrect team if they hatch in a hostile outpost. +- Fixed Linux systems failing to load content packages whose filelist.xml files aren't all lower-case. +- Fixed NPCs ignoring infected humans attacking them. +- Fixed mirrored items becoming unmirrored when swapped by perk points (e.g. a mirrored periscope base becoming an unmirrored periscope when purchasing a turret with a perk point). +- Fixed inability to rename already-hired bots if you no longer have the required reputation to hire the bot. +- Fixed WeaponDamageModifier (a multiplier in RangedWeapon which seems to be used for buffing the damage of variants of a weapon) not affecting explosion damage. Means that e.g. Harpoon-Coil Rifle or Autoshotgun's modifiers don't actually do much when using explosive ammo. +- Fixed creatures not being able to "properly leave a sub" if any of their severed limbs are still inside the sub. In practice, they'd still be considered to be inside the sub, and they would not collide with anything outside it. +- Fixed toggling layer visibility selecting that layer in the sub editor. +- Fixed pasting entities unhiding all of the layers in the sub editor. +- Fixed condition_out connections not taking into account the multipliers applied by the Tinkerer talent. +- Fixed bots still refusing to deconstruct items that yield nothing, even though you could order them to deconstruct those. + +Modding: +- Support for custom event-based missions. The mission simply triggers a specific event, and that event can control the success/failure of the mission using MissionStateAction. See the "TimeTrial" missions in Missions.xml for an usage example. +- Character, level and particle editors show fields set to the default values as gray. Makes it easier to tell which fields have been modified or are relevant for that specific character/level/particle. +- It's possible to add empty RequiredItem elements to item components to make them not have any requirements by default, but allowing them to be added in the submarine editor. +- Fixed limb's randomcolor attribute not working as expected: every character of the same type would get the same randomly chosen color, instead of a different color being chosen for each character. +- Added ForceSayAction which can be used by scripted events to make characters speak in the chat. ConversationAction can also now be used to display text in the chat in addition to the conversation prompt. +- CheckConditionalAction now fails instead of succeeding if it can't find the specified target. There's also a property called FailIfTargetNotFound to make it succeed instead. +- CountTargetsAction now fails instead of succeeding if it's trying to compare against the amount of some other target (e.g. "number of flooded hulls" is at least 30% of the "number of all hulls") and none of that other target can't be found. +- Fixed light offsets not being handled correctly on flipped items (did not affect any vanilla items). +- Adds a new status effect property called OffsetCopiesEntityTransform that can be used by status effects to configure the offset so it copies the current entity rotation/flipping/scaling. +- Fixed TargetSlot in RequiredItem not working properly on items that have multiple ItemContainers/inventories (only the first one was checked). +- Fixed melee weapons sometimes hitting characters whose limbs have been set to ignore collisions. More specifically, the weapon would still hit the character's "main collider". +- If a beacon station has a sonar transducer connected to the sonar monitor, and the monitor is set to use transducers, the transducer must be powered for the beacon mission to complete. +- Fix ContainedSpriteDepth being tied to the item's index in the container instead of the slot index. +- Fixed OnInserted StatusEffects triggering when you try to swap an item inside some other item but the swap fails. + +------------------------------------------------------------------------------------------------------------------------------------------------- v1.11.5.0 (Winter Update Hotfix 1) ------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Libraries/BarotraumaLibs/BarotraumaCore/BarotraumaCore.csproj b/Libraries/BarotraumaLibs/BarotraumaCore/BarotraumaCore.csproj index 43147b2b5..7cc2b89a1 100644 --- a/Libraries/BarotraumaLibs/BarotraumaCore/BarotraumaCore.csproj +++ b/Libraries/BarotraumaLibs/BarotraumaCore/BarotraumaCore.csproj @@ -1,28 +1,28 @@ - - - - net8.0 - Barotrauma - disable - enable - - - - full - ;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 - true - x64 - - - - full - ;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 - true - x64 - - - - - - - + + + + net8.0 + Barotrauma + disable + enable + + + + full + ;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 + true + x64 + + + + full + ;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 + true + x64 + + + + + + + diff --git a/Libraries/BarotraumaLibs/BarotraumaCore/Utils/ToolBoxCore.cs b/Libraries/BarotraumaLibs/BarotraumaCore/Utils/ToolBoxCore.cs index d4c7f54f2..e22177450 100644 --- a/Libraries/BarotraumaLibs/BarotraumaCore/Utils/ToolBoxCore.cs +++ b/Libraries/BarotraumaLibs/BarotraumaCore/Utils/ToolBoxCore.cs @@ -47,11 +47,17 @@ public static class ToolBoxCore byte[] inputBytes = Encoding.UTF8.GetBytes(str); byte[] hash = md5.ComputeHash(inputBytes); - UInt32 key = (UInt32)((str.Length & 0xff) << 24); //could use more of the hash here instead? - key |= (UInt32)(hash[hash.Length - 3] << 16); - key |= (UInt32)(hash[hash.Length - 2] << 8); - key |= (UInt32)(hash[hash.Length - 1]); - + //xor all of the bits of the hash together + UInt32 key = 0; + foreach (byte b in hash) + { + // Rotate the 32-bit value left by 5 bits: + // (key << 5) moves everything left, + // (key >> 27) brings the 5 bits that overflowed back around (32 - 5 = 27), + // OR'ing them together completes the rotate. + key = (key << 5) | (key >> 27); + key ^= b; + } return key; } diff --git a/Libraries/BarotraumaLibs/EosInterface/EosInterface.csproj b/Libraries/BarotraumaLibs/EosInterface/EosInterface.csproj index e366bb52f..1976acfca 100644 --- a/Libraries/BarotraumaLibs/EosInterface/EosInterface.csproj +++ b/Libraries/BarotraumaLibs/EosInterface/EosInterface.csproj @@ -1,26 +1,26 @@ - - - - net8.0 - disable - enable - Barotrauma - - - - ;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 - true - x64 - - - - ;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 - true - x64 - - - - - - - + + + + net8.0 + disable + enable + Barotrauma + + + + ;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 + true + x64 + + + + ;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765 + true + x64 + + + + + + + diff --git a/Libraries/Concentus/.gitignore b/Libraries/Concentus/.gitignore index 629fe1100..fcd9a1be9 100644 --- a/Libraries/Concentus/.gitignore +++ b/Libraries/Concentus/.gitignore @@ -1,240 +1,240 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ - -# Visual Studio 2015 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# DNX -project.lock.json -artifacts/ - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config -# NuGet v3's project.json files produces more ignoreable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Microsoft Azure ApplicationInsights config file -ApplicationInsights.config - -# Windows Store app package directory -AppPackages/ -BundleArtifacts/ - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.pfx -*.publishsettings -node_modules/ -orleans.codegen.cs - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe - -# FAKE - F# Make -.fake/ -/Java/Concentus/target/ -/Java/ContentusTestConsole/ContentusTestConsole/target/ -/Java/ContentusTestConsole/ConcentusTestConsole/target/ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Microsoft Azure ApplicationInsights config file +ApplicationInsights.config + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ +/Java/Concentus/target/ +/Java/ContentusTestConsole/ContentusTestConsole/target/ +/Java/ContentusTestConsole/ConcentusTestConsole/target/ /Java/ConcentusTestConsole/target/ \ No newline at end of file diff --git a/Libraries/Concentus/CSharp/Concentus/Concentus.NetStandard.csproj b/Libraries/Concentus/CSharp/Concentus/Concentus.NetStandard.csproj index 725dda808..99073a079 100644 --- a/Libraries/Concentus/CSharp/Concentus/Concentus.NetStandard.csproj +++ b/Libraries/Concentus/CSharp/Concentus/Concentus.NetStandard.csproj @@ -1,20 +1,20 @@ - - - - netstandard2.1 - AnyCPU;x64 - Concentus - Logan Stromberg - 1.1.6.0 - Copyright © Xiph.Org Foundation, Skype Limited, CSIRO, Microsoft Corp. - This package is a pure portable C# implementation of the Opus audio compression codec (see https://opus-codec.org/ for more details). This package contains the Opus encoder, decoder, multistream codecs, repacketizer, as well as a port of the libspeexdsp resampler. It does NOT contain code to parse .ogg or .opus container files or to manage RTP packet streams - - https://github.com/lostromb/concentus - - - - full - true - - - + + + + netstandard2.1 + AnyCPU;x64 + Concentus + Logan Stromberg + 1.1.6.0 + Copyright © Xiph.Org Foundation, Skype Limited, CSIRO, Microsoft Corp. + This package is a pure portable C# implementation of the Opus audio compression codec (see https://opus-codec.org/ for more details). This package contains the Opus encoder, decoder, multistream codecs, repacketizer, as well as a port of the libspeexdsp resampler. It does NOT contain code to parse .ogg or .opus container files or to manage RTP packet streams + + https://github.com/lostromb/concentus + + + + full + true + + + diff --git a/Libraries/Farseer Physics Engine 3.5/Farseer.NetStandard.csproj b/Libraries/Farseer Physics Engine 3.5/Farseer.NetStandard.csproj index 96e8ca92c..69fbafa2f 100644 --- a/Libraries/Farseer Physics Engine 3.5/Farseer.NetStandard.csproj +++ b/Libraries/Farseer Physics Engine 3.5/Farseer.NetStandard.csproj @@ -1,40 +1,40 @@ - - - - netstandard2.1 - FarseerPhysics - Copyright Ian Qvist © 2013 - Farseer Physics Engine - - 3.5.0.0 - Ian Qvist - AnyCPU;x64 - - - - TRACE - portable - true - - - - - - - - - - - - - - - - - - - - - - - + + + + netstandard2.1 + FarseerPhysics + Copyright Ian Qvist © 2013 + Farseer Physics Engine + + 3.5.0.0 + Ian Qvist + AnyCPU;x64 + + + + TRACE + portable + true + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Libraries/GameAnalytics/GA_SDK_NETSTANDARD/GA_SDK_NETSTANDARD.csproj b/Libraries/GameAnalytics/GA_SDK_NETSTANDARD/GA_SDK_NETSTANDARD.csproj index 6b99d030a..1a989ea0f 100644 --- a/Libraries/GameAnalytics/GA_SDK_NETSTANDARD/GA_SDK_NETSTANDARD.csproj +++ b/Libraries/GameAnalytics/GA_SDK_NETSTANDARD/GA_SDK_NETSTANDARD.csproj @@ -1,35 +1,35 @@ - - - - netstandard2.1 - GameAnalytics.NetStandard - GameAnalytics.Net - AnyCPU;x64 - Game Analytics - Copyright (c) 2016 Game Analytics - - - - TRACE;MONO - - - - TRACE;MONO - - - - TRACE;MONO - - - - TRACE;MONO - - - - - - - - - - + + + + netstandard2.1 + GameAnalytics.NetStandard + GameAnalytics.Net + AnyCPU;x64 + Game Analytics + Copyright (c) 2016 Game Analytics + + + + TRACE;MONO + + + + TRACE;MONO + + + + TRACE;MONO + + + + TRACE;MONO + + + + + + + + + + diff --git a/Libraries/SharpFont/Source/SharpFont/SharpFont.NetStandard.csproj b/Libraries/SharpFont/Source/SharpFont/SharpFont.NetStandard.csproj index a4477adaa..6f2368424 100644 --- a/Libraries/SharpFont/Source/SharpFont/SharpFont.NetStandard.csproj +++ b/Libraries/SharpFont/Source/SharpFont/SharpFont.NetStandard.csproj @@ -1,45 +1,45 @@ - - - - netstandard2.1 - SharpFont - SharpFont - Cross-platform FreeType bindings for C# - Robmaister - SharpFont - - Copyright (c) Robert Rouhani 2012-2016 - AnyCPU;x64 - - - - TRACE;DEBUG;SHARPFONT_PORTABLE - true - - - - TRACE;DEBUG;SHARPFONT_PORTABLE - true - 1701;1702;3021 - - - - TRACE;SHARPFONT_PORTABLE - true - - - - TRACE;SHARPFONT_PORTABLE - true - 1701;1702;3021 - - - - - - - - - - - + + + + netstandard2.1 + SharpFont + SharpFont + Cross-platform FreeType bindings for C# + Robmaister + SharpFont + + Copyright (c) Robert Rouhani 2012-2016 + AnyCPU;x64 + + + + TRACE;DEBUG;SHARPFONT_PORTABLE + true + + + + TRACE;DEBUG;SHARPFONT_PORTABLE + true + 1701;1702;3021 + + + + TRACE;SHARPFONT_PORTABLE + true + + + + TRACE;SHARPFONT_PORTABLE + true + 1701;1702;3021 + + + + + + + + + + + diff --git a/Libraries/XNATypes/XNATypes.csproj b/Libraries/XNATypes/XNATypes.csproj index 57fd8b083..b3b90d794 100644 --- a/Libraries/XNATypes/XNATypes.csproj +++ b/Libraries/XNATypes/XNATypes.csproj @@ -1,10 +1,10 @@ - - - - netstandard2.1 - AnyCPU;x64 - - - - - + + + + netstandard2.1 + AnyCPU;x64 + + + + + diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/common.props b/Libraries/webm_mem_playback/opus/win32/VS2015/common.props index 03cd45b0c..6c757d8b7 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/common.props +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/common.props @@ -1,82 +1,82 @@ - - - - - - $(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\$(ProjectName)\ - Unicode - - - true - true - false - - - false - false - true - - - - Level3 - false - false - ..\..;..\..\include;..\..\silk;..\..\celt;..\..\win32;%(AdditionalIncludeDirectories) - HAVE_CONFIG_H;WIN32;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) - false - false - - - Console - - - true - Console - - - - - Guard - ProgramDatabase - NoExtensions - false - true - false - Disabled - false - false - Disabled - MultiThreadedDebug - MultiThreadedDebugDLL - true - false - - - true - - - - - false - None - true - true - false - Speed - Fast - Precise - true - true - true - MaxSpeed - MultiThreaded - MultiThreadedDLL - 16Bytes - - - false - - - + + + + + + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\$(ProjectName)\ + Unicode + + + true + true + false + + + false + false + true + + + + Level3 + false + false + ..\..;..\..\include;..\..\silk;..\..\celt;..\..\win32;%(AdditionalIncludeDirectories) + HAVE_CONFIG_H;WIN32;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + false + false + + + Console + + + true + Console + + + + + Guard + ProgramDatabase + NoExtensions + false + true + false + Disabled + false + false + Disabled + MultiThreadedDebug + MultiThreadedDebugDLL + true + false + + + true + + + + + false + None + true + true + false + Speed + Fast + Precise + true + true + true + MaxSpeed + MultiThreaded + MultiThreadedDLL + 16Bytes + + + false + + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/opus.vcxproj b/Libraries/webm_mem_playback/opus/win32/VS2015/opus.vcxproj index fc2241116..ae420d508 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/opus.vcxproj +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/opus.vcxproj @@ -1,399 +1,399 @@ - - - - - DebugDLL_fixed - Win32 - - - DebugDLL_fixed - x64 - - - DebugDLL - Win32 - - - DebugDLL - x64 - - - Debug - Win32 - - - Debug - x64 - - - ReleaseDLL_fixed - Win32 - - - ReleaseDLL_fixed - x64 - - - ReleaseDLL - Win32 - - - ReleaseDLL - x64 - - - Release - Win32 - - - Release - x64 - - - - Win32Proj - opus - {219EC965-228A-1824-174D-96449D05F88A} - - - - StaticLibrary - v142 - - - DynamicLibrary - v142 - - - DynamicLibrary - v142 - - - StaticLibrary - v142 - - - DynamicLibrary - v142 - - - DynamicLibrary - v142 - - - StaticLibrary - v142 - - - DynamicLibrary - v142 - - - DynamicLibrary - v142 - - - StaticLibrary - v142 - - - DynamicLibrary - v142 - - - DynamicLibrary - v142 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ..\..\silk\fixed;..\..\silk\float;%(AdditionalIncludeDirectories) - DLL_EXPORT;%(PreprocessorDefinitions) - FIXED_POINT;%(PreprocessorDefinitions) - /arch:IA32 %(AdditionalOptions) - - - /ignore:4221 %(AdditionalOptions) - - - "$(ProjectDir)..\..\win32\genversion.bat" "$(ProjectDir)..\..\win32\version.h" PACKAGE_VERSION - Generating version.h - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 4244;%(DisableSpecificWarnings) - - - - - - - - - - - - - - - false - - - false - - - true - - - - - - - true - - - true - - - false - - - - - - - + + + + + DebugDLL_fixed + Win32 + + + DebugDLL_fixed + x64 + + + DebugDLL + Win32 + + + DebugDLL + x64 + + + Debug + Win32 + + + Debug + x64 + + + ReleaseDLL_fixed + Win32 + + + ReleaseDLL_fixed + x64 + + + ReleaseDLL + Win32 + + + ReleaseDLL + x64 + + + Release + Win32 + + + Release + x64 + + + + Win32Proj + opus + {219EC965-228A-1824-174D-96449D05F88A} + + + + StaticLibrary + v142 + + + DynamicLibrary + v142 + + + DynamicLibrary + v142 + + + StaticLibrary + v142 + + + DynamicLibrary + v142 + + + DynamicLibrary + v142 + + + StaticLibrary + v142 + + + DynamicLibrary + v142 + + + DynamicLibrary + v142 + + + StaticLibrary + v142 + + + DynamicLibrary + v142 + + + DynamicLibrary + v142 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ..\..\silk\fixed;..\..\silk\float;%(AdditionalIncludeDirectories) + DLL_EXPORT;%(PreprocessorDefinitions) + FIXED_POINT;%(PreprocessorDefinitions) + /arch:IA32 %(AdditionalOptions) + + + /ignore:4221 %(AdditionalOptions) + + + "$(ProjectDir)..\..\win32\genversion.bat" "$(ProjectDir)..\..\win32\version.h" PACKAGE_VERSION + Generating version.h + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4244;%(DisableSpecificWarnings) + + + + + + + + + + + + + + + false + + + false + + + true + + + + + + + true + + + true + + + false + + + + + + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/opus.vcxproj.filters b/Libraries/webm_mem_playback/opus/win32/VS2015/opus.vcxproj.filters index 47185c67d..97eb46551 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/opus.vcxproj.filters +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/opus.vcxproj.filters @@ -1,744 +1,744 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/opus_demo.vcxproj b/Libraries/webm_mem_playback/opus/win32/VS2015/opus_demo.vcxproj index fcd971bb6..7ad4b5e21 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/opus_demo.vcxproj +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/opus_demo.vcxproj @@ -1,171 +1,171 @@ - - - - - DebugDLL_fixed - Win32 - - - DebugDLL_fixed - x64 - - - DebugDLL - Win32 - - - DebugDLL - x64 - - - Debug - Win32 - - - Debug - x64 - - - ReleaseDLL_fixed - Win32 - - - ReleaseDLL_fixed - x64 - - - ReleaseDLL - Win32 - - - ReleaseDLL - x64 - - - Release - Win32 - - - Release - x64 - - - - - {219ec965-228a-1824-174d-96449d05f88a} - - - - - - - {016C739D-6389-43BF-8D88-24B2BF6F620F} - Win32Proj - opus_demo - - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + DebugDLL_fixed + Win32 + + + DebugDLL_fixed + x64 + + + DebugDLL + Win32 + + + DebugDLL + x64 + + + Debug + Win32 + + + Debug + x64 + + + ReleaseDLL_fixed + Win32 + + + ReleaseDLL_fixed + x64 + + + ReleaseDLL + Win32 + + + ReleaseDLL + x64 + + + Release + Win32 + + + Release + x64 + + + + + {219ec965-228a-1824-174d-96449d05f88a} + + + + + + + {016C739D-6389-43BF-8D88-24B2BF6F620F} + Win32Proj + opus_demo + + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/opus_demo.vcxproj.filters b/Libraries/webm_mem_playback/opus/win32/VS2015/opus_demo.vcxproj.filters index dbcc8ae92..2eb113ac8 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/opus_demo.vcxproj.filters +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/opus_demo.vcxproj.filters @@ -1,22 +1,22 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_api.vcxproj b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_api.vcxproj index e428bd3f7..4ba7c8ae5 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_api.vcxproj +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_api.vcxproj @@ -1,171 +1,171 @@ - - - - - DebugDLL_fixed - Win32 - - - DebugDLL_fixed - x64 - - - DebugDLL - Win32 - - - DebugDLL - x64 - - - Debug - Win32 - - - Debug - x64 - - - ReleaseDLL_fixed - Win32 - - - ReleaseDLL_fixed - x64 - - - ReleaseDLL - Win32 - - - ReleaseDLL - x64 - - - Release - Win32 - - - Release - x64 - - - - - - - - {219ec965-228a-1824-174d-96449d05f88a} - - - - {1D257A17-D254-42E5-82D6-1C87A6EC775A} - Win32Proj - test_opus_api - - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + DebugDLL_fixed + Win32 + + + DebugDLL_fixed + x64 + + + DebugDLL + Win32 + + + DebugDLL + x64 + + + Debug + Win32 + + + Debug + x64 + + + ReleaseDLL_fixed + Win32 + + + ReleaseDLL_fixed + x64 + + + ReleaseDLL + Win32 + + + ReleaseDLL + x64 + + + Release + Win32 + + + Release + x64 + + + + + + + + {219ec965-228a-1824-174d-96449d05f88a} + + + + {1D257A17-D254-42E5-82D6-1C87A6EC775A} + Win32Proj + test_opus_api + + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_api.vcxproj.filters b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_api.vcxproj.filters index 070c8ab01..383d19f71 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_api.vcxproj.filters +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_api.vcxproj.filters @@ -1,14 +1,14 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - - - Source Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_decode.vcxproj b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_decode.vcxproj index cbf562183..8e4640094 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_decode.vcxproj +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_decode.vcxproj @@ -1,171 +1,171 @@ - - - - - DebugDLL_fixed - Win32 - - - DebugDLL_fixed - x64 - - - DebugDLL - Win32 - - - DebugDLL - x64 - - - Debug - Win32 - - - Debug - x64 - - - ReleaseDLL_fixed - Win32 - - - ReleaseDLL_fixed - x64 - - - ReleaseDLL - Win32 - - - ReleaseDLL - x64 - - - Release - Win32 - - - Release - x64 - - - - - - - - {219ec965-228a-1824-174d-96449d05f88a} - - - - {8578322A-1883-486B-B6FA-E0094B65C9F2} - Win32Proj - test_opus_api - - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + DebugDLL_fixed + Win32 + + + DebugDLL_fixed + x64 + + + DebugDLL + Win32 + + + DebugDLL + x64 + + + Debug + Win32 + + + Debug + x64 + + + ReleaseDLL_fixed + Win32 + + + ReleaseDLL_fixed + x64 + + + ReleaseDLL + Win32 + + + ReleaseDLL + x64 + + + Release + Win32 + + + Release + x64 + + + + + + + + {219ec965-228a-1824-174d-96449d05f88a} + + + + {8578322A-1883-486B-B6FA-E0094B65C9F2} + Win32Proj + test_opus_api + + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_decode.vcxproj.filters b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_decode.vcxproj.filters index 588637e83..3036a4e70 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_decode.vcxproj.filters +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_decode.vcxproj.filters @@ -1,14 +1,14 @@ - - - - - {4a0dd677-931f-4728-afe5-b761149fc7eb} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - - - Source Files - - + + + + + {4a0dd677-931f-4728-afe5-b761149fc7eb} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_encode.vcxproj b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_encode.vcxproj index 5a313c31d..6804918a3 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_encode.vcxproj +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_encode.vcxproj @@ -1,172 +1,172 @@ - - - - - DebugDLL_fixed - Win32 - - - DebugDLL_fixed - x64 - - - DebugDLL - Win32 - - - DebugDLL - x64 - - - Debug - Win32 - - - Debug - x64 - - - ReleaseDLL_fixed - Win32 - - - ReleaseDLL_fixed - x64 - - - ReleaseDLL - Win32 - - - ReleaseDLL - x64 - - - Release - Win32 - - - Release - x64 - - - - - - - - - {219ec965-228a-1824-174d-96449d05f88a} - - - - {84DAA768-1A38-4312-BB61-4C78BB59E5B8} - Win32Proj - test_opus_api - - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - Application - v142 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + DebugDLL_fixed + Win32 + + + DebugDLL_fixed + x64 + + + DebugDLL + Win32 + + + DebugDLL + x64 + + + Debug + Win32 + + + Debug + x64 + + + ReleaseDLL_fixed + Win32 + + + ReleaseDLL_fixed + x64 + + + ReleaseDLL + Win32 + + + ReleaseDLL + x64 + + + Release + Win32 + + + Release + x64 + + + + + + + + + {219ec965-228a-1824-174d-96449d05f88a} + + + + {84DAA768-1A38-4312-BB61-4C78BB59E5B8} + Win32Proj + test_opus_api + + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + Application + v142 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_encode.vcxproj.filters b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_encode.vcxproj.filters index f04776380..4ed3bb9e7 100644 --- a/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_encode.vcxproj.filters +++ b/Libraries/webm_mem_playback/opus/win32/VS2015/test_opus_encode.vcxproj.filters @@ -1,17 +1,17 @@ - - - - - {546c8d9a-103e-4f78-972b-b44e8d3c8aba} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - - - Source Files - - - Source Files - - + + + + + {546c8d9a-103e-4f78-972b-b44e8d3c8aba} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + + Source Files + + \ No newline at end of file diff --git a/Libraries/webm_mem_playback/opus/win32/genversion.bat b/Libraries/webm_mem_playback/opus/win32/genversion.bat index aea557393..1def7460b 100644 --- a/Libraries/webm_mem_playback/opus/win32/genversion.bat +++ b/Libraries/webm_mem_playback/opus/win32/genversion.bat @@ -1,37 +1,37 @@ -@echo off - -setlocal enableextensions enabledelayedexpansion - -for /f %%v in ('cd "%~dp0.." ^&^& git status ^>NUL 2^>NUL ^&^& git describe --tags --match "v*" --dirty 2^>NUL') do set version=%%v - -if not "%version%"=="" set version=!version:~1! && goto :gotversion - -if exist "%~dp0..\package_version" goto :getversion - -echo Git cannot be found, nor can package_version. Generating unknown version. - -set version=unknown - -goto :gotversion - -:getversion - -for /f "delims== tokens=2" %%v in (%~dps0..\package_version) do set version=%%v -set version=!version:"=! - -:gotversion - -set version=!version: =! -set version_out=#define %~2 "%version%" - -echo %version_out%> "%~1_temp" - -echo n | comp "%~1_temp" "%~1" > NUL 2> NUL - -if not errorlevel 1 goto exit - -copy /y "%~1_temp" "%~1" - -:exit - -del "%~1_temp" +@echo off + +setlocal enableextensions enabledelayedexpansion + +for /f %%v in ('cd "%~dp0.." ^&^& git status ^>NUL 2^>NUL ^&^& git describe --tags --match "v*" --dirty 2^>NUL') do set version=%%v + +if not "%version%"=="" set version=!version:~1! && goto :gotversion + +if exist "%~dp0..\package_version" goto :getversion + +echo Git cannot be found, nor can package_version. Generating unknown version. + +set version=unknown + +goto :gotversion + +:getversion + +for /f "delims== tokens=2" %%v in (%~dps0..\package_version) do set version=%%v +set version=!version:"=! + +:gotversion + +set version=!version: =! +set version_out=#define %~2 "%version%" + +echo %version_out%> "%~1_temp" + +echo n | comp "%~1_temp" "%~1" > NUL 2> NUL + +if not errorlevel 1 goto exit + +copy /y "%~1_temp" "%~1" + +:exit + +del "%~1_temp" diff --git a/Libraries/webm_mem_playback/webm_mem_playback/webm-mem-playback.vcxproj b/Libraries/webm_mem_playback/webm_mem_playback/webm-mem-playback.vcxproj index 5a3253ee6..6e90faa56 100644 --- a/Libraries/webm_mem_playback/webm_mem_playback/webm-mem-playback.vcxproj +++ b/Libraries/webm_mem_playback/webm_mem_playback/webm-mem-playback.vcxproj @@ -1,148 +1,148 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {D0097438-DA4F-4E6D-87AC-7D99DDD276B2} - vpxmemplayback - 10.0 - webm_mem_playback - - - - DynamicLibrary - true - v142 - MultiByte - - - DynamicLibrary - false - v142 - true - MultiByte - - - DynamicLibrary - true - v142 - MultiByte - - - DynamicLibrary - false - v142 - true - MultiByte - - - - - - - - - - - - - - - - - - - - - $(ProjectName)_$(Platform) - - - - Level3 - Disabled - true - true - ..\libwebm_x86_64_vs15;..\libvpx_x86_64_vs15;%(AdditionalIncludeDirectories) - - - %(AdditionalDependencies) - - - - - Level3 - Disabled - true - true - %(AdditionalIncludeDirectories) - MultiThreadedDebug - - - %(AdditionalDependencies) - - - - - Level3 - MaxSpeed - true - true - true - true - ..\libwebm_x86_64_vs15;..\libvpx_x86_64_vs15;%(AdditionalIncludeDirectories) - - - true - true - %(AdditionalDependencies) - - - - - Level3 - MaxSpeed - true - true - true - true - ..\libwebm_x86_vs19;..\libvpx_x64_vs15;..\opus\include;%(AdditionalIncludeDirectories) - MultiThreaded - Speed - - - true - true - ../libvpx_x64_vs15/$(Platform)/$(Configuration)/vpxmt.lib;../libwebm_x64_vs19/Release/libwebm.lib;../opus/win32/VS2015/x64/Release/opus.lib;%(AdditionalDependencies) - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {D0097438-DA4F-4E6D-87AC-7D99DDD276B2} + vpxmemplayback + 10.0 + webm_mem_playback + + + + DynamicLibrary + true + v142 + MultiByte + + + DynamicLibrary + false + v142 + true + MultiByte + + + DynamicLibrary + true + v142 + MultiByte + + + DynamicLibrary + false + v142 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + $(ProjectName)_$(Platform) + + + + Level3 + Disabled + true + true + ..\libwebm_x86_64_vs15;..\libvpx_x86_64_vs15;%(AdditionalIncludeDirectories) + + + %(AdditionalDependencies) + + + + + Level3 + Disabled + true + true + %(AdditionalIncludeDirectories) + MultiThreadedDebug + + + %(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + true + ..\libwebm_x86_64_vs15;..\libvpx_x86_64_vs15;%(AdditionalIncludeDirectories) + + + true + true + %(AdditionalDependencies) + + + + + Level3 + MaxSpeed + true + true + true + true + ..\libwebm_x86_vs19;..\libvpx_x64_vs15;..\opus\include;%(AdditionalIncludeDirectories) + MultiThreaded + Speed + + + true + true + ../libvpx_x64_vs15/$(Platform)/$(Configuration)/vpxmt.lib;../libwebm_x64_vs19/Release/libwebm.lib;../opus/win32/VS2015/x64/Release/opus.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + \ No newline at end of file From 87e0191a975da1c081d59f7849c2d3c87464d393 Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 9 Apr 2026 08:35:17 -0400 Subject: [PATCH 285/288] - Debug tests. --- .../LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua index 600035344..98d641dd8 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/Lua/init.lua @@ -31,9 +31,15 @@ print("package", package.Name) local success, config = ConfigService.TryGetConfig(SettingBase.Single, package, "TestFloat") -print("config ", success, " ", config) +local success2, config2 = ConfigService.TryGetConfig(SettingBase.Int32, package, "TestSynchroServer") +local success3, config3 = ConfigService.TryGetConfig(SettingBase.Int32, package, "TestSynchroClient") + +print("config ", success, " ", config.Value) +print("config testsynchrosrv", success2, " ", config2.Value) +print("config testsynchrocli", success3, " ", config3.Value) local lastTime = 0 + Hook.Add("think", "printconfig", function() if lastTime > Timer.Time then return end From a9634988e9941cc224358b8b2141398a4379b638 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Thu, 9 Apr 2026 11:18:18 -0300 Subject: [PATCH 286/288] Fix signalReceived hook not being called --- .../SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs index eb58aebcd..00e3536fa 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/HarmonyEventPatchesService.cs @@ -244,7 +244,6 @@ internal class HarmonyEventPatchesService : ISystem { Connection recipient = wire.OtherConnection(__instance); if (recipient == null) { continue; } - if (recipient.Item == __instance.Item || signal.source?.LastSentSignalRecipients.LastOrDefault() == recipient) { continue; } _eventService.PublishEvent(x => x.OnSignalReceived(signal, recipient)); _eventService.Call("signalReceived." + recipient.Item.Prefab.Identifier, signal, recipient); From 928cfb4fdeef8b1ea10ed047f8b3a053dc6236e8 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Thu, 9 Apr 2026 12:09:00 -0300 Subject: [PATCH 287/288] Re-add loaded Lua hook call --- .../SharedSource/LuaCs/_Services/LuaScriptManagementService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs index 7a7eed6d5..adc0fd010 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/_Services/LuaScriptManagementService.cs @@ -533,6 +533,8 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService, } } + _eventService.Call("loaded"); + return result; } From 9b35f6b23f1cd5f4f2d08c7529642f0268c591f5 Mon Sep 17 00:00:00 2001 From: NotAlwaysTrue <77662224+NotAlwaysTrue@users.noreply.github.com> Date: Sat, 25 Apr 2026 12:10:24 +0800 Subject: [PATCH 288/288] Sync with upstream * Update bug-reports.yml * Fix modifyChatMessage hook * Add LuaCsSetup.Lua back for compatibility * Fix Game.AssignOnExecute having command arguments be passed as varargs instead of a table * Actually use the PackageId const everywhere we need to refer to our content package * Load languages files even if the package is disabled * Fix Hook.Remove not being implemented properly * - Changed event aliases to be case insensitive. * - Fixed assembly logging style. - Fixed double logging during execution. * Fix garbage network data being read by the game when reading LuaCs network messages * PackageId -> PackageName * Added caching toggle to PluginManagementService * Fix LuaCs initializing too late for singleplayer campaigns and rework the C# prompt to only show when enabling mods/joining server * Oops, fix NRE crash * Fix hide username in logs config not doing anything * Fix Cs prompt showing up more than one between rounds * Fix server host being prompted twice with the C# popup * Ignore our workshop packages from the game's dependency thing since it doesn't really make sense * Load console commands after executing and possible fix for the not console command permitted * Added fallback friendly name resolution for ModConfig assembly contents. * Register Voronoi2 stuff * Added configinfo null check to SettingBase.cs * Add safety check so this stops crashing when we look at it the wrong way * Fixed "Folder" attribute files not being found. * Keep the LuaCsConfig class laying around for compatibility, not sure anywhere in our code base (and shouldn't be) * Added fallback compilation for UseInternalsAwareAssembly if the publicized script compilation fails. * Added legacy overload of AddCommand for mod compat. * Added LoggerService to Lua env. Made ILoggerService compliant with LuaCsLogger API. * Changed csharp script compilation algorithm to be best effort. * Added "RunUnrestricted" mode for lua scripts that need to run outside of sandbox. * - Fixed networking sync vars failing to sync initially. - Fixed lua failing to differentiate overloads ISettingBase. * Add alias for human.CPRSuccess and human.CPRFailed * - Fixed up the settings menu. - Made SettingEntry throw an error if "Value" attribute is not found in XML. - Fixed saved values for settings sometimes not reloading after disabling and re-enabling a package. * Fix LuaCs net messages received during connection initialization to be read incorrectly, happened because we would reset the BitPosition in our harmony patch which would cause the message to be read incorrectly later * Allow reloadlua to force the state to running * New icon for settings and make the top left text more user friendly * Fix client.packages hook sending normal packages * Fixed OnUpdate() not passing in deltaTime instead of totalTime. * Missing diffs from bb21a09244 * Added networking tests for configs. * Added missing diffs for f61f852a256a03111072041ba892b062d25e33bc. * Some tweaks to the text * Remove missing Value error, it should just use the default value if it's not specified * Fix UseInternalAccessName * Always purge cashes for plugin content on unloading. * Fix texture not multiple of 4 * v1.12.7.0 (Spring Update 2026 Hotfix 1) --------- Co-authored-by: Joonas Rikkonen Co-authored-by: Evil Factory <36804725+evilfactory@users.noreply.github.com> Co-authored-by: MapleWheels --- .github/DISCUSSION_TEMPLATE/bug-reports.yml | 2 +- .../ClientSource/LuaCs/Data/SettingControl.cs | 4 +- .../ClientSource/LuaCs/LuaCsSetup.cs | 140 +++----- .../LuaCs/_Services/NetworkingService.cs | 6 +- .../_SettingsMenu/ModsGameplaySettingsMenu.cs | 214 ++++++++++-- .../_SettingsMenu/ModsSettingsMenuBase.cs | 4 +- .../_SettingsMenu/SettingsMenuSystem.cs | 11 +- .../Screens/MainMenuScreen/MainMenuScreen.cs | 29 +- .../ClientSource/Settings/SettingsMenu.cs | 45 ++- .../ClientSource/Steam/Workshop.cs | 14 +- .../BarotraumaClient/LinuxClient.csproj | 2 +- Barotrauma/BarotraumaClient/MacClient.csproj | 2 +- .../BarotraumaClient/WindowsClient.csproj | 2 +- .../BarotraumaServer/LinuxServer.csproj | 2 +- Barotrauma/BarotraumaServer/MacServer.csproj | 2 +- .../ServerSource/LuaCs/LuaCsInstaller.cs | 1 + .../LuaCs/_Services/NetworkingService.cs | 6 +- .../BarotraumaServer/WindowsServer.csproj | 2 +- .../Config/SettingsShared.xml | 2 +- .../LuaCsForBarotrauma/Lua/LuaSetup.lua | 40 --- .../LuaCsForBarotrauma/LuaCsSettingsIcon.png | Bin 0 -> 6346 bytes .../LocalMods/LuaCsForBarotrauma/Style.xml | 6 + .../LuaCsForBarotrauma/Texts/English.xml | 5 + .../LocalMods/LuaCsForBarotrauma/filelist.xml | 1 + .../OxygenDispenserTest.xml | 2 +- .../RotationAndFlippingTests.sub | Bin 13456 -> 13119 bytes .../[DebugOnlyTest]TestLuaMod/Lua/init.lua | 51 ++- .../[DebugOnlyTest]TestLuaMod/ModConfig.xml | 2 + .../[DebugOnlyTest]TestLuaMod/Settings.xml | 16 +- .../SettingsClient.xml | 8 + .../SettingsServer.xml | 8 + .../Characters/AI/EnemyAIController.cs | 21 +- .../ContentPackageManager.cs | 5 + .../Items/Components/ItemContainer.cs | 22 +- .../Items/Components/Machines/Controller.cs | 4 - .../LuaCs/Compatibility/ILuaCsHook.cs | 1 + .../LuaCs/Compatibility/LuaCsConfig.cs | 305 ++++++++++++++++++ .../Data/DataInterfaceImplementations.cs | 1 + .../LuaCs/Data/IResourceInfoDeclarations.cs | 6 + .../LuaCs/Data/ISettingTypeDef.cs | 5 +- .../SharedSource/LuaCs/Data/SettingBase.cs | 5 +- .../SharedSource/LuaCs/Data/SettingEntry.cs | 7 +- .../SharedSource/LuaCs/IEvents.cs | 37 ++- .../SharedSource/LuaCs/LuaCsSetup.cs | 95 ++++-- .../LuaCs/_Plugins/AssemblyLoader.cs | 16 +- .../LuaCs/_Services/ConfigService.cs | 8 +- .../LuaCs/_Services/EventService.cs | 31 +- .../_Services/HarmonyEventPatchesService.cs | 61 +++- .../LuaCs/_Services/LoggerService.cs | 17 +- .../LuaCs/_Services/LuaCsInfoProvider.cs | 6 +- .../_Services/LuaScriptManagementService.cs | 70 +++- .../LuaCs/_Services/MainMenuPatch.cs | 23 +- .../_Services/ModConfigFileParserService.cs | 38 ++- .../LuaCs/_Services/ModConfigService.cs | 8 +- .../_Services/PackageManagementService.cs | 42 ++- .../_Services/PluginManagementService.cs | 99 +++++- .../_Services/_Interfaces/ILoggerService.cs | 22 ++ .../_Interfaces/IPackageManagementService.cs | 4 +- .../_Services/_Lua/DefaultLuaRegistrar.cs | 7 + .../LuaCs/_Services/_Lua/ILuaEventService.cs | 2 +- .../_Services/_Lua/LuaClasses/LuaCsLogger.cs | 8 +- .../_Services/_Lua/LuaClasses/LuaGame.cs | 26 +- .../Map/Levels/LevelObjects/LevelTrigger.cs | 2 +- Barotrauma/BarotraumaShared/changelog.txt | 14 +- README.md | 2 +- 65 files changed, 1253 insertions(+), 396 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/LuaCsSettingsIcon.png create mode 100644 Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Style.xml create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/SettingsClient.xml create mode 100644 Barotrauma/BarotraumaShared/LocalMods/[DebugOnlyTest]TestLuaMod/SettingsServer.xml create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Compatibility/LuaCsConfig.cs 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 0000000000000000000000000000000000000000..434b6f2f28add3b4640c8a253fbf8b8c04e7e036 GIT binary patch literal 6346 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%0AWnc3aN%lCC3&9f@O^UKQYAJt826pkrf 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 SGN$0q0!UL$SG7{bChUJVUjGLG literal 0 HcmV?d00001 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 7eafd5c1c5bcc44e349a1aa1f7ce44430c4078cb..10d816db3920a87423d7c115a9f0bf7ae2ce249d 100644 GIT binary patch literal 13119 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 Vfe8+1e9ZoZ{plCTl*nfcEDpPREUs%m#v_p_gU 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 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).