From d6275260880da7f2d4874cb457027ac7350bde8c Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Tue, 17 Dec 2024 20:50:52 +0700 Subject: [PATCH] Move root bindings to native Lua --- .../ru/dbotthepony/kstarbound/Registry.kt | 17 +- .../ru/dbotthepony/kstarbound/Starbound.kt | 2 +- .../kstarbound/defs/item/ItemDescriptor.kt | 162 +++- .../kstarbound/item/ItemRegistry.kt | 4 + .../dbotthepony/kstarbound/item/ItemStack.kt | 9 +- .../dbotthepony/kstarbound/lua/Conversions.kt | 82 +- .../dbotthepony/kstarbound/lua/LuaThread.kt | 241 +++--- .../kstarbound/lua/bindings/RootBindings.kt | 761 +++++++++--------- src/main/resources/scripts/global.lua | 2 +- 9 files changed, 733 insertions(+), 547 deletions(-) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Registry.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Registry.kt index 3a083fd0..07772a2b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Registry.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Registry.kt @@ -17,6 +17,7 @@ import ru.dbotthepony.kommons.util.XXHash64 import ru.dbotthepony.kstarbound.io.StreamCodec import ru.dbotthepony.kstarbound.io.readInternedString import ru.dbotthepony.kstarbound.util.limit +import ru.dbotthepony.kstarbound.util.sbIntern import java.io.DataInputStream import java.io.DataOutputStream import java.util.Collections @@ -223,8 +224,11 @@ class Registry(val name: String, val storeJson: Boolean = true) { fun refOrThrow(index: String): Ref = get(index)?.ref ?: throw NoSuchElementException("No such $name: ${index.limit()}") fun ref(index: String): Ref = lock.write { - keyRefs.computeIfAbsent(index, Object2ObjectFunction { - val ref = RefImpl(Either.left(it as String)) + var lookup = keyRefs[index] + + if (lookup == null) { + val it = index.sbIntern() + val ref = RefImpl(Either.left(it)) ref.entry = keysInternal[it] if (hasBeenValidated && ref.entry == null) { @@ -236,10 +240,12 @@ class Registry(val name: String, val storeJson: Boolean = true) { } } - ref - }).also { - it.references++ + keyRefs[it] = ref + lookup = ref } + + lookup.references++ + lookup } /** @@ -306,6 +312,7 @@ class Registry(val name: String, val storeJson: Boolean = true) { isBuiltin: Boolean = false ): Entry { require(key != "") { "Adding $name with empty name (empty name is reserved)" } + val key = key.sbIntern() lock.write { if (key in keysInternal) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 1ea90aef..da253a25 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -105,7 +105,7 @@ import java.util.random.RandomGenerator import kotlin.NoSuchElementException import kotlin.collections.ArrayList -object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLocator { +object Starbound : BlockableEventLoop("Multiverse Thread"), Scheduler, ISBFileLocator { const val ENGINE_VERSION = "0.0.1" const val NATIVE_PROTOCOL_VERSION = 748 const val LEGACY_PROTOCOL_VERSION = 747 diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDescriptor.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDescriptor.kt index 22e0b527..2399efa3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDescriptor.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDescriptor.kt @@ -1,6 +1,5 @@ package ru.dbotthepony.kstarbound.defs.item -import com.google.gson.Gson import com.google.gson.JsonArray import com.google.gson.JsonElement import com.google.gson.JsonNull @@ -8,7 +7,6 @@ import com.google.gson.JsonObject import com.google.gson.JsonPrimitive import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter -import com.google.gson.annotations.JsonAdapter import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import org.apache.logging.log4j.LogManager @@ -35,6 +33,8 @@ import ru.dbotthepony.kstarbound.json.mergeJson import ru.dbotthepony.kstarbound.json.readJsonElement import ru.dbotthepony.kstarbound.json.writeJsonElement import ru.dbotthepony.kstarbound.lua.LuaEnvironment +import ru.dbotthepony.kstarbound.lua.LuaThread +import ru.dbotthepony.kstarbound.lua.LuaType import ru.dbotthepony.kstarbound.lua.StateMachine import ru.dbotthepony.kstarbound.lua.from import ru.dbotthepony.kstarbound.lua.get @@ -78,6 +78,118 @@ fun ItemDescriptor(data: JsonElement): ItemDescriptor { } } +private fun loadTableDescriptor(name: String, lua: LuaThread, position: Int): ItemDescriptor { + lua.push("count") + val cType = lua.loadTableValue(position) + + val count = if (cType.isNothing) { + lua.pop() + 1L + } else if (cType != LuaType.NUMBER) { + throw IllegalArgumentException("bad argument #${position}: ItemDescriptor has $cType at its 'count' value") + } else { + lua.popLong() ?: throw IllegalArgumentException("bad argument #${position}: ItemDescriptor has non-integer value at its 'count' index") + } + + lua.push("parameters") + var pType = lua.loadTableValue(position) + + if (pType.isNothing) { + lua.pop() + } else if (pType != LuaType.TABLE) { + throw IllegalArgumentException("bad argument #${position}: ItemDescriptor has $pType at its 'parameters' index") + } + + lua.push("data") + pType = lua.loadTableValue(position) + + if (pType.isNothing) { + lua.pop() + return ItemDescriptor(name, count) + } else if (pType != LuaType.TABLE) { + throw IllegalArgumentException("bad argument #${position}: ItemDescriptor has $pType at its 'data' index") + } + + val parameters = lua.popJson() as JsonObject + return ItemDescriptor(name, count, parameters) +} + +// handwritten item descriptor loader, which is faster than converting Lua value into json and then loading descriptor as json +fun ItemDescriptor(lua: LuaThread, position: Int = lua.stackTop): ItemDescriptor { + val peek = lua.typeAt(position) + + if (peek == LuaType.STRING) { + // name + return ItemDescriptor(lua.getString(position)!!, 1L) + } else if (peek == LuaType.TABLE) { + lua.push(1L) + var type = lua.loadTableValue(position) + + // {name, [count], [parameters]} + if (type == LuaType.STRING) { + val name = lua.popString()!! + lua.push(2L) + val cType = lua.loadTableValue(position) + + val count = if (cType.isNothing) { + lua.pop() + 1L + } else if (cType != LuaType.NUMBER) { + throw IllegalArgumentException("bad argument #${position}: ItemDescriptor has $cType as its second value instead of integer") + } else { + lua.popLong() ?: throw IllegalArgumentException("bad argument #${position}: ItemDescriptor has non-integer value as second value") + } + + lua.push(3L) + val pType = lua.loadTableValue(position) + + if (pType.isNothing) { + lua.pop() + return ItemDescriptor(name, count) + } else if (pType != LuaType.TABLE) { + throw IllegalArgumentException("bad argument #${position}: ItemDescriptor has $pType as its third value instead of table") + } + + val parameters = lua.popJson() as JsonObject + return ItemDescriptor(name, count, parameters) + } else if (!type.isNothing) { + throw IllegalArgumentException("bad argument #${position}: ItemDescriptor has $type as its first value instead of string") + } + + lua.pop() + lua.push("name") + type = lua.loadTableValue(position) + + // {name = "a", [count = 4], [parameters = {}]} + if (type == LuaType.STRING) { + return loadTableDescriptor(lua.popString()!!, lua, position) + } else if (!type.isNothing) { + throw IllegalArgumentException("bad argument #${position}: ItemDescriptor has $type as its 'name' key") + } + + lua.pop() + lua.push("item") + type = lua.loadTableValue(position) + + // {item = "a", [count = 4], [parameters = {}]} + if (type == LuaType.STRING) { + return loadTableDescriptor(lua.popString()!!, lua, position) + } else if (!type.isNothing) { + throw IllegalArgumentException("bad argument #${position}: ItemDescriptor has $type as its 'item' key") + } else { + throw IllegalArgumentException("bad argument #$position: ItemDescriptor is missing a name") + } + } else if (peek.isNothing) { + return ItemDescriptor.EMPTY + } else { + throw IllegalArgumentException("bad argument #$position: ItemDescriptor expected, got $peek") + } +} + +fun ItemDescriptor(args: LuaThread.ArgStack): ItemDescriptor { + return ItemDescriptor(args.lua, args.position) +} + fun ItemDescriptor(data: Table, stateMachine: StateMachine): Supplier { val name = stateMachine.index(data, 1L, "name", "item") val count = stateMachine.optionalIndex(data, 2L, "count") @@ -197,6 +309,21 @@ data class ItemDescriptor( } } + fun store(lua: LuaThread, pushParameters: Boolean = true): Boolean { + if (isEmpty) { + lua.push() + } else { + lua.pushTable(hashSize = 3) + lua.setTableValue("name", name) + lua.setTableValue("count", count) + + if (pushParameters) + lua.setTableValue("parameters", parameters) + } + + return !isEmpty + } + fun toTable(allocator: TableFactory): Table? { if (isEmpty) { return null @@ -211,9 +338,7 @@ data class ItemDescriptor( fun build(level: Double? = null, seed: Long? = null, random: RandomGenerator? = null): ItemStack { try { - val (config, parameters) = buildConfig(level, seed, random) - val jConfig = config.map({ it }, { toJsonFromLua(it).asJsonObject }) - val jParameters = parameters.map({ it }, { toJsonFromLua(it).asJsonObject }) + val (jConfig, jParameters) = buildConfig(level, seed, random) return ref.type.factory(ref, jConfig, jParameters, count) } catch (err: Throwable) { LOGGER.error("Error while building item '$name' using script ${ref.json["builder"]}", err) @@ -226,16 +351,27 @@ data class ItemDescriptor( * [level] and/or [seed] inside item parameters once it is built (otherwise we will get different or * reset stats on next item load from serialized state) */ - fun buildConfig(level: Double? = null, seed: Long? = null, random: RandomGenerator? = null): Pair, Either> { - val builder = ref.json["builder"]?.asString ?: return Either.left(ref.json) to Either.left(parameters.deepCopy()) + fun buildConfig(level: Double? = null, seed: Long? = null, random: RandomGenerator? = null): Pair { + val builder = ref.json["builder"]?.asString ?: return ref.json to parameters.deepCopy() - val lua = LuaEnvironment() - lua.attach(Starbound.loadScript(builder)) - lua.random = random ?: lua.random - lua.init(false) + val lua = LuaThread() - val (config, parameters) = lua.invokeGlobal("build", ref.directory + "/", lua.from(ref.json), lua.from(parameters), level, seed) - return Either.right(config as Table) to Either.right(parameters as Table) + try { + lua.attach(builder) + lua.random = random ?: lua.random + lua.initScripts(false) + + return lua.invokeGlobal("build", 2, { + push(ref.directory + "/") + push(ref.json) + push(parameters) + push(level) + push(seed) + 5 + }, { getJson() as JsonObject to getJson() as JsonObject }).get() + } finally { + lua.close() + } } fun write(stream: DataOutputStream) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/item/ItemRegistry.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/item/ItemRegistry.kt index 6e44a180..35b58074 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/item/ItemRegistry.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/item/ItemRegistry.kt @@ -74,6 +74,10 @@ object ItemRegistry { return entry } + fun getOrThrow(name: String): Entry { + return entries[name] ?: throw NoSuchElementException("No such item '$name'") + } + operator fun contains(name: String): Boolean { return name in entries } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/item/ItemStack.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/item/ItemStack.kt index 6b1abd65..bd1be672 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/item/ItemStack.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/item/ItemStack.kt @@ -39,6 +39,7 @@ import ru.dbotthepony.kstarbound.json.mergeJson import ru.dbotthepony.kstarbound.json.stream import ru.dbotthepony.kstarbound.json.writeJsonElement import ru.dbotthepony.kstarbound.lua.LuaEnvironment +import ru.dbotthepony.kstarbound.lua.LuaThread import ru.dbotthepony.kstarbound.lua.from import ru.dbotthepony.kstarbound.network.syncher.NetworkedElement import ru.dbotthepony.kstarbound.util.AssetPathStack @@ -373,14 +374,6 @@ open class ItemStack(val entry: ItemRegistry.Entry, val config: JsonObject, para return createDescriptor().toJson() } - fun toTable(allocator: TableFactory): Table? { - if (isEmpty) { - return null - } - - return createDescriptor().toTable(allocator) - } - class Adapter(gson: Gson) : TypeAdapter() { override fun write(out: JsonWriter, value: ItemStack?) { val json = value?.toJson() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt index 6e8ec74b..f1f0728b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt @@ -465,15 +465,15 @@ fun LuaThread.getPoly(stackIndex: Int = -1): Poly? { }?.let { Poly(it) } } -fun LuaThread.ArgStack.getPoly(position: Int = this.position++): Poly { +fun LuaThread.ArgStack.nextPoly(position: Int = this.position++): Poly { if (position !in 1 ..this.top) - throw IllegalArgumentException("Bad argument #$position: Poly expected, got nil") + throw IllegalArgumentException("bad argument #$position: Poly expected, got nil") return lua.getPoly(position) - ?: throw IllegalArgumentException("Bad argument #$position: Poly expected, got ${lua.typeAt(position)}") + ?: throw IllegalArgumentException("bad argument #$position: Poly expected, got ${lua.typeAt(position)}") } -fun LuaThread.ArgStack.getPolyOrNull(position: Int = this.position++): Poly? { +fun LuaThread.ArgStack.nextOptionalPoly(position: Int = this.position++): Poly? { if (position !in 1 ..this.top) return null @@ -503,15 +503,15 @@ fun LuaThread.getLine2d(stackIndex: Int = -1): Line2d? { return Line2d(x, y) } -fun LuaThread.ArgStack.getLine2d(position: Int = this.position++): Line2d { +fun LuaThread.ArgStack.nextLine2d(position: Int = this.position++): Line2d { if (position !in 1 ..this.top) - throw IllegalArgumentException("Bad argument #$position: Line2d expected, got nil") + throw IllegalArgumentException("bad argument #$position: Line2d expected, got nil") return lua.getLine2d(position) - ?: throw IllegalArgumentException("Bad argument #$position: Line2d expected, got ${lua.typeAt(position)}") + ?: throw IllegalArgumentException("bad argument #$position: Line2d expected, got ${lua.typeAt(position)}") } -fun LuaThread.ArgStack.getLine2dOrNull(position: Int = this.position++): Line2d? { +fun LuaThread.ArgStack.nextOptionalLine2d(position: Int = this.position++): Line2d? { if (position !in 1 ..this.top) return null @@ -541,12 +541,19 @@ fun LuaThread.getVector2d(stackIndex: Int = -1): Vector2d? { return Vector2d(x, y) } -fun LuaThread.ArgStack.getVector2d(position: Int = this.position++): Vector2d { +fun LuaThread.ArgStack.nextVector2d(position: Int = this.position++): Vector2d { if (position !in 1 ..this.top) - throw IllegalArgumentException("Bad argument #$position: Vector2d expected, got nil") + throw IllegalArgumentException("bad argument #$position: Vector2d expected, got nil") + + return lua.getVector2d(position) + ?: throw IllegalArgumentException("bad argument #$position: Vector2d expected, got ${lua.typeAt(position)}") +} + +fun LuaThread.ArgStack.nextOptionalVector2d(position: Int = this.position++): Vector2d? { + if (position !in 1 ..this.top) + return null return lua.getVector2d(position) - ?: throw IllegalArgumentException("Bad argument #$position: Vector2d expected, got ${lua.typeAt(position)}") } fun LuaThread.getVector2i(stackIndex: Int = -1): Vector2i? { @@ -572,20 +579,19 @@ fun LuaThread.getVector2i(stackIndex: Int = -1): Vector2i? { return Vector2i(x.toInt(), y.toInt()) } -fun LuaThread.ArgStack.getVector2i(position: Int = this.position++): Vector2i { +fun LuaThread.ArgStack.nextVector2i(position: Int = this.position++): Vector2i { if (position !in 1 ..this.top) - throw IllegalArgumentException("Bad argument #$position: Vector2i expected, got nil") + throw IllegalArgumentException("bad argument #$position: Vector2i expected, got nil") return lua.getVector2i(position) - ?: throw IllegalArgumentException("Bad argument #$position: Vector2i expected, got ${lua.typeAt(position)}") + ?: throw IllegalArgumentException("bad argument #$position: Vector2i expected, got ${lua.typeAt(position)}") } -fun LuaThread.ArgStack.getVector2iOrNull(position: Int = this.position++): Vector2i? { +fun LuaThread.ArgStack.nextOptionalVector2i(position: Int = this.position++): Vector2i? { if (position !in 1 ..this.top) return null - lua.typeAt(position).isTableOrNothing { "Bad argument #$position: optional Vector2i expected, got nil" } - + lua.typeAt(position).isTableOrNothing { "bad argument #$position: optional Vector2i expected, got $this" } return lua.getVector2i(position) } @@ -625,15 +631,15 @@ fun LuaThread.getColor(stackIndex: Int = -1): RGBAColor? { return RGBAColor(x.toInt(), y.toInt(), z.toInt(), w.toInt()) } -fun LuaThread.ArgStack.getColor(position: Int = this.position++): RGBAColor { +fun LuaThread.ArgStack.nextColor(position: Int = this.position++): RGBAColor { if (position !in 1 ..this.top) - throw IllegalArgumentException("Bad argument #$position: RGBAColor expected, got nil") + throw IllegalArgumentException("bad argument #$position: RGBAColor expected, got nil") return lua.getColor(position) - ?: throw IllegalArgumentException("Bad argument #$position: RGBAColor expected, got ${lua.typeAt(position)}") + ?: throw IllegalArgumentException("bad argument #$position: RGBAColor expected, got ${lua.typeAt(position)}") } -fun LuaThread.ArgStack.getColorOrNull(position: Int = this.position++): RGBAColor? { +fun LuaThread.ArgStack.nextOptionalColor(position: Int = this.position++): RGBAColor? { if (position !in 1 ..this.top) return null @@ -677,15 +683,15 @@ fun LuaThread.getAABB(stackIndex: Int = -1): AABB? { return AABB(Vector2d(x, y), Vector2d(z, w)) } -fun LuaThread.ArgStack.getAABB(position: Int = this.position++): AABB { +fun LuaThread.ArgStack.nextAABB(position: Int = this.position++): AABB { if (position !in 1 ..this.top) - throw IllegalArgumentException("Bad argument #$position: RGBAColor expected, got nil") + throw IllegalArgumentException("bad argument #$position: RGBAColor expected, got nil") return lua.getAABB(position) - ?: throw IllegalArgumentException("Bad argument #$position: RGBAColor expected, got ${lua.typeAt(position)}") + ?: throw IllegalArgumentException("bad argument #$position: RGBAColor expected, got ${lua.typeAt(position)}") } -fun LuaThread.ArgStack.getAABBOrNull(position: Int = this.position++): AABB? { +fun LuaThread.ArgStack.nextOptionalAABB(position: Int = this.position++): AABB? { if (position !in 1 ..this.top) return null @@ -729,15 +735,15 @@ fun LuaThread.getAABBi(stackIndex: Int = -1): AABBi? { return AABBi(Vector2i(x.toInt(), y.toInt()), Vector2i(z.toInt(), w.toInt())) } -fun LuaThread.ArgStack.getAABBi(position: Int = this.position++): AABBi { +fun LuaThread.ArgStack.nextAABBi(position: Int = this.position++): AABBi { if (position !in 1 ..this.top) - throw IllegalArgumentException("Bad argument #$position: RGBAColor expected, got nil") + throw IllegalArgumentException("bad argument #$position: RGBAColor expected, got nil") return lua.getAABBi(position) - ?: throw IllegalArgumentException("Bad argument #$position: RGBAColor expected, got ${lua.typeAt(position)}") + ?: throw IllegalArgumentException("bad argument #$position: RGBAColor expected, got ${lua.typeAt(position)}") } -fun LuaThread.ArgStack.getAABBiOrNull(position: Int = this.position++): AABBi? { +fun LuaThread.ArgStack.nextOptionalAABBi(position: Int = this.position++): AABBi? { if (position !in 1 ..this.top) return null @@ -750,19 +756,19 @@ fun LuaThread.push(value: IStruct4i) { val (x, y, z, w) = value push(1) - push(x) + push(x.toLong()) setTableValue(table) push(2) - push(y) + push(y.toLong()) setTableValue(table) push(3) - push(z) + push(z.toLong()) setTableValue(table) push(4) - push(w) + push(w.toLong()) setTableValue(table) } @@ -772,15 +778,15 @@ fun LuaThread.push(value: IStruct3i) { val (x, y, z) = value push(1) - push(x) + push(x.toLong()) setTableValue(table) push(2) - push(y) + push(y.toLong()) setTableValue(table) push(3) - push(z) + push(z.toLong()) setTableValue(table) } @@ -790,11 +796,11 @@ fun LuaThread.push(value: IStruct2i) { val (x, y) = value push(1) - push(x) + push(x.toLong()) setTableValue(table) push(2) - push(y) + push(y.toLong()) setTableValue(table) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaThread.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaThread.kt index 07a09f1b..7f659f85 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaThread.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaThread.kt @@ -72,14 +72,14 @@ class LuaThread private constructor( this.storeGlobal("utf8") push { - LOGGER.info(getString()) + LOGGER.info(it.nextString()) 0 } storeGlobal("__print") push { - val path = getString() + val path = it.nextString() try { load(Starbound.readLuaScript(path).join(), "@$path") @@ -100,14 +100,14 @@ class LuaThread private constructor( storeGlobal("__random_double") push { - push(random.nextLong(getLong(), getLong())) + push(random.nextLong(it.nextLong(), it.nextLong())) 1 } storeGlobal("__random_long") push { - random = random(getLong()) + random = random(it.nextLong()) 0 } @@ -117,6 +117,10 @@ class LuaThread private constructor( call() } + fun interface Fn { + fun invoke(args: ArgStack): Int + } + private var cleanable: Cleaner.Cleanable? = null private var randomHolder: Delegate by Delegates.notNull() @@ -269,7 +273,7 @@ class LuaThread private constructor( } } - inline fun eval(chunk: String, name: String = "eval", arguments: LuaThread.() -> Int) { + inline fun eval(chunk: String, name: String = "eval", arguments: LuaThread.() -> Int) { val top = stackTop try { @@ -281,7 +285,18 @@ class LuaThread private constructor( } } - private val attachedScripts = ArrayList() + fun eval(chunk: String, name: String = "eval") { + val top = stackTop + + try { + load(chunk, name) + call() + } finally { + setTop(top) + } + } + + private val attachedScripts = ArrayList() private var initCalled = false fun initScripts(callInit: Boolean = true): Boolean { @@ -292,7 +307,7 @@ class LuaThread private constructor( return true } - val loadScripts = attachedScripts.map { Starbound.readLuaScript(it.fullPath) to it.fullPath } + val loadScripts = attachedScripts.map { Starbound.readLuaScript(it) to it } attachedScripts.clear() try { @@ -328,9 +343,13 @@ class LuaThread private constructor( } fun attach(script: AssetPath) { + attach(script.fullPath) + } + + fun attach(script: String) { if (initCalled) { // minor hiccups during unpopulated script cache should be tolerable - load(Starbound.readLuaScript(script.fullPath).join(), "@" + script.fullPath) + load(Starbound.readLuaScript(script).join(), "@$script") call() } else { attachedScripts.add(script) @@ -341,6 +360,11 @@ class LuaThread private constructor( script.forEach { attach(it) } } + @JvmName("attachAsStrings") + fun attach(script: Collection) { + script.forEach { attach(it) } + } + fun getString(stackIndex: Int = -1, limit: Long = DEFAULT_STRING_LIMIT): String? { if (!this.isString(stackIndex)) return null @@ -709,24 +733,13 @@ class LuaThread private constructor( return values } - fun getTableValue(stackIndex: Int = -2, limit: Long = DEFAULT_STRING_LIMIT): JsonElement? { - this.loadTableValue(stackIndex) - return this.getJson(limit = limit) + fun loadTableValue(stackIndex: Int = -2): LuaType { + return LuaType.valueOf(LuaJNR.INSTANCE.lua_gettable(this.pointer, stackIndex)) } - fun loadTableValue(stackIndex: Int = -2, allowNothing: Boolean = false) { - val abs = this.absStackIndex(stackIndex) - - if (!this.isTable(abs)) - throw IllegalArgumentException("Attempt to index an ${this.typeAt(abs)} value") - - if (LuaJNR.INSTANCE.lua_gettable(this.pointer, abs) == LUA_TNONE && !allowNothing) - throw IllegalStateException("loaded TNONE from Lua table") - } - - fun loadTableValue(name: String, stackIndex: Int = -2) { - this.push(name) - this.loadTableValue(stackIndex) + fun loadTableValue(name: String): LuaType { + push(name) + return loadTableValue() } fun popBoolean(): Boolean? { @@ -798,95 +811,88 @@ class LuaThread private constructor( val lua get() = this@LuaThread var position = 1 - fun hasSomethingAt(position: Int): Boolean { + fun peek(position: Int = this.position): LuaType { + if (position !in 1 .. top) + return LuaType.NONE + + return this@LuaThread.typeAt(position) + } + + fun nextString(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): String { if (position !in 1 ..this.top) - return false - - return this@LuaThread.typeAt(position) != LuaType.NONE - } - - fun hasSomethingAt(): Boolean { - if (hasSomethingAt(this.position + 1)) - return true - - this.position++ - return false - } - - fun isStringAt(position: Int = this.position): Boolean { - return this@LuaThread.typeAt(position) == LuaType.STRING - } - - fun isNumberAt(position: Int = this.position): Boolean { - return this@LuaThread.typeAt(position) == LuaType.NUMBER - } - - fun getString(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): String { - if (position !in 1 ..this.top) - throw IllegalArgumentException("Bad argument #$position: string expected, got nil") + throw IllegalArgumentException("bad argument #$position: string expected, got nil") return this@LuaThread.getString(position, limit = limit) - ?: throw IllegalArgumentException("Bad argument #$position: string expected, got ${this@LuaThread.typeAt(position)}") + ?: throw IllegalArgumentException("bad argument #$position: string expected, got ${this@LuaThread.typeAt(position)}") } - fun getStringOrNull(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): String? { + fun nextOptionalString(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): String? { val type = this@LuaThread.typeAt(position) if (type != LuaType.STRING && type != LuaType.NIL && type != LuaType.NONE) - throw IllegalArgumentException("Bad argument #$position: string expected, got $type") + throw IllegalArgumentException("bad argument #$position: string expected, got $type") return this@LuaThread.getString(position, limit = limit) } - fun getLong(position: Int = this.position++): Long { + fun nextLong(position: Int = this.position++): Long { if (position !in 1 ..this.top) - throw IllegalArgumentException("Bad argument #$position: number expected, got nil") + throw IllegalArgumentException("bad argument #$position: number expected, got nil") return this@LuaThread.getLong(position) - ?: throw IllegalArgumentException("Bad argument #$position: long expected, got ${this@LuaThread.typeAt(position)}") + ?: throw IllegalArgumentException("bad argument #$position: long expected, got ${this@LuaThread.typeAt(position)}") } - fun getJson(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonElement { + fun nextJson(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonElement { if (position !in 1 ..this.top) - throw IllegalArgumentException("Bad argument #$position: json expected, got nil") + throw IllegalArgumentException("bad argument #$position: json expected, got nil") val value = this@LuaThread.getJson(position, limit = limit) - return value ?: throw IllegalArgumentException("Bad argument #$position: anything expected, got ${this@LuaThread.typeAt(position)}") + return value ?: throw IllegalArgumentException("bad argument #$position: anything expected, got ${this@LuaThread.typeAt(position)}") } - fun getTable(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonObject { + fun nextTable(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonObject { if (position !in 1 ..this.top) - throw IllegalArgumentException("Bad argument #$position: table expected, got nil") + throw IllegalArgumentException("bad argument #$position: table expected, got nil") val value = this@LuaThread.getTable(position, limit = limit) - return value ?: throw IllegalArgumentException("Lua code error: Bad argument #$position: table expected, got ${this@LuaThread.typeAt(position)}") + return value ?: throw IllegalArgumentException("Lua code error: bad argument #$position: table expected, got ${this@LuaThread.typeAt(position)}") } - fun getAnything(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonElement? { + fun nextAny(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonElement? { if (position !in 1 ..this.top) - throw IllegalArgumentException("Bad argument #$position: json expected, got nil") + throw IllegalArgumentException("bad argument #$position: json expected, got nil") return this@LuaThread.getJson(position, limit = limit) } - fun getDouble(position: Int = this.position++): Double { + fun nextDouble(position: Int = this.position++): Double { if (position !in 1 ..this.top) - throw IllegalArgumentException("Bad argument #$position: number expected, got nil") + throw IllegalArgumentException("bad argument #$position: number expected, got nil") return this@LuaThread.getDouble(position) - ?: throw IllegalArgumentException("Bad argument #$position: number expected, got ${this@LuaThread.typeAt(position)}") + ?: throw IllegalArgumentException("bad argument #$position: number expected, got ${this@LuaThread.typeAt(position)}") } - fun getDoubleOrNull(position: Int = this.position++): Double? { + fun nextOptionalDouble(position: Int = this.position++): Double? { val type = this@LuaThread.typeAt(position) if (type != LuaType.NUMBER && type != LuaType.NIL && type != LuaType.NONE) - throw IllegalArgumentException("Bad argument #$position: double expected, got $type") + throw IllegalArgumentException("bad argument #$position: double expected, got $type") return this@LuaThread.getDouble(position) } - fun getBooleanOrNull(position: Int = this.position++): Boolean? { + fun nextOptionalLong(position: Int = this.position++): Long? { + val type = this@LuaThread.typeAt(position) + + if (type != LuaType.NUMBER && type != LuaType.NIL && type != LuaType.NONE) + throw IllegalArgumentException("bad argument #$position: integer expected, got $type") + + return this@LuaThread.getLong(position) + } + + fun nextOptionalBoolean(position: Int = this.position++): Boolean? { val type = this@LuaThread.typeAt(position) if (type == LuaType.NIL || type == LuaType.NONE) @@ -894,28 +900,19 @@ class LuaThread private constructor( else if (type == LuaType.BOOLEAN) return this@LuaThread.getBoolean(position) else - throw IllegalArgumentException("Lua code error: Bad argument #$position: boolean expected, got $type") + throw IllegalArgumentException("Lua code error: bad argument #$position: boolean expected, got $type") } - fun getBoolean(position: Int = this.position++): Boolean { + fun nextBoolean(position: Int = this.position++): Boolean { if (position !in 1 ..this.top) - throw IllegalArgumentException("Bad argument #$position: boolean expected, got nil") + throw IllegalArgumentException("bad argument #$position: boolean expected, got nil") return this@LuaThread.getBoolean(position) - ?: throw IllegalArgumentException("Bad argument #$position: boolean expected, got ${this@LuaThread.typeAt(position)}") + ?: throw IllegalArgumentException("bad argument #$position: boolean expected, got ${this@LuaThread.typeAt(position)}") } - - fun push() = this@LuaThread.push() - fun push(value: Int) = this@LuaThread.push(value) - fun push(value: Long) = this@LuaThread.push(value) - fun push(value: Double) = this@LuaThread.push(value) - fun push(value: Float) = this@LuaThread.push(value) - fun push(value: Boolean) = this@LuaThread.push(value) - fun push(value: String) = this@LuaThread.push(value) - fun push(value: JsonElement?) = this@LuaThread.push(value) } - fun push(function: ArgStack.() -> Int, performanceCritical: Boolean) { + fun push(function: Fn, performanceCritical: Boolean) { LuaJNI.lua_pushcclosure(pointer.address()) lazy@{ val realLuaState: LuaThread @@ -982,7 +979,7 @@ class LuaThread private constructor( } } - fun push(function: ArgStack.() -> Int) = this.push(function, !RECORD_STACK_TRACES) + fun push(function: Fn) = this.push(function, !RECORD_STACK_TRACES) fun moveStackValuesOnto(other: LuaThread, amount: Int = 1) { LuaJNR.INSTANCE.lua_xmove(pointer, other.pointer, amount) @@ -992,14 +989,18 @@ class LuaThread private constructor( LuaJNR.INSTANCE.lua_pushnil(this.pointer) } - fun push(value: Int) { - LuaJNR.INSTANCE.lua_pushinteger(this.pointer, value.toLong()) - } - fun push(value: Long) { LuaJNR.INSTANCE.lua_pushinteger(this.pointer, value) } + fun push(value: Long?) { + if (value == null) { + LuaJNR.INSTANCE.lua_pushnil(pointer) + } else { + LuaJNR.INSTANCE.lua_pushinteger(this.pointer, value) + } + } + fun push(value: Double) { LuaJNR.INSTANCE.lua_pushnumber(this.pointer, value) } @@ -1012,6 +1013,30 @@ class LuaThread private constructor( LuaJNR.INSTANCE.lua_pushboolean(this.pointer, if (value) 1 else 0) } + fun push(value: Double?) { + if (value == null) { + LuaJNR.INSTANCE.lua_pushnil(pointer) + } else { + LuaJNR.INSTANCE.lua_pushnumber(this.pointer, value) + } + } + + fun push(value: Float?) { + if (value == null) { + LuaJNR.INSTANCE.lua_pushnil(pointer) + } else { + LuaJNR.INSTANCE.lua_pushnumber(this.pointer, value.toDouble()) + } + } + + fun push(value: Boolean?) { + if (value == null) { + LuaJNR.INSTANCE.lua_pushnil(pointer) + } else { + LuaJNR.INSTANCE.lua_pushboolean(this.pointer, if (value) 1 else 0) + } + } + fun push(value: String) { pushStringIntoThread(this, value) } @@ -1020,6 +1045,16 @@ class LuaThread private constructor( LuaJNR.INSTANCE.lua_copy(pointer, fromIndex, toIndex) } + fun dup() { + push() + copy(-2, -1) + } + + fun dup(fromIndex: Int) { + push() + copy(fromIndex, -1) + } + fun setTop(topIndex: Int) { LuaJNR.INSTANCE.lua_settop(pointer, topIndex) } @@ -1036,15 +1071,26 @@ class LuaThread private constructor( LuaJNR.INSTANCE.lua_settable(this.pointer, stackIndex) } + fun setTableValue(key: String, value: Fn) { + this.push(key) + this.push(value) + this.setTableValue() + } + fun setTableValue(key: String, value: JsonElement?) { this.push(key) this.push(value) this.setTableValue() } + @Deprecated("Lua function is a stub") + fun setTableValueToStub(key: String) { + setTableValue(key) { throw NotImplementedError("NYI: $key") } + } + fun setTableValue(key: String, value: Int) { this.push(key) - this.push(value) + this.push(value.toLong()) this.setTableValue() } @@ -1098,10 +1144,9 @@ class LuaThread private constructor( } fun setTableValue(key: Long, value: JsonElement?) { - val table = this.stackTop this.push(key) this.push(value) - this.setTableValue(table) + this.setTableValue() } fun setTableValue(key: Long, value: Int) { @@ -1109,17 +1154,15 @@ class LuaThread private constructor( } fun setTableValue(key: Long, value: Long) { - val table = this.stackTop this.push(key) this.push(value) - this.setTableValue(table) + this.setTableValue() } fun setTableValue(key: Long, value: String) { - val table = this.stackTop this.push(key) this.push(value) - this.setTableValue(table) + this.setTableValue() } fun setTableValue(key: Long, value: Float) { @@ -1127,10 +1170,9 @@ class LuaThread private constructor( } fun setTableValue(key: Long, value: Double) { - val table = this.stackTop this.push(key) this.push(value) - this.setTableValue(table) + this.setTableValue() } fun push(value: JsonElement?) { @@ -1243,6 +1285,11 @@ class LuaThread private constructor( } fun pushStringIntoThread(lua: LuaThread, value: String) { + if (value.isEmpty()) { + LuaJNR.INSTANCE.lua_pushlstring(lua.pointer, lua.pointer.address(), 0) + return + } + val bytes = value.toByteArray(Charsets.UTF_8) if (bytes.size < DEFAULT_STRING_LIMIT) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/RootBindings.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/RootBindings.kt index c218067a..e5db29d6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/RootBindings.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/RootBindings.kt @@ -6,10 +6,6 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap import org.apache.logging.log4j.LogManager import org.classdump.luna.ByteString import org.classdump.luna.LuaRuntimeException -import org.classdump.luna.Table -import org.classdump.luna.lib.ArgumentIterator -import org.classdump.luna.runtime.ExecutionContext -import org.classdump.luna.runtime.LuaFunction import ru.dbotthepony.kstarbound.Globals import ru.dbotthepony.kstarbound.item.RecipeRegistry import ru.dbotthepony.kstarbound.Registries @@ -20,508 +16,505 @@ import ru.dbotthepony.kstarbound.defs.image.Image import ru.dbotthepony.kstarbound.defs.image.SpriteReference import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor import ru.dbotthepony.kstarbound.item.ItemRegistry -import ru.dbotthepony.kstarbound.lua.LUA_HINT_ARRAY import ru.dbotthepony.kstarbound.lua.LuaEnvironment -import ru.dbotthepony.kstarbound.lua.StateMachine -import ru.dbotthepony.kstarbound.lua.createJsonArray -import ru.dbotthepony.kstarbound.lua.createJsonObject +import ru.dbotthepony.kstarbound.lua.LuaThread +import ru.dbotthepony.kstarbound.lua.LuaType import ru.dbotthepony.kstarbound.lua.from -import ru.dbotthepony.kstarbound.lua.get -import ru.dbotthepony.kstarbound.lua.indexNoYield -import ru.dbotthepony.kstarbound.lua.indexSetNoYield -import ru.dbotthepony.kstarbound.lua.iterator +import ru.dbotthepony.kstarbound.lua.nextVector2i import ru.dbotthepony.kstarbound.lua.luaFunction import ru.dbotthepony.kstarbound.lua.luaFunctionN -import ru.dbotthepony.kstarbound.lua.luaFunctionNS import ru.dbotthepony.kstarbound.lua.luaStub -import ru.dbotthepony.kstarbound.lua.nextOptionalFloat -import ru.dbotthepony.kstarbound.lua.nextOptionalInteger +import ru.dbotthepony.kstarbound.lua.push import ru.dbotthepony.kstarbound.lua.set -import ru.dbotthepony.kstarbound.lua.tableMapOf import ru.dbotthepony.kstarbound.lua.tableOf -import ru.dbotthepony.kstarbound.lua.toByteString -import ru.dbotthepony.kstarbound.lua.toJsonFromLua -import ru.dbotthepony.kstarbound.lua.toLuaInteger import ru.dbotthepony.kstarbound.util.random.random -import kotlin.collections.component1 -import kotlin.collections.component2 import kotlin.collections.isNotEmpty -import kotlin.collections.random import kotlin.collections.set import kotlin.collections.withIndex -import kotlin.math.max private val LOGGER = LogManager.getLogger() -private fun lookup(registry: Registry, key: Any?): Registry.Entry? { - if (key is ByteString) { - return registry[key.decode()] - } else if (key is Number) { - return registry[key.toInt()] - } else { - return null +private fun lookup(registry: Registry, args: LuaThread.ArgStack): Registry.Entry? { + return when (val type = args.peek()) { + LuaType.NUMBER -> registry[args.nextLong().toInt()] + LuaType.STRING -> registry[args.nextString()] + LuaType.NONE, LuaType.NIL -> null + else -> throw IllegalArgumentException("Invalid registry key type: $type") } } -private fun lookupStrict(registry: Registry, key: Any?): Registry.Entry { - if (key is ByteString) { - return registry[key.decode()] ?: throw LuaRuntimeException("No such ${registry.name}: $key") - } else if (key is Number) { - return registry[key.toInt()] ?: throw LuaRuntimeException("No such ${registry.name}: $key") - } else { - throw LuaRuntimeException("No such ${registry.name}: $key") - } -} - -private val evalFunction = luaFunction { name: ByteString, value: Double -> - val fn = Registries.jsonFunctions[name.decode()] ?: throw LuaRuntimeException("No such function $name") - returnBuffer.setTo(fn.value.evaluate(value)) -} - -private val evalFunction2 = luaFunction { name: ByteString, value: Double, value2: Double -> - val fn = Registries.json2Functions[name.decode()] ?: throw LuaRuntimeException("No such function $name") - returnBuffer.setTo(fn.value.evaluate(value, value2)) -} - -private val imageSize = luaFunction { name: ByteString -> - val ref = SpriteReference.create(name.decode()) - val sprite = ref.sprite ?: throw LuaRuntimeException("No such image or sprite $ref") - returnBuffer.setTo(tableOf(sprite.width, sprite.height)) -} - -private fun imageSpaces(context: ExecutionContext, arguments: ArgumentIterator): StateMachine { - val machine = StateMachine() - - val name = arguments.nextString() - val image = Image.get(name.decode()) ?: throw LuaRuntimeException("No such image $name") - - val pixelOffset = machine.loadVector2i(arguments.nextTable()) - val fillFactor = arguments.nextFloat() - val flip = arguments.nextOptionalBoolean(false) - - return machine - .add { - val values = image.worldSpaces(pixelOffset.get(), fillFactor, flip) - val table = context.newTable(values.size, 0) - - for ((i, value) in values.withIndex()) { - table.rawset(i.toLong() + 1, value) - } +private fun lookupStrict(registry: Registry, args: LuaThread.ArgStack): Registry.Entry { + return when (val type = args.peek()) { + LuaType.NUMBER -> { + val key = args.nextLong().toInt() + registry[key] ?: throw LuaRuntimeException("No such ${registry.name}: $key") } -} -private fun nonEmptyRegion(context: ExecutionContext, name: ByteString) { - val image = Image.get(name.decode()) ?: throw LuaRuntimeException("No such image $name") - context.returnBuffer.setTo(context.from(image.nonEmptyRegion)) -} + LuaType.STRING -> { + val key = args.nextString() + registry[key] ?: throw LuaRuntimeException("No such ${registry.name}: $key") + } -private fun registryDef(registry: Registry<*>): LuaFunction { - return luaFunction { name -> - val value = registry[name.decode()] ?: throw LuaRuntimeException("No such NPC type $name") - returnBuffer.setTo(from(value.json)) + else -> throw IllegalArgumentException("Invalid registry key type: $type") } } -private fun registryDef2(registry: Registry<*>): LuaFunction { - return luaFunction { name -> - val def = lookup(registry, name) +private fun registryDef(registry: Registry<*>): LuaThread.Fn { + return LuaThread.Fn { args -> + val name = args.nextString() + val value = registry[name] ?: throw LuaRuntimeException("No such ${registry.name}: $name") + args.lua.push(value.json) + return@Fn 1 + } +} + +private fun registryDef2(registry: Registry<*>): LuaThread.Fn { + return LuaThread.Fn { args -> + val def = lookup(registry, args) if (def != null) { - returnBuffer.setTo(newTable(0, 2).also { - it["path"] = def.file?.computeFullPath().toByteString() - it["config"] = from(def.json) - }) + args.lua.pushTable(hashSize = 2) + args.lua.setTableValue("path", def.file?.computeFullPath()) + args.lua.setTableValue("config", def.json) + + return@Fn 1 } else { - returnBuffer.setTo() + return@Fn 0 } } } -private fun registryDefExists(registry: Registry<*>): LuaFunction { - return luaFunction { name -> - returnBuffer.setTo(name.decode() in registry) +private fun registryDefExists(registry: Registry<*>): LuaThread.Fn { + return LuaThread.Fn { args -> + args.lua.push(args.nextString() in registry) + return@Fn 1 } } -private val recipesForItem = luaFunction { name: ByteString -> - val list = RecipeRegistry.output2recipes[name.decode()] +private fun recipesForItem(args: LuaThread.ArgStack): Int { + val list = RecipeRegistry.output2recipes[args.nextString()] if (list == null) { - returnBuffer.setTo(tableOf()) + args.lua.pushTable() } else { - returnBuffer.setTo(newTable(list.size, 0).also { - for ((i, v) in list.withIndex()) { - it.rawset(i + 1L, from(v.json)) - } - }) + args.lua.pushTable(list.size) + + for ((i, v) in list.withIndex()) { + args.lua.setTableValue(i + 1, v.json) + } } + + return 1 } -private fun getMatchingTenants(context: ExecutionContext, tags: Table) { +private fun getMatchingTenants(args: LuaThread.ArgStack): Int { + val tags = args.lua.readTable(args.position++, { getString() ?: throw IllegalArgumentException("Table contains non-string keys") }, { getLong()?.toInt() ?: throw IllegalArgumentException("Table contains non-integer values") }) + ?: throw IllegalArgumentException("bad argument #1 to getMatchingTenants: table expected, got ${args.peek(1)}") + val actualTags = Object2IntOpenHashMap() for ((k, v) in tags) { - if (k is ByteString && v is Number) { - actualTags[k.decode()] = v.toInt() - } + actualTags[k] = v } val result = Registries.tenants.keys.values .stream() .filter { it.value.test(actualTags) } .sorted { a, b -> b.value.compareTo(a.value) } - .map { context.from(it.json) } .toList() - context.returnBuffer.setTo(context.newTable(result.size, 0).also { - for ((k, v) in result.withIndex()) { - it[k + 1] = v - } - }) -} + args.lua.pushTable(result.size) -private val liquidStatusEffects = luaFunction { id: Any -> - val liquid = lookup(Registries.liquid, id) - - if (liquid == null) { - returnBuffer.setTo(tableOf()) - } else { - returnBuffer.setTo(tableOf(*liquid.value.statusEffects.map { it.key.map({ it }, { it }) }.toTypedArray())) + for ((k, v) in result.withIndex()) { + args.lua.setTableValue(k + 1, v.key) } + + return 1 } -private fun materialMiningSound(context: ExecutionContext, arguments: ArgumentIterator) { - val tile = lookup(Registries.tiles, arguments.nextAny()) - val mod = lookup(Registries.tiles, arguments.nextOptionalAny(null)) +private fun liquidStatusEffects(args: LuaThread.ArgStack): Int { + val liquid = lookup(Registries.liquid, args) + + if (liquid != null) { + args.lua.pushTable(liquid.value.statusEffects.size) + + for ((i, effect) in liquid.value.statusEffects.withIndex()) { + effect.key.map({ args.lua.setTableValue(i + 1L, it) }, { args.lua.setTableValue(i + 1L, it) }) + } + } else { + args.lua.pushTable() + } + + return 1 +} + +private fun materialMiningSound(args: LuaThread.ArgStack): Int { + val tile = lookup(Registries.tiles, args) + val mod = lookup(Registries.tiles, args) if (mod != null && mod.value.miningSounds.map({ it.isNotEmpty() }, { true })) { - context.returnBuffer.setTo(mod.value.miningSounds.map({ it.random() }, { it }).toByteString()) - return + args.lua.push(mod.value.miningSounds.map({ it.random(args.lua.random) }, { it })) + } else if (tile != null && tile.value.miningSounds.map({ it.isNotEmpty() }, { true })) { + args.lua.push(tile.value.miningSounds.map({ it.random(args.lua.random) }, { it })) + } else { + // original engine parity + args.lua.push("") } - if (tile != null && tile.value.miningSounds.map({ it.isNotEmpty() }, { true })) { - context.returnBuffer.setTo(tile.value.miningSounds.map({ it.random() }, { it }).toByteString()) - return - } - - // original engine parity - context.returnBuffer.setTo("".toByteString()) + return 1 } -private fun materialFootstepSound(context: ExecutionContext, arguments: ArgumentIterator) { - val tile = lookup(Registries.tiles, arguments.nextAny()) - val mod = lookup(Registries.tiles, arguments.nextOptionalAny(null)) +private fun materialFootstepSound(args: LuaThread.ArgStack): Int { + val tile = lookup(Registries.tiles, args) + val mod = lookup(Registries.tiles, args) if (mod != null && mod.value.footstepSound.map({ it.isNotEmpty() }, { true })) { - context.returnBuffer.setTo(mod.value.footstepSound.map({ it.random() }, { it }).toByteString()) - return - } - - if (tile != null && tile.value.footstepSound.map({ it.isNotEmpty() }, { true })) { - context.returnBuffer.setTo(tile.value.footstepSound.map({ it.random() }, { it }).toByteString()) - return - } - - context.returnBuffer.setTo(Globals.client.defaultFootstepSound.map({ it }, { it.random() }).toByteString()) -} - -private val materialHealth = luaFunction { id: Any -> - returnBuffer.setTo(lookupStrict(Registries.tiles, id).value.actualDamageTable.totalHealth) -} - -private val liquidName = luaFunction { id: Any -> - returnBuffer.setTo(lookupStrict(Registries.liquid, id).key.toByteString()) -} - -private val liquidId = luaFunction { id: Any -> - returnBuffer.setTo(lookupStrict(Registries.liquid, id).id) -} - -private val techType = luaFunction { id: Any -> - returnBuffer.setTo(lookupStrict(Registries.techs, id).value.type.toByteString()) -} - -private val techConfig = luaFunction { id: Any -> - returnBuffer.setTo(from(lookupStrict(Registries.techs, id).json)) -} - -private val jobject = luaFunction { returnBuffer.setTo(createJsonObject()) } -private val jarray = luaFunction { returnBuffer.setTo(createJsonArray()) } - -private val jremove = luaFunction { self: Table, key: Any -> - val nils = self.metatable?.rawget("__nils") as? Table - - if (nils != null) { - nils[key] = 0L - } - - self[key] = null as Any? -} - -private val jsize = luaFunction { self: Table -> - var elemCount = 0L - var highestIndex = 0L - var hintList = false - - val meta = self.metatable - - if (meta != null) { - if (meta["__typehint"] == LUA_HINT_ARRAY) { - hintList = true - } - - val nils = meta["__nils"] - - if (nils is Table) { - for ((k, v) in nils) { - val ik = k.toLuaInteger() - - if (ik != null) { - highestIndex = max(ik, highestIndex) - } else { - hintList = false - } - } - } - } - - for ((k, v) in self) { - val ik = k.toLuaInteger() - - if (ik != null) { - highestIndex = max(ik, highestIndex) - } else { - hintList = false - } - - elemCount++ - } - - if (hintList) { - returnBuffer.setTo(highestIndex) + args.lua.push(mod.value.footstepSound.map({ it.random(args.lua.random) }, { it })) + } else if (tile != null && tile.value.footstepSound.map({ it.isNotEmpty() }, { true })) { + args.lua.push(tile.value.footstepSound.map({ it.random(args.lua.random) }, { it })) } else { - returnBuffer.setTo(elemCount) - } -} - -// why is this a thing? -private val jresize = luaFunction { self: Table, target: Long -> - val nils = self.metatable?.rawget("__nils") as? Table - - if (nils != null) { - val keysToRemove = ArrayList() - - for ((k, v) in nils) { - val ik = k.toLuaInteger() - - if (ik != null && ik > 0L && ik > target) - keysToRemove.add(k) - } - - for (k in keysToRemove) { - nils[k] = null as Any? - } + args.lua.push(Globals.client.defaultFootstepSound.map({ it }, { it.random(args.lua.random) })) } - val keysToRemove = ArrayList() - - for ((k, v) in self) { - val ik = k.toLuaInteger() - - if (ik != null && ik > 0L && ik > target) - keysToRemove.add(k) - } - - for (k in keysToRemove) { - self[k] = null as Any? - } - - indexSetNoYield(self, target, indexNoYield(self, target)) + return 1 } -private val assetJson = luaFunction { path: ByteString -> - returnBuffer.setTo(from(Starbound.loadJsonAsset(path.decode()).get())) +private fun materialHealth(args: LuaThread.ArgStack): Int { + args.lua.push(lookupStrict(Registries.tiles, args).value.actualDamageTable.totalHealth) + return 1 } -private val makeCurrentVersionedJson = luaFunction { identifier: ByteString, content: Any? -> - returnBuffer.setTo(from(VersionRegistry.make(identifier.decode(), toJsonFromLua(content)).toJson())) +private fun liquidName(args: LuaThread.ArgStack): Int { + args.lua.push(lookupStrict(Registries.liquid, args).key) + return 1 } -private val loadVersionedJson = luaFunction { data: Any?, identifier: ByteString -> - returnBuffer.setTo(from(VersionRegistry.load(identifier.decode(), toJsonFromLua(data)))) +private fun liquidId(args: LuaThread.ArgStack): Int { + args.lua.push(lookupStrict(Registries.liquid, args).id?.toLong()) + return 1 } -private val createBiome = luaFunction { name: ByteString, seed: Number, verticalMidPoint: Number, threatLevel: Number -> +private fun techType(args: LuaThread.ArgStack): Int { + args.lua.push(lookupStrict(Registries.techs, args).value.type) + return 1 +} + +private fun techConfig(args: LuaThread.ArgStack): Int { + args.lua.push(lookupStrict(Registries.techs, args).json) + return 1 +} + +private fun createBiome(args: LuaThread.ArgStack): Int { + val name = args.nextString() + val seed = args.nextLong() + val verticalMidPoint = args.nextLong().toInt() + val threatLevel = args.nextDouble() + try { val biome = Registries.biomes - .getOrThrow(name.decode()) + .getOrThrow(name) .value - .create(random(seed.toLong()), verticalMidPoint.toInt(), threatLevel.toDouble()) + .create(random(seed), verticalMidPoint, threatLevel) - returnBuffer.setTo(from(Starbound.gson.toJsonTree(biome))) + args.lua.push(Starbound.gson.toJsonTree(biome)) + return 1 } catch (err: Throwable) { LOGGER.error("Exception while creating biome for Lua script of name $name with seed $seed, with verticalMidPoint $verticalMidPoint, with threatLevel $threatLevel", err) + return 0 } } -private val treeStemDirectory = luaFunction { name: ByteString -> - returnBuffer.setTo(Registries.treeStemVariants[name.decode()]?.file?.computeDirectory(true).toByteString() ?: "/".toByteString()) +private fun treeStemDirectory(args: LuaThread.ArgStack): Int { + val name = args.nextString() + args.lua.push(Registries.treeStemVariants[name]?.file?.computeDirectory(true) ?: "/") + return 1 } -private val treeFoliageDirectory = luaFunction { name: ByteString -> - returnBuffer.setTo(Registries.treeFoliageVariants[name.decode()]?.file?.computeDirectory(true).toByteString() ?: "/".toByteString()) +private fun treeFoliageDirectory(args: LuaThread.ArgStack): Int { + val name = args.nextString() + args.lua.push(Registries.treeFoliageVariants[name]?.file?.computeDirectory(true) ?: "/") + return 1 } -private val itemConfig = luaFunction { descriptor: Any, level: Number?, seed: Number? -> - val desc = ItemDescriptor(descriptor) +private fun itemConfig(args: LuaThread.ArgStack): Int { + val desc = ItemDescriptor(args) + val level = args.nextOptionalDouble() + val seed = args.nextOptionalLong() if (desc.name !in ItemRegistry) { - returnBuffer.setTo() + return 0 } else { - val (config, params) = desc.buildConfig(level?.toDouble(), seed?.toLong()) + val (config, parameters) = desc.buildConfig(level, seed) - returnBuffer.setTo(tableMapOf( - "directory" to ItemRegistry[desc.name].directory, - "config" to config.map({ from(it) }, { it }), - "parameters" to params.map({ from(it) }, { it }) - )) + args.lua.pushTable(hashSize = 3) + args.lua.setTableValue("directory", ItemRegistry[desc.name].directory) + args.lua.setTableValue("config", config) + args.lua.setTableValue("parameters", parameters) + + return 1 } } // why -private val createItem = luaFunction { descriptor: Any, level: Number?, seed: Number? -> - val desc = ItemDescriptor(descriptor) +private fun createItem(args: LuaThread.ArgStack): Int { + val desc = ItemDescriptor(args) + val level = args.nextOptionalDouble() + val seed = args.nextOptionalLong() if (desc.name !in ItemRegistry) { throw LuaRuntimeException("No such item ${desc.name}") } else { - val (_, params) = desc.buildConfig(level?.toDouble(), seed?.toLong()) - val tab = desc.toTable(this) + val (_, params) = desc.buildConfig(level, seed) - if (tab != null) - tab["parameters"] = params.map({ from(it) }, { it }) + if (desc.store(args.lua, pushParameters = false)) { + args.lua.setTableValue("parameters", params) + } - returnBuffer.setTo(tab) + return 1 } } -private val itemType = luaFunction { identifier: ByteString -> - returnBuffer.setTo(ItemRegistry[identifier.decode()].type.jsonName.toByteString()) +private fun itemType(args: LuaThread.ArgStack): Int { + args.lua.push(ItemRegistry.getOrThrow(args.nextString()).type.jsonName) + return 1 } -private val itemTags = luaFunction { identifier: ByteString -> - returnBuffer.setTo(tableOf(*ItemRegistry[identifier.decode()].itemTags.toTypedArray())) +private fun itemTags(args: LuaThread.ArgStack): Int { + val tags = ItemRegistry.getOrThrow(args.nextString()).itemTags + args.lua.pushTable(tags.size) + + for ((i, tag) in tags.withIndex()) { + args.lua.setTableValue(i + 1L, tag) + } + + return 1 } -private val itemHasTag = luaFunction { identifier: ByteString, tag: ByteString -> - returnBuffer.setTo(tag.decode() in ItemRegistry[identifier.decode()].itemTags) +private fun itemHasTag(args: LuaThread.ArgStack): Int { + val item = args.nextString() + val tag = args.nextString() + + args.lua.push(tag in ItemRegistry.getOrThrow(item).itemTags) + return 1 } -private val monsterSkillParameter = luaFunction { skillName: ByteString, configParameterName: ByteString -> - val skill = Registries.monsterSkills[skillName.decode()] +private fun monsterSkillParameter(args: LuaThread.ArgStack): Int { + val skillName = args.nextString() + val configParameterName = args.nextString() + val skill = Registries.monsterSkills[skillName] if (skill != null) { - returnBuffer.setTo(from(skill.value.config[configParameterName.decode()] ?: JsonNull.INSTANCE)) + args.lua.push(skill.value.config[configParameterName] ?: JsonNull.INSTANCE) + return 1 + } else { + return 0 } } -private val monsterParameters = luaFunction { monsterType: ByteString, seed: Number? -> - returnBuffer.setTo(from(Registries.monsterTypes.getOrThrow(monsterType.decode()).value.create(seed?.toLong() ?: 0L, JsonObject()).parameters)) +private fun monsterParameters(args: LuaThread.ArgStack): Int { + val monsterType = args.nextString() + val seed = args.nextOptionalLong() + args.lua.push(Registries.monsterTypes.getOrThrow(monsterType).value.create(seed ?: 0L, JsonObject()).parameters) + return 1 } -private val monsterMovementSettings = luaFunction { monsterType: ByteString, seed: Number? -> - returnBuffer.setTo(from(Registries.monsterTypes.getOrThrow(monsterType.decode()).value.create(seed?.toLong() ?: 0L, JsonObject()).parameters["movementSettings"] ?: JsonObject())) +private fun monsterMovementSettings(args: LuaThread.ArgStack): Int { + val monsterType = args.nextString() + val seed = args.nextOptionalLong() + args.lua.push(Registries.monsterTypes.getOrThrow(monsterType).value.create(seed ?: 0L, JsonObject()).parameters["movementSettings"] ?: JsonObject()) + return 1 } -private val elementalResistance = luaFunction { damageKindName: ByteString -> - returnBuffer.setTo(Globals.elementalTypes[Registries.damageKinds.getOrThrow(damageKindName.decode()).value.elementalType]!!.resistanceStat.toByteString()) +private fun elementalResistance(args: LuaThread.ArgStack): Int { + val name = args.nextString() + val type = Registries.damageKinds.getOrThrow(name).value.elementalType + val elemental = Globals.elementalTypes[type] ?: throw NoSuchElementException("Damage kind $name has specified $type as its elemental damage type, but it is missing from /damage/elementaltypes.config") + args.lua.push(elemental.resistanceStat) + return 1 } -private val dungeonMetadata = luaFunction { dungeon: ByteString -> - returnBuffer.setTo(from(Registries.dungeons.getOrThrow(dungeon.decode()).jsonObject["metadata"])) +private fun dungeonMetadata(args: LuaThread.ArgStack): Int { + args.lua.push(Registries.dungeons.getOrThrow(args.nextString()).jsonObject["metadata"]) + return 1 } private val hasTech = registryDefExists(Registries.techs) -fun provideRootBindings(lua: LuaEnvironment) { - val table = lua.newTable() - lua.globals["root"] = table - lua.globals["jobject"] = jobject - lua.globals["jarray"] = jarray - lua.globals["jremove"] = jremove - lua.globals["jsize"] = jsize - lua.globals["jresize"] = jresize - - table["assetJson"] = assetJson - table["makeCurrentVersionedJson"] = makeCurrentVersionedJson - table["loadVersionedJson"] = loadVersionedJson - - table["evalFunction"] = evalFunction - table["evalFunction2"] = evalFunction2 - table["imageSize"] = imageSize - table["imageSpaces"] = luaFunctionNS("imageSpaces", ::imageSpaces) - table["nonEmptyRegion"] = luaFunction(::nonEmptyRegion) - //table["npcConfig"] = registryDef(Registries.npcTypes) - - table["npcVariant"] = luaStub("npcVariant") - table["projectileGravityMultiplier"] = luaStub("projectileGravityMultiplier") - table["projectileConfig"] = registryDef(Registries.projectiles) - - table["recipesForItem"] = recipesForItem - table["itemType"] = itemType - table["itemTags"] = itemTags - table["itemHasTag"] = itemHasTag - table["itemConfig"] = itemConfig - table["createItem"] = createItem - - table["tenantConfig"] = registryDef(Registries.tenants) - - table["getMatchingTenants"] = luaFunction(::getMatchingTenants) - table["liquidStatusEffects"] = liquidStatusEffects - - table["generateName"] = luaFunction { asset: ByteString, seed: Number? -> - returnBuffer.setTo(Starbound.generateName(asset.decode(), if (seed == null) lua.random else random(seed.toLong()))) - } - - table["questConfig"] = registryDef(Registries.questTemplates) - - table["npcPortrait"] = luaStub("npcPortrait") - table["monsterPortrait"] = luaStub("monsterPortrait") - table["npcPortrait"] = luaStub("npcPortrait") - - table["isTreasurePool"] = registryDefExists(Registries.treasurePools) - - table["createTreasure"] = luaFunction { pool: ByteString, level: Number, seed: Number? -> - val get = Registries.treasurePools[pool.decode()] ?: throw LuaRuntimeException("No such treasure pool $pool") - val random = if (seed == null) lua.random else random(seed.toLong()) - returnBuffer.setTo(tableOf(*get.value.evaluate(random, level.toDouble()).filter { it.isNotEmpty }.map { from(it.toJson()) }.toTypedArray())) - } - - table["materialMiningSound"] = luaFunctionN("materialMiningSound", ::materialMiningSound) - table["materialFootstepSound"] = luaFunctionN("materialFootstepSound", ::materialFootstepSound) - table["materialHealth"] = materialHealth - - table["materialConfig"] = registryDef2(Registries.tiles) - table["modConfig"] = registryDef2(Registries.tileModifiers) - - table["liquidName"] = liquidName - table["liquidId"] = liquidId - - table["createBiome"] = createBiome - - table["monsterSkillParameter"] = monsterSkillParameter - table["monsterParameters"] = monsterParameters - table["monsterMovementSettings"] = monsterMovementSettings - - table["hasTech"] = hasTech - table["techType"] = techType - table["techConfig"] = techConfig - - table["treeStemDirectory"] = treeStemDirectory - table["treeFoliageDirectory"] = treeFoliageDirectory - - table["collection"] = luaStub("collection") - table["collectables"] = luaStub("collectables") - table["elementalResistance"] = elementalResistance - table["dungeonMetadata"] = dungeonMetadata +private fun assetJson(args: LuaThread.ArgStack): Int { + args.lua.push(Starbound.loadJsonAsset(args.nextString()).get()) + return 1 +} + +private fun makeCurrentVersionedJson(args: LuaThread.ArgStack): Int { + args.lua.push(VersionRegistry.make(args.nextString(), args.nextJson()).toJson()) + return 1 +} + +private fun loadVersionedJson(args: LuaThread.ArgStack): Int { + args.lua.push(VersionRegistry.load(args.nextString(), args.nextJson())) + return 1 +} + +private fun evalFunction(args: LuaThread.ArgStack): Int { + val name = args.nextString() + val value = args.nextDouble() + val fn = Registries.jsonFunctions[name] ?: throw LuaRuntimeException("No such function $name") + args.lua.push(fn.value.evaluate(value)) + return 1 +} + +private fun evalFunction2(args: LuaThread.ArgStack): Int { + val name = args.nextString() + val value = args.nextDouble() + val value2 = args.nextDouble() + val fn = Registries.json2Functions[name] ?: throw LuaRuntimeException("No such function $name") + args.lua.push(fn.value.evaluate(value, value2)) + return 1 +} + +private fun imageSize(args: LuaThread.ArgStack): Int { + val name = args.nextString() + val ref = SpriteReference.create(name) + val sprite = ref.sprite ?: throw LuaRuntimeException("No such image or sprite $ref") + args.lua.pushTable(2) + args.lua.setTableValue(1, sprite.width) + args.lua.setTableValue(2, sprite.height) + return 1 +} + +private fun imageSpaces(args: LuaThread.ArgStack): Int { + val name = args.nextString() + val image = Image.get(name) ?: throw LuaRuntimeException("No such image $name") + + val pixelOffset = args.nextVector2i() + val fillFactor = args.nextDouble() + val flip = args.nextOptionalBoolean() ?: false + + val values = image.worldSpaces(pixelOffset, fillFactor, flip) + args.lua.pushTable(values.size, 0) + + for ((i, value) in values.withIndex()) { + args.lua.push(i + 1L) + args.lua.push(value) + args.lua.setTableValue() + } + + return 1 +} + +private fun nonEmptyRegion(args: LuaThread.ArgStack): Int { + val name = args.nextString() + val image = Image.get(name) ?: throw LuaRuntimeException("No such image $name") + args.lua.push(image.nonEmptyRegion) + return 1 +} + +private fun generateName(args: LuaThread.ArgStack): Int { + val asset = args.nextString() + val seed = args.nextOptionalLong() + args.lua.push(Starbound.generateName(asset, if (seed == null) args.lua.random else random(seed.toLong()))) + return 1 +} + +private fun createTreasure(args: LuaThread.ArgStack): Int { + val pool = args.nextString() + val level = args.nextDouble() + val seed = args.nextOptionalLong() + + val get = Registries.treasurePools.getOrThrow(pool) + val random = if (seed == null) args.lua.random else random(seed.toLong()) + + args.lua.pushTable() + var i = 1L + + for (loot in get.value.evaluate(random, level)) { + if (loot.isNotEmpty) { + args.lua.push(i++) + loot.createDescriptor().store(args.lua) + args.lua.setTableValue() + } + } + + return 1 +} + +private val projectileConfig = registryDef(Registries.projectiles) +private val tenantConfig = registryDef(Registries.tenants) +private val questConfig = registryDef(Registries.questTemplates) +private val isTreasurePool = registryDefExists(Registries.treasurePools) + +private val materialConfig = registryDef2(Registries.tiles) +private val modConfig = registryDef2(Registries.tileModifiers) + +fun provideRootBindings(lua: LuaThread) { + lua.pushTable() + lua.dup() + lua.storeGlobal("root") + + lua.setTableValue("assetJson", ::assetJson) + + lua.setTableValue("makeCurrentVersionedJson", ::makeCurrentVersionedJson) + lua.setTableValue("loadVersionedJson", ::loadVersionedJson) + + lua.setTableValue("evalFunction", ::evalFunction) + lua.setTableValue("evalFunction2", ::evalFunction2) + + lua.setTableValue("imageSize", ::imageSize) + lua.setTableValue("imageSpaces", ::imageSpaces) + lua.setTableValue("nonEmptyRegion", ::nonEmptyRegion) + + lua.setTableValueToStub("npcConfig") + lua.setTableValueToStub("npcVariant") + lua.setTableValueToStub("projectileGravityMultiplier") + lua.setTableValueToStub("npcPortrait") + lua.setTableValueToStub("monsterPortrait") + + lua.setTableValue("recipesForItem", ::recipesForItem) + lua.setTableValue("itemType", ::itemType) + lua.setTableValue("itemTags", ::itemTags) + lua.setTableValue("itemHasTag", ::itemHasTag) + lua.setTableValue("itemConfig", ::itemConfig) + lua.setTableValue("createItem", ::createItem) + + lua.setTableValue("projectileConfig", projectileConfig) + lua.setTableValue("tenantConfig", tenantConfig) + + lua.setTableValue("getMatchingTenants", ::getMatchingTenants) + lua.setTableValue("liquidStatusEffects", ::liquidStatusEffects) + + lua.setTableValue("generateName", ::generateName) + lua.setTableValue("questConfig", questConfig) + + lua.setTableValue("isTreasurePool", isTreasurePool) + lua.setTableValue("createTreasure", ::createTreasure) + + lua.setTableValue("materialMiningSound", ::materialMiningSound) + lua.setTableValue("materialFootstepSound", ::materialFootstepSound) + lua.setTableValue("materialHealth", ::materialHealth) + + lua.setTableValue("materialConfig", materialConfig) + lua.setTableValue("modConfig", modConfig) + + lua.setTableValue("liquidName", ::liquidName) + lua.setTableValue("liquidId", ::liquidId) + + lua.setTableValue("createBiome", ::createBiome) + lua.setTableValue("monsterSkillParameter", ::monsterSkillParameter) + lua.setTableValue("monsterParameters", ::monsterParameters) + lua.setTableValue("monsterMovementSettings", ::monsterMovementSettings) + + lua.setTableValue("hasTech", hasTech) + lua.setTableValue("techType", ::techType) + lua.setTableValue("techType", ::techConfig) + + lua.setTableValue("treeStemDirectory", ::treeStemDirectory) + lua.setTableValue("treeFoliageDirectory", ::treeFoliageDirectory) + + lua.setTableValueToStub("collection") + lua.setTableValueToStub("collectables") + + lua.setTableValue("elementalResistance", ::elementalResistance) + lua.setTableValue("dungeonMetadata", ::dungeonMetadata) + + lua.pop() } diff --git a/src/main/resources/scripts/global.lua b/src/main/resources/scripts/global.lua index c63bcdaf..d5378c38 100644 --- a/src/main/resources/scripts/global.lua +++ b/src/main/resources/scripts/global.lua @@ -238,7 +238,7 @@ do error('interval is empty (low: ' .. low .. ', high: ' .. high .. ')', 2) elseif low == high then return floor(low) - elseif high >= 9223372036854775807 || low <= -9223372036854775808 then + elseif high >= 9223372036854775807 or low <= -9223372036854775808 then error('interval too large', 2) else return __random_long(floor(low), floor(high))