Make it compile against PUC Lua

This commit is contained in:
DBotThePony 2024-12-28 21:09:44 +07:00
parent 148ceba239
commit 9a958ecccb
Signed by: DBot
GPG Key ID: DCC23B5715498507
30 changed files with 530 additions and 1864 deletions

View File

@ -17,9 +17,6 @@ import kotlinx.coroutines.future.await
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.apache.logging.log4j.LogManager
import org.classdump.luna.compiler.CompilerChunkLoader
import org.classdump.luna.compiler.CompilerSettings
import org.classdump.luna.load.ChunkFactory
import ru.dbotthepony.kstarbound.math.AABBTypeAdapter
import ru.dbotthepony.kommons.gson.EitherTypeAdapter
import ru.dbotthepony.kommons.gson.KOptionalTypeAdapter
@ -281,26 +278,6 @@ object Starbound : BlockableEventLoop("Multiverse Thread"), Scheduler, ISBFileLo
}
}
private val loader = CompilerChunkLoader.of(CompilerSettings.defaultNoAccountingSettings(), "sb_lua_")
private val scriptCache = ConcurrentHashMap<String, ChunkFactory>()
private fun loadScript0(path: String): ChunkFactory {
val find = locate(path)
if (!find.exists) {
throw NoSuchElementException("Script $path does not exist")
}
val time = System.nanoTime()
val result = loader.compileTextChunk(path, find.readToString())
LOGGER.debug("Compiled {} in {} ms", path, (System.nanoTime() - time) / 1_000_000L)
return result
}
fun loadScript(path: String): ChunkFactory {
return scriptCache.computeIfAbsent(path, ::loadScript0)
}
private val luaScriptCache = Caffeine.newBuilder()
.maximumSize(LUA_SCRIPT_CACHE_SIZE)
.expireAfterAccess(Duration.ofHours(1L))

View File

@ -10,11 +10,6 @@ import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
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.TableFactory
import org.classdump.luna.runtime.ExecutionContext
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.value
@ -32,16 +27,8 @@ import ru.dbotthepony.kstarbound.item.ItemStack
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
import ru.dbotthepony.kstarbound.lua.indexNoYield
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.lua.toJson
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import java.io.DataInputStream
import java.io.DataOutputStream
import java.util.function.Supplier
@ -190,74 +177,6 @@ fun ItemDescriptor(args: LuaThread.ArgStack): ItemDescriptor {
return ItemDescriptor(args.lua, args.position)
}
fun ItemDescriptor(data: Table, stateMachine: StateMachine): Supplier<ItemDescriptor> {
val name = stateMachine.index(data, 1L, "name", "item")
val count = stateMachine.optionalIndex(data, 2L, "count")
val parameters = stateMachine.optionalIndex(data, 3L, "parameters", "data")
var result: KOptional<ItemDescriptor> = KOptional.empty()
stateMachine.add {
val iname = name.get()
val icount = count.get().orElse(1L)
val iparameters = parameters.get().map { if (it is Table) it.toJson(true) else throw LuaRuntimeException("Invalid item descriptor parameters ($it)") }.orElse { JsonObject() }
if (iname !is ByteString) throw LuaRuntimeException("Invalid item descriptor name (${iname})")
if (icount !is Number) throw LuaRuntimeException("Invalid item descriptor count (${icount})")
result = KOptional(ItemDescriptor(iname.decode(), icount.toLong(), iparameters as JsonObject))
}
return Supplier { result.value }
}
fun ExecutionContext.ItemDescriptor(data: Table): ItemDescriptor {
if (data.metatable?.rawget("__nils") != null)
return ItemDescriptor(toJsonFromLua(data)) // assume it is json
val name = indexNoYield(data, 1L) ?: indexNoYield(data, "name") ?: indexNoYield(data, "item")
val count = indexNoYield(data, 2L) ?: indexNoYield(data, "count") ?: 1L
val parameters = indexNoYield(data, 3L) ?: indexNoYield(data, "parameters") ?: indexNoYield(data, "data")
if (name !is ByteString) throw LuaRuntimeException("Invalid item descriptor name (${name})")
if (count !is Number) throw LuaRuntimeException("Invalid item descriptor count (${count})")
if (parameters == null) {
return ItemDescriptor(name.decode(), count.toLong())
} else if (parameters is Table) {
return ItemDescriptor(name.decode(), count.toLong(), parameters.toJson(true) as JsonObject)
} else {
throw LuaRuntimeException("Invalid item descriptor parameters ($parameters)")
}
}
fun ExecutionContext.ItemDescriptor(data: Any?): ItemDescriptor {
if (data is ByteString) {
return ItemDescriptor(data.decode(), 1L, JsonObject())
} else if (data is Table) {
return ItemDescriptor(data)
} else {
return ItemDescriptor.EMPTY
}
}
@Deprecated("Does not obey meta methods, find replacement where possible")
fun ItemDescriptor(data: Table): ItemDescriptor {
val name = data[1L] ?: data["name"] ?: data["item"]
val count = data[2L] ?: data["count"] ?: 1L
val parameters = data[3L] ?: data["parameters"] ?: data["data"]
if (name !is ByteString) throw LuaRuntimeException("Invalid item descriptor name (${name})")
if (count !is Number) throw LuaRuntimeException("Invalid item descriptor count (${count})")
if (parameters == null) {
return ItemDescriptor(name.decode(), count.toLong())
} else if (parameters is Table) {
return ItemDescriptor(name.decode(), count.toLong(), parameters.toJson(true) as JsonObject)
} else {
throw LuaRuntimeException("Invalid item descriptor parameters ($parameters)")
}
}
fun ItemDescriptor(stream: DataInputStream): ItemDescriptor {
val name = stream.readInternedString()
val count = stream.readVarLong()
@ -324,18 +243,6 @@ data class ItemDescriptor(
return !isEmpty
}
fun toTable(allocator: TableFactory): Table? {
if (isEmpty) {
return null
}
return allocator.newTable(0, 3).also {
it.rawset("name", name.toByteString())
it.rawset("count", count)
it.rawset("parameters", allocator.from(parameters))
}
}
fun build(level: Double? = null, seed: Long? = null, random: RandomGenerator? = null): ItemStack {
try {
val (jConfig, jParameters) = buildConfig(level, seed, random)

View File

@ -11,13 +11,10 @@ import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.google.gson.TypeAdapter
import com.google.gson.annotations.JsonAdapter
import com.google.gson.internal.bind.TypeAdapters
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.objects.ObjectArrayList
import org.classdump.luna.Table
import org.classdump.luna.TableFactory
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.get
@ -26,9 +23,7 @@ import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.io.writeVarLong
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kstarbound.math.vector.Vector2f
import ru.dbotthepony.kstarbound.Globals
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.Drawable
import ru.dbotthepony.kstarbound.defs.actor.PersistentStatusEffect
@ -38,9 +33,8 @@ import ru.dbotthepony.kstarbound.defs.item.ItemRarity
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.math.vector.Vector2f
import ru.dbotthepony.kstarbound.network.syncher.NetworkedElement
import ru.dbotthepony.kstarbound.util.AssetPathStack
import ru.dbotthepony.kstarbound.util.ManualLazy
@ -236,17 +230,17 @@ open class ItemStack(val entry: ItemRegistry.Entry, val config: JsonObject, para
data class AgingResult(val new: ItemStack?, val ageUpdated: Boolean)
private val agingScripts: LuaEnvironment? by lazy {
//val config = config.value ?: return@lazy null
//if (config.itemTags)
null
}
open fun advanceAge(by: Double): AgingResult {
val agingScripts = agingScripts ?: return AgingResult(null, false)
// TODO
val agingScripts = null as LuaThread? ?: return AgingResult(null, false)
val descriptor = createDescriptor()
val updated = ItemDescriptor(agingScripts.invokeGlobal("ageItem", descriptor.toTable(agingScripts), by)[0] as Table)
val updated = agingScripts.invokeGlobal(
"ageItem", 1,
{ descriptor.store(this); push(by); 2 },
::ItemDescriptor
).orThrow { IllegalStateException("Aging script did not return new item state") }
if (descriptor != updated) {
if (descriptor.name == updated.name) {
@ -374,14 +368,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<ItemStack>() {
override fun write(out: JsonWriter, value: ItemStack?) {
val json = value?.toJson()

View File

@ -1,22 +1,7 @@
package ru.dbotthepony.kstarbound.lua
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap
import org.classdump.luna.ByteString
import org.classdump.luna.LuaRuntimeException
import org.classdump.luna.Table
import org.classdump.luna.TableFactory
import org.classdump.luna.impl.NonsuspendableFunctionException
import org.classdump.luna.runtime.AbstractFunction3
import org.classdump.luna.runtime.ExecutionContext
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kstarbound.math.AABB
import ru.dbotthepony.kommons.util.IStruct2d
import ru.dbotthepony.kommons.util.IStruct2f
import ru.dbotthepony.kommons.util.IStruct2i
@ -27,438 +12,14 @@ import ru.dbotthepony.kommons.util.IStruct4d
import ru.dbotthepony.kommons.util.IStruct4f
import ru.dbotthepony.kommons.util.IStruct4i
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.math.AABB
import ru.dbotthepony.kstarbound.math.AABBi
import ru.dbotthepony.kstarbound.math.Line2d
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.math.vector.Vector2f
import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
import ru.dbotthepony.kstarbound.math.AABBi
import ru.dbotthepony.kstarbound.math.Line2d
import ru.dbotthepony.kstarbound.util.sbIntern
import ru.dbotthepony.kstarbound.world.physics.Poly
fun ExecutionContext.toVector2i(table: Any): Vector2i {
val x = indexNoYield(table, 1L)
val y = indexNoYield(table, 2L)
returnBuffer.setTo()
if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x")
if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y")
return Vector2i(x.toInt(), y.toInt())
}
fun ExecutionContext.toVector2d(table: Any): Vector2d {
val x = indexNoYield(table, 1L)
val y = indexNoYield(table, 2L)
returnBuffer.setTo()
if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x")
if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y")
return Vector2d(x.toDouble(), y.toDouble())
}
fun ExecutionContext.toLine2d(table: Any): Line2d {
val p0 = toVector2d(indexNoYield(table, 1L) ?: throw LuaRuntimeException("Invalid line: $table"))
val p1 = toVector2d(indexNoYield(table, 2L) ?: throw LuaRuntimeException("Invalid line: $table"))
return Line2d(p0, p1)
}
fun String?.toByteString(): ByteString? {
return if (this == null) null else ByteString.of(this)
}
fun ExecutionContext.toPoly(table: Table): Poly {
val vertices = ArrayList<Vector2d>()
for ((_, v) in table) {
vertices.add(toVector2d(v))
}
return Poly(vertices)
}
fun ExecutionContext.toVector2f(table: Any): Vector2f {
val x = indexNoYield(table, 1L)
val y = indexNoYield(table, 2L)
returnBuffer.setTo()
if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x")
if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y")
return Vector2f(x.toFloat(), y.toFloat())
}
fun ExecutionContext.toColor(table: Any): RGBAColor {
val x = indexNoYield(table, 1L)
val y = indexNoYield(table, 2L)
val z = indexNoYield(table, 3L)
val w = indexNoYield(table, 4L) ?: 255
returnBuffer.setTo()
if (x !is Number) throw ClassCastException("Expected table representing a Color, but value at [1] is not a number: $x")
if (y !is Number) throw ClassCastException("Expected table representing a Color, but value at [2] is not a number: $y")
if (z !is Number) throw ClassCastException("Expected table representing a Color, but value at [3] is not a number: $z")
if (w !is Number) throw ClassCastException("Expected table representing a Color, but value at [4] is not a number: $w")
return RGBAColor(x.toInt(), y.toInt(), z.toInt(), w.toInt())
}
fun ExecutionContext.toAABB(table: Any): AABB {
val x = indexNoYield(table, 1L)
val y = indexNoYield(table, 2L)
val z = indexNoYield(table, 3L)
val w = indexNoYield(table, 4L)
returnBuffer.setTo()
if (x !is Number) throw ClassCastException("Expected table representing a AABB, but value at [1] is not a number: $x")
if (y !is Number) throw ClassCastException("Expected table representing a AABB, but value at [2] is not a number: $y")
if (z !is Number) throw ClassCastException("Expected table representing a AABB, but value at [3] is not a number: $z")
if (w !is Number) throw ClassCastException("Expected table representing a AABB, but value at [4] is not a number: $w")
return AABB(Vector2d(x.toDouble(), y.toDouble()), Vector2d(z.toDouble(), w.toDouble()))
}
fun ExecutionContext.toAABBi(table: Any): AABBi {
val x = indexNoYield(table, 1L)
val y = indexNoYield(table, 2L)
val z = indexNoYield(table, 3L)
val w = indexNoYield(table, 4L)
returnBuffer.setTo()
if (x !is Number) throw ClassCastException("Expected table representing a AABBi, but value at [1] is not a number: $x")
if (y !is Number) throw ClassCastException("Expected table representing a AABBi, but value at [2] is not a number: $y")
if (z !is Number) throw ClassCastException("Expected table representing a AABBi, but value at [3] is not a number: $z")
if (w !is Number) throw ClassCastException("Expected table representing a AABBi, but value at [4] is not a number: $w")
return AABBi(Vector2i(x.toInt(), y.toInt()), Vector2i(z.toInt(), w.toInt()))
}
fun toJsonFromLua(value: Any?): JsonElement {
return when (value) {
null, is JsonNull -> JsonNull.INSTANCE
is String -> JsonPrimitive(value.sbIntern())
is ByteString -> JsonPrimitive(value.decode().sbIntern())
is Number -> JsonPrimitive(value)
is Boolean -> InternedJsonElementAdapter.of(value)
is Table -> value.toJson()
else -> throw IllegalArgumentException("Unable to translate $value into json!")
}
}
fun Table.toJson(forceObject: Boolean = false): JsonElement {
val arrayValues = Long2ObjectAVLTreeMap<JsonElement>()
val hashValues = HashMap<String, JsonElement>()
val meta = metatable
var hint = LUA_HINT_NONE
if (meta != null) {
val getHint = meta["__typehint"]
if (getHint is Number) {
hint = getHint.toLong()
}
val nils = meta["__nils"]
if (nils is Table) {
// Nil entries just have a garbage integer as their value
for ((k, v) in nils) {
val ik = k.toLuaInteger()
if (ik != null) {
arrayValues[ik] = JsonNull.INSTANCE
} else {
hashValues[k.toString()] = JsonNull.INSTANCE
}
}
}
}
for ((k, v) in this) {
val ik = k.toLuaInteger()
if (ik != null) {
arrayValues[ik] = toJsonFromLua(v)
} else {
hashValues[k.toString()] = toJsonFromLua(v)
}
}
val interpretAsList = !forceObject && hashValues.isEmpty() && hint != LUA_HINT_OBJECT
if (interpretAsList) {
val list = JsonArray()
for ((k, v) in arrayValues) {
val ik = k.toInt() - 1
while (list.size() <= ik) {
list.add(JsonNull.INSTANCE)
}
list[ik] = v
}
return list
} else {
for ((k, v) in arrayValues) {
hashValues[(k - 1L).toString()] = v
}
return JsonObject().apply {
for ((k, v) in hashValues) {
this[k] = v
}
}
}
}
fun TableFactory.from(value: JsonElement?): Any? {
when (value) {
is JsonPrimitive -> {
if (value.isNumber) {
return when (val v = value.asNumber) {
is Float, is Double -> value.asDouble
is Int, is Long -> value.asLong
else -> {
// hi com.google.gson.internal.LazilyParsedNumber
val doubleBits by lazy { v.toDouble() }
if (v.toString().contains('.') || doubleBits % 1.0 != 0.0) {
v.toDouble()
} else {
v.toLong()
}
}
}
} else if (value.isString) {
return ByteString.of(value.asString)
} else if (value.isBoolean) {
return value.asBoolean
} else {
throw RuntimeException("unreachable code")
}
}
is JsonArray -> return from(value)
is JsonObject -> return from(value)
null, is JsonNull -> return null
else -> throw RuntimeException(value::class.qualifiedName)
}
}
private data class JsonTable(val metatable: Table, val nils: Table, val data: Table)
fun Any?.toLuaInteger(): Long? {
if (this == null)
return null
else if (this is Long)
return this
else if (this is Double) {
if (this % 1.0 == 0.0) {
return this.toLong()
} else {
return null
}
} else if (this is ByteString) {
val decoded = decode()
if (decoded.contains('.'))
return null
return decoded.toLongOrNull()
} else {
return null
}
}
const val LUA_HINT_NONE = 0L
const val LUA_HINT_ARRAY = 1L
const val LUA_HINT_OBJECT = 2L
private object JsonTableIndex : AbstractFunction3<Table, Any, Any?>() {
override fun resume(context: ExecutionContext?, suspendedState: Any?) {
throw NonsuspendableFunctionException(this::class.java)
}
private val __nils: ByteString = ByteString.of("__nils")
override fun invoke(context: ExecutionContext, self: Table, key: Any, value: Any?) {
val meta = self.metatable
val nils = meta[__nils] as Table
// If we are setting an entry to nil, need to add a bogus integer entry
// to the __nils table, otherwise need to set the entry *in* the __nils
// table to nil and remove it.
// TODO: __newindex is called only when assigning non-existing keys to values,
// TODO: as per Lua manual.
// TODO: Chucklefish weren't aware of this?
if (value == null) {
nils[key] = 0L
} else {
nils[key] = null as Any?
}
self[key] = value
}
}
private fun TableFactory.createJsonTable(typeHint: Long, size: Int, hash: Int): JsonTable {
val metatable = newTable()
val nils = newTable()
val data = newTable(size, hash)
metatable["__newindex"] = JsonTableIndex
metatable["__nils"] = nils
metatable["__typehint"] = typeHint
data.metatable = metatable
return JsonTable(metatable, nils, data)
}
fun TableFactory.from(value: JsonObject): Table {
val (_, nils, data) = createJsonTable(LUA_HINT_OBJECT, 0, value.size())
for ((k, v) in value.entrySet()) {
if (v.isJsonNull) {
nils[k] = 0L
} else {
data[k] = from(v)
}
}
return data
}
fun TableFactory.from(value: Int?): Long? {
return value?.toLong()
}
fun TableFactory.from(value: Long?): Long? {
return value
}
fun TableFactory.from(value: String?): ByteString? {
return value.toByteString()
}
fun TableFactory.from(value: ByteString?): ByteString? {
return value
}
fun TableFactory.from(value: JsonArray): Table {
val (_, nils, data) = createJsonTable(LUA_HINT_ARRAY, 0, value.size())
for ((i, v) in value.withIndex()) {
if (v.isJsonNull) {
nils[i + 1L] = 0L
} else {
data[i + 1L] = from(v)
}
}
return data
}
fun TableFactory.createJsonObject(): Table {
return createJsonTable(LUA_HINT_OBJECT, 0, 0).data
}
fun TableFactory.createJsonArray(): Table {
return createJsonTable(LUA_HINT_ARRAY, 0, 0).data
}
fun TableFactory.from(value: IStruct2d?): Table? {
value ?: return null
return newTable(2, 0).apply {
this[1L] = value.component1()
this[2L] = value.component2()
}
}
fun TableFactory.from(value: Poly?): Table? {
value ?: return null
return newTable(value.vertices.size, 0).apply {
value.vertices.withIndex().forEach { (i, v) -> this[i + 1L] = from(v) }
}
}
fun TableFactory.from(value: IStruct2i?): Table? {
value ?: return null
return newTable(2, 0).also {
it.rawset(1L, value.component1())
it.rawset(2L, value.component2())
}
}
fun TableFactory.from(value: IStruct3i?): Table? {
value ?: return null
return newTable(3, 0).also {
it.rawset(1L, value.component1())
it.rawset(2L, value.component2())
it.rawset(3L, value.component3())
}
}
fun TableFactory.from(value: IStruct4i?): Table? {
value ?: return null
return newTable(3, 0).also {
it.rawset(1L, value.component1())
it.rawset(2L, value.component2())
it.rawset(3L, value.component3())
it.rawset(4L, value.component4())
}
}
fun TableFactory.from(value: RGBAColor?): Table? {
value ?: return null
return newTable(3, 0).also {
it.rawset(1L, value.redInt.toLong())
it.rawset(2L, value.greenInt.toLong())
it.rawset(3L, value.blueInt.toLong())
it.rawset(4L, value.alphaInt.toLong())
}
}
fun TableFactory.from(value: Collection<Any>): Table {
return newTable(value.size, 0).also {
for ((i, v) in value.withIndex()) {
it.rawset(i + 1L, v)
}
}
}
fun TableFactory.from(value: AABB?): Table? {
value ?: return null
return newTable(3, 0).also {
it.rawset(1L, value.mins.x)
it.rawset(2L, value.mins.y)
it.rawset(3L, value.maxs.x)
it.rawset(4L, value.maxs.y)
}
}
fun TableFactory.tableFrom(collection: Collection<Any?>): Table {
val alloc = newTable(collection.size, 0)
for ((i, v) in collection.withIndex())
alloc[i + 1L] = v
return alloc
}
// TODO: error reporting when argument was provided, but it is malformed
// currently, invalid data gets silently discarded, and treated as "no value" aka null
fun LuaThread.getPoly(stackIndex: Int = -1): Poly? {

View File

@ -1,400 +0,0 @@
package ru.dbotthepony.kstarbound.lua
import com.google.gson.JsonElement
import it.unimi.dsi.fastutil.objects.ObjectIterators
import org.classdump.luna.LuaRuntimeException
import org.classdump.luna.Table
import org.classdump.luna.TableFactory
import org.classdump.luna.impl.NonsuspendableFunctionException
import org.classdump.luna.lib.ArgumentIterator
import org.classdump.luna.lib.TableLib
import org.classdump.luna.runtime.AbstractFunction0
import org.classdump.luna.runtime.AbstractFunction1
import org.classdump.luna.runtime.AbstractFunction2
import org.classdump.luna.runtime.AbstractFunction3
import org.classdump.luna.runtime.AbstractFunction4
import org.classdump.luna.runtime.AbstractFunctionAnyArg
import org.classdump.luna.runtime.Dispatch
import org.classdump.luna.runtime.ExecutionContext
import org.classdump.luna.runtime.LuaFunction
import org.classdump.luna.runtime.UnresolvedControlThrowable
import java.util.Spliterator
import java.util.Spliterators
import java.util.stream.Stream
import java.util.stream.StreamSupport
import kotlin.math.max
import kotlin.math.min
fun ExecutionContext.indexNoYield(table: Any, key: Any): Any? {
return try {
Dispatch.index(this, table, key)
returnBuffer.get0()
} catch (err: UnresolvedControlThrowable) {
throw RuntimeException("attempt to yield across C boundary", err)
}
}
fun ExecutionContext.indexSetNoYield(table: Any, key: Any, value: Any?) {
try {
Dispatch.setindex(this, table, key, value)
} catch (err: UnresolvedControlThrowable) {
throw RuntimeException("attempt to yield across C boundary", err)
}
}
fun ArgumentIterator.nextOptionalFloat(): Double? {
if (hasNext()) {
return nextFloat()
} else {
return null
}
}
fun ArgumentIterator.nextOptionalInteger(): Long? {
if (hasNext()) {
return nextInteger()
} else {
return null
}
}
operator fun Table.set(index: Any, value: Any?) {
rawset(index, value)
}
@Deprecated("Trying to set JSON to Lua table", level = DeprecationLevel.ERROR)
operator fun Table.set(index: Any, value: JsonElement?) {
rawset(index, value)
}
operator fun Table.set(index: Long, value: Any?) {
rawset(index, value)
}
@Deprecated("Trying to set JSON to Lua table", level = DeprecationLevel.ERROR)
operator fun Table.set(index: Long, value: JsonElement?) {
rawset(index, value)
}
operator fun Table.set(index: Int, value: Any?) {
rawset(index.toLong(), value)
}
@Deprecated("Trying to set JSON to Lua table", level = DeprecationLevel.ERROR)
operator fun Table.set(index: Int, value: JsonElement?) {
rawset(index.toLong(), value)
}
operator fun Table.get(index: Any): Any? = rawget(index)
operator fun Table.get(index: Long): Any? = rawget(index)
operator fun Table.get(index: Int): Any? = rawget(index.toLong())
operator fun Table.contains(index: Any): Boolean {
return rawget(index) != null
}
operator fun Table.contains(index: Long): Boolean {
return rawget(index) != null
}
operator fun Table.iterator(): Iterator<Map.Entry<Any, Any>> {
var key: Any? = initialKey() ?: return ObjectIterators.emptyIterator()
data class Pair(override val key: Any, override val value: Any) : Map.Entry<Any, Any>
return object : Iterator<Map.Entry<Any, Any>> {
override fun hasNext(): Boolean {
return key != null
}
override fun next(): Map.Entry<Any, Any> {
val ikey = key ?: throw NoSuchElementException()
val value = get(ikey)!!
key = successorKeyOf(ikey)
return Pair(ikey, value)
}
}
}
fun Table.spliterator(): Spliterator<Map.Entry<Any, Any>> {
return Spliterators.spliteratorUnknownSize(iterator(), Spliterator.ORDERED)
}
fun Table.stream(): Stream<Map.Entry<Any, Any>> {
return StreamSupport.stream(spliterator(), false)
}
/**
* to be used in places where we need to "unpack" table, like this:
*
* ```lua
* local array = unpack(tab)
* ```
*
* except this function unpacks using rawget
*/
fun Table.unpackAsArray(): Array<Any?> {
var min = Long.MAX_VALUE
var max = 0L
for ((k, v) in this) {
if (k is Long) {
max = max(k, max)
min = min(k, min)
}
}
val length = max - min
if (length <= 0L)
return arrayOf()
val array = arrayOfNulls<Any>(length.toInt())
var i2 = 0
for (i in min .. max) {
array[i2++] = this[i]
}
return array
}
fun TableFactory.tableOf(vararg values: Any?): Table {
val table = newTable(values.size, 0)
for ((i, v) in values.withIndex()) {
table[i + 1L] = v
}
return table
}
fun TableFactory.tableOf(vararg values: Int): Table {
val table = newTable(values.size, 0)
for ((i, v) in values.withIndex()) {
table[i + 1L] = v.toLong()
}
return table
}
fun TableFactory.tableOf(vararg values: Long): Table {
val table = newTable(values.size, 0)
for ((i, v) in values.withIndex()) {
table[i + 1L] = v
}
return table
}
fun TableFactory.tableMapOf(vararg values: Pair<Any, Any?>): Table {
val table = newTable(0, values.size)
for ((k, v) in values) {
table[k] = v
}
return table
}
fun TableFactory.tableOf(): Table {
return newTable()
}
@Deprecated("Function is a stub")
fun luaStub(message: String = "not yet implemented"): LuaFunction<Any?, Any?, Any?, Any?, Any?> {
return object : LuaFunction<Any?, Any?, Any?, Any?, Any?>() {
override fun resume(context: ExecutionContext?, suspendedState: Any?) {
throw NonsuspendableFunctionException(this::class.java)
}
override fun invoke(context: ExecutionContext?) {
throw LuaRuntimeException("NYI: $message")
}
override fun invoke(context: ExecutionContext?, arg1: Any?) {
throw LuaRuntimeException("NYI: $message")
}
override fun invoke(context: ExecutionContext?, arg1: Any?, arg2: Any?) {
throw LuaRuntimeException("NYI: $message")
}
override fun invoke(context: ExecutionContext?, arg1: Any?, arg2: Any?, arg3: Any?) {
throw LuaRuntimeException("NYI: $message")
}
override fun invoke(context: ExecutionContext?, arg1: Any?, arg2: Any?, arg3: Any?, arg4: Any?) {
throw LuaRuntimeException("NYI: $message")
}
override fun invoke(context: ExecutionContext?, arg1: Any?, arg2: Any?, arg3: Any?, arg4: Any?, arg5: Any?) {
throw LuaRuntimeException("NYI: $message")
}
override fun invoke(context: ExecutionContext?, args: Array<out Any>?) {
throw LuaRuntimeException("NYI: $message")
}
}
}
fun luaFunctionN(name: String, callable: ExecutionContext.(ArgumentIterator) -> Unit): LuaFunction<Any, Any, Any, Any, Any> {
return object : AbstractFunctionAnyArg() {
override fun resume(context: ExecutionContext, suspendedState: Any) {
throw NonsuspendableFunctionException(this::class.java)
}
override fun invoke(context: ExecutionContext, args: Array<out Any>) {
context.returnBuffer.setTo()
try {
callable.invoke(context, ArgumentIterator.of(context, name, args))
} catch (err: ClassCastException) {
throw LuaRuntimeException(err)
} catch (err: NullPointerException) {
throw LuaRuntimeException(err)
}
}
}
}
fun luaFunctionNS(name: String, callable: ExecutionContext.(ArgumentIterator) -> StateMachine): LuaFunction<Any, Any, Any, Any, Any> {
return object : AbstractFunctionAnyArg() {
override fun resume(context: ExecutionContext, suspendedState: Any) {
(suspendedState as StateMachine).run(context)
}
override fun invoke(context: ExecutionContext, args: Array<out Any>) {
context.returnBuffer.setTo()
try {
callable.invoke(context, ArgumentIterator.of(context, name, args)).run(context)
} catch (err: ClassCastException) {
throw LuaRuntimeException(err)
} catch (err: NullPointerException) {
throw LuaRuntimeException(err)
}
}
}
}
fun luaFunctionArray(callable: ExecutionContext.(Array<out Any?>) -> Unit): LuaFunction<Any, Any, Any, Any, Any> {
return object : AbstractFunctionAnyArg() {
override fun resume(context: ExecutionContext, suspendedState: Any) {
throw NonsuspendableFunctionException(this::class.java)
}
override fun invoke(context: ExecutionContext, args: Array<out Any?>) {
context.returnBuffer.setTo()
try {
callable.invoke(context, args)
} catch (err: ClassCastException) {
throw LuaRuntimeException(err)
} catch (err: NullPointerException) {
throw LuaRuntimeException(err)
}
}
}
}
fun luaFunction(callable: ExecutionContext.() -> Unit): LuaFunction<*, *, *, *, *> {
return object : AbstractFunction0() {
override fun resume(context: ExecutionContext, suspendedState: Any) {
throw NonsuspendableFunctionException(this::class.java)
}
override fun invoke(context: ExecutionContext) {
context.returnBuffer.setTo()
try {
callable.invoke(context)
} catch (err: ClassCastException) {
throw LuaRuntimeException(err)
} catch (err: NullPointerException) {
throw LuaRuntimeException(err)
}
}
}
}
fun <T> luaFunction(callable: ExecutionContext.(T) -> Unit): LuaFunction<T, *, *, *, *> {
return object : AbstractFunction1<T>() {
override fun resume(context: ExecutionContext, suspendedState: Any) {
throw NonsuspendableFunctionException(this::class.java)
}
override fun invoke(context: ExecutionContext, arg1: T) {
context.returnBuffer.setTo()
try {
callable.invoke(context, arg1)
} catch (err: ClassCastException) {
throw LuaRuntimeException(err)
} catch (err: NullPointerException) {
throw LuaRuntimeException(err)
}
}
}
}
fun <T, T2> luaFunction(callable: ExecutionContext.(T, T2) -> Unit): LuaFunction<T, T2, *, *, *> {
return object : AbstractFunction2<T, T2>() {
override fun resume(context: ExecutionContext, suspendedState: Any) {
throw NonsuspendableFunctionException(this::class.java)
}
override fun invoke(context: ExecutionContext, arg1: T, arg2: T2) {
context.returnBuffer.setTo()
try {
callable.invoke(context, arg1, arg2)
} catch (err: ClassCastException) {
throw LuaRuntimeException(err)
} catch (err: NullPointerException) {
throw LuaRuntimeException(err)
}
}
}
}
fun <T, T2, T3> luaFunction(callable: ExecutionContext.(T, T2, T3) -> Unit): LuaFunction<T, T2, T3, *, *> {
return object : AbstractFunction3<T, T2, T3>() {
override fun resume(context: ExecutionContext, suspendedState: Any) {
throw NonsuspendableFunctionException(this::class.java)
}
override fun invoke(context: ExecutionContext, arg1: T, arg2: T2, arg3: T3) {
context.returnBuffer.setTo()
try {
callable.invoke(context, arg1, arg2, arg3)
} catch (err: ClassCastException) {
throw LuaRuntimeException(err)
} catch (err: NullPointerException) {
throw LuaRuntimeException(err)
}
}
}
}
fun <T, T2, T3, T4> luaFunction(callable: ExecutionContext.(T, T2, T3, T4) -> Unit): LuaFunction<T, T2, T3, T4, *> {
return object : AbstractFunction4<T, T2, T3, T4>() {
override fun resume(context: ExecutionContext, suspendedState: Any) {
throw NonsuspendableFunctionException(this::class.java)
}
override fun invoke(context: ExecutionContext, arg1: T, arg2: T2, arg3: T3, arg4: T4) {
context.returnBuffer.setTo()
try {
callable.invoke(context, arg1, arg2, arg3, arg4)
} catch (err: ClassCastException) {
throw LuaRuntimeException(err)
} catch (err: NullPointerException) {
throw LuaRuntimeException(err)
}
}
}
}

View File

@ -1,339 +0,0 @@
package ru.dbotthepony.kstarbound.lua
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import org.apache.logging.log4j.LogManager
import org.classdump.luna.ByteString
import org.classdump.luna.LuaObject
import org.classdump.luna.LuaRuntimeException
import org.classdump.luna.LuaType
import org.classdump.luna.StateContext
import org.classdump.luna.Table
import org.classdump.luna.Variable
import org.classdump.luna.compiler.CompilerChunkLoader
import org.classdump.luna.compiler.CompilerSettings
import org.classdump.luna.env.RuntimeEnvironments
import org.classdump.luna.exec.CallPausedException
import org.classdump.luna.exec.Continuation
import org.classdump.luna.exec.DirectCallExecutor
import org.classdump.luna.impl.DefaultTable
import org.classdump.luna.impl.NonsuspendableFunctionException
import org.classdump.luna.lib.BasicLib
import org.classdump.luna.lib.CoroutineLib
import org.classdump.luna.lib.MathLib
import org.classdump.luna.lib.OsLib
import org.classdump.luna.lib.StringLib
import org.classdump.luna.lib.TableLib
import org.classdump.luna.lib.Utf8Lib
import org.classdump.luna.load.ChunkFactory
import org.classdump.luna.runtime.AbstractFunction1
import org.classdump.luna.runtime.Coroutine
import org.classdump.luna.runtime.ExecutionContext
import org.classdump.luna.runtime.LuaFunction
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.AssetPath
import ru.dbotthepony.kstarbound.lua.bindings.provideRootBindings
import ru.dbotthepony.kstarbound.lua.bindings.provideUtilityBindings
import ru.dbotthepony.kstarbound.util.random.random
import java.util.concurrent.atomic.AtomicLong
class LuaEnvironment : StateContext {
private var nilMeta: Table? = null
private var booleanMeta: Table? = null
private var numberMeta: Table? = null
private var stringMeta: Table? = null
private var functionMeta: Table? = null
private var threadMeta: Table? = null
private var lightUserdataMeta: Table? = null
override fun getNilMetatable(): Table? {
return nilMeta
}
override fun getBooleanMetatable(): Table? {
return booleanMeta
}
override fun getNumberMetatable(): Table? {
return numberMeta
}
override fun getStringMetatable(): Table? {
return stringMeta
}
override fun getFunctionMetatable(): Table? {
return functionMeta
}
override fun getThreadMetatable(): Table? {
return threadMeta
}
override fun getLightUserdataMetatable(): Table? {
return lightUserdataMeta
}
override fun getMetatable(instance: Any?): Table? {
if (instance is LuaObject)
return instance.metatable
return when (val type = LuaType.typeOf(instance)!!) {
LuaType.NIL -> nilMeta
LuaType.BOOLEAN -> booleanMeta
LuaType.NUMBER -> numberMeta
LuaType.STRING -> stringMeta
LuaType.FUNCTION -> functionMeta
LuaType.USERDATA -> lightUserdataMeta
LuaType.THREAD -> threadMeta
else -> throw IllegalArgumentException("Illegal type: $type")
}
}
override fun setNilMetatable(table: Table?): Table? {
val old = nilMeta; nilMeta = table; return old
}
override fun setBooleanMetatable(table: Table?): Table? {
val old = booleanMeta; booleanMeta = table; return old
}
override fun setNumberMetatable(table: Table?): Table? {
val old = numberMeta; numberMeta = table; return old
}
override fun setStringMetatable(table: Table?): Table? {
val old = stringMeta; stringMeta = table; return old
}
override fun setFunctionMetatable(table: Table?): Table? {
val old = functionMeta; functionMeta = table; return old
}
override fun setThreadMetatable(table: Table?): Table? {
val old = threadMeta; threadMeta = table; return old
}
override fun setLightUserdataMetatable(table: Table?): Table? {
val old = lightUserdataMeta; lightUserdataMeta = table; return old
}
override fun setMetatable(instance: Any?, table: Table?): Table? {
if (instance is LuaObject)
return instance.setMetatable(table)
else
throw IllegalArgumentException("Can not set metatable of ${LuaType.typeOf(instance)}")
}
override fun newTable(): Table {
return DefaultTable()
}
override fun newTable(array: Int, hash: Int): Table {
return DefaultTable()
}
val globals: Table = newTable()
val executor: DirectCallExecutor = DirectCallExecutor.newExecutor()
var random = random()
init {
globals["_G"] = globals
globals["assert"] = BasicLib.assertFn()
globals["error"] = BasicLib.error()
globals["getmetatable"] = BasicLib.getmetatable()
globals["ipairs"] = BasicLib.ipairs()
globals["next"] = BasicLib.next()
globals["pairs"] = BasicLib.pairs()
globals["pcall"] = BasicLib.pcall()
globals["rawequal"] = BasicLib.rawequal()
globals["rawget"] = BasicLib.rawget()
globals["rawlen"] = BasicLib.rawlen()
globals["rawset"] = BasicLib.rawset()
globals["select"] = BasicLib.select()
globals["setmetatable"] = BasicLib.setmetatable()
globals["tostring"] = BasicLib.tostring()
globals["tonumber"] = BasicLib.tonumber()
globals["type"] = BasicLib.type()
globals["_VERSION"] = BasicLib._VERSION
globals["xpcall"] = BasicLib.xpcall()
globals["print"] = PrintFunction(globals)
// why not use _ENV anyway lol
globals["self"] = newTable()
CoroutineLib.installInto(this, globals)
TableLib.installInto(this, globals)
MathLib.installInto(this, globals)
StringLib.installInto(this, globals)
OsLib.installInto(this, globals, RuntimeEnvironments.system())
val math = globals["math"] as Table
math["random"] = luaFunction { origin: Number?, bound: Number? ->
if (origin == null && bound == null) {
returnBuffer.setTo(random.nextDouble())
} else if (bound == null) {
val origin = origin!!.toLong()
if (origin > 1L) {
returnBuffer.setTo(random.nextLong(1L, origin))
} else if (origin == 1L) {
returnBuffer.setTo(1L)
} else {
throw LuaRuntimeException("bad argument #1 to 'random' (interval is empty)")
}
} else {
val origin = origin!!.toLong()
val bound = bound.toLong()
if (bound > origin) {
returnBuffer.setTo(random.nextLong(origin, bound))
} else if (bound == origin) {
returnBuffer.setTo(origin)
} else {
throw LuaRuntimeException("bad argument #1 to 'random' (interval is empty)")
}
}
}
math["randomseed"] = luaFunction { seed: Number ->
random = random(seed.toLong())
}
// TODO: NYI, maybe polyfill?
Utf8Lib.installInto(this, globals)
// provideRootBindings(this)
// provideUtilityBindings(this)
}
private val scripts = ObjectArraySet<ChunkFactory>()
private var initCalled = false
private val loadedScripts = ObjectArraySet<String>()
val require = LuaRequire()
inner class LuaRequire : AbstractFunction1<ByteString>() {
override fun resume(context: ExecutionContext?, suspendedState: Any?) {
throw NonsuspendableFunctionException(this::class.java)
}
override fun invoke(context: ExecutionContext, arg1: ByteString) {
val name = arg1.decode()
if (loadedScripts.add(name)) {
val script = Starbound.loadScript(name)
executor.call(this@LuaEnvironment, script.newInstance(Variable(globals)))
}
}
}
init {
globals["require"] = require
}
fun attach(script: ChunkFactory) {
if (initCalled) {
executor.call(this@LuaEnvironment, script.newInstance(Variable(globals)))
} else {
scripts.add(script)
}
}
fun attach(scripts: Collection<AssetPath>) {
if (initCalled) {
for (name in scripts) {
if (loadedScripts.add(name.fullPath)) {
val script = Starbound.loadScript(name.fullPath)
executor.call(this@LuaEnvironment, script.newInstance(Variable(globals)))
}
}
} else {
for (script in scripts) {
if (loadedScripts.add(script.fullPath)) {
this.scripts.add(Starbound.loadScript(script.fullPath))
}
}
}
}
fun run(chunk: ChunkFactory): Array<out Any?> {
return executor.call(this, chunk.newInstance(Variable(globals)))
}
var errorState = false
private set
fun markErrored() {
errorState = true
}
fun call(fn: Any, vararg args: Any?) = executor.call(this, fn, *args)
fun init(callInit: Boolean = true): Boolean {
check(!initCalled) { "Already called init()" }
initCalled = true
for (script in scripts) {
try {
executor.call(this, script.newInstance(Variable(globals)))
} catch (err: Throwable) {
// errorState = true
LOGGER.error("Failed to attach script to environment", err)
scripts.clear()
return false
}
}
scripts.clear()
if (callInit) {
val init = globals["init"]
if (init is LuaFunction<*, *, *, *, *>) {
try {
executor.call(this, init)
} catch (err: Throwable) {
// errorState = true
LOGGER.error("Exception on init()", err)
return false
}
}
}
return true
}
fun invokeGlobal(name: String, vararg arguments: Any?): Array<Any?> {
if (errorState || !initCalled)
return arrayOf()
val load = globals[name]
if (load is LuaFunction<*, *, *, *, *>) {
return try {
executor.call(this, load, *arguments)
} catch (err: Throwable) {
// errorState = true
LOGGER.error("Exception while calling global $name", err)
arrayOf()
}
}
return arrayOf()
}
private val loader by lazy { CompilerChunkLoader.of(CompilerSettings.defaultNoAccountingSettings(), "sb_lua${COUNTER.getAndIncrement()}_") }
// leaks memory until LuaEnvironment goes out of scope. Too bad!
fun eval(chunk: String, name: String = "eval"): Array<Any?> {
return executor.call(this, loader.compileTextChunk(chunk, name).newInstance(Variable(globals)))
}
companion object {
private val LOGGER = LogManager.getLogger()
private val COUNTER = AtomicLong()
}
}

View File

@ -1,11 +1,12 @@
package ru.dbotthepony.kstarbound.lua
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.util.ActionPacer
import ru.dbotthepony.kstarbound.util.sbIntern
class LuaMessageHandlerComponent(lua: LuaThread, val nameProvider: () -> String) {
class LuaMessageHandlerComponent(val lua: LuaThread, val nameProvider: () -> String) {
private val handlers = HashMap<String, LuaHandle>()
private fun setHandler(args: LuaThread.ArgStack): Int {
@ -37,7 +38,7 @@ class LuaMessageHandlerComponent(lua: LuaThread, val nameProvider: () -> String)
return handlers[name]
}
inline fun handle(lua: LuaThread, message: String, isLocal: Boolean, arguments: LuaThread.() -> Int): JsonElement? {
inline fun handle(message: String, isLocal: Boolean, arguments: LuaThread.() -> Int): JsonElement? {
val handler = lookupHandler(message) ?: return null
val top = lua.stackTop
@ -60,6 +61,18 @@ class LuaMessageHandlerComponent(lua: LuaThread, val nameProvider: () -> String)
}
}
fun handle(message: String, isLocal: Boolean, arguments: JsonArray): JsonElement? {
return handle(message, isLocal) {
ensureExtraCapacity(arguments.size() + 4)
for (argument in arguments) {
push(argument)
}
arguments.size()
}
}
companion object {
val LOGGER = LogManager.getLogger()
}

View File

@ -4,11 +4,12 @@ import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture
import ru.dbotthepony.kstarbound.lua.userdata.LuaPathFinder
import ru.dbotthepony.kstarbound.util.random.random
import java.io.Closeable
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.random.RandomGenerator
import kotlin.properties.Delegates
class LuaSharedState(val handlesThread: LuaThread) {
class LuaSharedState(val handlesThread: LuaThread) : Closeable {
private val pendingFree = ConcurrentLinkedQueue<Int>()
private val freeHandles = IntAVLTreeSet()
private var nextHandle = 0
@ -21,6 +22,15 @@ class LuaSharedState(val handlesThread: LuaThread) {
var commonHandles by Delegates.notNull<CommonHandleRegistry>()
private set
var isValid = true
private set
override fun close() {
if (!isValid) return
isValid = false
namedHandles.clear()
}
fun initializeHandles(mainThread: LuaThread) {
val future = LuaFuture.initializeHandle(mainThread)
val pathFinder = LuaPathFinder.initializeHandle(mainThread)
@ -32,6 +42,7 @@ class LuaSharedState(val handlesThread: LuaThread) {
}
fun freeHandle(handle: Int, key: Any?) {
if (!isValid) return
pendingFree.add(handle)
if (key != null) {
@ -40,6 +51,7 @@ class LuaSharedState(val handlesThread: LuaThread) {
}
fun cleanup() {
check(isValid) { "Shared state is no longer valid" }
if (handlesInUse == 0) return
var handle = pendingFree.poll()
@ -55,6 +67,7 @@ class LuaSharedState(val handlesThread: LuaThread) {
}
fun allocateHandle(name: Any?): LuaHandle {
check(isValid) { "Shared state is no longer valid" }
require(name == null || name !in namedHandles) { "Named handle '$name' already exists" }
handlesInUse++
@ -80,10 +93,12 @@ class LuaSharedState(val handlesThread: LuaThread) {
}
fun getNamedHandle(key: Any): LuaHandle {
check(isValid) { "Shared state is no longer valid" }
return namedHandles[key] ?: throw NoSuchElementException("No such handle: $key")
}
fun findNamedHandle(key: Any): LuaHandle? {
check(isValid) { "Shared state is no longer valid" }
return namedHandles[key]
}
}

View File

@ -140,7 +140,10 @@ class LuaThread private constructor(
}
override fun close() {
this.cleanable?.clean()
if (cleanable != null) {
cleanable!!.clean()
sharedState.close()
}
}
val stackTop: Int get() {
@ -232,6 +235,27 @@ class LuaThread private constructor(
}
}
/**
* Returns boolean indicating whenever function exists
*/
fun invokeGlobal(name: String, numArguments: Int): Boolean {
val top = stackTop
try {
val type = loadGlobal(name)
if (type != LuaType.FUNCTION) {
pop(numArguments)
return false
}
call(numArguments)
return true
} finally {
setTop(top)
}
}
/**
* Returns empty [KOptional] if function does not exist
*/
@ -279,12 +303,13 @@ class LuaThread private constructor(
}
}
fun eval(chunk: String, name: String = "eval") {
fun eval(chunk: String, name: String = "eval"): JsonElement {
val top = stackTop
try {
load(chunk, name)
call()
call(numResults = 1)
return getJson() ?: JsonNull.INSTANCE
} finally {
setTop(top)
}

View File

@ -1,63 +0,0 @@
package ru.dbotthepony.kstarbound.lua
import org.apache.logging.log4j.LogManager
import org.classdump.luna.ByteString
import org.classdump.luna.Conversions
import org.classdump.luna.LuaRuntimeException
import org.classdump.luna.runtime.AbstractFunctionAnyArg
import org.classdump.luna.runtime.Dispatch
import org.classdump.luna.runtime.ExecutionContext
class PrintFunction(val env: Any) : AbstractFunctionAnyArg() {
private inner class State(arguments: Array<out Any>) {
private val resolved = ArrayList<ByteString>(arguments.size)
private var tostring: Any? = null
private val machine = StateMachine()
init {
machine.add { Dispatch.index(it, env, "tostring") }
machine.add {
tostring = it.returnBuffer.get0()
if (tostring == null) {
throw LuaRuntimeException("global 'tostring' is nil (required by 'print')")
}
}
for (argument in arguments) {
machine.add { Dispatch.call(it, tostring, argument) }
machine.add {
val canonical = Conversions.canonicalRepresentationOf(it.returnBuffer.get0())
if (canonical is ByteString) {
resolved.add(canonical)
} else {
throw LuaRuntimeException("Illegal value returned by 'tostring' (required by 'print')")
}
}
}
machine.add {
LOGGER.info(resolved.joinToString("\t") { it.decode() })
it.returnBuffer.setTo()
}
}
fun run(context: ExecutionContext) {
machine.run(context, this@PrintFunction, this)
}
}
override fun resume(context: ExecutionContext, suspendedState: Any) {
(suspendedState as State).run(context)
}
override fun invoke(context: ExecutionContext, args: Array<out Any>) {
State(args).run(context)
}
companion object {
private val LOGGER = LogManager.getLogger()
}
}

View File

@ -1,103 +0,0 @@
package ru.dbotthepony.kstarbound.lua
import org.classdump.luna.LuaRuntimeException
import org.classdump.luna.Table
import org.classdump.luna.runtime.Dispatch
import org.classdump.luna.runtime.ExecutionContext
import org.classdump.luna.runtime.Resumable
import org.classdump.luna.runtime.UnresolvedControlThrowable
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.math.vector.Vector2i
import java.util.function.Supplier
class StateMachine : Resumable {
private val states = ArrayDeque<(ExecutionContext) -> Unit>()
fun add(state: (ExecutionContext) -> Unit): StateMachine {
states.add(state)
return this
}
fun mergeInto(other: StateMachine): StateMachine {
states.addAll(other.states)
return other
}
fun mergeFrom(other: StateMachine): StateMachine {
other.mergeInto(this)
return this
}
override fun resume(context: ExecutionContext, suspendedState: Any) {
run(context, this, this)
}
fun run(context: ExecutionContext): Boolean = run(context, this, this)
fun run(context: ExecutionContext, resumable: Resumable, suspendState: Any): Boolean {
if (states.isEmpty())
return false
while (states.isNotEmpty()) {
val next = states.removeFirst()
try {
next.invoke(context)
} catch (err: UnresolvedControlThrowable) {
throw err.resolve(resumable, suspendState)
}
}
return true
}
fun index(table: Table, vararg indexes: Any): Supplier<Any> {
var value: KOptional<Any> = KOptional.empty()
for (index in indexes) {
add {
if (!value.isPresent) {
value = KOptional.ofNullable(Dispatch.index(it, table, index))
}
}
}
return Supplier {
if (!value.isPresent) {
throw LuaRuntimeException("Unable to locate value in table using following keys: ${indexes.joinToString()}")
}
}
}
fun optionalIndex(table: Table, vararg indexes: Any): Supplier<KOptional<Any>> {
var value: KOptional<Any> = KOptional.empty()
for (index in indexes) {
add {
if (!value.isPresent) {
value = KOptional.ofNullable(Dispatch.index(it, table, index))
}
}
}
return Supplier { value }
}
fun loadVector2i(table: Table): Supplier<Vector2i> {
val x = index(table, 1, "x")
val y = index(table, 2, "y")
var value = KOptional.empty<Vector2i>()
add {
val ix = x.get()
val iy = y.get()
if (ix !is Number) throw LuaRuntimeException("Invalid 'x' value for vector: ${x.get()}")
if (iy !is Number) throw LuaRuntimeException("Invalid 'y' value for vector: ${x.get()}")
value = KOptional(Vector2i(ix.toInt(), iy.toInt()))
}
return Supplier { value.value }
}
}

View File

@ -16,7 +16,7 @@ import ru.dbotthepony.kstarbound.world.entities.AnchorNetworkState
import ru.dbotthepony.kstarbound.world.physics.Poly
import kotlin.math.PI
class MovementControllerBindings(val self: ActorMovementController) {
class MovementControllerBindings(val self: ActorMovementController, lua: LuaThread) {
private fun mass(args: LuaThread.ArgStack): Int {
args.lua.push(self.mass)
return 1
@ -303,7 +303,7 @@ class MovementControllerBindings(val self: ActorMovementController) {
return 0
}
fun init(lua: LuaThread) {
init {
lua.pushTable()
lua.dup()
lua.storeGlobal("mcontroller")

View File

@ -4,7 +4,6 @@ import com.google.gson.JsonNull
import com.google.gson.JsonObject
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import org.apache.logging.log4j.LogManager
import org.classdump.luna.LuaRuntimeException
import ru.dbotthepony.kstarbound.Globals
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Registry
@ -36,13 +35,11 @@ private fun <T : Any> lookup(registry: Registry<T>, args: LuaThread.ArgStack): R
private fun <T : Any> lookupStrict(registry: Registry<T>, args: LuaThread.ArgStack): Registry.Entry<T> {
return when (val type = args.peek()) {
LuaType.NUMBER -> {
val key = args.nextInt()
registry[key] ?: throw LuaRuntimeException("No such ${registry.name}: $key")
registry.getOrThrow(args.nextInt())
}
LuaType.STRING -> {
val key = args.nextString()
registry[key] ?: throw LuaRuntimeException("No such ${registry.name}: $key")
registry.getOrThrow(args.nextString())
}
else -> throw IllegalArgumentException("Invalid registry key type: $type")
@ -51,8 +48,7 @@ private fun <T : Any> lookupStrict(registry: Registry<T>, args: LuaThread.ArgSta
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")
val value = registry.getOrThrow(args.nextString())
args.lua.push(value.json)
return@Fn 1
}
@ -252,7 +248,7 @@ private fun createItem(args: LuaThread.ArgStack): Int {
val seed = args.nextOptionalLong()
if (desc.name !in ItemRegistry) {
throw LuaRuntimeException("No such item ${desc.name}")
throw NoSuchElementException("No such item ${desc.name}")
} else {
val (_, params) = desc.buildConfig(level, seed)
@ -348,7 +344,7 @@ private fun loadVersionedJson(args: LuaThread.ArgStack): Int {
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")
val fn = Registries.jsonFunctions[name] ?: throw NoSuchElementException("No such function $name")
args.lua.push(fn.value.evaluate(value))
return 1
}
@ -357,7 +353,7 @@ 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")
val fn = Registries.json2Functions[name] ?: throw NoSuchElementException("No such function $name")
args.lua.push(fn.value.evaluate(value, value2))
return 1
}
@ -365,7 +361,7 @@ private fun evalFunction2(args: LuaThread.ArgStack): Int {
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")
val sprite = ref.sprite ?: throw NoSuchElementException("No such image or sprite $ref")
args.lua.pushTable(2)
args.lua.setTableValue(1, sprite.width)
args.lua.setTableValue(2, sprite.height)
@ -374,7 +370,7 @@ private fun imageSize(args: LuaThread.ArgStack): Int {
private fun imageSpaces(args: LuaThread.ArgStack): Int {
val name = args.nextString()
val image = Image.get(name) ?: throw LuaRuntimeException("No such image $name")
val image = Image.get(name) ?: throw NoSuchElementException("No such image $name")
val pixelOffset = args.nextVector2i()
val fillFactor = args.nextDouble()
@ -394,7 +390,7 @@ private fun imageSpaces(args: LuaThread.ArgStack): Int {
private fun nonEmptyRegion(args: LuaThread.ArgStack): Int {
val name = args.nextString()
val image = Image.get(name) ?: throw LuaRuntimeException("No such image $name")
val image = Image.get(name) ?: throw NoSuchElementException("No such image $name")
args.lua.push(image.nonEmptyRegion)
return 1
}

View File

@ -1,7 +1,6 @@
package ru.dbotthepony.kstarbound.lua.bindings
import org.apache.logging.log4j.LogManager
import org.classdump.luna.LuaRuntimeException
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Starbound
@ -18,7 +17,6 @@ import ru.dbotthepony.kstarbound.lua.nextAABBi
import ru.dbotthepony.kstarbound.lua.nextVector2d
import ru.dbotthepony.kstarbound.lua.nextVector2i
import ru.dbotthepony.kstarbound.lua.push
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture
import ru.dbotthepony.kstarbound.lua.userdata.push
import ru.dbotthepony.kstarbound.server.world.ServerWorld
@ -239,10 +237,7 @@ private fun players(self: ServerWorld, args: LuaThread.ArgStack): Int {
private fun setSkyTime(self: ServerWorld, args: LuaThread.ArgStack): Int {
val time = args.nextDouble()
if (time < 0.0)
throw LuaRuntimeException("Negative time? $time")
require(time >= 0.0) { "Negative time? $time" }
self.sky.time = time
return 0
}
@ -301,7 +296,7 @@ private fun setDungeonGravity(self: ServerWorld, args: LuaThread.ArgStack): Int
} else if (peek == LuaType.TABLE) {
self.setDungeonGravity(id, args.nextVector2d())
} else {
throw LuaRuntimeException("Illegal gravity argument to setDungeonGravity: $peek")
throw IllegalArgumentException("Illegal gravity argument to setDungeonGravity: $peek")
}
return 0
@ -336,7 +331,7 @@ private fun enqueuePlacement(self: ServerWorld, args: LuaThread.ArgStack): Int {
for (v in distributions) {
// original engine treats distributions table as if it was originating from biome json files
val unprepared = Starbound.gson.fromJson(toJsonFromLua(v), BiomePlaceablesDefinition.DistributionItem::class.java)
val unprepared = Starbound.gson.fromJson(v, BiomePlaceablesDefinition.DistributionItem::class.java)
val prepared = unprepared.create(BiomeDefinition.CreationParams(hueShift = 0.0, random = args.lua.random))
items.add(prepared)
}

View File

@ -21,7 +21,6 @@ import ru.dbotthepony.kstarbound.json.mergeJson
import ru.dbotthepony.kstarbound.lua.LuaHandle
import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.LuaType
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.util.valueOf
private fun replaceBehaviorTag(parameter: NodeParameterValue, treeParameters: Map<String, NodeParameterValue>): NodeParameterValue {
@ -246,7 +245,7 @@ private fun createBehaviorTree(args: LuaThread.ArgStack): Int {
} else {
mergedParams = Starbound.gson.fromJsonFast(base.json.deepCopy().also {
it as JsonObject
it["parameters"] = mergeJson(it["parameters"] ?: JsonNull.INSTANCE, toJsonFromLua(parameters))
it["parameters"] = mergeJson(it["parameters"] ?: JsonNull.INSTANCE, parameters)
}, BehaviorDefinition::class.java)
}
} else {

View File

@ -5,8 +5,6 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.future.await
import kotlinx.coroutines.launch
import org.apache.logging.log4j.LogManager
import org.classdump.luna.ByteString
import ru.dbotthepony.kstarbound.lua.tableMapOf
import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
@ -99,7 +97,10 @@ class LegacyWireProcessor(val world: ServerWorld) {
if (newState != node.state) {
try {
node.state = newState
entity.lua.invokeGlobal("onInputNodeChange", entity.lua.tableMapOf(NODE_KEY to i.toLong(), LEVEL_KEY to newState))
entity.lua.pushTable(hashSize = 2)
entity.lua.setTableValue("node", i)
entity.lua.setTableValue("level", newState)
entity.lua.invokeGlobal("onInputNodeChange", 1)
} catch (err: Throwable) {
LOGGER.error("Exception while updating wire state of $entity at ${entity.tilePosition} (input node index $i)", err)
}
@ -159,8 +160,6 @@ class LegacyWireProcessor(val world: ServerWorld) {
}
companion object {
val NODE_KEY: ByteString = ByteString.of("node")
val LEVEL_KEY: ByteString = ByteString.of("level")
private val LOGGER = LogManager.getLogger()
}
}

View File

@ -215,6 +215,8 @@ class ServerWorld private constructor(
it.client.enqueueWarp(WarpAlias.Return)
}
callUninitOnEntities()
if (!uncleanShutdown) {
saveMetadata()
storage.commit()

View File

@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableCollection
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import it.unimi.dsi.fastutil.bytes.ByteConsumer
import org.classdump.luna.ByteString
import ru.dbotthepony.kommons.util.IStruct2d
import ru.dbotthepony.kommons.util.IStruct2f
import ru.dbotthepony.kommons.util.IStruct2i
@ -76,7 +75,6 @@ fun staticRandom32FromList(values: Iterable<Any?>): Int {
for (value in values) {
when (value) {
is String -> digest.update(value.toByteArray())
is ByteString -> digest.update(value.bytes)
is Byte -> digest.update(value)
is Boolean -> digest.update(if (value) 1 else 0)
is Short -> toBytes(digest::update, value)
@ -134,7 +132,6 @@ fun staticRandom64FromList(values: Iterable<Any?>): Long {
for (value in values) {
when (value) {
is String -> digest.update(value.toByteArray())
is ByteString -> digest.update(value.bytes)
is Byte -> digest.update(value)
is Boolean -> digest.update(if (value) 1 else 0)
is Short -> toBytes(digest::update, value)

View File

@ -566,6 +566,16 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
}
protected fun callUninitOnEntities() {
entityList.forEach {
try {
it.uninit(this)
} catch (err: Throwable) {
LOGGER.error("Exception while calling uninit on $it", err)
}
}
}
open var centralStructure: WorldStructure = WorldStructure()
protected set

View File

@ -175,10 +175,21 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
protected open fun onJoinWorld(world: World<*, *>) { }
/**
* Called upon being removed from [world], regardless if entity is local or not
* Called upon being removed from [world], regardless if entity is local or not,
* but **not** when world is being unloaded from memory ([uninit] should be used in latter case)
*/
protected open fun onRemove(world: World<*, *>, reason: RemovalReason) { }
/**
* Called upon being removed from [world], regardless if entity is local or not,
* including when world is being unloaded from memory
*
* When being removed from world, called right after [onRemove]
*
* Should be used to free native data structures, or remove data from global structures
*/
open fun uninit(world: World<*, *>) { }
val networkGroup = MasterElement(NetworkedGroup())
abstract fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean)
@ -286,6 +297,12 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
LOGGER.error("Exception while removing $this from $world", err)
}
try {
uninit(world)
} catch (err: Throwable) {
LOGGER.error("Exception while calling uninit on $this from $world", err)
}
spatialEntry?.remove()
spatialEntry = null
innerWorld = null

View File

@ -2,23 +2,15 @@ package ru.dbotthepony.kstarbound.world.entities
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import org.classdump.luna.ByteString
import org.classdump.luna.Table
import ru.dbotthepony.kommons.util.IStruct2d
import ru.dbotthepony.kstarbound.defs.DamageNotification
import ru.dbotthepony.kstarbound.defs.DamageSource
import com.google.gson.JsonNull
import com.google.gson.JsonPrimitive
import ru.dbotthepony.kstarbound.defs.Drawable
import ru.dbotthepony.kstarbound.defs.InteractAction
import ru.dbotthepony.kstarbound.defs.InteractRequest
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.tableMapOf
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.lua.setTableValue
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
import ru.dbotthepony.kstarbound.network.packets.HitRequestPacket
import ru.dbotthepony.kstarbound.world.Direction
import ru.dbotthepony.kstarbound.world.World
@ -35,8 +27,6 @@ abstract class ActorEntity : DynamicEntity(), InteractiveEntity, ScriptedEntity
// ticked manually in final classes, to ensure proper order of operations
val effects = EffectEmitter(this)
abstract val lua: LuaEnvironment
override fun move(delta: Double) {
statusController.applyMovementControls()
super.move(delta)
@ -66,6 +56,11 @@ abstract class ActorEntity : DynamicEntity(), InteractiveEntity, ScriptedEntity
statusController.init()
}
override fun uninit(world: World<*, *>) {
super.uninit(world)
statusController.uninit()
}
override fun tick(delta: Double) {
super.tick(delta)
@ -114,31 +109,34 @@ abstract class ActorEntity : DynamicEntity(), InteractiveEntity, ScriptedEntity
}
override fun callScript(fnName: String, arguments: JsonArray): JsonElement {
//require(isLocal) { "Calling script on remote entity" }
//return lua.invokeGlobal(fnName, *arguments)
TODO()
require(isLocal) { "Calling script on remote entity" }
return super.callScript(fnName, arguments)
}
override fun evalScript(code: String): Array<Any?> {
override fun evalScript(code: String): JsonElement {
require(isLocal) { "Calling script on remote entity" }
return lua.eval(code)
return super.evalScript(code)
}
override fun interact(request: InteractRequest): InteractAction {
val result = lua.invokeGlobal("interact", lua.tableMapOf(
"sourceId" to request.source,
"sourcePosition" to lua.from(request.sourcePos)
))
val result = lua.invokeGlobal("interact", 1, {
pushTable(hashSize = 2)
if (result.isEmpty() || result[0] == null)
setTableValue("sourcePosition", request.sourcePos)
setTableValue("sourceId", request.source)
1
}, { getJson() ?: JsonNull.INSTANCE })
if (result.isEmpty || result.value.isJsonNull)
return InteractAction.NONE
val value = result[0]
val value = result.value
if (value is ByteString)
return InteractAction(value.decode(), entityID)
if (value is JsonPrimitive)
return InteractAction(value.asString, entityID)
value as Table
return InteractAction((value[1L] as ByteString).decode(), entityID, toJsonFromLua(value[2L]))
value as JsonArray
return InteractAction(value[1].asString, entityID, if (value.size() >= 2) value[2] else JsonNull.INSTANCE)
}
}

View File

@ -21,14 +21,14 @@ abstract class DynamicEntity() : AbstractEntity() {
abstract val movement: MovementController
/**
* Called in multiple threads
* Called in multiple threads, when [isLocal] is true
*/
protected open fun move(delta: Double) {
movement.move(delta)
}
/**
* Called in multiple threads
* Called in multiple threads, when [isLocal] is false
*/
protected open fun moveRemote(delta: Double) {
movement.tickRemote(delta)

View File

@ -8,11 +8,8 @@ import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import org.classdump.luna.ByteString
import org.classdump.luna.Table
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kstarbound.io.map
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.kommons.util.setValue
@ -26,8 +23,6 @@ import ru.dbotthepony.kstarbound.defs.DamageSource
import ru.dbotthepony.kstarbound.defs.EntityDamageTeam
import ru.dbotthepony.kstarbound.defs.EntityType
import ru.dbotthepony.kstarbound.defs.HitType
import ru.dbotthepony.kstarbound.defs.InteractAction
import ru.dbotthepony.kstarbound.defs.InteractRequest
import ru.dbotthepony.kstarbound.defs.JumpProfile
import ru.dbotthepony.kstarbound.defs.PhysicsForceRegion
import ru.dbotthepony.kstarbound.defs.actor.StatModifier
@ -38,21 +33,17 @@ import ru.dbotthepony.kstarbound.defs.monster.MonsterVariant
import ru.dbotthepony.kstarbound.fromJsonFast
import ru.dbotthepony.kstarbound.io.DoubleValueCodec
import ru.dbotthepony.kstarbound.io.FloatValueCodec
import ru.dbotthepony.kstarbound.io.map
import ru.dbotthepony.kstarbound.io.nullable
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.LuaMessageHandlerComponent
import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent
import ru.dbotthepony.kstarbound.lua.bindings.MovementControllerBindings
import ru.dbotthepony.kstarbound.lua.bindings.provideAnimatorBindings
import ru.dbotthepony.kstarbound.lua.bindings.provideConfigBindings
import ru.dbotthepony.kstarbound.lua.bindings.provideConfigBinding
import ru.dbotthepony.kstarbound.lua.bindings.provideEntityBindings
import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.tableMapOf
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.lua.userdata.BehaviorState
import ru.dbotthepony.kstarbound.lua.userdata.provideBehaviorBindings
import ru.dbotthepony.kstarbound.math.AABB
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
@ -70,9 +61,9 @@ import ru.dbotthepony.kstarbound.util.random.MWCRandom
import ru.dbotthepony.kstarbound.util.random.random
import ru.dbotthepony.kstarbound.world.Direction
import ru.dbotthepony.kstarbound.world.World
import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
import ru.dbotthepony.kstarbound.world.physics.Poly
import java.io.DataOutputStream
import kotlin.properties.Delegates
class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorEntity() {
override val type: EntityType
@ -82,16 +73,17 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
team.accept(EntityDamageTeam(variant.commonParameters.damageTeamType, variant.commonParameters.damageTeam))
}
override val lua = LuaEnvironment()
val luaUpdate = LuaUpdateComponent(lua)
val luaMovement = MovementControllerBindings(movement)
val luaMessages = LuaMessageHandlerComponent(lua) { toString() }
private var scriptStorage = JsonObject()
override var lua by Delegates.notNull<LuaThread>()
private set
var luaUpdate by Delegates.notNull<LuaUpdateComponent>()
private set
var luaMovement by Delegates.notNull<MovementControllerBindings>()
private set
var luaMessages by Delegates.notNull<LuaMessageHandlerComponent>()
private set
val animator = Animator(variant.animationConfig)
init {
lua.globals["storage"] = lua.newTable()
}
override fun move(delta: Double) {
luaMovement.apply()
super.move(delta)
@ -169,7 +161,7 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
this.uniqueID.value = deserialized.uniqueId
this.team.value = deserialized.team
this.lua.globals["storage"] = lua.from(deserialized.scriptStorage)
scriptStorage = deserialized.scriptStorage as? JsonObject ?: JsonObject()
}
override fun serialize(data: JsonObject) {
@ -188,7 +180,10 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
uniqueID.value,
team.value,
effectEmitter = effects.serialize(),
scriptStorage = toJsonFromLua(lua.globals["storage"]),
scriptStorage = run {
lua.loadGlobal("storage")
lua.popJson() ?: JsonObject()
},
)
for ((k, v) in Starbound.gson.toJsonTree(moreData).asJsonObject.entrySet()) {
@ -263,24 +258,41 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
monsterLevel = monsterLevel ?: world.template.threatLevel
if (!isRemote) {
if (isLocal) {
val healthMultiplier = (variant.commonParameters.healthLevelFunction.value?.evaluate(monsterLevel!!) ?: 1.0) * variant.commonParameters.healthMultiplier
statusController.setPersistentEffects("innate", listOf(Either.left(StatModifier("maxHealth", healthMultiplier, StatModifierType.BASE_MULTIPLICATION))))
lua = LuaThread()
luaUpdate = LuaUpdateComponent(lua, this)
luaMovement = MovementControllerBindings(movement, lua)
luaMessages = LuaMessageHandlerComponent(lua) { toString() }
lua.push(scriptStorage)
lua.storeGlobal("storage")
// free up memory
scriptStorage = JsonObject()
provideEntityBindings(this, lua)
provideAnimatorBindings(animator, lua)
provideConfigBindings(lua) { key, default ->
key.find(variant.parameters) ?: default
provideConfigBinding(lua) { key ->
key.find(variant.parameters)
}
lua.attach(variant.commonParameters.scripts)
luaUpdate.stepCount = variant.commonParameters.initialScriptDelta
BehaviorState.provideBindings(lua)
provideBehaviorBindings(lua)
luaMovement.init(lua)
lua.init()
lua.initScripts()
}
}
override fun uninit(world: World<*, *>) {
super.uninit(world)
if (isLocal) {
lua.close()
}
}
@ -320,12 +332,13 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
// TODO: hitDamageNotificationLimiter++ < Globals.npcs.hitDamageNotificationLimit maybe?
if (totalDamage > 0.0) {
lua.invokeGlobal("damage", lua.tableMapOf(
"sourceId" to damage.request.sourceEntityId,
"damage" to totalDamage,
"sourceDamage" to damage.request.damage,
"sourceKind" to damage.request.damageSourceKind
))
lua.pushTable(hashSize = 4)
lua.setTableValue("sourceId", damage.request.sourceEntityId)
lua.setTableValue("damage", totalDamage)
lua.setTableValue("sourceDamage", damage.request.damage)
lua.setTableValue("sourceKind", damage.request.damageSourceKind)
lua.invokeGlobal("damage", 1)
}
if (health <= 0.0) {
@ -336,8 +349,8 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
}
private val shouldDie: Boolean get() {
val result = lua.invokeGlobal("shouldDie")
return result.isNotEmpty() && result[0] is Boolean && result[0] as Boolean || health <= 0.0 //|| lua.errorState
val result = lua.invokeGlobal("shouldDie", 1, { 0 }, { getBoolean() == true }).orElse(false)
return result || health <= 0.0 //|| lua.errorState
}
override fun tickParallel(delta: Double) {

View File

@ -6,8 +6,6 @@ import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import org.classdump.luna.ByteString
import org.classdump.luna.Table
import ru.dbotthepony.kommons.collect.filterNotNull
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.util.getValue
@ -22,8 +20,6 @@ import ru.dbotthepony.kstarbound.defs.DamageSource
import ru.dbotthepony.kstarbound.defs.EntityDamageTeam
import ru.dbotthepony.kstarbound.defs.EntityType
import ru.dbotthepony.kstarbound.defs.HitType
import ru.dbotthepony.kstarbound.defs.InteractAction
import ru.dbotthepony.kstarbound.defs.InteractRequest
import ru.dbotthepony.kstarbound.defs.actor.HumanoidConfig
import ru.dbotthepony.kstarbound.defs.actor.HumanoidEmote
import ru.dbotthepony.kstarbound.defs.actor.HumanoidIdentity
@ -35,19 +31,13 @@ import ru.dbotthepony.kstarbound.io.BinaryStringCodec
import ru.dbotthepony.kstarbound.io.NullableBinaryStringCodec
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.mergeJson
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.LuaMessageHandlerComponent
import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent
import ru.dbotthepony.kstarbound.lua.bindings.MovementControllerBindings
import ru.dbotthepony.kstarbound.lua.bindings.provideConfigBindings
import ru.dbotthepony.kstarbound.lua.bindings.provideConfigBinding
import ru.dbotthepony.kstarbound.lua.bindings.provideEntityBindings
import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.tableMapOf
import ru.dbotthepony.kstarbound.lua.tableOf
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.lua.userdata.BehaviorState
import ru.dbotthepony.kstarbound.lua.userdata.provideBehaviorBindings
import ru.dbotthepony.kstarbound.math.AABB
import ru.dbotthepony.kstarbound.math.Interpolator
import ru.dbotthepony.kstarbound.math.vector.Vector2d
@ -61,12 +51,10 @@ import ru.dbotthepony.kstarbound.network.syncher.networkedJsonElement
import ru.dbotthepony.kstarbound.network.syncher.networkedString
import ru.dbotthepony.kstarbound.util.GameTimer
import ru.dbotthepony.kstarbound.util.random.staticRandomInt
import ru.dbotthepony.kstarbound.util.valueOf
import ru.dbotthepony.kstarbound.world.World
import ru.dbotthepony.kstarbound.world.entities.api.InteractiveEntity
import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
import java.io.DataOutputStream
import kotlin.math.absoluteValue
import kotlin.properties.Delegates
class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
override val type: EntityType
@ -155,14 +143,15 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
override var isPersistent: Boolean = variant.persistent
override var keepAlive: Boolean = variant.keepAlive
override val lua = LuaEnvironment()
val luaUpdate = LuaUpdateComponent(lua)
val luaMovement = MovementControllerBindings(movement)
val luaMessages = LuaMessageHandlerComponent(lua) { toString() }
init {
lua.globals["storage"] = lua.tableOf()
}
private var scriptStorage = JsonObject()
override var lua by Delegates.notNull<LuaThread>()
private set
var luaUpdate by Delegates.notNull<LuaUpdateComponent>()
private set
var luaMovement by Delegates.notNull<MovementControllerBindings>()
private set
var luaMessages by Delegates.notNull<LuaMessageHandlerComponent>()
private set
override fun handleMessage(connection: Int, message: String, arguments: JsonArray): JsonElement? {
return luaMessages.handle(message, connection == connectionID, arguments) ?: statusController.handleMessage(message, connection == connectionID, arguments)
@ -201,6 +190,7 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
val deathParticleBurst: String? = null,
val dropPools: ImmutableList<Registry.Ref<TreasurePoolDefinition>> = ImmutableList.of(),
val aggressive: Boolean = false,
val scriptStorage: JsonObject = JsonObject(),
)
override fun deserialize(data: JsonObject) {
@ -231,6 +221,8 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
dropPools.addAll(serialized.dropPools.filter { it.isPresent })
isAggressive = serialized.aggressive
blinkTimer.reset(0.0)
scriptStorage = serialized.scriptStorage
}
override fun serialize(data: JsonObject) {
@ -257,6 +249,10 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
deathParticleBurst = deathParticleBurst,
dropPools = dropPools.stream().filter { it.isPresent }.collect(ImmutableList.toImmutableList()),
aggressive = isAggressive,
scriptStorage = run {
lua.loadGlobal("storage")
lua.popJson() as? JsonObject ?: JsonObject()
}
)
mergeJson(data, Starbound.gson.toJsonTree(serialized))
@ -265,24 +261,41 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
override fun onJoinWorld(world: World<*, *>) {
super.onJoinWorld(world)
if (!isRemote) {
if (isLocal) {
for ((slot, item) in variant.items) {
setItem(slot, item)
}
lua = LuaThread()
luaUpdate = LuaUpdateComponent(lua, this)
luaMovement = MovementControllerBindings(movement, lua)
luaMessages = LuaMessageHandlerComponent(lua) { toString() }
lua.push(scriptStorage)
lua.storeGlobal("storage")
// free up memory
scriptStorage = JsonObject()
provideEntityBindings(this, lua)
provideConfigBindings(lua) { key, default ->
key.find(variant.scriptConfig) ?: default
provideConfigBinding(lua) { key ->
key.find(variant.scriptConfig)
}
BehaviorState.provideBindings(lua)
provideBehaviorBindings(lua)
luaUpdate.stepCount = variant.initialScriptDelta
lua.attach(variant.scripts)
luaMovement.init(lua)
lua.init()
lua.initScripts()
}
}
override fun uninit(world: World<*, *>) {
super.uninit(world)
if (isLocal) {
lua.close()
}
}
@ -319,12 +332,13 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
val totalDamage = notifications.sumOf { it.healthLost }
if (totalDamage > 0.0 && hitDamageNotificationLimiter++ < Globals.npcs.hitDamageNotificationLimit) {
lua.invokeGlobal("damage", lua.tableMapOf(
"sourceId" to damage.request.sourceEntityId,
"damage" to totalDamage,
"sourceDamage" to damage.request.damage,
"sourceKind" to damage.request.damageSourceKind
))
lua.pushTable(hashSize = 4)
lua.setTableValue("sourceId", damage.request.sourceEntityId)
lua.setTableValue("damage", totalDamage)
lua.setTableValue("sourceDamage", damage.request.damage)
lua.setTableValue("sourceKind", damage.request.damageSourceKind)
lua.invokeGlobal("damage", 1)
}
return notifications
@ -388,14 +402,14 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
override fun tick(delta: Double) {
super.tick(delta)
if (!isRemote) {
if (isDead || lua.errorState) {
if (isLocal) {
if (isDead) {
remove(RemovalReason.DYING)
return
} else {
val shouldDie = lua.invokeGlobal("shouldDie")
val shouldDie = lua.invokeGlobal("shouldDie", 1, { 0 }, { getBoolean() == true }).orElse(false)
if (shouldDie.isNotEmpty() && shouldDie[0] is Boolean && shouldDie[0] as Boolean) {
if (shouldDie) {
remove(RemovalReason.DYING)
return
}
@ -410,7 +424,8 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
override fun onRemove(world: World<*, *>, reason: RemovalReason) {
super.onRemove(world, reason)
lua.invokeGlobal("die")
if (isLocal)
lua.invokeGlobal("die", 0)
val dropPools by lazy { dropPools.stream().map { it.entry }.filterNotNull().toList() }

View File

@ -1,36 +1,25 @@
package ru.dbotthepony.kstarbound.world.entities
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import org.apache.logging.log4j.LogManager
import org.classdump.luna.Table
import org.classdump.luna.TableFactory
import ru.dbotthepony.kommons.collect.map
import ru.dbotthepony.kommons.collect.reduce
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.setTableValue
import ru.dbotthepony.kstarbound.lua.tableOf
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.math.AABB
import ru.dbotthepony.kstarbound.math.AABBi
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.util.CarriedExecutor
import ru.dbotthepony.kstarbound.util.supplyAsync
import ru.dbotthepony.kstarbound.world.World
import ru.dbotthepony.kstarbound.world.physics.CollisionType
import java.util.PriorityQueue
import java.util.*
import java.util.function.Supplier
import kotlin.math.PI
import kotlin.math.absoluteValue
import kotlin.math.min
import kotlin.math.roundToInt
import kotlin.math.sign
import kotlin.math.sqrt
@ -48,8 +37,6 @@ class PathFinder(val world: World<*, *>, val start: Vector2d, val goal: Vector2d
SWIM(false, "Swim"),
FLY(false, "Fly"),
LAND(false, "Land");
val luaName = jsonName.toByteString()!!
}
data class Edge(
@ -154,13 +141,6 @@ class PathFinder(val world: World<*, *>, val start: Vector2d, val goal: Vector2d
var totalCost: Double = 0.0
var parent: Edge? = null
fun toTable(tables: TableFactory): Table {
val table = tables.newTable(2, 0)
table[positionIndex] = tables.from(position)
table[velocityIndex] = tables.from(velocity)
return table
}
fun store(lua: LuaThread) {
lua.pushTable(hashSize = 2)
lua.setTableValue("position", position)
@ -721,8 +701,5 @@ class PathFinder(val world: World<*, *>, val start: Vector2d, val goal: Vector2d
private val LOGGER = LogManager.getLogger()
const val ARC_SIMULATION_FIDELTITY = 0.5
const val NODE_GRANULARITY = 1.0
private val positionIndex = "position".toByteString()!!
private val velocityIndex = "velocity".toByteString()!!
}
}

View File

@ -19,13 +19,8 @@ import ru.dbotthepony.kstarbound.json.jsonArrayOf
import ru.dbotthepony.kstarbound.json.putAll
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.LuaUpdateComponent
import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.tableOf
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.math.AABB
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.network.syncher.networkedFloat
@ -33,6 +28,7 @@ import ru.dbotthepony.kstarbound.world.World
import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
import java.io.DataInputStream
import java.io.DataOutputStream
import kotlin.properties.Delegates
class StagehandEntity(isRemote: Boolean = false) : AbstractEntity(), ScriptedEntity {
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readJsonElement().asJsonObject, true)
@ -69,12 +65,11 @@ class StagehandEntity(isRemote: Boolean = false) : AbstractEntity(), ScriptedEnt
var isScripted = false
private set
val lua = LuaEnvironment()
val luaUpdate = LuaUpdateComponent(lua)
override var lua by Delegates.notNull<LuaThread>()
private set
init {
lua.globals["storage"] = lua.tableOf()
}
var luaUpdate by Delegates.notNull<LuaUpdateComponent>()
private set
override fun deserialize(data: JsonObject) {
super.deserialize(data)
@ -98,22 +93,32 @@ class StagehandEntity(isRemote: Boolean = false) : AbstractEntity(), ScriptedEnt
}
boundingBox = broadcastArea ?: AABB.withSide(Vector2d.ZERO, 5.0)
if (isScripted && isLocal) {
lua.attach(config["scripts"].asJsonArray.map { AssetPath(it.asString) })
luaUpdate.stepCount = config.get("scriptDelta", 5.0)
if ("scriptStorage" in config) {
lua.globals["storage"] = lua.from(config["scriptStorage"])
}
}
}
override fun onJoinWorld(world: World<*, *>) {
super.onJoinWorld(world)
if (isLocal && isScripted) {
lua.init()
lua = LuaThread()
luaUpdate = LuaUpdateComponent(lua, this)
lua.attach(config["scripts"].asJsonArray.map { AssetPath(it.asString) })
luaUpdate.stepCount = config.get("scriptDelta", 5.0)
if ("scriptStorage" in config) {
lua.push(config["scriptStorage"])
lua.storeGlobal("storage")
} else {
lua.pushTable()
lua.storeGlobal("storage")
}
lua.initScripts()
}
}
override fun uninit(world: World<*, *>) {
if (isLocal && isScripted) {
lua.close()
}
}
@ -128,8 +133,10 @@ class StagehandEntity(isRemote: Boolean = false) : AbstractEntity(), ScriptedEnt
else
data.remove("uniqueId")
if (isScripted)
data["scriptStorage"] = toJsonFromLua(lua.globals["storage"])
if (isScripted) {
lua.loadGlobal("storage")
data["scriptStorage"] = lua.popJson()
}
}
override fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) {
@ -146,10 +153,12 @@ class StagehandEntity(isRemote: Boolean = false) : AbstractEntity(), ScriptedEnt
get() = "Technical entity responsible for doing cool stuff"
override fun callScript(fnName: String, arguments: JsonArray): JsonElement {
TODO()
require(isLocal) { "Calling script on remote entity" }
return super.callScript(fnName, arguments)
}
override fun evalScript(code: String): Array<Any?> {
return lua.eval(code)
override fun evalScript(code: String): JsonElement {
require(isLocal) { "Calling script on remote entity" }
return super.evalScript(code)
}
}

View File

@ -9,12 +9,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
import it.unimi.dsi.fastutil.ints.IntArrayList
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
import org.apache.logging.log4j.LogManager
import org.classdump.luna.ByteString
import org.classdump.luna.LuaRuntimeException
import org.classdump.luna.Table
import ru.dbotthepony.kommons.collect.ListenableMap
import ru.dbotthepony.kommons.collect.collect
import ru.dbotthepony.kommons.collect.map
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.io.readKOptional
@ -42,19 +37,14 @@ import ru.dbotthepony.kstarbound.io.StreamCodec
import ru.dbotthepony.kstarbound.io.nullable
import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.LuaMessageHandlerComponent
import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent
import ru.dbotthepony.kstarbound.lua.bindings.MovementControllerBindings
import ru.dbotthepony.kstarbound.lua.bindings.createConfigBinding
import ru.dbotthepony.kstarbound.lua.bindings.provideAnimatorBindings
import ru.dbotthepony.kstarbound.lua.bindings.provideConfigBindings
import ru.dbotthepony.kstarbound.lua.bindings.provideConfigBinding
import ru.dbotthepony.kstarbound.lua.bindings.provideEntityBindings
import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.iterator
import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.math.Interpolator
import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket
import ru.dbotthepony.kstarbound.network.syncher.NetworkedDynamicGroup
@ -74,7 +64,7 @@ import ru.dbotthepony.kstarbound.world.entities.api.StatusEffectEntity
import java.io.DataInputStream
import java.io.DataOutputStream
import java.util.*
import java.util.stream.Collectors
import kotlin.properties.Delegates
// this is unnatural to have this class separated, but since it contains
// lots of internal state, it would be nice to have it encapsulated;
@ -131,26 +121,39 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
networkGroup.add(statNetworkGroup)
}
val lua = LuaEnvironment()
val luaUpdate = LuaUpdateComponent(lua)
val luaMovement = MovementControllerBindings(entity.movement)
val luaMessages = LuaMessageHandlerComponent(lua) { toString() }
var lua by Delegates.notNull<LuaThread>()
private set
var luaUpdate by Delegates.notNull<LuaUpdateComponent>()
private set
var luaMovement by Delegates.notNull<MovementControllerBindings>()
private set
var luaMessages by Delegates.notNull<LuaMessageHandlerComponent>()
private set
private val animator: EffectAnimator?
private val animatorID: Int?
fun uninit() {
if (entity.isLocal) {
lua.close()
}
}
fun init() {
if (!entity.isRemote) {
if (entity.isLocal) {
lua = LuaThread()
luaUpdate = LuaUpdateComponent(lua, this)
luaMovement = MovementControllerBindings(entity.movement, lua)
luaMessages = LuaMessageHandlerComponent(lua) { toString() }
luaUpdate.stepCount = config.primaryScriptDelta
lua.attach(config.primaryScriptSources)
// provideStatusControllerBindings(this, lua) // provided through provideEntityBindings
provideEntityBindings(entity, lua)
luaMovement.init(lua)
// TODO: Once we have brand new object-oriented Lua API, expose proper entity bindings here
// TODO: Expose world bindings
lua.init()
lua.initScripts()
}
}
@ -180,14 +183,17 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
fun recentDamageDealt(since: Long = 0L) = recentDamageDealt.query(since)
fun experienceDamage(damage: DamageData): List<DamageNotification> {
val results = lua.invokeGlobal("applyDamageRequest", lua.from(Starbound.gson.toJsonTree(damage)))
val results = lua.invokeGlobal("applyDamageRequest", 1, {
push(Starbound.gson.toJsonTree(damage))
1
}, { getJson() as? JsonArray }).flatMap { KOptional.ofNullable(it) }
if (results.isNotEmpty() && results[0] is Table) {
if (results.isPresent) {
val parsed = ArrayList<DamageNotification>()
for ((_, v) in (results[0] as Table)) {
for (v in results.value) {
try {
parsed.add(Starbound.gson.fromJsonFast(toJsonFromLua(v), DamageNotification::class.java))
parsed.add(Starbound.gson.fromJsonFast(v, DamageNotification::class.java))
} catch (err: Throwable) {
LOGGER.error("Exception while parsing results returned by applyDamageRequest (primary scripts: ${config.primaryScriptSources})", err)
}
@ -457,10 +463,14 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
var parentDirectives: String = ""
val modifierGroups = IntArrayList()
val lua = LuaEnvironment()
val luaMessages = LuaMessageHandlerComponent(lua) { "Unique effect" }
val luaMovement = MovementControllerBindings(entity.movement)
val luaUpdate = LuaUpdateComponent(lua)
var lua by Delegates.notNull<LuaThread>()
private set
var luaUpdate by Delegates.notNull<LuaUpdateComponent>()
private set
var luaMovement by Delegates.notNull<MovementControllerBindings>()
private set
var luaMessages by Delegates.notNull<LuaMessageHandlerComponent>()
private set
val metadata = UniqueEffectNetworkedValues()
val metadataNetworkID = uniqueEffectMetadata.add(metadata)
@ -485,14 +495,18 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
animatorNetworkID = null
}
if (!entity.isRemote && effect.value.scripts.isNotEmpty()) {
if (entity.isLocal && effect.value.scripts.isNotEmpty()) {
lua = LuaThread()
luaMessages = LuaMessageHandlerComponent(lua) { "Unique effect" }
luaMovement = MovementControllerBindings(entity.movement, lua)
luaUpdate = LuaUpdateComponent(lua, this)
// unique effects shouldn't be initialized on remotes in first place
// but whatever
lua.attach(effect.value.scripts)
// provideStatusControllerBindings(this@StatusController, lua) // provided through provideEntityBindings
provideEntityBindings(entity, lua)
luaMovement.init(lua)
provideEffectBindings()
if (animator != null) {
@ -502,84 +516,103 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
provideAnimatorBindings(animator.animator, lua)
}
provideConfigBindings(lua) { path, default ->
path.find(effect.json) ?: default
provideConfigBinding(lua) { path ->
path.find(effect.json)
}
lua.init()
lua.initScripts()
}
}
private fun duration(args: LuaThread.ArgStack): Int {
args.lua.push(metadata.duration)
return 1
}
private fun modifyDuration(args: LuaThread.ArgStack): Int {
val duration = args.nextDouble()
require(duration.isFinite()) { "Infinite duration provided" }
require(!duration.isNaN()) { "NaN duration provided" }
if (!isPersistent)
metadata.duration += duration
return 0
}
private fun expire(args: LuaThread.ArgStack): Int {
if (!isPersistent)
metadata.duration = 0.0
return 0
}
private fun sourceEntity(args: LuaThread.ArgStack): Int {
args.lua.push(metadata.sourceEntity?.toLong() ?: entity.entityID.toLong())
return 1
}
private fun setParentDirectives(args: LuaThread.ArgStack): Int {
parentDirectives = args.nextString().sbIntern()
return 0
}
private fun addStatModifierGroup(args: LuaThread.ArgStack): Int {
val modifiers = args.readTableValues {
Starbound.gson.fromJsonFast(getJson(it)!!, StatModifier::class.java)
}
val id = statModifiers.add(ArrayList(modifiers))
modifierGroups.add(id)
args.lua.push(id.toLong())
return 1
}
private fun setStatModifierGroup(args: LuaThread.ArgStack): Int {
val id = args.nextInt()
if (id !in modifierGroups)
throw IllegalStateException("$id stat modifier group does not belong to this effect")
val modifiers = args.readTableValues {
Starbound.gson.fromJsonFast(getJson(it)!!, StatModifier::class.java)
}
statModifiers[id] = ArrayList(modifiers)
return 0
}
private fun removeStatModifierGroup(args: LuaThread.ArgStack): Int {
val id = args.nextInt()
if (modifierGroups.rem(id)) {
statModifiers.remove(id)
} else {
throw IllegalStateException("$id stat modifier group does not belong to this effect")
}
return 0
}
private fun provideEffectBindings() {
val callbacks = lua.newTable()
lua.globals["effect"] = callbacks
lua.pushTable()
lua.dup()
lua.storeGlobal("effect")
callbacks["duration"] = luaFunction {
returnBuffer.setTo(metadata.duration)
}
lua.setTableValue("duration", ::duration)
lua.setTableValue("modifyDuration", ::modifyDuration)
lua.setTableValue("expire", ::expire)
lua.setTableValue("sourceEntity", ::sourceEntity)
lua.setTableValue("setParentDirectives", ::setParentDirectives)
lua.setTableValue("addStatModifierGroup", ::addStatModifierGroup)
lua.setTableValue("setStatModifierGroup", ::setStatModifierGroup)
lua.setTableValue("removeStatModifierGroup", ::removeStatModifierGroup)
callbacks["modifyDuration"] = luaFunction { duration: Number ->
val value = duration.toDouble()
check(value.isFinite()) { "Infinite duration provided" }
check(!value.isNaN()) { "NaN duration provided" }
lua.push("getParameter")
createConfigBinding(lua) { path -> path.find(effect.json) }
lua.setTableValue()
if (!isPersistent) {
metadata.duration += value
}
}
callbacks["expire"] = luaFunction {
if (!isPersistent) {
metadata.duration = 0.0
}
}
callbacks["sourceEntity"] = luaFunction {
returnBuffer.setTo(metadata.sourceEntity ?: entity.entityID)
}
callbacks["setParentDirectives"] = luaFunction { directives: ByteString ->
parentDirectives = directives.decode().sbIntern()
}
callbacks["getParameter"] = createConfigBinding { path, default ->
path.find(effect.json) ?: default
}
callbacks["addStatModifierGroup"] = luaFunction { modifiers: Table ->
val actual = modifiers
.iterator()
.map { (_, v) -> Starbound.gson.fromJsonFast(toJsonFromLua(v), StatModifier::class.java) }
.collect(Collectors.toCollection(::ArrayList))
val id = statModifiers.add(actual)
modifierGroups.add(id)
returnBuffer.setTo(id)
}
callbacks["setStatModifierGroup"] = luaFunction { groupID: Number, modifiers: Table ->
val id = groupID.toInt()
if (id !in modifierGroups)
throw LuaRuntimeException("$groupID stat modifier group does not belong to this effect")
val actual = modifiers
.iterator()
.map { (_, v) -> Starbound.gson.fromJsonFast(toJsonFromLua(v), StatModifier::class.java) }
.collect(Collectors.toCollection(::ArrayList))
statModifiers[id] = actual
}
callbacks["removeStatModifierGroup"] = luaFunction { groupID: Number ->
val id = groupID.toInt()
if (modifierGroups.rem(id)) {
statModifiers.remove(id)
} else {
throw LuaRuntimeException("$groupID stat modifier group does not belong to this effect")
}
}
lua.pop()
}
fun promoteToPersistent() {
@ -591,7 +624,7 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
// remove an ephemeral effect.
// Original engine only updates the duration (setting it to null),
// but for proper promotion we also need to reset cause entity, otherwise
// but for proper promotion we also need to reset "cause" entity, otherwise
// persistent effect will be sourced to whoever put ephemeral effect
metadata.duration = 0.0
metadata.sourceEntity = null
@ -599,7 +632,9 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
}
fun remove() {
lua.invokeGlobal("onExpire")
if (entity.isLocal)
lua.invokeGlobal("onExpire", 0)
uniqueEffectMetadata.remove(metadataNetworkID)
for (group in modifierGroups) {

View File

@ -2,13 +2,29 @@ package ru.dbotthepony.kstarbound.world.entities.api
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import ru.dbotthepony.kstarbound.lua.LuaThread
interface ScriptedEntity {
val lua: LuaThread
// Call a script function directly with the given arguments, should return
// nothing only on failure.
fun callScript(fnName: String, arguments: JsonArray): JsonElement
fun callScript(fnName: String, arguments: JsonArray): JsonElement {
return lua.invokeGlobal(fnName, 1, {
ensureExtraCapacity(arguments.size() + 4)
for (argument in arguments) {
push(argument)
}
arguments.size()
}, { getJson() ?: JsonNull.INSTANCE }).orElse(JsonNull.INSTANCE)
}
// Execute the given code directly in the underlying context, return nothing
// on failure.
fun evalScript(code: String): Array<Any?>
fun evalScript(code: String): JsonElement {
return lua.eval(code)
}
}

View File

@ -16,7 +16,7 @@ import ru.dbotthepony.kstarbound.defs.actor.HumanoidEmote
import ru.dbotthepony.kstarbound.defs.actor.player.PlayerGamemode
import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.math.AABB
import ru.dbotthepony.kstarbound.math.Interpolator
import ru.dbotthepony.kstarbound.math.vector.Vector2d
@ -101,7 +101,7 @@ class PlayerEntity() : HumanoidActorEntity() {
val newChatMessage = networkGroup.upstream.add(networkedEventCounter())
var emote by networkGroup.upstream.add(networkedEnumExtraStupid(HumanoidEmote.IDLE))
override val lua: LuaEnvironment
override val lua: LuaThread
get() = TODO("Not yet implemented")
override val emoteCooldownTimer: GameTimer = GameTimer()
override val danceTimer: GameTimer?

View File

@ -12,29 +12,20 @@ import com.google.gson.TypeAdapter
import com.google.gson.reflect.TypeToken
import it.unimi.dsi.fastutil.objects.ObjectArrayList
import org.apache.logging.log4j.LogManager
import org.classdump.luna.ByteString
import org.classdump.luna.Table
import ru.dbotthepony.kommons.gson.JsonArrayCollector
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.guava.immutableSet
import ru.dbotthepony.kstarbound.io.StreamCodec
import ru.dbotthepony.kstarbound.io.map
import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kstarbound.math.AABB
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.kommons.util.setValue
import ru.dbotthepony.kstarbound.Globals
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.DamageNotification
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.defs.DamageSource
import ru.dbotthepony.kstarbound.defs.EntityType
import ru.dbotthepony.kstarbound.defs.HitType
@ -42,15 +33,30 @@ import ru.dbotthepony.kstarbound.defs.InteractAction
import ru.dbotthepony.kstarbound.defs.InteractRequest
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation
import ru.dbotthepony.kstarbound.defs.`object`.ObjectType
import ru.dbotthepony.kstarbound.defs.quest.QuestArcDescriptor
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
import ru.dbotthepony.kstarbound.io.RGBACodec
import ru.dbotthepony.kstarbound.io.StreamCodec
import ru.dbotthepony.kstarbound.io.Vector2iCodec
import ru.dbotthepony.kstarbound.io.map
import ru.dbotthepony.kstarbound.json.JsonPath
import ru.dbotthepony.kstarbound.json.jsonArrayOf
import ru.dbotthepony.kstarbound.json.mergeJson
import ru.dbotthepony.kstarbound.json.stream
import ru.dbotthepony.kstarbound.json.writeJsonElement
import ru.dbotthepony.kstarbound.lua.LuaMessageHandlerComponent
import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent
import ru.dbotthepony.kstarbound.lua.bindings.provideAnimatorBindings
import ru.dbotthepony.kstarbound.lua.bindings.provideEntityBindings
import ru.dbotthepony.kstarbound.lua.setTableValue
import ru.dbotthepony.kstarbound.math.AABB
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
import ru.dbotthepony.kstarbound.network.syncher.InternedStringCodec
import ru.dbotthepony.kstarbound.network.syncher.JsonElementCodec
import ru.dbotthepony.kstarbound.network.syncher.NetworkedList
@ -64,21 +70,6 @@ import ru.dbotthepony.kstarbound.network.syncher.networkedFloat
import ru.dbotthepony.kstarbound.network.syncher.networkedJsonElement
import ru.dbotthepony.kstarbound.network.syncher.networkedPointer
import ru.dbotthepony.kstarbound.network.syncher.networkedString
import ru.dbotthepony.kstarbound.json.writeJsonElement
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.LuaMessageHandlerComponent
import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent
import ru.dbotthepony.kstarbound.lua.bindings.provideAnimatorBindings
import ru.dbotthepony.kstarbound.lua.bindings.provideEntityBindings
import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.tableMapOf
import ru.dbotthepony.kstarbound.lua.tableOf
import ru.dbotthepony.kstarbound.lua.toJson
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket
import ru.dbotthepony.kstarbound.server.world.LegacyWireProcessor
import ru.dbotthepony.kstarbound.util.ManualLazy
import ru.dbotthepony.kstarbound.util.asStringOrNull
import ru.dbotthepony.kstarbound.util.random.random
@ -92,19 +83,20 @@ import ru.dbotthepony.kstarbound.world.entities.api.InteractiveEntity
import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
import ru.dbotthepony.kstarbound.world.physics.Poly
import java.io.DataOutputStream
import java.util.Collections
import java.util.HashMap
import java.util.*
import java.util.random.RandomGenerator
import kotlin.math.min
import kotlin.properties.Delegates
open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntity(), ScriptedEntity, InteractiveEntity {
private var scriptStorage = JsonObject()
override fun deserialize(data: JsonObject) {
super.deserialize(data)
direction = data.get("direction", directions) { Direction.LEFT }
orientationIndex = data.get("orientationIndex", -1).toLong()
isInteractive = data.get("interactive", false)
lua.globals["storage"] = lua.from(data.get("scriptStorage") { JsonObject() })
scriptStorage = data.get("scriptStorage") { JsonObject() }
loadParameters(data.get("parameters") { JsonObject() })
@ -159,10 +151,11 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
data["inputWireNodes"] = inputNodes.stream().map { it.serialize() }.collect(JsonArrayCollector)
data["outputWireNodes"] = outputNodes.stream().map { it.serialize() }.collect(JsonArrayCollector)
val scriptStorage = lua.globals["storage"]
lua.loadGlobal("storage")
val scriptStorage = lua.popJson()
if (scriptStorage != null && scriptStorage is Table) {
data["scriptStorage"] = scriptStorage.toJson(true)
if (scriptStorage != null && scriptStorage is JsonObject) {
data["scriptStorage"] = scriptStorage
}
uniqueID.get()?.let {
@ -313,13 +306,13 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
fun addConnection(connection: WireConnection) {
if (connection !in connectionsInternal) {
connectionsInternal.add(connection.copy())
lua.invokeGlobal("onNodeConnectionChange")
lua.invokeGlobal("onNodeConnectionChange", 0)
}
}
fun removeConnection(connection: WireConnection) {
if (connectionsInternal.remove(connection)) {
lua.invokeGlobal("onNodeConnectionChange")
lua.invokeGlobal("onNodeConnectionChange", 0)
}
}
@ -332,20 +325,20 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
val any = otherConnections?.getOrNull(it.index)?.connectionsInternal?.removeIf { it.entityLocation == tilePosition && it.index == index }
if (any == true) {
otherEntity!!.lua.invokeGlobal("onNodeConnectionChange")
otherEntity!!.lua.invokeGlobal("onNodeConnectionChange", 0)
}
any == true
}
if (any)
lua.invokeGlobal("onNodeConnectionChange")
lua.invokeGlobal("onNodeConnectionChange", 0)
}
}
fun removeConnectionsTo(pos: Vector2i) {
if (connectionsInternal.removeIf { it.entityLocation == pos }) {
lua.invokeGlobal("onNodeConnectionChange")
lua.invokeGlobal("onNodeConnectionChange", 0)
}
}
@ -431,13 +424,12 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
networkGroup.upstream.add(animator.networkGroup)
}
val lua = LuaEnvironment()
val luaUpdate = LuaUpdateComponent(lua)
val luaMessageHandler = LuaMessageHandlerComponent(lua) { toString() }
init {
lua.globals["storage"] = lua.newTable()
}
final override var lua by Delegates.notNull<LuaThread>()
private set
var luaUpdate by Delegates.notNull<LuaUpdateComponent>()
private set
var luaMessageHandler by Delegates.notNull<LuaMessageHandlerComponent>()
private set
val unbreakable by ManualLazy {
lookupProperty("unbreakable") { JsonPrimitive(false) }.asBoolean
@ -553,14 +545,23 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
animator.setPartTag(k, "partImage", v.asString)
}
lua = LuaThread()
lua.push(scriptStorage)
lua.storeGlobal("storage")
luaUpdate = LuaUpdateComponent(lua, this)
luaMessageHandler = LuaMessageHandlerComponent(lua) { toString() }
updateMaterialSpacesNow()
provideEntityBindings(this, lua)
provideAnimatorBindings(animator, lua)
lua.attach(config.value.scripts)
luaUpdate.stepCount = lookupProperty(JsonPath("scriptDelta")) { JsonPrimitive(5) }.asDouble
lua.init()
lua.initScripts()
}
// free up memory
scriptStorage = JsonObject()
// as original code puts it:
// Don't animate the initial state when first spawned IF you're dumb, which by default
// you would be, and don't know how to use transition and static states properly. Someday
@ -593,21 +594,25 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
override fun interact(request: InteractRequest): InteractAction {
val diff = world.geometry.diff(request.sourcePos, position)
val result = lua.invokeGlobal("onInteraction", lua.newTable().apply {
this["source"] = lua.tableOf(diff.x, diff.y)
this["sourceId"] = request.source
})
val result = lua.invokeGlobal("onInteraction", 1, {
pushTable(hashSize = 2)
if (result.isNotEmpty()) {
if (result[0] == null)
setTableValue("source", diff)
setTableValue("sourceId", request.source)
1
}, { getJson() ?: JsonNull.INSTANCE })
if (result.isPresent) {
if (result.value.isJsonNull)
return InteractAction.NONE
else if (result[0] is ByteString) {
val decoded = (result[0] as ByteString).decode()
else if (result.value is JsonPrimitive) {
val decoded = result.value.asString
return InteractAction(InteractAction.Type.entries.firstOrNull { it.jsonName == decoded } ?: throw NoSuchElementException("Unknown interaction action type $decoded!"), entityID)
} else {
val data = result[0] as Table
val decoded = (data[1L] as ByteString).decode()
return InteractAction(InteractAction.Type.entries.firstOrNull { it.jsonName == decoded } ?: throw NoSuchElementException("Unknown interaction action type $decoded!"), entityID, toJsonFromLua(data[2L]))
val data = result.value as JsonArray
val decoded = data[0].asString
return InteractAction(InteractAction.Type.entries.firstOrNull { it.jsonName == decoded } ?: throw NoSuchElementException("Unknown interaction action type $decoded!"), entityID, if (data.size() >= 2) data[1] else JsonNull.INSTANCE)
}
}
@ -670,7 +675,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
// break connection if other entity got removed
if (connection.otherEntity?.removalReason?.removal == true) {
itr.remove()
lua.invokeGlobal("onNodeConnectionChange")
lua.invokeGlobal("onNodeConnectionChange", 0)
continue
}
@ -686,7 +691,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
// break connection if we point at invalid node
if (otherNode == null) {
itr.remove()
lua.invokeGlobal("onNodeConnectionChange")
lua.invokeGlobal("onNodeConnectionChange", 0)
} else {
newState = newState!! || otherNode.state
}
@ -701,7 +706,10 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
// otherwise, keep current node state
if (newState != null && node.state != newState) {
node.state = newState
lua.invokeGlobal("onInputNodeChange", lua.tableMapOf(LegacyWireProcessor.NODE_KEY to i.toLong(), LegacyWireProcessor.LEVEL_KEY to newState))
lua.pushTable(hashSize = 2)
lua.setTableValue("node", i)
lua.setTableValue("level", newState)
lua.invokeGlobal("onInputNodeChange", 1)
}
}
}
@ -734,7 +742,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
override fun onRemove(world: World<*, *>, reason: RemovalReason) {
super.onRemove(world, reason)
val doSmash = config.value.smashable && (health <= 0.0 || lookupProperty("smashOnBreak") { JsonPrimitive(config.value.smashOnBreak) }.asBoolean)
val doSmash = reason.dying && config.value.smashable && (health <= 0.0 || lookupProperty("smashOnBreak") { JsonPrimitive(config.value.smashOnBreak) }.asBoolean)
fun spawnRandomItems(poolName: String, optionsName: String, seedName: String): Boolean {
val dropPool = lookupProperty(poolName) { JsonPrimitive("") }.asString
@ -774,7 +782,8 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
}
if (!isRemote && reason.dying) {
lua.invokeGlobal("die", health <= 0.0)
lua.push(health <= 0.0)
lua.invokeGlobal("die", 1)
try {
if (doSmash) {
@ -791,7 +800,8 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
}
parameters.remove("owner")
parameters["scriptStorage"] = (lua.globals["storage"] as Table).toJson(true)
lua.loadGlobal("storage")
parameters["scriptStorage"] = lua.popJson()
}
val entity = ItemDropEntity(ItemDescriptor(config.key, 1L, parameters))
@ -805,6 +815,14 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
}
}
override fun uninit(world: World<*, *>) {
super.uninit(world)
if (!isRemote) {
lua.close()
}
}
override fun damageTileEntity(damageSpaces: List<Vector2i>, source: Vector2d, damage: TileDamage): Boolean {
if (unbreakable)
return false
@ -864,15 +882,6 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
return luaMessageHandler.handle(message, connectionID == connection, arguments)
}
override fun callScript(fnName: String, arguments: JsonArray): JsonElement {
//return lua.invokeGlobal(fnName, *arguments)
TODO()
}
override fun evalScript(code: String): Array<Any?> {
return lua.eval(code)
}
init {
isInteractive = !interactAction.isJsonNull
}