diff --git a/CheatEnabler/CheatEnabler.csproj b/CheatEnabler/CheatEnabler.csproj
index 9f08495..49ecb83 100644
--- a/CheatEnabler/CheatEnabler.csproj
+++ b/CheatEnabler/CheatEnabler.csproj
@@ -29,6 +29,6 @@
-
+
diff --git a/DSP_Mods.sln b/DSP_Mods.sln
index f1fa876..ebcd76b 100644
--- a/DSP_Mods.sln
+++ b/DSP_Mods.sln
@@ -34,6 +34,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UXAssist", "UXAssist\UXAssi
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UserCloak", "UserCloak\UserCloak.csproj", "{096D2E4B-D1CE-424D-9954-C36A23E9E279}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaScriptEngine", "LuaScriptEngine\LuaScriptEngine.csproj", "{E9375F0E-26DC-4CEC-80DC-9C48F23340BC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -96,5 +98,9 @@ Global
{096D2E4B-D1CE-424D-9954-C36A23E9E279}.Debug|Any CPU.Build.0 = Debug|Any CPU
{096D2E4B-D1CE-424D-9954-C36A23E9E279}.Release|Any CPU.ActiveCfg = Release|Any CPU
{096D2E4B-D1CE-424D-9954-C36A23E9E279}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E9375F0E-26DC-4CEC-80DC-9C48F23340BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E9375F0E-26DC-4CEC-80DC-9C48F23340BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E9375F0E-26DC-4CEC-80DC-9C48F23340BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E9375F0E-26DC-4CEC-80DC-9C48F23340BC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/Dustbin/Dustbin.csproj b/Dustbin/Dustbin.csproj
index 8633170..2c878f9 100644
--- a/Dustbin/Dustbin.csproj
+++ b/Dustbin/Dustbin.csproj
@@ -33,6 +33,6 @@
-
+
diff --git a/HideTips/HideTips.csproj b/HideTips/HideTips.csproj
index 55f6188..0f069ac 100644
--- a/HideTips/HideTips.csproj
+++ b/HideTips/HideTips.csproj
@@ -24,6 +24,6 @@
-
+
diff --git a/LabOpt/LabOpt.csproj b/LabOpt/LabOpt.csproj
index 21129e0..b383fbf 100644
--- a/LabOpt/LabOpt.csproj
+++ b/LabOpt/LabOpt.csproj
@@ -24,6 +24,6 @@
-
+
diff --git a/LuaScriptEngine/CHANGELOG.md b/LuaScriptEngine/CHANGELOG.md
new file mode 100644
index 0000000..83aadba
--- /dev/null
+++ b/LuaScriptEngine/CHANGELOG.md
@@ -0,0 +1,7 @@
+## Changlog
+* 1.0.0
+ + Initial release
+
+## 更新日志
+* 1.0.0
+ + 初始版本
diff --git a/LuaScriptEngine/LuaScriptEngine.cs b/LuaScriptEngine/LuaScriptEngine.cs
new file mode 100644
index 0000000..0b1b656
--- /dev/null
+++ b/LuaScriptEngine/LuaScriptEngine.cs
@@ -0,0 +1,198 @@
+using System;
+using System.Collections.Generic;
+using BepInEx;
+using BepInEx.Logging;
+using HarmonyLib;
+using NLua;
+
+namespace LuaScriptEngine;
+
+[BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
+public class LuaScriptEngine : BaseUnityPlugin
+{
+ private class Timer
+ {
+ public Timer(LuaFunction func, long startInterval, long repeatInterval = 0L)
+ {
+ _func = func;
+ _repeatInterval = repeatInterval;
+ _nextTick = GameMain.gameTick + startInterval;
+ }
+
+ public bool Check(long gameTick)
+ {
+ if (gameTick < _nextTick) return false;
+ try
+ {
+ _func.Call();
+ }
+ catch (Exception e)
+ {
+ Logger.LogError($"Error in Lua script: {e}");
+ }
+
+ if (_repeatInterval <= 0L) return true;
+ _nextTick += _repeatInterval;
+ if (_nextTick < gameTick)
+ _nextTick = gameTick + 1;
+ return false;
+ }
+
+ private readonly LuaFunction _func;
+ private readonly long _repeatInterval;
+ private long _nextTick;
+ }
+ public new static readonly ManualLogSource Logger =
+ BepInEx.Logging.Logger.CreateLogSource(PluginInfo.PLUGIN_NAME);
+
+ private Harmony _harmony;
+
+ private static readonly Lua LuaState = new();
+ private static readonly List PreUpdateFuncs = [];
+ private static readonly List PostUpdateFuncs = [];
+ private static readonly List PreGameBeginFuncs = [];
+ private static readonly List PostGameBeginFuncs = [];
+ private static readonly List PreGameEndFuncs = [];
+ private static readonly List PostGameEndFuncs = [];
+ private static readonly List Timers = [];
+ private static readonly List RemovedTimers = [];
+
+ private void Awake()
+ {
+ LuaState.LoadCLRPackage();
+ LuaState.DoString("import('Assembly-CSharp')");
+ LuaState["register_callback"] = (string tp, bool pre, LuaFunction action) =>
+ {
+ switch (tp)
+ {
+ case "update":
+ (pre ? PreUpdateFuncs : PostUpdateFuncs).Add(action);
+ break;
+ case "game_begin":
+ (pre ? PreGameBeginFuncs : PostGameBeginFuncs).Add(action);
+ break;
+ case "game_end":
+ (pre ? PreGameEndFuncs : PostGameEndFuncs).Add(action);
+ break;
+ }
+ };
+ LuaState["add_timer"] = int(LuaFunction func, long firstInterval, long repeatInterval) =>
+ {
+ var timer = new Timer(func, firstInterval, repeatInterval);
+ if (RemovedTimers.Count <= 0)
+ {
+ Timers.Add(timer);
+ return Timers.Count - 1;
+ }
+ var index = RemovedTimers[RemovedTimers.Count - 1];
+ Timers[index] = timer;
+ RemovedTimers.RemoveAt(RemovedTimers.Count - 1);
+ return index;
+ };
+ LuaState["remove_timer"] = void (int index) =>
+ {
+ if (index < 0 || index >= Timers.Count) return;
+ Timers[index] = null;
+ RemovedTimers.Add(index);
+ };
+
+ var assemblyPath = System.IO.Path.Combine(
+ System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)!,
+ "scripts"
+ );
+
+ foreach (var file in System.IO.Directory.GetFiles(assemblyPath, "*.lua"))
+ {
+ Logger.LogInfo($"Loading Lua script: {file}");
+ LuaState.DoFile(file);
+ }
+ _harmony = Harmony.CreateAndPatchAll(typeof(Patches));
+ }
+
+ private void OnDestroy()
+ {
+ RemovedTimers.Clear();
+ Timers.Clear();
+ PreUpdateFuncs.Clear();
+ PostUpdateFuncs.Clear();
+ PreGameBeginFuncs.Clear();
+ PostGameBeginFuncs.Clear();
+ PreGameEndFuncs.Clear();
+ PostGameEndFuncs.Clear();
+
+ _harmony?.UnpatchSelf();
+ LuaState.Dispose();
+ }
+
+ private static class Patches
+ {
+ private static void LoopCall(List funcs)
+ {
+ foreach (var func in funcs)
+ {
+ try
+ {
+ func.Call();
+ }
+ catch (Exception e)
+ {
+ Logger.LogError($"Error in Lua script: {e}");
+ }
+ }
+ }
+
+ [HarmonyPrefix]
+ [HarmonyPatch(typeof(GameMain), nameof(GameMain.FixedUpdate))]
+ private static void GameMain_FixedUpdate_Prefix()
+ {
+ if (Timers.Count > 0)
+ {
+ var gameTick = GameMain.gameTick;
+ for (var index = Timers.Count - 1; index >= 0; index--)
+ {
+ var timer = Timers[index];
+ if (timer == null || !timer.Check(gameTick)) continue;
+ Timers[index] = null;
+ RemovedTimers.Add(index);
+ }
+ }
+
+ LoopCall(PreUpdateFuncs);
+ }
+
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(GameMain), nameof(GameMain.FixedUpdate))]
+ private static void GameMain_FixedUpdate_Postfix()
+ {
+ LoopCall(PostUpdateFuncs);
+ }
+
+ [HarmonyPrefix]
+ [HarmonyPatch(typeof(GameMain), nameof(GameMain.Begin))]
+ private static void GameMain_Begin_Prefix()
+ {
+ LoopCall(PreGameBeginFuncs);
+ }
+
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(GameMain), nameof(GameMain.Begin))]
+ private static void GameMain_Begin_Postfix()
+ {
+ LoopCall(PostGameBeginFuncs);
+ }
+
+ [HarmonyPrefix]
+ [HarmonyPatch(typeof(GameMain), nameof(GameMain.End))]
+ private static void GameMain_End_Prefix()
+ {
+ LoopCall(PreGameEndFuncs);
+ }
+
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(GameMain), nameof(GameMain.End))]
+ private static void GameMain_End_Postfix()
+ {
+ LoopCall(PostGameEndFuncs);
+ }
+ }
+}
\ No newline at end of file
diff --git a/LuaScriptEngine/LuaScriptEngine.csproj b/LuaScriptEngine/LuaScriptEngine.csproj
new file mode 100644
index 0000000..29d55b3
--- /dev/null
+++ b/LuaScriptEngine/LuaScriptEngine.csproj
@@ -0,0 +1,30 @@
+
+
+
+
+ net472
+ LuaScriptEngine
+ org.soardev.luascriptengine
+ DSP MOD - LuaScriptEngine
+ 1.0.0
+ true
+ latest
+ https://nuget.bepinex.dev/v3/index.json
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LuaScriptEngine/README.md b/LuaScriptEngine/README.md
new file mode 100644
index 0000000..a7a1119
--- /dev/null
+++ b/LuaScriptEngine/README.md
@@ -0,0 +1,8 @@
+# LuaScriptEngine
+
+#### Write mod functions in lua scripts
+#### 用lua脚本编写mod功能
+
+## Usage
+
+## 使用说明
diff --git a/LuaScriptEngine/package/icon.png b/LuaScriptEngine/package/icon.png
new file mode 100644
index 0000000..84fa77d
Binary files /dev/null and b/LuaScriptEngine/package/icon.png differ
diff --git a/LuaScriptEngine/package/manifest.json b/LuaScriptEngine/package/manifest.json
new file mode 100644
index 0000000..27ea4d4
--- /dev/null
+++ b/LuaScriptEngine/package/manifest.json
@@ -0,0 +1,9 @@
+{
+ "name": "LuaScriptEngine",
+ "version_number": "1.0.0",
+ "website_url": "https://github.com/soarqin/DSP_Mods/tree/master/LuaScriptEngine",
+ "description": "Write mod functions in lua scripts / 用lua脚本编写mod功能",
+ "dependencies": [
+ "xiaoye97-BepInEx-5.4.17"
+ ]
+}
diff --git a/MechaDronesTweaks/MechaDronesTweaks.csproj b/MechaDronesTweaks/MechaDronesTweaks.csproj
index 9ff8c51..a286cae 100644
--- a/MechaDronesTweaks/MechaDronesTweaks.csproj
+++ b/MechaDronesTweaks/MechaDronesTweaks.csproj
@@ -24,6 +24,6 @@
-
+
diff --git a/OCBatchBuild/OCBatchBuild.csproj b/OCBatchBuild/OCBatchBuild.csproj
index e7101f7..988e55e 100644
--- a/OCBatchBuild/OCBatchBuild.csproj
+++ b/OCBatchBuild/OCBatchBuild.csproj
@@ -24,6 +24,6 @@
-
+
diff --git a/OverclockEverything/OverclockEverything.csproj b/OverclockEverything/OverclockEverything.csproj
index 9c1a6f0..a61565f 100644
--- a/OverclockEverything/OverclockEverything.csproj
+++ b/OverclockEverything/OverclockEverything.csproj
@@ -26,6 +26,6 @@
-
+
diff --git a/PoolOpt/PoolOpt.csproj b/PoolOpt/PoolOpt.csproj
index 53c64c5..1f447d2 100644
--- a/PoolOpt/PoolOpt.csproj
+++ b/PoolOpt/PoolOpt.csproj
@@ -24,6 +24,6 @@
-
+
diff --git a/UXAssist/UXAssist.csproj b/UXAssist/UXAssist.csproj
index 70bce5a..427f444 100644
--- a/UXAssist/UXAssist.csproj
+++ b/UXAssist/UXAssist.csproj
@@ -37,6 +37,6 @@
-
+
diff --git a/UniverseGenTweaks/UniverseGenTweaks.csproj b/UniverseGenTweaks/UniverseGenTweaks.csproj
index 46433b8..66c6a39 100644
--- a/UniverseGenTweaks/UniverseGenTweaks.csproj
+++ b/UniverseGenTweaks/UniverseGenTweaks.csproj
@@ -29,6 +29,6 @@
-
+
diff --git a/UserCloak/UserCloak.csproj b/UserCloak/UserCloak.csproj
index b64603b..36ba930 100644
--- a/UserCloak/UserCloak.csproj
+++ b/UserCloak/UserCloak.csproj
@@ -24,6 +24,6 @@
-
+