Move root bindings to native Lua

This commit is contained in:
DBotThePony 2024-12-17 20:50:52 +07:00
parent e2b8c0ed29
commit d627526088
Signed by: DBot
GPG Key ID: DCC23B5715498507
9 changed files with 733 additions and 547 deletions

View File

@ -17,6 +17,7 @@ import ru.dbotthepony.kommons.util.XXHash64
import ru.dbotthepony.kstarbound.io.StreamCodec import ru.dbotthepony.kstarbound.io.StreamCodec
import ru.dbotthepony.kstarbound.io.readInternedString import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.util.limit import ru.dbotthepony.kstarbound.util.limit
import ru.dbotthepony.kstarbound.util.sbIntern
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
import java.util.Collections import java.util.Collections
@ -223,8 +224,11 @@ class Registry<T : Any>(val name: String, val storeJson: Boolean = true) {
fun refOrThrow(index: String): Ref<T> = get(index)?.ref ?: throw NoSuchElementException("No such $name: ${index.limit()}") fun refOrThrow(index: String): Ref<T> = get(index)?.ref ?: throw NoSuchElementException("No such $name: ${index.limit()}")
fun ref(index: String): Ref<T> = lock.write { fun ref(index: String): Ref<T> = lock.write {
keyRefs.computeIfAbsent(index, Object2ObjectFunction { var lookup = keyRefs[index]
val ref = RefImpl(Either.left(it as String))
if (lookup == null) {
val it = index.sbIntern()
val ref = RefImpl(Either.left(it))
ref.entry = keysInternal[it] ref.entry = keysInternal[it]
if (hasBeenValidated && ref.entry == null) { if (hasBeenValidated && ref.entry == null) {
@ -236,10 +240,12 @@ class Registry<T : Any>(val name: String, val storeJson: Boolean = true) {
} }
} }
ref keyRefs[it] = ref
}).also { lookup = ref
it.references++
} }
lookup.references++
lookup
} }
/** /**
@ -306,6 +312,7 @@ class Registry<T : Any>(val name: String, val storeJson: Boolean = true) {
isBuiltin: Boolean = false isBuiltin: Boolean = false
): Entry<T> { ): Entry<T> {
require(key != "") { "Adding $name with empty name (empty name is reserved)" } require(key != "") { "Adding $name with empty name (empty name is reserved)" }
val key = key.sbIntern()
lock.write { lock.write {
if (key in keysInternal) { if (key in keysInternal) {

View File

@ -105,7 +105,7 @@ import java.util.random.RandomGenerator
import kotlin.NoSuchElementException import kotlin.NoSuchElementException
import kotlin.collections.ArrayList 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 ENGINE_VERSION = "0.0.1"
const val NATIVE_PROTOCOL_VERSION = 748 const val NATIVE_PROTOCOL_VERSION = 748
const val LEGACY_PROTOCOL_VERSION = 747 const val LEGACY_PROTOCOL_VERSION = 747

View File

@ -1,6 +1,5 @@
package ru.dbotthepony.kstarbound.defs.item package ru.dbotthepony.kstarbound.defs.item
import com.google.gson.Gson
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.google.gson.JsonNull import com.google.gson.JsonNull
@ -8,7 +7,6 @@ import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive import com.google.gson.JsonPrimitive
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
import com.google.gson.annotations.JsonAdapter
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import org.apache.logging.log4j.LogManager 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.readJsonElement
import ru.dbotthepony.kstarbound.json.writeJsonElement import ru.dbotthepony.kstarbound.json.writeJsonElement
import ru.dbotthepony.kstarbound.lua.LuaEnvironment 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.StateMachine
import ru.dbotthepony.kstarbound.lua.from import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.get 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<ItemDescriptor> { fun ItemDescriptor(data: Table, stateMachine: StateMachine): Supplier<ItemDescriptor> {
val name = stateMachine.index(data, 1L, "name", "item") val name = stateMachine.index(data, 1L, "name", "item")
val count = stateMachine.optionalIndex(data, 2L, "count") 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? { fun toTable(allocator: TableFactory): Table? {
if (isEmpty) { if (isEmpty) {
return null return null
@ -211,9 +338,7 @@ data class ItemDescriptor(
fun build(level: Double? = null, seed: Long? = null, random: RandomGenerator? = null): ItemStack { fun build(level: Double? = null, seed: Long? = null, random: RandomGenerator? = null): ItemStack {
try { try {
val (config, parameters) = buildConfig(level, seed, random) val (jConfig, jParameters) = buildConfig(level, seed, random)
val jConfig = config.map({ it }, { toJsonFromLua(it).asJsonObject })
val jParameters = parameters.map({ it }, { toJsonFromLua(it).asJsonObject })
return ref.type.factory(ref, jConfig, jParameters, count) return ref.type.factory(ref, jConfig, jParameters, count)
} catch (err: Throwable) { } catch (err: Throwable) {
LOGGER.error("Error while building item '$name' using script ${ref.json["builder"]}", err) 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 * [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) * reset stats on next item load from serialized state)
*/ */
fun buildConfig(level: Double? = null, seed: Long? = null, random: RandomGenerator? = null): Pair<Either<JsonObject, Table>, Either<JsonObject, Table>> { fun buildConfig(level: Double? = null, seed: Long? = null, random: RandomGenerator? = null): Pair<JsonObject, JsonObject> {
val builder = ref.json["builder"]?.asString ?: return Either.left<JsonObject, Table>(ref.json) to Either.left(parameters.deepCopy()) val builder = ref.json["builder"]?.asString ?: return ref.json to parameters.deepCopy()
val lua = LuaEnvironment() val lua = LuaThread()
lua.attach(Starbound.loadScript(builder))
lua.random = random ?: lua.random
lua.init(false)
val (config, parameters) = lua.invokeGlobal("build", ref.directory + "/", lua.from(ref.json), lua.from(parameters), level, seed) try {
return Either.right<JsonObject, Table>(config as Table) to Either.right(parameters as Table) 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) { fun write(stream: DataOutputStream) {

View File

@ -74,6 +74,10 @@ object ItemRegistry {
return entry return entry
} }
fun getOrThrow(name: String): Entry {
return entries[name] ?: throw NoSuchElementException("No such item '$name'")
}
operator fun contains(name: String): Boolean { operator fun contains(name: String): Boolean {
return name in entries return name in entries
} }

View File

@ -39,6 +39,7 @@ import ru.dbotthepony.kstarbound.json.mergeJson
import ru.dbotthepony.kstarbound.json.stream import ru.dbotthepony.kstarbound.json.stream
import ru.dbotthepony.kstarbound.json.writeJsonElement import ru.dbotthepony.kstarbound.json.writeJsonElement
import ru.dbotthepony.kstarbound.lua.LuaEnvironment import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.from import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.network.syncher.NetworkedElement import ru.dbotthepony.kstarbound.network.syncher.NetworkedElement
import ru.dbotthepony.kstarbound.util.AssetPathStack import ru.dbotthepony.kstarbound.util.AssetPathStack
@ -373,14 +374,6 @@ open class ItemStack(val entry: ItemRegistry.Entry, val config: JsonObject, para
return createDescriptor().toJson() return createDescriptor().toJson()
} }
fun toTable(allocator: TableFactory): Table? {
if (isEmpty) {
return null
}
return createDescriptor().toTable(allocator)
}
class Adapter(gson: Gson) : TypeAdapter<ItemStack>() { class Adapter(gson: Gson) : TypeAdapter<ItemStack>() {
override fun write(out: JsonWriter, value: ItemStack?) { override fun write(out: JsonWriter, value: ItemStack?) {
val json = value?.toJson() val json = value?.toJson()

View File

@ -465,15 +465,15 @@ fun LuaThread.getPoly(stackIndex: Int = -1): Poly? {
}?.let { Poly(it) } }?.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) 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) 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) if (position !in 1 ..this.top)
return null return null
@ -503,15 +503,15 @@ fun LuaThread.getLine2d(stackIndex: Int = -1): Line2d? {
return Line2d(x, y) 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) 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) 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) if (position !in 1 ..this.top)
return null return null
@ -541,12 +541,19 @@ fun LuaThread.getVector2d(stackIndex: Int = -1): Vector2d? {
return Vector2d(x, y) 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) 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) return lua.getVector2d(position)
?: throw IllegalArgumentException("Bad argument #$position: Vector2d expected, got ${lua.typeAt(position)}")
} }
fun LuaThread.getVector2i(stackIndex: Int = -1): Vector2i? { fun LuaThread.getVector2i(stackIndex: Int = -1): Vector2i? {
@ -572,20 +579,19 @@ fun LuaThread.getVector2i(stackIndex: Int = -1): Vector2i? {
return Vector2i(x.toInt(), y.toInt()) 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) 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) 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) if (position !in 1 ..this.top)
return null 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) 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()) 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) 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) 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) if (position !in 1 ..this.top)
return null return null
@ -677,15 +683,15 @@ fun LuaThread.getAABB(stackIndex: Int = -1): AABB? {
return AABB(Vector2d(x, y), Vector2d(z, w)) 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) 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) 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) if (position !in 1 ..this.top)
return null 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())) 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) 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) 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) if (position !in 1 ..this.top)
return null return null
@ -750,19 +756,19 @@ fun LuaThread.push(value: IStruct4i) {
val (x, y, z, w) = value val (x, y, z, w) = value
push(1) push(1)
push(x) push(x.toLong())
setTableValue(table) setTableValue(table)
push(2) push(2)
push(y) push(y.toLong())
setTableValue(table) setTableValue(table)
push(3) push(3)
push(z) push(z.toLong())
setTableValue(table) setTableValue(table)
push(4) push(4)
push(w) push(w.toLong())
setTableValue(table) setTableValue(table)
} }
@ -772,15 +778,15 @@ fun LuaThread.push(value: IStruct3i) {
val (x, y, z) = value val (x, y, z) = value
push(1) push(1)
push(x) push(x.toLong())
setTableValue(table) setTableValue(table)
push(2) push(2)
push(y) push(y.toLong())
setTableValue(table) setTableValue(table)
push(3) push(3)
push(z) push(z.toLong())
setTableValue(table) setTableValue(table)
} }
@ -790,11 +796,11 @@ fun LuaThread.push(value: IStruct2i) {
val (x, y) = value val (x, y) = value
push(1) push(1)
push(x) push(x.toLong())
setTableValue(table) setTableValue(table)
push(2) push(2)
push(y) push(y.toLong())
setTableValue(table) setTableValue(table)
} }

View File

@ -72,14 +72,14 @@ class LuaThread private constructor(
this.storeGlobal("utf8") this.storeGlobal("utf8")
push { push {
LOGGER.info(getString()) LOGGER.info(it.nextString())
0 0
} }
storeGlobal("__print") storeGlobal("__print")
push { push {
val path = getString() val path = it.nextString()
try { try {
load(Starbound.readLuaScript(path).join(), "@$path") load(Starbound.readLuaScript(path).join(), "@$path")
@ -100,14 +100,14 @@ class LuaThread private constructor(
storeGlobal("__random_double") storeGlobal("__random_double")
push { push {
push(random.nextLong(getLong(), getLong())) push(random.nextLong(it.nextLong(), it.nextLong()))
1 1
} }
storeGlobal("__random_long") storeGlobal("__random_long")
push { push {
random = random(getLong()) random = random(it.nextLong())
0 0
} }
@ -117,6 +117,10 @@ class LuaThread private constructor(
call() call()
} }
fun interface Fn {
fun invoke(args: ArgStack): Int
}
private var cleanable: Cleaner.Cleanable? = null private var cleanable: Cleaner.Cleanable? = null
private var randomHolder: Delegate<RandomGenerator> by Delegates.notNull() private var randomHolder: Delegate<RandomGenerator> by Delegates.notNull()
@ -269,7 +273,7 @@ class LuaThread private constructor(
} }
} }
inline fun <T> eval(chunk: String, name: String = "eval", arguments: LuaThread.() -> Int) { inline fun eval(chunk: String, name: String = "eval", arguments: LuaThread.() -> Int) {
val top = stackTop val top = stackTop
try { try {
@ -281,7 +285,18 @@ class LuaThread private constructor(
} }
} }
private val attachedScripts = ArrayList<AssetPath>() fun eval(chunk: String, name: String = "eval") {
val top = stackTop
try {
load(chunk, name)
call()
} finally {
setTop(top)
}
}
private val attachedScripts = ArrayList<String>()
private var initCalled = false private var initCalled = false
fun initScripts(callInit: Boolean = true): Boolean { fun initScripts(callInit: Boolean = true): Boolean {
@ -292,7 +307,7 @@ class LuaThread private constructor(
return true return true
} }
val loadScripts = attachedScripts.map { Starbound.readLuaScript(it.fullPath) to it.fullPath } val loadScripts = attachedScripts.map { Starbound.readLuaScript(it) to it }
attachedScripts.clear() attachedScripts.clear()
try { try {
@ -328,9 +343,13 @@ class LuaThread private constructor(
} }
fun attach(script: AssetPath) { fun attach(script: AssetPath) {
attach(script.fullPath)
}
fun attach(script: String) {
if (initCalled) { if (initCalled) {
// minor hiccups during unpopulated script cache should be tolerable // minor hiccups during unpopulated script cache should be tolerable
load(Starbound.readLuaScript(script.fullPath).join(), "@" + script.fullPath) load(Starbound.readLuaScript(script).join(), "@$script")
call() call()
} else { } else {
attachedScripts.add(script) attachedScripts.add(script)
@ -341,6 +360,11 @@ class LuaThread private constructor(
script.forEach { attach(it) } script.forEach { attach(it) }
} }
@JvmName("attachAsStrings")
fun attach(script: Collection<String>) {
script.forEach { attach(it) }
}
fun getString(stackIndex: Int = -1, limit: Long = DEFAULT_STRING_LIMIT): String? { fun getString(stackIndex: Int = -1, limit: Long = DEFAULT_STRING_LIMIT): String? {
if (!this.isString(stackIndex)) if (!this.isString(stackIndex))
return null return null
@ -709,24 +733,13 @@ class LuaThread private constructor(
return values return values
} }
fun getTableValue(stackIndex: Int = -2, limit: Long = DEFAULT_STRING_LIMIT): JsonElement? { fun loadTableValue(stackIndex: Int = -2): LuaType {
this.loadTableValue(stackIndex) return LuaType.valueOf(LuaJNR.INSTANCE.lua_gettable(this.pointer, stackIndex))
return this.getJson(limit = limit)
} }
fun loadTableValue(stackIndex: Int = -2, allowNothing: Boolean = false) { fun loadTableValue(name: String): LuaType {
val abs = this.absStackIndex(stackIndex) push(name)
return loadTableValue()
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 popBoolean(): Boolean? { fun popBoolean(): Boolean? {
@ -798,95 +811,88 @@ class LuaThread private constructor(
val lua get() = this@LuaThread val lua get() = this@LuaThread
var position = 1 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) if (position !in 1 ..this.top)
return false throw IllegalArgumentException("bad argument #$position: string expected, got nil")
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")
return this@LuaThread.getString(position, limit = limit) 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) val type = this@LuaThread.typeAt(position)
if (type != LuaType.STRING && type != LuaType.NIL && type != LuaType.NONE) 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) 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) 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) 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) 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) 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) 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) 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) 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) 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) 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) 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) val type = this@LuaThread.typeAt(position)
if (type != LuaType.NUMBER && type != LuaType.NIL && type != LuaType.NONE) 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) 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) val type = this@LuaThread.typeAt(position)
if (type == LuaType.NIL || type == LuaType.NONE) if (type == LuaType.NIL || type == LuaType.NONE)
@ -894,28 +900,19 @@ class LuaThread private constructor(
else if (type == LuaType.BOOLEAN) else if (type == LuaType.BOOLEAN)
return this@LuaThread.getBoolean(position) return this@LuaThread.getBoolean(position)
else 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) 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) 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@{ LuaJNI.lua_pushcclosure(pointer.address()) lazy@{
val realLuaState: LuaThread 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) { fun moveStackValuesOnto(other: LuaThread, amount: Int = 1) {
LuaJNR.INSTANCE.lua_xmove(pointer, other.pointer, amount) LuaJNR.INSTANCE.lua_xmove(pointer, other.pointer, amount)
@ -992,14 +989,18 @@ class LuaThread private constructor(
LuaJNR.INSTANCE.lua_pushnil(this.pointer) LuaJNR.INSTANCE.lua_pushnil(this.pointer)
} }
fun push(value: Int) {
LuaJNR.INSTANCE.lua_pushinteger(this.pointer, value.toLong())
}
fun push(value: Long) { fun push(value: Long) {
LuaJNR.INSTANCE.lua_pushinteger(this.pointer, value) 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) { fun push(value: Double) {
LuaJNR.INSTANCE.lua_pushnumber(this.pointer, value) 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) 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) { fun push(value: String) {
pushStringIntoThread(this, value) pushStringIntoThread(this, value)
} }
@ -1020,6 +1045,16 @@ class LuaThread private constructor(
LuaJNR.INSTANCE.lua_copy(pointer, fromIndex, toIndex) 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) { fun setTop(topIndex: Int) {
LuaJNR.INSTANCE.lua_settop(pointer, topIndex) LuaJNR.INSTANCE.lua_settop(pointer, topIndex)
} }
@ -1036,15 +1071,26 @@ class LuaThread private constructor(
LuaJNR.INSTANCE.lua_settable(this.pointer, stackIndex) 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?) { fun setTableValue(key: String, value: JsonElement?) {
this.push(key) this.push(key)
this.push(value) this.push(value)
this.setTableValue() this.setTableValue()
} }
@Deprecated("Lua function is a stub")
fun setTableValueToStub(key: String) {
setTableValue(key) { throw NotImplementedError("NYI: $key") }
}
fun setTableValue(key: String, value: Int) { fun setTableValue(key: String, value: Int) {
this.push(key) this.push(key)
this.push(value) this.push(value.toLong())
this.setTableValue() this.setTableValue()
} }
@ -1098,10 +1144,9 @@ class LuaThread private constructor(
} }
fun setTableValue(key: Long, value: JsonElement?) { fun setTableValue(key: Long, value: JsonElement?) {
val table = this.stackTop
this.push(key) this.push(key)
this.push(value) this.push(value)
this.setTableValue(table) this.setTableValue()
} }
fun setTableValue(key: Long, value: Int) { fun setTableValue(key: Long, value: Int) {
@ -1109,17 +1154,15 @@ class LuaThread private constructor(
} }
fun setTableValue(key: Long, value: Long) { fun setTableValue(key: Long, value: Long) {
val table = this.stackTop
this.push(key) this.push(key)
this.push(value) this.push(value)
this.setTableValue(table) this.setTableValue()
} }
fun setTableValue(key: Long, value: String) { fun setTableValue(key: Long, value: String) {
val table = this.stackTop
this.push(key) this.push(key)
this.push(value) this.push(value)
this.setTableValue(table) this.setTableValue()
} }
fun setTableValue(key: Long, value: Float) { fun setTableValue(key: Long, value: Float) {
@ -1127,10 +1170,9 @@ class LuaThread private constructor(
} }
fun setTableValue(key: Long, value: Double) { fun setTableValue(key: Long, value: Double) {
val table = this.stackTop
this.push(key) this.push(key)
this.push(value) this.push(value)
this.setTableValue(table) this.setTableValue()
} }
fun push(value: JsonElement?) { fun push(value: JsonElement?) {
@ -1243,6 +1285,11 @@ class LuaThread private constructor(
} }
fun pushStringIntoThread(lua: LuaThread, value: String) { 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) val bytes = value.toByteArray(Charsets.UTF_8)
if (bytes.size < DEFAULT_STRING_LIMIT) { if (bytes.size < DEFAULT_STRING_LIMIT) {

View File

@ -6,10 +6,6 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.classdump.luna.ByteString import org.classdump.luna.ByteString
import org.classdump.luna.LuaRuntimeException 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.Globals
import ru.dbotthepony.kstarbound.item.RecipeRegistry import ru.dbotthepony.kstarbound.item.RecipeRegistry
import ru.dbotthepony.kstarbound.Registries 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.image.SpriteReference
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
import ru.dbotthepony.kstarbound.item.ItemRegistry 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.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.StateMachine import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.createJsonArray import ru.dbotthepony.kstarbound.lua.LuaType
import ru.dbotthepony.kstarbound.lua.createJsonObject
import ru.dbotthepony.kstarbound.lua.from import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.get import ru.dbotthepony.kstarbound.lua.nextVector2i
import ru.dbotthepony.kstarbound.lua.indexNoYield
import ru.dbotthepony.kstarbound.lua.indexSetNoYield
import ru.dbotthepony.kstarbound.lua.iterator
import ru.dbotthepony.kstarbound.lua.luaFunction import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.luaFunctionN import ru.dbotthepony.kstarbound.lua.luaFunctionN
import ru.dbotthepony.kstarbound.lua.luaFunctionNS
import ru.dbotthepony.kstarbound.lua.luaStub import ru.dbotthepony.kstarbound.lua.luaStub
import ru.dbotthepony.kstarbound.lua.nextOptionalFloat import ru.dbotthepony.kstarbound.lua.push
import ru.dbotthepony.kstarbound.lua.nextOptionalInteger
import ru.dbotthepony.kstarbound.lua.set import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.tableMapOf
import ru.dbotthepony.kstarbound.lua.tableOf 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 ru.dbotthepony.kstarbound.util.random.random
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.isNotEmpty import kotlin.collections.isNotEmpty
import kotlin.collections.random
import kotlin.collections.set import kotlin.collections.set
import kotlin.collections.withIndex import kotlin.collections.withIndex
import kotlin.math.max
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
private fun <T : Any> lookup(registry: Registry<T>, key: Any?): Registry.Entry<T>? { private fun <T : Any> lookup(registry: Registry<T>, args: LuaThread.ArgStack): Registry.Entry<T>? {
if (key is ByteString) { return when (val type = args.peek()) {
return registry[key.decode()] LuaType.NUMBER -> registry[args.nextLong().toInt()]
} else if (key is Number) { LuaType.STRING -> registry[args.nextString()]
return registry[key.toInt()] LuaType.NONE, LuaType.NIL -> null
} else { else -> throw IllegalArgumentException("Invalid registry key type: $type")
return null
} }
} }
private fun <T : Any> lookupStrict(registry: Registry<T>, key: Any?): Registry.Entry<T> { private fun <T : Any> lookupStrict(registry: Registry<T>, args: LuaThread.ArgStack): Registry.Entry<T> {
if (key is ByteString) { return when (val type = args.peek()) {
return registry[key.decode()] ?: throw LuaRuntimeException("No such ${registry.name}: $key") LuaType.NUMBER -> {
} else if (key is Number) { val key = args.nextLong().toInt()
return registry[key.toInt()] ?: throw LuaRuntimeException("No such ${registry.name}: $key") registry[key] ?: 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 nonEmptyRegion(context: ExecutionContext, name: ByteString) { LuaType.STRING -> {
val image = Image.get(name.decode()) ?: throw LuaRuntimeException("No such image $name") val key = args.nextString()
context.returnBuffer.setTo(context.from(image.nonEmptyRegion)) registry[key] ?: throw LuaRuntimeException("No such ${registry.name}: $key")
} }
private fun registryDef(registry: Registry<*>): LuaFunction<ByteString, *, *, *, *> { else -> throw IllegalArgumentException("Invalid registry key type: $type")
return luaFunction { name ->
val value = registry[name.decode()] ?: throw LuaRuntimeException("No such NPC type $name")
returnBuffer.setTo(from(value.json))
} }
} }
private fun registryDef2(registry: Registry<*>): LuaFunction<Any?, *, *, *, *> { private fun registryDef(registry: Registry<*>): LuaThread.Fn {
return luaFunction { name -> return LuaThread.Fn { args ->
val def = lookup(registry, name) 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) { if (def != null) {
returnBuffer.setTo(newTable(0, 2).also { args.lua.pushTable(hashSize = 2)
it["path"] = def.file?.computeFullPath().toByteString() args.lua.setTableValue("path", def.file?.computeFullPath())
it["config"] = from(def.json) args.lua.setTableValue("config", def.json)
})
return@Fn 1
} else { } else {
returnBuffer.setTo() return@Fn 0
} }
} }
} }
private fun registryDefExists(registry: Registry<*>): LuaFunction<ByteString, *, *, *, *> { private fun registryDefExists(registry: Registry<*>): LuaThread.Fn {
return luaFunction { name -> return LuaThread.Fn { args ->
returnBuffer.setTo(name.decode() in registry) args.lua.push(args.nextString() in registry)
return@Fn 1
} }
} }
private val recipesForItem = luaFunction { name: ByteString -> private fun recipesForItem(args: LuaThread.ArgStack): Int {
val list = RecipeRegistry.output2recipes[name.decode()] val list = RecipeRegistry.output2recipes[args.nextString()]
if (list == null) { if (list == null) {
returnBuffer.setTo(tableOf()) args.lua.pushTable()
} else { } else {
returnBuffer.setTo(newTable(list.size, 0).also { args.lua.pushTable(list.size)
for ((i, v) in list.withIndex()) {
it.rawset(i + 1L, from(v.json)) 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<String>() val actualTags = Object2IntOpenHashMap<String>()
for ((k, v) in tags) { for ((k, v) in tags) {
if (k is ByteString && v is Number) { actualTags[k] = v
actualTags[k.decode()] = v.toInt()
}
} }
val result = Registries.tenants.keys.values val result = Registries.tenants.keys.values
.stream() .stream()
.filter { it.value.test(actualTags) } .filter { it.value.test(actualTags) }
.sorted { a, b -> b.value.compareTo(a.value) } .sorted { a, b -> b.value.compareTo(a.value) }
.map { context.from(it.json) }
.toList() .toList()
context.returnBuffer.setTo(context.newTable(result.size, 0).also { args.lua.pushTable(result.size)
for ((k, v) in result.withIndex()) {
it[k + 1] = v
}
})
}
private val liquidStatusEffects = luaFunction { id: Any -> for ((k, v) in result.withIndex()) {
val liquid = lookup(Registries.liquid, id) args.lua.setTableValue(k + 1, v.key)
if (liquid == null) {
returnBuffer.setTo(tableOf())
} else {
returnBuffer.setTo(tableOf(*liquid.value.statusEffects.map { it.key.map({ it }, { it }) }.toTypedArray()))
} }
return 1
} }
private fun materialMiningSound(context: ExecutionContext, arguments: ArgumentIterator) { private fun liquidStatusEffects(args: LuaThread.ArgStack): Int {
val tile = lookup(Registries.tiles, arguments.nextAny()) val liquid = lookup(Registries.liquid, args)
val mod = lookup(Registries.tiles, arguments.nextOptionalAny(null))
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 })) { if (mod != null && mod.value.miningSounds.map({ it.isNotEmpty() }, { true })) {
context.returnBuffer.setTo(mod.value.miningSounds.map({ it.random() }, { it }).toByteString()) args.lua.push(mod.value.miningSounds.map({ it.random(args.lua.random) }, { it }))
return } 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 })) { return 1
context.returnBuffer.setTo(tile.value.miningSounds.map({ it.random() }, { it }).toByteString())
return
}
// original engine parity
context.returnBuffer.setTo("".toByteString())
} }
private fun materialFootstepSound(context: ExecutionContext, arguments: ArgumentIterator) { private fun materialFootstepSound(args: LuaThread.ArgStack): Int {
val tile = lookup(Registries.tiles, arguments.nextAny()) val tile = lookup(Registries.tiles, args)
val mod = lookup(Registries.tiles, arguments.nextOptionalAny(null)) val mod = lookup(Registries.tiles, args)
if (mod != null && mod.value.footstepSound.map({ it.isNotEmpty() }, { true })) { if (mod != null && mod.value.footstepSound.map({ it.isNotEmpty() }, { true })) {
context.returnBuffer.setTo(mod.value.footstepSound.map({ it.random() }, { it }).toByteString()) args.lua.push(mod.value.footstepSound.map({ it.random(args.lua.random) }, { it }))
return } else if (tile != null && tile.value.footstepSound.map({ it.isNotEmpty() }, { true })) {
} args.lua.push(tile.value.footstepSound.map({ it.random(args.lua.random) }, { it }))
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)
} else { } else {
returnBuffer.setTo(elemCount) args.lua.push(Globals.client.defaultFootstepSound.map({ it }, { it.random(args.lua.random) }))
}
}
// 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<Any>()
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?
}
} }
val keysToRemove = ArrayList<Any>() return 1
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))
} }
private val assetJson = luaFunction { path: ByteString -> private fun materialHealth(args: LuaThread.ArgStack): Int {
returnBuffer.setTo(from(Starbound.loadJsonAsset(path.decode()).get())) args.lua.push(lookupStrict(Registries.tiles, args).value.actualDamageTable.totalHealth)
return 1
} }
private val makeCurrentVersionedJson = luaFunction { identifier: ByteString, content: Any? -> private fun liquidName(args: LuaThread.ArgStack): Int {
returnBuffer.setTo(from(VersionRegistry.make(identifier.decode(), toJsonFromLua(content)).toJson())) args.lua.push(lookupStrict(Registries.liquid, args).key)
return 1
} }
private val loadVersionedJson = luaFunction { data: Any?, identifier: ByteString -> private fun liquidId(args: LuaThread.ArgStack): Int {
returnBuffer.setTo(from(VersionRegistry.load(identifier.decode(), toJsonFromLua(data)))) 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 { try {
val biome = Registries.biomes val biome = Registries.biomes
.getOrThrow(name.decode()) .getOrThrow(name)
.value .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) { } 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) 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 -> private fun treeStemDirectory(args: LuaThread.ArgStack): Int {
returnBuffer.setTo(Registries.treeStemVariants[name.decode()]?.file?.computeDirectory(true).toByteString() ?: "/".toByteString()) val name = args.nextString()
args.lua.push(Registries.treeStemVariants[name]?.file?.computeDirectory(true) ?: "/")
return 1
} }
private val treeFoliageDirectory = luaFunction { name: ByteString -> private fun treeFoliageDirectory(args: LuaThread.ArgStack): Int {
returnBuffer.setTo(Registries.treeFoliageVariants[name.decode()]?.file?.computeDirectory(true).toByteString() ?: "/".toByteString()) 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? -> private fun itemConfig(args: LuaThread.ArgStack): Int {
val desc = ItemDescriptor(descriptor) val desc = ItemDescriptor(args)
val level = args.nextOptionalDouble()
val seed = args.nextOptionalLong()
if (desc.name !in ItemRegistry) { if (desc.name !in ItemRegistry) {
returnBuffer.setTo() return 0
} else { } else {
val (config, params) = desc.buildConfig(level?.toDouble(), seed?.toLong()) val (config, parameters) = desc.buildConfig(level, seed)
returnBuffer.setTo(tableMapOf( args.lua.pushTable(hashSize = 3)
"directory" to ItemRegistry[desc.name].directory, args.lua.setTableValue("directory", ItemRegistry[desc.name].directory)
"config" to config.map({ from(it) }, { it }), args.lua.setTableValue("config", config)
"parameters" to params.map({ from(it) }, { it }) args.lua.setTableValue("parameters", parameters)
))
return 1
} }
} }
// why // why
private val createItem = luaFunction { descriptor: Any, level: Number?, seed: Number? -> private fun createItem(args: LuaThread.ArgStack): Int {
val desc = ItemDescriptor(descriptor) val desc = ItemDescriptor(args)
val level = args.nextOptionalDouble()
val seed = args.nextOptionalLong()
if (desc.name !in ItemRegistry) { if (desc.name !in ItemRegistry) {
throw LuaRuntimeException("No such item ${desc.name}") throw LuaRuntimeException("No such item ${desc.name}")
} else { } else {
val (_, params) = desc.buildConfig(level?.toDouble(), seed?.toLong()) val (_, params) = desc.buildConfig(level, seed)
val tab = desc.toTable(this)
if (tab != null) if (desc.store(args.lua, pushParameters = false)) {
tab["parameters"] = params.map({ from(it) }, { it }) args.lua.setTableValue("parameters", params)
}
returnBuffer.setTo(tab) return 1
} }
} }
private val itemType = luaFunction { identifier: ByteString -> private fun itemType(args: LuaThread.ArgStack): Int {
returnBuffer.setTo(ItemRegistry[identifier.decode()].type.jsonName.toByteString()) args.lua.push(ItemRegistry.getOrThrow(args.nextString()).type.jsonName)
return 1
} }
private val itemTags = luaFunction { identifier: ByteString -> private fun itemTags(args: LuaThread.ArgStack): Int {
returnBuffer.setTo(tableOf(*ItemRegistry[identifier.decode()].itemTags.toTypedArray())) 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 -> private fun itemHasTag(args: LuaThread.ArgStack): Int {
returnBuffer.setTo(tag.decode() in ItemRegistry[identifier.decode()].itemTags) 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 -> private fun monsterSkillParameter(args: LuaThread.ArgStack): Int {
val skill = Registries.monsterSkills[skillName.decode()] val skillName = args.nextString()
val configParameterName = args.nextString()
val skill = Registries.monsterSkills[skillName]
if (skill != null) { 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? -> private fun monsterParameters(args: LuaThread.ArgStack): Int {
returnBuffer.setTo(from(Registries.monsterTypes.getOrThrow(monsterType.decode()).value.create(seed?.toLong() ?: 0L, JsonObject()).parameters)) 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? -> private fun monsterMovementSettings(args: LuaThread.ArgStack): Int {
returnBuffer.setTo(from(Registries.monsterTypes.getOrThrow(monsterType.decode()).value.create(seed?.toLong() ?: 0L, JsonObject()).parameters["movementSettings"] ?: JsonObject())) 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 -> private fun elementalResistance(args: LuaThread.ArgStack): Int {
returnBuffer.setTo(Globals.elementalTypes[Registries.damageKinds.getOrThrow(damageKindName.decode()).value.elementalType]!!.resistanceStat.toByteString()) 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 -> private fun dungeonMetadata(args: LuaThread.ArgStack): Int {
returnBuffer.setTo(from(Registries.dungeons.getOrThrow(dungeon.decode()).jsonObject["metadata"])) args.lua.push(Registries.dungeons.getOrThrow(args.nextString()).jsonObject["metadata"])
return 1
} }
private val hasTech = registryDefExists(Registries.techs) private val hasTech = registryDefExists(Registries.techs)
fun provideRootBindings(lua: LuaEnvironment) { private fun assetJson(args: LuaThread.ArgStack): Int {
val table = lua.newTable() args.lua.push(Starbound.loadJsonAsset(args.nextString()).get())
lua.globals["root"] = table return 1
lua.globals["jobject"] = jobject }
lua.globals["jarray"] = jarray
lua.globals["jremove"] = jremove private fun makeCurrentVersionedJson(args: LuaThread.ArgStack): Int {
lua.globals["jsize"] = jsize args.lua.push(VersionRegistry.make(args.nextString(), args.nextJson()).toJson())
lua.globals["jresize"] = jresize return 1
}
table["assetJson"] = assetJson
table["makeCurrentVersionedJson"] = makeCurrentVersionedJson private fun loadVersionedJson(args: LuaThread.ArgStack): Int {
table["loadVersionedJson"] = loadVersionedJson args.lua.push(VersionRegistry.load(args.nextString(), args.nextJson()))
return 1
table["evalFunction"] = evalFunction }
table["evalFunction2"] = evalFunction2
table["imageSize"] = imageSize private fun evalFunction(args: LuaThread.ArgStack): Int {
table["imageSpaces"] = luaFunctionNS("imageSpaces", ::imageSpaces) val name = args.nextString()
table["nonEmptyRegion"] = luaFunction(::nonEmptyRegion) val value = args.nextDouble()
//table["npcConfig"] = registryDef(Registries.npcTypes) val fn = Registries.jsonFunctions[name] ?: throw LuaRuntimeException("No such function $name")
args.lua.push(fn.value.evaluate(value))
table["npcVariant"] = luaStub("npcVariant") return 1
table["projectileGravityMultiplier"] = luaStub("projectileGravityMultiplier") }
table["projectileConfig"] = registryDef(Registries.projectiles)
private fun evalFunction2(args: LuaThread.ArgStack): Int {
table["recipesForItem"] = recipesForItem val name = args.nextString()
table["itemType"] = itemType val value = args.nextDouble()
table["itemTags"] = itemTags val value2 = args.nextDouble()
table["itemHasTag"] = itemHasTag val fn = Registries.json2Functions[name] ?: throw LuaRuntimeException("No such function $name")
table["itemConfig"] = itemConfig args.lua.push(fn.value.evaluate(value, value2))
table["createItem"] = createItem return 1
}
table["tenantConfig"] = registryDef(Registries.tenants)
private fun imageSize(args: LuaThread.ArgStack): Int {
table["getMatchingTenants"] = luaFunction(::getMatchingTenants) val name = args.nextString()
table["liquidStatusEffects"] = liquidStatusEffects val ref = SpriteReference.create(name)
val sprite = ref.sprite ?: throw LuaRuntimeException("No such image or sprite $ref")
table["generateName"] = luaFunction { asset: ByteString, seed: Number? -> args.lua.pushTable(2)
returnBuffer.setTo(Starbound.generateName(asset.decode(), if (seed == null) lua.random else random(seed.toLong()))) args.lua.setTableValue(1, sprite.width)
} args.lua.setTableValue(2, sprite.height)
return 1
table["questConfig"] = registryDef(Registries.questTemplates) }
table["npcPortrait"] = luaStub("npcPortrait") private fun imageSpaces(args: LuaThread.ArgStack): Int {
table["monsterPortrait"] = luaStub("monsterPortrait") val name = args.nextString()
table["npcPortrait"] = luaStub("npcPortrait") val image = Image.get(name) ?: throw LuaRuntimeException("No such image $name")
table["isTreasurePool"] = registryDefExists(Registries.treasurePools) val pixelOffset = args.nextVector2i()
val fillFactor = args.nextDouble()
table["createTreasure"] = luaFunction { pool: ByteString, level: Number, seed: Number? -> val flip = args.nextOptionalBoolean() ?: false
val get = Registries.treasurePools[pool.decode()] ?: throw LuaRuntimeException("No such treasure pool $pool")
val random = if (seed == null) lua.random else random(seed.toLong()) val values = image.worldSpaces(pixelOffset, fillFactor, flip)
returnBuffer.setTo(tableOf(*get.value.evaluate(random, level.toDouble()).filter { it.isNotEmpty }.map { from(it.toJson()) }.toTypedArray())) args.lua.pushTable(values.size, 0)
}
for ((i, value) in values.withIndex()) {
table["materialMiningSound"] = luaFunctionN("materialMiningSound", ::materialMiningSound) args.lua.push(i + 1L)
table["materialFootstepSound"] = luaFunctionN("materialFootstepSound", ::materialFootstepSound) args.lua.push(value)
table["materialHealth"] = materialHealth args.lua.setTableValue()
}
table["materialConfig"] = registryDef2(Registries.tiles)
table["modConfig"] = registryDef2(Registries.tileModifiers) return 1
}
table["liquidName"] = liquidName
table["liquidId"] = liquidId private fun nonEmptyRegion(args: LuaThread.ArgStack): Int {
val name = args.nextString()
table["createBiome"] = createBiome val image = Image.get(name) ?: throw LuaRuntimeException("No such image $name")
args.lua.push(image.nonEmptyRegion)
table["monsterSkillParameter"] = monsterSkillParameter return 1
table["monsterParameters"] = monsterParameters }
table["monsterMovementSettings"] = monsterMovementSettings
private fun generateName(args: LuaThread.ArgStack): Int {
table["hasTech"] = hasTech val asset = args.nextString()
table["techType"] = techType val seed = args.nextOptionalLong()
table["techConfig"] = techConfig args.lua.push(Starbound.generateName(asset, if (seed == null) args.lua.random else random(seed.toLong())))
return 1
table["treeStemDirectory"] = treeStemDirectory }
table["treeFoliageDirectory"] = treeFoliageDirectory
private fun createTreasure(args: LuaThread.ArgStack): Int {
table["collection"] = luaStub("collection") val pool = args.nextString()
table["collectables"] = luaStub("collectables") val level = args.nextDouble()
table["elementalResistance"] = elementalResistance val seed = args.nextOptionalLong()
table["dungeonMetadata"] = dungeonMetadata
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()
} }

View File

@ -238,7 +238,7 @@ do
error('interval is empty (low: ' .. low .. ', high: ' .. high .. ')', 2) error('interval is empty (low: ' .. low .. ', high: ' .. high .. ')', 2)
elseif low == high then elseif low == high then
return floor(low) return floor(low)
elseif high >= 9223372036854775807 || low <= -9223372036854775808 then elseif high >= 9223372036854775807 or low <= -9223372036854775808 then
error('interval too large', 2) error('interval too large', 2)
else else
return __random_long(floor(low), floor(high)) return __random_long(floor(low), floor(high))