diff --git a/Project.xml b/Project.xml
index 03373c78..38d1404a 100644
--- a/Project.xml
+++ b/Project.xml
@@ -125,17 +125,23 @@
+
+
+
+
+
+
diff --git a/buildnumber.txt b/buildnumber.txt
index 40a31ff8..f55a6f7c 100644
--- a/buildnumber.txt
+++ b/buildnumber.txt
@@ -1 +1 @@
-2071
\ No newline at end of file
+2107
\ No newline at end of file
diff --git a/hmm.json b/hmm.json
index a20ce299..2b4c7816 100644
--- a/hmm.json
+++ b/hmm.json
@@ -53,6 +53,13 @@
"ref": "master",
"url": "https://github.com/Aidan63/linc_discord-rpc"
},
+ {
+ "name": "linc_luajit",
+ "type": "git",
+ "dir": null,
+ "ref": "master",
+ "url": "https://github.com/nebulazorua/linc_luajit"
+ },
{
"name": "markdown",
"type": "haxelib",
diff --git a/source/funkin/scripting/LuaScript.hx b/source/funkin/scripting/LuaScript.hx
new file mode 100644
index 00000000..18866d45
--- /dev/null
+++ b/source/funkin/scripting/LuaScript.hx
@@ -0,0 +1,208 @@
+package funkin.scripting;
+
+#if ENABLE_LUA
+import llua.Lua;
+import llua.LuaL;
+import llua.State;
+import llua.Convert;
+
+import openfl.utils.Assets;
+
+using llua.Lua;
+using llua.LuaL;
+using llua.Convert;
+
+class LuaScript extends Script {
+ public var state:State = null;
+
+ public var lastStackID:Int = 0;
+ public var stack:Map = [];
+
+ public var luaCallbacks:Map = [];
+
+ public override function onCreate(path:String) {
+ super.onCreate(path);
+
+ state = LuaL.newstate();
+ Lua.set_callbacks_function(cpp.Callable.fromStaticFunction(callback_handler));
+ LuaL.openlibs(state);
+ Lua.register_hxtrace_func(cpp.Callable.fromStaticFunction(print_function));
+ state.register_hxtrace_lib();
+
+ luaCallbacks["__onPointerIndex"] = onPointerIndex;
+ luaCallbacks["__onPointerNewIndex"] = onPointerNewIndex;
+ luaCallbacks["__gc"] = onGarbageCollection;
+
+ state.newmetatable("__funkinMetaTable");
+
+ state.pushstring('__index');
+ state.pushcfunction(cpp.Callable.fromStaticFunction(__index));
+ state.settable(-3);
+
+ state.pushstring('__newindex');
+ state.pushcfunction(cpp.Callable.fromStaticFunction(__newindex));
+ state.settable(-3);
+ }
+
+ public override function onLoad() {
+ state.dostring(Assets.getText(path));
+ }
+
+ public static var callbackReturnVariables = [];
+
+ public override function onCall(funcName:String, args:Array):Dynamic {
+ state.settop(0);
+ state.getglobal(funcName);
+
+ if (state.type(-1) != Lua.LUA_TFUNCTION)
+ return null;
+
+ for (k=>val in args)
+ pushArg(val);
+
+ if (state.pcall(args.length, 1, 0) != 0) {
+ this.error('${state.tostring(-1)}');
+ return null;
+ }
+
+ return fromLua(state.gettop());
+ }
+
+ public override function set(variable:String, value:Dynamic) {
+ pushArg(value);
+ state.setglobal(variable);
+ }
+
+ public override function onDestroy() {
+ super.onDestroy();
+
+ if (state != null) {
+ Lua.close(state);
+ state = null;
+ }
+ }
+ public override function reload() {
+ Logs.trace('Hot-reloading is currently not supported on Lua.', WARNING);
+ }
+
+ // UTILS
+ #if REGION
+
+ static inline function print_function(s:String) : Int {
+ if (Script.curScript != null)
+ Script.curScript.trace(s);
+ return 0;
+ }
+
+ public function fromLua(stackPos:Int):Dynamic {
+ var v:Dynamic = state.fromLua(stackPos);
+ if (v is Dynamic && Reflect.hasField(v, "__stack_id")) {
+ // is a "pointer"! convert it back.
+ var pos:Int = Reflect.field(v, "__stack_id");
+ return stack[pos];
+ }
+ return v;
+ }
+ public function pushArg(val:Dynamic) {
+ switch (Type.typeof(val)) {
+ case Type.ValueType.TNull:
+ state.pushnil();
+ case Type.ValueType.TBool:
+ state.pushboolean(val);
+ case Type.ValueType.TInt:
+ state.pushinteger(cast(val, Int));
+ case Type.ValueType.TFloat:
+ state.pushnumber(val);
+ case Type.ValueType.TClass(String):
+ state.pushstring(cast(val, String));
+ case Type.ValueType.TClass(Array):
+ state.arrayToLua(val);
+ case Type.ValueType.TObject:
+ @:privateAccess
+ state.objectToLua(val); // {}
+ default:
+
+ var p = {
+ __stack_id: lastStackID++,
+ };
+ state.toLua(p);
+ state.getmetatable("__funkinMetaTable");
+ state.setmetatable(-2);
+
+ state.pushstring('__gc');
+ state.pushcfunction(cpp.Callable.fromStaticFunction(__gc));
+ state.settable(-3);
+
+ stack[p.__stack_id] = val;
+ }
+ }
+ public static function __index(state:StatePointer):Int {
+ return callback_handler(cast cpp.Pointer.fromRaw(state).ref, "__onPointerIndex");
+ }
+ public static function __newindex(state:StatePointer):Int {
+ return callback_handler(cast cpp.Pointer.fromRaw(state).ref, "__onPointerNewIndex");
+ }
+ public static function __gc(state:StatePointer):Int {
+ // callbackPreventAutoConvert = true;
+ var v = callback_handler(cast cpp.Pointer.fromRaw(state).ref, "__gc");
+ // callbackPreventAutoConvert = false;
+ return v;
+ }
+
+ public function onPointerIndex(obj:Dynamic, key:String) {
+ if (obj != null)
+ return Reflect.getProperty(obj, key);
+ return null;
+ }
+
+ public function onPointerNewIndex(obj:Dynamic, key:String, val:Dynamic) {
+ if (obj != null)
+ Reflect.setProperty(obj, key, val);
+ }
+
+ public function onGarbageCollection(obj:Dynamic) {
+ trace(obj);
+ if (Reflect.hasField(obj, "__stack_id")) {
+ trace('Clearing item ID: ${obj.__stack_id} from stack due to garbage collection');
+ stack.remove(obj.__stack_id);
+ }
+ }
+
+ private static var callbackPreventAutoConvert:Bool = false;
+ public static inline function callback_handler(l:State, fname:String):Int {
+
+ if (!(Script.curScript is LuaScript))
+ return 0;
+ var curLua:LuaScript = cast Script.curScript;
+
+ var cbf = curLua.luaCallbacks.get(fname);
+ callbackReturnVariables = [];
+
+ if (cbf == null || !Reflect.isFunction(cbf))
+ return 0;
+
+ var nparams:Int = Lua.gettop(l);
+ var args:Array = callbackPreventAutoConvert ? [for(i in 0...nparams) l.fromLua(i + 1)] : [for(i in 0...nparams) curLua.fromLua(i + 1)];
+
+ var ret:Dynamic = null;
+
+ try {
+ ret = (nparams > 0) ? Reflect.callMethod(null, cbf, args) : cbf();
+ } catch(e) {
+ curLua.error(e.details()); // for super cool mega logging!!!
+ throw e;
+ }
+ Lua.settop(l, 0);
+
+ if (callbackReturnVariables.length <= 0)
+ callbackReturnVariables.push(ret);
+ for(e in callbackReturnVariables)
+ curLua.pushArg(e);
+
+ /* return the number of results */
+ return callbackReturnVariables.length;
+
+ }
+ #end
+}
+#end
\ No newline at end of file
diff --git a/source/funkin/scripting/Script.hx b/source/funkin/scripting/Script.hx
index 843b2dc4..9fd154f0 100644
--- a/source/funkin/scripting/Script.hx
+++ b/source/funkin/scripting/Script.hx
@@ -110,13 +110,14 @@ class Script extends FlxBasic implements IFlxDestroyable {
"SOFTCODED_STATES" => #if SOFTCODED_STATES true #else false #end,
"USE_SOURCE_ASSETS" => #if USE_SOURCE_ASSETS true #else false #end,
"USE_ADAPTED_ASSETS" => #if USE_ADAPTED_ASSETS true #else false #end,
+ "ENABLE_LUA" => #if ENABLE_LUA true #else false #end,
];
}
/**
* All available script extensions
*/
public static var scriptExtensions:Array = [
- "hx", "hscript", "hsc", "hxs", "lua" /** ACTUALLY NOT SUPPORTED, ONLY FOR THE MESSAGE **/
+ "hx", "hscript", "hsc", "hxs", "lua"
];
/**
@@ -144,8 +145,7 @@ class Script extends FlxBasic implements IFlxDestroyable {
case "hx" | "hscript" | "hsc" | "hxs":
new HScript(path);
case "lua":
- Logs.trace("Lua is not supported in this engine. Use HScript instead.", ERROR);
- new DummyScript(path);
+ new LuaScript(path);
default:
new DummyScript(path);
}
@@ -160,6 +160,10 @@ class Script extends FlxBasic implements IFlxDestroyable {
public function new(path:String) {
super();
+ // #if cpp
+ // cpp.vm.Gc.setFinalizer(this, cpp.Callable.fromStaticFunction(onFinalize));
+ // #end
+
fileName = Path.withoutDirectory(path);
this.path = path;
onCreate(path);
@@ -263,10 +267,20 @@ class Script extends FlxBasic implements IFlxDestroyable {
public function onLoad() {}
- public function onDestroy() {};
+ public function onDestroy() {}
public override function destroy() {
super.destroy();
onDestroy();
}
+
+ private static var __scriptType:Class