using MoonSharp.Interpreter;
using System.Diagnostics;
namespace anjianjingling.Lua
{
public enum LuaState { Idle, Running, Paused }
/// 脚本中止信号(停止 / 急停 / 主动 stop)。
internal sealed class AbortSignal : Exception
{
public bool ByPanic { get; }
public AbortSignal(bool byPanic) : base(byPanic ? "鼠标触及屏幕左上角" : "脚本已停止")
=> ByPanic = byPanic;
}
///
/// Lua 脚本执行器:MoonSharp 宿主 + 暂停/停止/急停 + 任务队列。
///
public sealed class LuaEngine
{
private readonly ILuaHost _host;
private CancellationTokenSource? _cts;
private readonly ManualResetEventSlim _pauseGate = new(true);
private Task? _task;
private volatile bool _paused;
private volatile bool _byPanic;
private long _lastPanicCheckTick;
public LuaState State { get; private set; } = LuaState.Idle;
public event Action? StateChanged;
/// 执行速度倍率:只影响 delay 等待时长。
public double Speed { get; set; } = 1.0;
public LuaEngine(ILuaHost host) => _host = host;
// ===================== 语法检查 =====================
/// 只编译不执行;返回 null 表示语法通过。
public string? Validate(string code)
{
try
{
var script = CreateScript();
script.LoadString(code, null, "脚本");
return null;
}
catch (SyntaxErrorException ex) { return Localize(ex.Message); }
catch (Exception ex) { return ex.Message; }
}
// ===================== 运行 =====================
/// 运行单段脚本(一次)。
public void Run(string code)
{
if (State != LuaState.Idle) { _host.Log("已有脚本在运行"); return; }
DynValue chunk;
try
{
var script = CreateScript();
chunk = script.LoadString(code, null, "脚本");
}
catch (SyntaxErrorException ex)
{
_host.Log("语法错误:" + Localize(ex.Message));
return;
}
RunBackground(() =>
{
chunk.Function.Call();
_host.Log("脚本执行完毕");
});
}
/// 按顺序执行勾选的任务;每个任务按自身循环设置重复。
public void RunQueue(IReadOnlyList tasks)
{
if (State != LuaState.Idle) { _host.Log("已有脚本在运行"); return; }
var enabled = tasks.Where(t => t.Enabled).ToList();
if (enabled.Count == 0) { _host.Log("没有勾选的任务"); return; }
// 启动前统一编译,提前暴露语法错误
var prepared = new List<(LuaTask Task, DynValue Chunk)>();
foreach (var t in enabled)
{
try
{
var script = CreateScript();
prepared.Add((t, script.LoadString(t.Script, null, t.Name)));
}
catch (SyntaxErrorException ex)
{
_host.Log($"任务「{t.Name}」语法错误:{Localize(ex.Message)}");
return;
}
}
RunBackground(() =>
{
for (int i = 0; i < prepared.Count; i++)
{
var (task, chunk) = prepared[i];
_host.Log($"▶ 开始任务 {i + 1}/{prepared.Count}:{task.Name}({task.ModeText})");
int round = 0;
while (true)
{
Tick();
if (!task.Infinite && round >= task.LoopCount) break;
chunk.Function.Call();
round++;
bool more = task.Infinite || round < task.LoopCount;
if (more && task.IntervalMs > 0) Sleep(task.IntervalMs);
}
_host.Log($"✔ 任务完成:{task.Name}({round} 轮)");
}
_host.Log("★ 任务队列全部执行完毕");
});
}
private void RunBackground(Action body)
{
_paused = false;
_byPanic = false;
_pauseGate.Set();
_cts = new CancellationTokenSource();
SetState(LuaState.Running);
_task = Task.Run(() =>
{
try { body(); }
catch (AbortSignal ex)
{
_host.Log(ex.ByPanic ? "■ 鼠标触及屏幕左上角,紧急停止" : "■ 脚本已停止");
}
catch (SyntaxErrorException ex) { _host.Log("语法错误:" + Localize(ex.Message)); }
catch (ScriptRuntimeException ex) { _host.Log("运行错误:" + Localize(ex.Message)); }
catch (InterpreterException ex) { _host.Log("运行错误:" + Localize(ex.Message)); }
catch (Exception ex) { _host.Log("运行错误:" + ex.Message); }
finally
{
_cts?.Dispose();
_cts = null;
_pauseGate.Set();
_paused = false;
SetState(LuaState.Idle);
}
});
}
private Script CreateScript()
{
var script = new Script(CoreModules.Preset_HardSandbox
| CoreModules.String | CoreModules.Math
| CoreModules.Table | CoreModules.Coroutine);
script.Options.DebugPrint = s => _host.Log(s);
ScriptApi.Bind(script, this, _host);
return script;
}
// ===================== 控制 =====================
public void PauseOrResume()
{
if (State == LuaState.Running)
{
_paused = true;
_pauseGate.Reset();
_host.Log("‖ 已暂停(F9 继续)");
SetState(LuaState.Paused);
}
else if (State == LuaState.Paused)
{
_paused = false;
_pauseGate.Set();
_host.Log("▶ 继续执行");
SetState(LuaState.Running);
}
}
public void Stop()
{
if (State == LuaState.Idle) return;
_byPanic = false;
_paused = false;
_pauseGate.Set();
_cts?.Cancel();
}
private void SetState(LuaState s)
{
State = s;
StateChanged?.Invoke(s);
}
// ===================== 中断点(API 回调内调用)=====================
internal void Tick()
{
var cts = _cts;
if (cts is { IsCancellationRequested: true }) throw new AbortSignal(_byPanic);
if (_paused)
{
_pauseGate.Wait();
if (cts is { IsCancellationRequested: true }) throw new AbortSignal(_byPanic);
}
// 急停检查节流到 100ms 一次,避免每条命令都调系统 API
long now = Environment.TickCount64;
if (now - _lastPanicCheckTick < 100) return;
_lastPanicCheckTick = now;
Native.Win32.GetCursorPos(out var p);
if (p.X > 0 || p.Y > 0) return;
Thread.Sleep(120);
Native.Win32.GetCursorPos(out var p2);
if (p2.X <= 0 && p2.Y <= 0)
{
_byPanic = true;
_cts?.Cancel();
throw new AbortSignal(true);
}
}
/// 可中断、可暂停的延时(分片 20ms,支持倍速)。
internal void Sleep(double ms)
{
ms = Math.Max(0, ms / Math.Max(0.05, Speed));
var sw = Stopwatch.StartNew();
while (sw.Elapsed.TotalMilliseconds < ms)
{
Tick();
int slice = (int)Math.Min(20, Math.Max(1, ms - sw.Elapsed.TotalMilliseconds));
_pauseGate.Wait(slice);
}
}
// ===================== 错误信息中文化 =====================
private static readonly (string From, string To)[] Dict =
{
("unexpected symbol near ''", "语句未写完(意外到达文件结尾)"),
("unexpected symbol", "意外的符号"),
("'end' expected", "缺少 end"),
("'=' expected", "缺少 ="),
("'then' expected", "缺少 then"),
("'do' expected", "缺少 do"),
("unfinished string", "字符串未闭合"),
("malformed number", "数字格式错误"),
("attempt to index a nil value", "访问了空值(nil)"),
("attempt to call a nil value", "调用了不存在的函数"),
("attempt to perform arithmetic", "算术运算类型错误"),
("attempt to compare", "比较运算类型错误"),
("index out of range", "索引越界"),
("cannot convert", "类型转换失败"),
("invalid arguments", "参数不合法"),
};
private static string Localize(string msg)
{
if (string.IsNullOrEmpty(msg)) return msg;
foreach (var (from, to) in Dict)
if (msg.Contains(from, StringComparison.OrdinalIgnoreCase))
msg = msg.Replace(from, to, StringComparison.OrdinalIgnoreCase);
return msg.Trim();
}
}
}