1
0
mirror of https://github.com/soarqin/DSP_Mods.git synced 2026-03-22 17:23:25 +08:00
Files
DSP_Mods/LuaScriptEngine/LuaState.cs

405 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
using NLua;
using OBSWebsocketDotNet;
namespace LuaScriptEngine;
public class LuaState : IDisposable
{
private readonly Lua state = new();
private readonly List<LuaFunction> PostDataLoadedFuncs = [];
private readonly List<LuaFunction> PreUpdateFuncs = [];
private readonly List<LuaFunction> PostUpdateFuncs = [];
private readonly List<LuaFunction> PreGameBeginFuncs = [];
private readonly List<LuaFunction> PostGameBeginFuncs = [];
private readonly List<LuaFunction> PreGameEndFuncs = [];
private readonly List<LuaFunction> PostGameEndFuncs = [];
private readonly HashSet<Timer> Timers = [];
private readonly List<Timer> TimersToRemove = [];
private readonly OBSWebsocket _obs = new();
private readonly Dictionary<string, string> _scheduledText = [];
private long _lastSeedKey = 0L;
private string _lastClusterString = "";
public LuaState()
{
var assemblyPath = System.IO.Path.Combine(
System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)!,
"scripts"
);
state.State.Encoding = Encoding.UTF8;
state.LoadCLRPackage();
state.DoString("import('Assembly-CSharp')");
state.DoString($"package.path = '{assemblyPath.Replace('\\', '/')}/?.lua'");
RegisterFunctions();
foreach (var file in System.IO.Directory.GetFiles(assemblyPath, "*.lua"))
{
LuaScriptEngine.Logger.LogInfo($"Loading Lua script: {file}");
state.DoFile(file);
}
}
public void Dispose()
{
state.Dispose();
if (_obs.IsConnected)
{
_obs.Disconnect();
}
_scheduledText.Clear();
Timers.Clear();
PreUpdateFuncs.Clear();
PostUpdateFuncs.Clear();
PreGameBeginFuncs.Clear();
PostGameBeginFuncs.Clear();
PreGameEndFuncs.Clear();
PostGameEndFuncs.Clear();
}
private void RegisterFunctions()
{
#region Callback Functions
state["register_callback"] = (string tp, LuaFunction action) =>
{
switch (tp)
{
case "data_loaded":
PostDataLoadedFuncs.Add(action);
break;
case "pre_update":
PreUpdateFuncs.Add(action);
break;
case "post_update":
PostUpdateFuncs.Add(action);
break;
case "pre_game_begin":
PreGameBeginFuncs.Add(action);
break;
case "post_game_begin":
PostGameBeginFuncs.Add(action);
break;
case "pre_game_end":
PreGameEndFuncs.Add(action);
break;
case "post_game_end":
PostGameEndFuncs.Add(action);
break;
}
};
state["add_timer"] = Timer (LuaFunction func, long firstInterval, long repeatInterval) =>
{
var timer = new Timer(func, firstInterval, repeatInterval);
Timers.Add(timer);
return timer;
};
state["remove_timer"] = void (Timer timer) => { Timers.Remove(timer); };
#endregion
#region OBS Functions
state["obs_connect"] = void (string server, string password) =>
{
if (_obs.IsConnected)
{
LuaScriptEngine.Logger.LogInfo("Already connected to OBS");
return;
}
_obs.Connected += (_, _) =>
{
LuaScriptEngine.Logger.LogInfo($"Connected to OBS at {server}");
foreach (var (sourceName, text) in _scheduledText)
{
try
{
_obs.SetInputSettings(sourceName,
new JObject
{
{ "text", text }
});
}
catch (Exception e)
{
LuaScriptEngine.Logger.LogError($"Error setting {sourceName}'s text to `{text}`: {e}");
}
}
_scheduledText.Clear();
};
_obs.Disconnected += (_, _) =>
{
_obs.ConnectAsync(server, password);
};
_obs.ConnectAsync(server, password);
};
state["obs_disconnect"] = void () =>
{
_obs.Disconnect();
};
state["obs_set_source_text"] = void (string sourceName, string text) =>
{
if (_obs.IsConnected)
{
try
{
_obs.SetInputSettings(sourceName, new JObject
{
{ "text", text }
});
}
catch (Exception e)
{
LuaScriptEngine.Logger.LogError($"Error setting source text: {e}");
_obs.Disconnect();
_scheduledText[sourceName] = text;
}
}
else
{
_scheduledText[sourceName] = text;
}
};
#endregion
#region Common Data Retrieval Functions
state["get_current_cluster"] = string () =>
{
var data = GameMain.data;
if (data == null) return "";
var seedKey = data.GetClusterSeedKey();
if (seedKey == _lastSeedKey) return _lastClusterString;
_lastSeedKey = seedKey;
_lastClusterString = data.gameDesc.clusterString;
return _lastClusterString;
};
var getCurrentResearch = () =>
{
var history = GameMain.history;
if (history == null) return (0, 0, 0L, 0L);
var current = history.currentTech;
if (current == 0) return (0, 0, 0L, 0L);
var techStates = history.techStates;
if (!techStates.TryGetValue(current, out var value)) return (0, 0, 0L, 0L);
return (current, value.curLevel, value.hashUploaded, value.hashNeeded);
};
state["get_current_research"] = getCurrentResearch;
state["get_current_research_str"] = string (string format) =>
{
var (current, level, hashUploaded, hashNeeded) = getCurrentResearch();
if (current == 0) return "";
return string.Format(format, LDB.techs.Select(current).name, level, JournalUtility.TranslateKMGValue(hashUploaded), JournalUtility.TranslateKMGValue(hashNeeded));
};
var getTechLevel = (NLua.LuaTable techIds) =>
{
var techStates = GameMain.history?.techStates;
if (techStates == null) return 0;
int level = 0;
foreach (var techId in techIds.Values.Cast<long>())
{
if (!techStates.TryGetValue((int)techId, out var value))
{
return level;
}
var newLevel = value.unlocked ? value.curLevel : value.curLevel - 1;
if (newLevel > level)
{
level = newLevel;
}
if (!value.unlocked)
{
return level;
}
}
return level;
};
state["get_tech_level"] = getTechLevel;
state["get_tech_level_str"] = string (string format, NLua.LuaTable techIds) =>
{
var level = getTechLevel(techIds);
if (level <= 0) return "";
return string.Format(format, level);
};
var getFactoryStat = (int itemId) =>
{
var gameData = GameMain.data;
if (gameData == null) return (0, 0);
var statPool = GameMain.statistics?.production.factoryStatPool;
if (statPool == null) return (0, 0);
long productTotal = 0L, consumeTotal = 0L;
for (var i = gameData.factoryCount - 1; i >= 0; i--)
{
var stat = statPool[i];
if (stat == null) continue;
var index = stat.productIndices[itemId];
var ppool = stat.productPool[index];
if (ppool == null) continue;
var cursor = ppool.cursor[4];
if (cursor > 0)
{
productTotal += ppool.count[cursor - 1];
}
cursor = ppool.cursor[4 + 6];
if (cursor > 0)
{
consumeTotal += ppool.count[cursor - 1];
}
}
return (productTotal, consumeTotal);
};
state["get_factory_stat"] = getFactoryStat;
state["get_factory_stat_str"] = string (string format, int itemId) =>
{
var (productTotal, consumeTotal) = getFactoryStat(itemId);
return string.Format(format, JournalUtility.TranslateKMGValue(productTotal), JournalUtility.TranslateKMGValue(consumeTotal));
};
var getDysonSphereTotalGen = () =>
{
var data = GameMain.data;
if (data == null) return 0;
return data.GetDysonSphereTotalGen();
};
state["get_dyson_sphere_power_gen"] = getDysonSphereTotalGen;
state["get_dyson_sphere_power_gen_str"] = string (string format) =>
{
var totalGen = getDysonSphereTotalGen();
if (totalGen <= 0) return "";
return string.Format(format, JournalUtility.TranslateKMGValue(totalGen * 60L));
};
#endregion
}
public void PostDataLoaded()
{
LoopCall(PostDataLoadedFuncs);
}
public void PreUpdate()
{
if (Timers.Count > 0)
{
var gameTick = GameMain.gameTick;
foreach (var timer in Timers)
{
if (timer == null || !timer.Check(gameTick)) continue;
TimersToRemove.Add(timer);
}
if (TimersToRemove.Count > 0)
{
foreach (var timer in TimersToRemove)
{
Timers.Remove(timer);
}
TimersToRemove.Clear();
}
}
LoopCall(PreUpdateFuncs);
}
public void PostUpdate()
{
LoopCall(PostUpdateFuncs);
}
public void PreGameBegin()
{
var tick = GameMain.gameTick;
foreach (var timer in Timers)
{
if (timer.Reset(tick))
{
TimersToRemove.Add(timer);
}
}
if (TimersToRemove.Count > 0)
{
foreach (var timer in TimersToRemove)
{
Timers.Remove(timer);
}
TimersToRemove.Clear();
}
LoopCall(PreGameBeginFuncs);
}
public void PostGameBegin()
{
LoopCall(PostGameBeginFuncs);
}
public void PreGameEnd()
{
LoopCall(PreGameEndFuncs);
}
public void PostGameEnd()
{
LoopCall(PostGameEndFuncs);
}
private void LoopCall(List<LuaFunction> funcs)
{
foreach (var func in funcs)
{
try
{
func.Call();
}
catch (Exception e)
{
LuaScriptEngine.Logger.LogError($"Error in Lua script: {e}");
}
}
}
private class Timer(LuaFunction func, long startInterval, long repeatInterval = 0L)
{
public bool Check(long gameTick)
{
if (gameTick < _nextTick) return false;
try
{
_func.Call();
}
catch (Exception e)
{
LuaScriptEngine.Logger.LogError($"Error in Lua script: {e}");
}
if (_repeatInterval <= 0L) return true;
_nextTick += _repeatInterval;
if (_nextTick < gameTick)
_nextTick = gameTick + 1;
return false;
}
public bool Reset(long gameTick)
{
if (_repeatInterval <= 0L) return true;
_nextTick = gameTick + _repeatInterval;
return false;
}
private readonly LuaFunction _func = func;
private readonly long _repeatInterval = repeatInterval;
private long _nextTick = GameMain.gameTick + startInterval;
}
}