168 lines
7.3 KiBLFS
C#
Executable File
168 lines
7.3 KiBLFS
C#
Executable File
// SPDX-FileCopyrightText: 2023 Matheus Izvekov <mizvekov@gmail.com>
|
|
// SPDX-License-Identifier: ISC
|
|
|
|
using Barotrauma;
|
|
using Barotrauma.Items.Components;
|
|
using Barotrauma.Networking;
|
|
using MoonSharp.Interpreter.Interop;
|
|
using MoonSharp.Interpreter;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace miz {
|
|
partial class MicroLua : ItemComponent, IServerSerializable {
|
|
private readonly static Regex inpRegex = new Regex(@"^signal_in(\d+)$");
|
|
private readonly static Regex outRegex = new Regex(@"^signal_out(\d+)$");
|
|
private List<Connection> outConns = new();
|
|
|
|
private Script script;
|
|
|
|
private readonly struct EventData : IEventData {
|
|
public readonly string data;
|
|
public EventData(string data) { this.data = data; }
|
|
}
|
|
|
|
private void handleException(string source, InterpreterException e) {
|
|
LuaCsLogger.LogError($"[{Item}] [{source}] {e.DecoratedMessage}", LuaCsMessageOrigin.LuaMod);
|
|
}
|
|
|
|
private bool toNumber(string s, out double value) {
|
|
return double.TryParse(s, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint,
|
|
CultureInfo.InvariantCulture, out value);
|
|
}
|
|
|
|
private string source;
|
|
[InGameEditable, Serialize("", IsPropertySaveable.Yes, description: "The Lua source code.", alwaysUseInstanceValues: true)]
|
|
public string Source {
|
|
get => source;
|
|
set {
|
|
if (value == null) { return; }
|
|
source = value;
|
|
script = null;
|
|
IsActive = false;
|
|
|
|
if (source.IsNullOrWhiteSpace()) { return; }
|
|
Script newscript = new(CoreModules.Preset_HardSandbox | CoreModules.Metatables |
|
|
CoreModules.ErrorHandling | CoreModules.Coroutine);
|
|
newscript.Options.DebugPrint = (string o) => LuaCsLogger.LogMessage($"[{Item}] {o}");
|
|
newscript.Options.CheckThreadAccess = false;
|
|
|
|
newscript.Globals.Get("table").Table["clear"] = (Table t) => t.Clear();
|
|
|
|
// MoonSharp's tonumber is too permissive. However, ours does not support base.
|
|
newscript.Globals["tonumber"] = double? (string s) => toNumber(s, out double value) ? value : null;
|
|
|
|
newscript.Globals["mode"] = GameMain.NetworkMember?.IsClient switch {
|
|
null => "single", false => "server", true => "client"
|
|
};
|
|
newscript.Globals["out"] = UserData.Create(this, new OutDesc());
|
|
newscript.Globals["time"] = () => GameMain.GameScreen.GameTime;
|
|
|
|
if (GameMain.NetworkMember is not null) {
|
|
#if SERVER
|
|
newscript.Globals["send"] = (string data) => item.CreateServerEvent(this, new EventData(data));
|
|
#endif
|
|
}
|
|
|
|
try { newscript.DoString(source, codeFriendlyName: "source"); }
|
|
catch (SyntaxErrorException e) {
|
|
handleException("syntax", e);
|
|
return;
|
|
}
|
|
catch (ScriptRuntimeException e) {
|
|
handleException("runtime", e);
|
|
return;
|
|
}
|
|
|
|
IsActive = true;
|
|
script = newscript;
|
|
}
|
|
}
|
|
|
|
public MicroLua(Item item, ContentXElement element) : base(item, element) {}
|
|
|
|
class OutDesc : IUserDataDescriptor {
|
|
public string Name {
|
|
get { return "out"; }
|
|
}
|
|
public Type Type {
|
|
get { return typeof(OutDesc); }
|
|
}
|
|
|
|
public DynValue Index(Script script, object obj, DynValue index, bool isDirectIndexing) {
|
|
throw new ScriptRuntimeException("__index metamethod not implemented");
|
|
}
|
|
public bool SetIndex(Script script, object obj, DynValue index, DynValue value, bool isDirectIndexing) {
|
|
var parent = (MicroLua)obj;
|
|
if (index.Type != DataType.Number) { throw new ScriptRuntimeException("pin must be an integer"); }
|
|
var indexNumber = index.Number;
|
|
var pin = (int)indexNumber;
|
|
if ((double)pin != indexNumber) { throw new ScriptRuntimeException("pin must be an integer"); }
|
|
|
|
if (pin < 0 || pin > parent.outConns.Count) { throw new ScriptRuntimeException($"invalid pin {pin}"); }
|
|
var connection = parent.outConns[pin - 1] ?? throw new ScriptRuntimeException($"invalid pin {pin}");
|
|
parent.item.SendSignal(new Signal(value.Type switch {
|
|
DataType.Number => value.Number.ToString(CultureInfo.InvariantCulture),
|
|
DataType.String => value.String,
|
|
_ => throw new ScriptRuntimeException($"invalid value type {value.Type}")
|
|
}), connection);
|
|
|
|
return true;
|
|
}
|
|
public string AsString(object obj) { throw new ScriptRuntimeException("__tostring metamethod not implemented"); }
|
|
public DynValue MetaIndex(Script script, object obj, string metaname) { throw new ScriptRuntimeException($"{metaname} metamethod not implemented"); }
|
|
public bool IsTypeCompatible(Type type, object obj) { return type.IsInstanceOfType(obj); }
|
|
};
|
|
|
|
public override void OnItemLoaded() {
|
|
base.OnItemLoaded();
|
|
foreach(var connection in item.Connections) {
|
|
var match = outRegex.Match(connection.Name);
|
|
if (!match.Success) { continue; }
|
|
var index = int.Parse(match.Groups[1].Value);
|
|
outConns.Insert(index - 1, connection);
|
|
}
|
|
}
|
|
|
|
private void Call(string field, params object[] objs) {
|
|
DynValue cb = script.Globals.Get(field);
|
|
if (cb.Type == DataType.Nil) { return; }
|
|
try { script.Call(cb, objs); }
|
|
catch (ScriptRuntimeException e) { handleException(field, e); }
|
|
}
|
|
|
|
public override void Update(float deltaTime, Camera cam) { Call("upd", deltaTime); }
|
|
|
|
public override void ReceiveSignal(Signal signal, Connection connection) {
|
|
if (script == null) { return; }
|
|
|
|
var match = inpRegex.Match(connection.Name);
|
|
if (!match.Success) { return; }
|
|
|
|
var index = int.Parse(match.Groups[1].Value);
|
|
var value = toNumber(signal.value, out double dynvalue) ? DynValue.NewNumber(dynvalue) :
|
|
DynValue.NewString(signal.value);
|
|
DynValue inp = script.Globals.Get("inp");
|
|
switch(inp.Type) {
|
|
case DataType.Nil: break;
|
|
case DataType.Table: inp.Table.Set(index, value); break;
|
|
default:
|
|
try { script.Call(inp, index, value); }
|
|
catch (ScriptRuntimeException e) { handleException("inp", e); }
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if CLIENT
|
|
public void ClientEventRead(IReadMessage msg, float sendingTime) { Call("recv", msg.ReadString()); }
|
|
#endif
|
|
#if SERVER
|
|
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData) {
|
|
msg.WriteString(ExtractEventData<EventData>(extraData).data);
|
|
}
|
|
#endif
|
|
}
|
|
}
|