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.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.apache.logging.log4j.LogManager 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.kstarbound.math.AABBTypeAdapter
import ru.dbotthepony.kommons.gson.EitherTypeAdapter import ru.dbotthepony.kommons.gson.EitherTypeAdapter
import ru.dbotthepony.kommons.gson.KOptionalTypeAdapter 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() private val luaScriptCache = Caffeine.newBuilder()
.maximumSize(LUA_SCRIPT_CACHE_SIZE) .maximumSize(LUA_SCRIPT_CACHE_SIZE)
.expireAfterAccess(Duration.ofHours(1L)) .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.JsonReader
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
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.contains
import ru.dbotthepony.kommons.gson.get import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.value 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.mergeJson
import ru.dbotthepony.kstarbound.json.readJsonElement import ru.dbotthepony.kstarbound.json.readJsonElement
import ru.dbotthepony.kstarbound.json.writeJsonElement import ru.dbotthepony.kstarbound.json.writeJsonElement
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.LuaThread import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.LuaType 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.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
import java.util.function.Supplier import java.util.function.Supplier
@ -190,74 +177,6 @@ fun ItemDescriptor(args: LuaThread.ArgStack): ItemDescriptor {
return ItemDescriptor(args.lua, args.position) 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 { fun ItemDescriptor(stream: DataInputStream): ItemDescriptor {
val name = stream.readInternedString() val name = stream.readInternedString()
val count = stream.readVarLong() val count = stream.readVarLong()
@ -324,18 +243,6 @@ data class ItemDescriptor(
return !isEmpty 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 { fun build(level: Double? = null, seed: Long? = null, random: RandomGenerator? = null): ItemStack {
try { try {
val (jConfig, jParameters) = buildConfig(level, seed, random) 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.JsonPrimitive
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
import com.google.gson.annotations.JsonAdapter import com.google.gson.annotations.JsonAdapter
import com.google.gson.internal.bind.TypeAdapters
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.objects.ObjectArrayList 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.consumeNull
import ru.dbotthepony.kommons.gson.contains import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.get 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.io.writeVarLong
import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kstarbound.math.vector.Vector2f
import ru.dbotthepony.kstarbound.Globals import ru.dbotthepony.kstarbound.Globals
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.Drawable import ru.dbotthepony.kstarbound.defs.Drawable
import ru.dbotthepony.kstarbound.defs.actor.PersistentStatusEffect 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.mergeJson
import ru.dbotthepony.kstarbound.json.stream import ru.dbotthepony.kstarbound.json.stream
import ru.dbotthepony.kstarbound.json.writeJsonElement import ru.dbotthepony.kstarbound.json.writeJsonElement
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.LuaThread 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.network.syncher.NetworkedElement
import ru.dbotthepony.kstarbound.util.AssetPathStack import ru.dbotthepony.kstarbound.util.AssetPathStack
import ru.dbotthepony.kstarbound.util.ManualLazy 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) 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 { 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 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 != updated) {
if (descriptor.name == updated.name) { if (descriptor.name == updated.name) {
@ -374,14 +368,6 @@ open class ItemStack(val entry: ItemRegistry.Entry, val config: JsonObject, para
return createDescriptor().toJson() return createDescriptor().toJson()
} }
fun toTable(allocator: TableFactory): Table? {
if (isEmpty) {
return null
}
return createDescriptor().toTable(allocator)
}
class Adapter(gson: Gson) : TypeAdapter<ItemStack>() { class Adapter(gson: Gson) : TypeAdapter<ItemStack>() {
override fun write(out: JsonWriter, value: ItemStack?) { override fun write(out: JsonWriter, value: ItemStack?) {
val json = value?.toJson() val json = value?.toJson()

View File

@ -1,22 +1,7 @@
package ru.dbotthepony.kstarbound.lua 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.math.RGBAColor
import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kstarbound.math.AABB
import ru.dbotthepony.kommons.util.IStruct2d import ru.dbotthepony.kommons.util.IStruct2d
import ru.dbotthepony.kommons.util.IStruct2f import ru.dbotthepony.kommons.util.IStruct2f
import ru.dbotthepony.kommons.util.IStruct2i 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.IStruct4f
import ru.dbotthepony.kommons.util.IStruct4i import ru.dbotthepony.kommons.util.IStruct4i
import ru.dbotthepony.kstarbound.Registry 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.Vector2d
import ru.dbotthepony.kstarbound.math.vector.Vector2f import ru.dbotthepony.kstarbound.math.vector.Vector2f
import ru.dbotthepony.kstarbound.math.vector.Vector2i 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 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 // TODO: error reporting when argument was provided, but it is malformed
// currently, invalid data gets silently discarded, and treated as "no value" aka null // currently, invalid data gets silently discarded, and treated as "no value" aka null
fun LuaThread.getPoly(stackIndex: Int = -1): Poly? { 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 package ru.dbotthepony.kstarbound.lua
import com.google.gson.JsonArray
import com.google.gson.JsonElement import com.google.gson.JsonElement
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.util.ActionPacer import ru.dbotthepony.kstarbound.util.ActionPacer
import ru.dbotthepony.kstarbound.util.sbIntern 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 val handlers = HashMap<String, LuaHandle>()
private fun setHandler(args: LuaThread.ArgStack): Int { private fun setHandler(args: LuaThread.ArgStack): Int {
@ -37,7 +38,7 @@ class LuaMessageHandlerComponent(lua: LuaThread, val nameProvider: () -> String)
return handlers[name] 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 handler = lookupHandler(message) ?: return null
val top = lua.stackTop 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 { companion object {
val LOGGER = LogManager.getLogger() 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.LuaFuture
import ru.dbotthepony.kstarbound.lua.userdata.LuaPathFinder import ru.dbotthepony.kstarbound.lua.userdata.LuaPathFinder
import ru.dbotthepony.kstarbound.util.random.random import ru.dbotthepony.kstarbound.util.random.random
import java.io.Closeable
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
import java.util.random.RandomGenerator import java.util.random.RandomGenerator
import kotlin.properties.Delegates import kotlin.properties.Delegates
class LuaSharedState(val handlesThread: LuaThread) { class LuaSharedState(val handlesThread: LuaThread) : Closeable {
private val pendingFree = ConcurrentLinkedQueue<Int>() private val pendingFree = ConcurrentLinkedQueue<Int>()
private val freeHandles = IntAVLTreeSet() private val freeHandles = IntAVLTreeSet()
private var nextHandle = 0 private var nextHandle = 0
@ -21,6 +22,15 @@ class LuaSharedState(val handlesThread: LuaThread) {
var commonHandles by Delegates.notNull<CommonHandleRegistry>() var commonHandles by Delegates.notNull<CommonHandleRegistry>()
private set private set
var isValid = true
private set
override fun close() {
if (!isValid) return
isValid = false
namedHandles.clear()
}
fun initializeHandles(mainThread: LuaThread) { fun initializeHandles(mainThread: LuaThread) {
val future = LuaFuture.initializeHandle(mainThread) val future = LuaFuture.initializeHandle(mainThread)
val pathFinder = LuaPathFinder.initializeHandle(mainThread) val pathFinder = LuaPathFinder.initializeHandle(mainThread)
@ -32,6 +42,7 @@ class LuaSharedState(val handlesThread: LuaThread) {
} }
fun freeHandle(handle: Int, key: Any?) { fun freeHandle(handle: Int, key: Any?) {
if (!isValid) return
pendingFree.add(handle) pendingFree.add(handle)
if (key != null) { if (key != null) {
@ -40,6 +51,7 @@ class LuaSharedState(val handlesThread: LuaThread) {
} }
fun cleanup() { fun cleanup() {
check(isValid) { "Shared state is no longer valid" }
if (handlesInUse == 0) return if (handlesInUse == 0) return
var handle = pendingFree.poll() var handle = pendingFree.poll()
@ -55,6 +67,7 @@ class LuaSharedState(val handlesThread: LuaThread) {
} }
fun allocateHandle(name: Any?): LuaHandle { fun allocateHandle(name: Any?): LuaHandle {
check(isValid) { "Shared state is no longer valid" }
require(name == null || name !in namedHandles) { "Named handle '$name' already exists" } require(name == null || name !in namedHandles) { "Named handle '$name' already exists" }
handlesInUse++ handlesInUse++
@ -80,10 +93,12 @@ class LuaSharedState(val handlesThread: LuaThread) {
} }
fun getNamedHandle(key: Any): LuaHandle { fun getNamedHandle(key: Any): LuaHandle {
check(isValid) { "Shared state is no longer valid" }
return namedHandles[key] ?: throw NoSuchElementException("No such handle: $key") return namedHandles[key] ?: throw NoSuchElementException("No such handle: $key")
} }
fun findNamedHandle(key: Any): LuaHandle? { fun findNamedHandle(key: Any): LuaHandle? {
check(isValid) { "Shared state is no longer valid" }
return namedHandles[key] return namedHandles[key]
} }
} }

View File

@ -140,7 +140,10 @@ class LuaThread private constructor(
} }
override fun close() { override fun close() {
this.cleanable?.clean() if (cleanable != null) {
cleanable!!.clean()
sharedState.close()
}
} }
val stackTop: Int get() { 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 * 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 val top = stackTop
try { try {
load(chunk, name) load(chunk, name)
call() call(numResults = 1)
return getJson() ?: JsonNull.INSTANCE
} finally { } finally {
setTop(top) 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 ru.dbotthepony.kstarbound.world.physics.Poly
import kotlin.math.PI import kotlin.math.PI
class MovementControllerBindings(val self: ActorMovementController) { class MovementControllerBindings(val self: ActorMovementController, lua: LuaThread) {
private fun mass(args: LuaThread.ArgStack): Int { private fun mass(args: LuaThread.ArgStack): Int {
args.lua.push(self.mass) args.lua.push(self.mass)
return 1 return 1
@ -303,7 +303,7 @@ class MovementControllerBindings(val self: ActorMovementController) {
return 0 return 0
} }
fun init(lua: LuaThread) { init {
lua.pushTable() lua.pushTable()
lua.dup() lua.dup()
lua.storeGlobal("mcontroller") lua.storeGlobal("mcontroller")

View File

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

View File

@ -1,7 +1,6 @@
package ru.dbotthepony.kstarbound.lua.bindings package ru.dbotthepony.kstarbound.lua.bindings
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.classdump.luna.LuaRuntimeException
import ru.dbotthepony.kommons.util.KOptional import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.Registries import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Starbound 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.nextVector2d
import ru.dbotthepony.kstarbound.lua.nextVector2i import ru.dbotthepony.kstarbound.lua.nextVector2i
import ru.dbotthepony.kstarbound.lua.push 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.LuaFuture
import ru.dbotthepony.kstarbound.lua.userdata.push import ru.dbotthepony.kstarbound.lua.userdata.push
import ru.dbotthepony.kstarbound.server.world.ServerWorld 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 { private fun setSkyTime(self: ServerWorld, args: LuaThread.ArgStack): Int {
val time = args.nextDouble() val time = args.nextDouble()
require(time >= 0.0) { "Negative time? $time" }
if (time < 0.0)
throw LuaRuntimeException("Negative time? $time")
self.sky.time = time self.sky.time = time
return 0 return 0
} }
@ -301,7 +296,7 @@ private fun setDungeonGravity(self: ServerWorld, args: LuaThread.ArgStack): Int
} else if (peek == LuaType.TABLE) { } else if (peek == LuaType.TABLE) {
self.setDungeonGravity(id, args.nextVector2d()) self.setDungeonGravity(id, args.nextVector2d())
} else { } else {
throw LuaRuntimeException("Illegal gravity argument to setDungeonGravity: $peek") throw IllegalArgumentException("Illegal gravity argument to setDungeonGravity: $peek")
} }
return 0 return 0
@ -336,7 +331,7 @@ private fun enqueuePlacement(self: ServerWorld, args: LuaThread.ArgStack): Int {
for (v in distributions) { for (v in distributions) {
// original engine treats distributions table as if it was originating from biome json files // 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)) val prepared = unprepared.create(BiomeDefinition.CreationParams(hueShift = 0.0, random = args.lua.random))
items.add(prepared) 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.LuaHandle
import ru.dbotthepony.kstarbound.lua.LuaThread import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.LuaType import ru.dbotthepony.kstarbound.lua.LuaType
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.util.valueOf import ru.dbotthepony.kstarbound.util.valueOf
private fun replaceBehaviorTag(parameter: NodeParameterValue, treeParameters: Map<String, NodeParameterValue>): NodeParameterValue { private fun replaceBehaviorTag(parameter: NodeParameterValue, treeParameters: Map<String, NodeParameterValue>): NodeParameterValue {
@ -246,7 +245,7 @@ private fun createBehaviorTree(args: LuaThread.ArgStack): Int {
} else { } else {
mergedParams = Starbound.gson.fromJsonFast(base.json.deepCopy().also { mergedParams = Starbound.gson.fromJsonFast(base.json.deepCopy().also {
it as JsonObject it as JsonObject
it["parameters"] = mergeJson(it["parameters"] ?: JsonNull.INSTANCE, toJsonFromLua(parameters)) it["parameters"] = mergeJson(it["parameters"] ?: JsonNull.INSTANCE, parameters)
}, BehaviorDefinition::class.java) }, BehaviorDefinition::class.java)
} }
} else { } else {

View File

@ -5,8 +5,6 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.future.await import kotlinx.coroutines.future.await
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.apache.logging.log4j.LogManager 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.math.vector.Vector2i
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
@ -99,7 +97,10 @@ class LegacyWireProcessor(val world: ServerWorld) {
if (newState != node.state) { if (newState != node.state) {
try { try {
node.state = newState 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) { } catch (err: Throwable) {
LOGGER.error("Exception while updating wire state of $entity at ${entity.tilePosition} (input node index $i)", err) 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 { companion object {
val NODE_KEY: ByteString = ByteString.of("node")
val LEVEL_KEY: ByteString = ByteString.of("level")
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
} }
} }

View File

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

View File

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

View File

@ -175,10 +175,21 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
protected open fun onJoinWorld(world: World<*, *>) { } 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) { } 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()) val networkGroup = MasterElement(NetworkedGroup())
abstract fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) 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) 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?.remove()
spatialEntry = null spatialEntry = null
innerWorld = null innerWorld = null

View File

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

View File

@ -8,11 +8,8 @@ import com.google.gson.JsonElement
import com.google.gson.JsonNull import com.google.gson.JsonNull
import com.google.gson.JsonObject import com.google.gson.JsonObject
import it.unimi.dsi.fastutil.objects.ObjectArraySet 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.get
import ru.dbotthepony.kommons.gson.set import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kstarbound.io.map
import ru.dbotthepony.kommons.util.Either import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kommons.util.getValue import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.kommons.util.setValue 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.EntityDamageTeam
import ru.dbotthepony.kstarbound.defs.EntityType import ru.dbotthepony.kstarbound.defs.EntityType
import ru.dbotthepony.kstarbound.defs.HitType 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.JumpProfile
import ru.dbotthepony.kstarbound.defs.PhysicsForceRegion import ru.dbotthepony.kstarbound.defs.PhysicsForceRegion
import ru.dbotthepony.kstarbound.defs.actor.StatModifier 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.fromJsonFast
import ru.dbotthepony.kstarbound.io.DoubleValueCodec import ru.dbotthepony.kstarbound.io.DoubleValueCodec
import ru.dbotthepony.kstarbound.io.FloatValueCodec import ru.dbotthepony.kstarbound.io.FloatValueCodec
import ru.dbotthepony.kstarbound.io.map
import ru.dbotthepony.kstarbound.io.nullable import ru.dbotthepony.kstarbound.io.nullable
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.LuaMessageHandlerComponent import ru.dbotthepony.kstarbound.lua.LuaMessageHandlerComponent
import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent
import ru.dbotthepony.kstarbound.lua.bindings.MovementControllerBindings import ru.dbotthepony.kstarbound.lua.bindings.MovementControllerBindings
import ru.dbotthepony.kstarbound.lua.bindings.provideAnimatorBindings 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.bindings.provideEntityBindings
import ru.dbotthepony.kstarbound.lua.from import ru.dbotthepony.kstarbound.lua.userdata.provideBehaviorBindings
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.math.AABB import ru.dbotthepony.kstarbound.math.AABB
import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.network.packets.DamageRequestPacket 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.util.random.random
import ru.dbotthepony.kstarbound.world.Direction import ru.dbotthepony.kstarbound.world.Direction
import ru.dbotthepony.kstarbound.world.World import ru.dbotthepony.kstarbound.world.World
import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
import ru.dbotthepony.kstarbound.world.physics.Poly import ru.dbotthepony.kstarbound.world.physics.Poly
import java.io.DataOutputStream import java.io.DataOutputStream
import kotlin.properties.Delegates
class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorEntity() { class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorEntity() {
override val type: EntityType 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)) team.accept(EntityDamageTeam(variant.commonParameters.damageTeamType, variant.commonParameters.damageTeam))
} }
override val lua = LuaEnvironment() private var scriptStorage = JsonObject()
val luaUpdate = LuaUpdateComponent(lua) override var lua by Delegates.notNull<LuaThread>()
val luaMovement = MovementControllerBindings(movement) private set
val luaMessages = LuaMessageHandlerComponent(lua) { toString() } 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) val animator = Animator(variant.animationConfig)
init {
lua.globals["storage"] = lua.newTable()
}
override fun move(delta: Double) { override fun move(delta: Double) {
luaMovement.apply() luaMovement.apply()
super.move(delta) super.move(delta)
@ -169,7 +161,7 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
this.uniqueID.value = deserialized.uniqueId this.uniqueID.value = deserialized.uniqueId
this.team.value = deserialized.team this.team.value = deserialized.team
this.lua.globals["storage"] = lua.from(deserialized.scriptStorage) scriptStorage = deserialized.scriptStorage as? JsonObject ?: JsonObject()
} }
override fun serialize(data: JsonObject) { override fun serialize(data: JsonObject) {
@ -188,7 +180,10 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
uniqueID.value, uniqueID.value,
team.value, team.value,
effectEmitter = effects.serialize(), 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()) { 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 monsterLevel = monsterLevel ?: world.template.threatLevel
if (!isRemote) { if (isLocal) {
val healthMultiplier = (variant.commonParameters.healthLevelFunction.value?.evaluate(monsterLevel!!) ?: 1.0) * variant.commonParameters.healthMultiplier 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)))) 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) provideEntityBindings(this, lua)
provideAnimatorBindings(animator, lua) provideAnimatorBindings(animator, lua)
provideConfigBindings(lua) { key, default -> provideConfigBinding(lua) { key ->
key.find(variant.parameters) ?: default key.find(variant.parameters)
} }
lua.attach(variant.commonParameters.scripts) lua.attach(variant.commonParameters.scripts)
luaUpdate.stepCount = variant.commonParameters.initialScriptDelta luaUpdate.stepCount = variant.commonParameters.initialScriptDelta
BehaviorState.provideBindings(lua) provideBehaviorBindings(lua)
luaMovement.init(lua) lua.initScripts()
lua.init() }
}
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? // TODO: hitDamageNotificationLimiter++ < Globals.npcs.hitDamageNotificationLimit maybe?
if (totalDamage > 0.0) { if (totalDamage > 0.0) {
lua.invokeGlobal("damage", lua.tableMapOf( lua.pushTable(hashSize = 4)
"sourceId" to damage.request.sourceEntityId, lua.setTableValue("sourceId", damage.request.sourceEntityId)
"damage" to totalDamage, lua.setTableValue("damage", totalDamage)
"sourceDamage" to damage.request.damage, lua.setTableValue("sourceDamage", damage.request.damage)
"sourceKind" to damage.request.damageSourceKind lua.setTableValue("sourceKind", damage.request.damageSourceKind)
))
lua.invokeGlobal("damage", 1)
} }
if (health <= 0.0) { if (health <= 0.0) {
@ -336,8 +349,8 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE
} }
private val shouldDie: Boolean get() { private val shouldDie: Boolean get() {
val result = lua.invokeGlobal("shouldDie") val result = lua.invokeGlobal("shouldDie", 1, { 0 }, { getBoolean() == true }).orElse(false)
return result.isNotEmpty() && result[0] is Boolean && result[0] as Boolean || health <= 0.0 //|| lua.errorState return result || health <= 0.0 //|| lua.errorState
} }
override fun tickParallel(delta: Double) { 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.JsonElement
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive 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.collect.filterNotNull
import ru.dbotthepony.kommons.gson.set import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.util.getValue 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.EntityDamageTeam
import ru.dbotthepony.kstarbound.defs.EntityType import ru.dbotthepony.kstarbound.defs.EntityType
import ru.dbotthepony.kstarbound.defs.HitType 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.HumanoidConfig
import ru.dbotthepony.kstarbound.defs.actor.HumanoidEmote import ru.dbotthepony.kstarbound.defs.actor.HumanoidEmote
import ru.dbotthepony.kstarbound.defs.actor.HumanoidIdentity 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.io.NullableBinaryStringCodec
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.mergeJson import ru.dbotthepony.kstarbound.json.mergeJson
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.LuaMessageHandlerComponent import ru.dbotthepony.kstarbound.lua.LuaMessageHandlerComponent
import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent
import ru.dbotthepony.kstarbound.lua.bindings.MovementControllerBindings 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.bindings.provideEntityBindings
import ru.dbotthepony.kstarbound.lua.from import ru.dbotthepony.kstarbound.lua.userdata.provideBehaviorBindings
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.math.AABB import ru.dbotthepony.kstarbound.math.AABB
import ru.dbotthepony.kstarbound.math.Interpolator import ru.dbotthepony.kstarbound.math.Interpolator
import ru.dbotthepony.kstarbound.math.vector.Vector2d 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.network.syncher.networkedString
import ru.dbotthepony.kstarbound.util.GameTimer import ru.dbotthepony.kstarbound.util.GameTimer
import ru.dbotthepony.kstarbound.util.random.staticRandomInt import ru.dbotthepony.kstarbound.util.random.staticRandomInt
import ru.dbotthepony.kstarbound.util.valueOf
import ru.dbotthepony.kstarbound.world.World 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 java.io.DataOutputStream
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.properties.Delegates
class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() { class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
override val type: EntityType override val type: EntityType
@ -155,14 +143,15 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
override var isPersistent: Boolean = variant.persistent override var isPersistent: Boolean = variant.persistent
override var keepAlive: Boolean = variant.keepAlive override var keepAlive: Boolean = variant.keepAlive
override val lua = LuaEnvironment() private var scriptStorage = JsonObject()
val luaUpdate = LuaUpdateComponent(lua) override var lua by Delegates.notNull<LuaThread>()
val luaMovement = MovementControllerBindings(movement) private set
val luaMessages = LuaMessageHandlerComponent(lua) { toString() } var luaUpdate by Delegates.notNull<LuaUpdateComponent>()
private set
init { var luaMovement by Delegates.notNull<MovementControllerBindings>()
lua.globals["storage"] = lua.tableOf() private set
} var luaMessages by Delegates.notNull<LuaMessageHandlerComponent>()
private set
override fun handleMessage(connection: Int, message: String, arguments: JsonArray): JsonElement? { override fun handleMessage(connection: Int, message: String, arguments: JsonArray): JsonElement? {
return luaMessages.handle(message, connection == connectionID, arguments) ?: statusController.handleMessage(message, connection == connectionID, arguments) 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 deathParticleBurst: String? = null,
val dropPools: ImmutableList<Registry.Ref<TreasurePoolDefinition>> = ImmutableList.of(), val dropPools: ImmutableList<Registry.Ref<TreasurePoolDefinition>> = ImmutableList.of(),
val aggressive: Boolean = false, val aggressive: Boolean = false,
val scriptStorage: JsonObject = JsonObject(),
) )
override fun deserialize(data: JsonObject) { override fun deserialize(data: JsonObject) {
@ -231,6 +221,8 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
dropPools.addAll(serialized.dropPools.filter { it.isPresent }) dropPools.addAll(serialized.dropPools.filter { it.isPresent })
isAggressive = serialized.aggressive isAggressive = serialized.aggressive
blinkTimer.reset(0.0) blinkTimer.reset(0.0)
scriptStorage = serialized.scriptStorage
} }
override fun serialize(data: JsonObject) { override fun serialize(data: JsonObject) {
@ -257,6 +249,10 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
deathParticleBurst = deathParticleBurst, deathParticleBurst = deathParticleBurst,
dropPools = dropPools.stream().filter { it.isPresent }.collect(ImmutableList.toImmutableList()), dropPools = dropPools.stream().filter { it.isPresent }.collect(ImmutableList.toImmutableList()),
aggressive = isAggressive, aggressive = isAggressive,
scriptStorage = run {
lua.loadGlobal("storage")
lua.popJson() as? JsonObject ?: JsonObject()
}
) )
mergeJson(data, Starbound.gson.toJsonTree(serialized)) mergeJson(data, Starbound.gson.toJsonTree(serialized))
@ -265,24 +261,41 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
override fun onJoinWorld(world: World<*, *>) { override fun onJoinWorld(world: World<*, *>) {
super.onJoinWorld(world) super.onJoinWorld(world)
if (!isRemote) { if (isLocal) {
for ((slot, item) in variant.items) { for ((slot, item) in variant.items) {
setItem(slot, item) 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) provideEntityBindings(this, lua)
provideConfigBindings(lua) { key, default -> provideConfigBinding(lua) { key ->
key.find(variant.scriptConfig) ?: default key.find(variant.scriptConfig)
} }
BehaviorState.provideBindings(lua) provideBehaviorBindings(lua)
luaUpdate.stepCount = variant.initialScriptDelta luaUpdate.stepCount = variant.initialScriptDelta
lua.attach(variant.scripts) lua.attach(variant.scripts)
luaMovement.init(lua) lua.initScripts()
lua.init() }
}
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 } val totalDamage = notifications.sumOf { it.healthLost }
if (totalDamage > 0.0 && hitDamageNotificationLimiter++ < Globals.npcs.hitDamageNotificationLimit) { if (totalDamage > 0.0 && hitDamageNotificationLimiter++ < Globals.npcs.hitDamageNotificationLimit) {
lua.invokeGlobal("damage", lua.tableMapOf( lua.pushTable(hashSize = 4)
"sourceId" to damage.request.sourceEntityId, lua.setTableValue("sourceId", damage.request.sourceEntityId)
"damage" to totalDamage, lua.setTableValue("damage", totalDamage)
"sourceDamage" to damage.request.damage, lua.setTableValue("sourceDamage", damage.request.damage)
"sourceKind" to damage.request.damageSourceKind lua.setTableValue("sourceKind", damage.request.damageSourceKind)
))
lua.invokeGlobal("damage", 1)
} }
return notifications return notifications
@ -388,14 +402,14 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
override fun tick(delta: Double) { override fun tick(delta: Double) {
super.tick(delta) super.tick(delta)
if (!isRemote) { if (isLocal) {
if (isDead || lua.errorState) { if (isDead) {
remove(RemovalReason.DYING) remove(RemovalReason.DYING)
return return
} else { } 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) remove(RemovalReason.DYING)
return return
} }
@ -410,7 +424,8 @@ class NPCEntity(val variant: NPCVariant) : HumanoidActorEntity() {
override fun onRemove(world: World<*, *>, reason: RemovalReason) { override fun onRemove(world: World<*, *>, reason: RemovalReason) {
super.onRemove(world, reason) super.onRemove(world, reason)
lua.invokeGlobal("die") if (isLocal)
lua.invokeGlobal("die", 0)
val dropPools by lazy { dropPools.stream().map { it.entry }.filterNotNull().toList() } val dropPools by lazy { dropPools.stream().map { it.entry }.filterNotNull().toList() }

View File

@ -1,36 +1,25 @@
package ru.dbotthepony.kstarbound.world.entities package ru.dbotthepony.kstarbound.world.entities
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import org.apache.logging.log4j.LogManager 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.map
import ru.dbotthepony.kommons.collect.reduce import ru.dbotthepony.kommons.collect.reduce
import ru.dbotthepony.kommons.util.KOptional import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.lua.LuaThread 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.setTableValue
import ru.dbotthepony.kstarbound.lua.tableOf
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.math.AABB import ru.dbotthepony.kstarbound.math.AABB
import ru.dbotthepony.kstarbound.math.AABBi import ru.dbotthepony.kstarbound.math.AABBi
import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.math.vector.Vector2i 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.World
import ru.dbotthepony.kstarbound.world.physics.CollisionType import ru.dbotthepony.kstarbound.world.physics.CollisionType
import java.util.PriorityQueue import java.util.*
import java.util.function.Supplier import java.util.function.Supplier
import kotlin.math.PI import kotlin.math.PI
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt
import kotlin.math.sign import kotlin.math.sign
import kotlin.math.sqrt import kotlin.math.sqrt
@ -48,8 +37,6 @@ class PathFinder(val world: World<*, *>, val start: Vector2d, val goal: Vector2d
SWIM(false, "Swim"), SWIM(false, "Swim"),
FLY(false, "Fly"), FLY(false, "Fly"),
LAND(false, "Land"); LAND(false, "Land");
val luaName = jsonName.toByteString()!!
} }
data class Edge( data class Edge(
@ -154,13 +141,6 @@ class PathFinder(val world: World<*, *>, val start: Vector2d, val goal: Vector2d
var totalCost: Double = 0.0 var totalCost: Double = 0.0
var parent: Edge? = null 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) { fun store(lua: LuaThread) {
lua.pushTable(hashSize = 2) lua.pushTable(hashSize = 2)
lua.setTableValue("position", position) lua.setTableValue("position", position)
@ -721,8 +701,5 @@ class PathFinder(val world: World<*, *>, val start: Vector2d, val goal: Vector2d
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
const val ARC_SIMULATION_FIDELTITY = 0.5 const val ARC_SIMULATION_FIDELTITY = 0.5
const val NODE_GRANULARITY = 1.0 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.putAll
import ru.dbotthepony.kstarbound.json.readJsonElement import ru.dbotthepony.kstarbound.json.readJsonElement
import ru.dbotthepony.kstarbound.json.writeJsonElement import ru.dbotthepony.kstarbound.json.writeJsonElement
import ru.dbotthepony.kstarbound.lua.LuaEnvironment import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent 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.AABB
import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.network.syncher.networkedFloat 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 ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
import kotlin.properties.Delegates
class StagehandEntity(isRemote: Boolean = false) : AbstractEntity(), ScriptedEntity { class StagehandEntity(isRemote: Boolean = false) : AbstractEntity(), ScriptedEntity {
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readJsonElement().asJsonObject, true) constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readJsonElement().asJsonObject, true)
@ -69,12 +65,11 @@ class StagehandEntity(isRemote: Boolean = false) : AbstractEntity(), ScriptedEnt
var isScripted = false var isScripted = false
private set private set
val lua = LuaEnvironment() override var lua by Delegates.notNull<LuaThread>()
val luaUpdate = LuaUpdateComponent(lua) private set
init { var luaUpdate by Delegates.notNull<LuaUpdateComponent>()
lua.globals["storage"] = lua.tableOf() private set
}
override fun deserialize(data: JsonObject) { override fun deserialize(data: JsonObject) {
super.deserialize(data) super.deserialize(data)
@ -98,22 +93,32 @@ class StagehandEntity(isRemote: Boolean = false) : AbstractEntity(), ScriptedEnt
} }
boundingBox = broadcastArea ?: AABB.withSide(Vector2d.ZERO, 5.0) 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<*, *>) { override fun onJoinWorld(world: World<*, *>) {
super.onJoinWorld(world) super.onJoinWorld(world)
if (isLocal && isScripted) { 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 else
data.remove("uniqueId") data.remove("uniqueId")
if (isScripted) if (isScripted) {
data["scriptStorage"] = toJsonFromLua(lua.globals["storage"]) lua.loadGlobal("storage")
data["scriptStorage"] = lua.popJson()
}
} }
override fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) { 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" get() = "Technical entity responsible for doing cool stuff"
override fun callScript(fnName: String, arguments: JsonArray): JsonElement { 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?> { override fun evalScript(code: String): JsonElement {
return lua.eval(code) 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.ints.IntArrayList
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
import org.apache.logging.log4j.LogManager 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.ListenableMap
import ru.dbotthepony.kommons.collect.collect
import ru.dbotthepony.kommons.collect.map
import ru.dbotthepony.kommons.gson.contains import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.set import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.io.readKOptional 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.nullable
import ru.dbotthepony.kstarbound.io.readInternedString import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.json.builder.JsonFactory import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.LuaMessageHandlerComponent import ru.dbotthepony.kstarbound.lua.LuaMessageHandlerComponent
import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent
import ru.dbotthepony.kstarbound.lua.bindings.MovementControllerBindings import ru.dbotthepony.kstarbound.lua.bindings.MovementControllerBindings
import ru.dbotthepony.kstarbound.lua.bindings.createConfigBinding import ru.dbotthepony.kstarbound.lua.bindings.createConfigBinding
import ru.dbotthepony.kstarbound.lua.bindings.provideAnimatorBindings 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.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.math.Interpolator
import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket import ru.dbotthepony.kstarbound.network.packets.DamageNotificationPacket
import ru.dbotthepony.kstarbound.network.syncher.NetworkedDynamicGroup 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.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
import java.util.* import java.util.*
import java.util.stream.Collectors import kotlin.properties.Delegates
// this is unnatural to have this class separated, but since it contains // this is unnatural to have this class separated, but since it contains
// lots of internal state, it would be nice to have it encapsulated; // 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) networkGroup.add(statNetworkGroup)
} }
val lua = LuaEnvironment() var lua by Delegates.notNull<LuaThread>()
val luaUpdate = LuaUpdateComponent(lua) private set
val luaMovement = MovementControllerBindings(entity.movement) var luaUpdate by Delegates.notNull<LuaUpdateComponent>()
val luaMessages = LuaMessageHandlerComponent(lua) { toString() } private set
var luaMovement by Delegates.notNull<MovementControllerBindings>()
private set
var luaMessages by Delegates.notNull<LuaMessageHandlerComponent>()
private set
private val animator: EffectAnimator? private val animator: EffectAnimator?
private val animatorID: Int? private val animatorID: Int?
fun uninit() {
if (entity.isLocal) {
lua.close()
}
}
fun init() { 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 luaUpdate.stepCount = config.primaryScriptDelta
lua.attach(config.primaryScriptSources) lua.attach(config.primaryScriptSources)
// provideStatusControllerBindings(this, lua) // provided through provideEntityBindings // provideStatusControllerBindings(this, lua) // provided through provideEntityBindings
provideEntityBindings(entity, lua) provideEntityBindings(entity, lua)
luaMovement.init(lua)
// TODO: Once we have brand new object-oriented Lua API, expose proper entity bindings here // TODO: Once we have brand new object-oriented Lua API, expose proper entity bindings here
// TODO: Expose world bindings // 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 recentDamageDealt(since: Long = 0L) = recentDamageDealt.query(since)
fun experienceDamage(damage: DamageData): List<DamageNotification> { 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>() val parsed = ArrayList<DamageNotification>()
for ((_, v) in (results[0] as Table)) { for (v in results.value) {
try { try {
parsed.add(Starbound.gson.fromJsonFast(toJsonFromLua(v), DamageNotification::class.java)) parsed.add(Starbound.gson.fromJsonFast(v, DamageNotification::class.java))
} catch (err: Throwable) { } catch (err: Throwable) {
LOGGER.error("Exception while parsing results returned by applyDamageRequest (primary scripts: ${config.primaryScriptSources})", err) 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 = "" var parentDirectives: String = ""
val modifierGroups = IntArrayList() val modifierGroups = IntArrayList()
val lua = LuaEnvironment() var lua by Delegates.notNull<LuaThread>()
val luaMessages = LuaMessageHandlerComponent(lua) { "Unique effect" } private set
val luaMovement = MovementControllerBindings(entity.movement) var luaUpdate by Delegates.notNull<LuaUpdateComponent>()
val luaUpdate = LuaUpdateComponent(lua) private set
var luaMovement by Delegates.notNull<MovementControllerBindings>()
private set
var luaMessages by Delegates.notNull<LuaMessageHandlerComponent>()
private set
val metadata = UniqueEffectNetworkedValues() val metadata = UniqueEffectNetworkedValues()
val metadataNetworkID = uniqueEffectMetadata.add(metadata) val metadataNetworkID = uniqueEffectMetadata.add(metadata)
@ -485,14 +495,18 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
animatorNetworkID = null 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 // unique effects shouldn't be initialized on remotes in first place
// but whatever // but whatever
lua.attach(effect.value.scripts) lua.attach(effect.value.scripts)
// provideStatusControllerBindings(this@StatusController, lua) // provided through provideEntityBindings // provideStatusControllerBindings(this@StatusController, lua) // provided through provideEntityBindings
provideEntityBindings(entity, lua) provideEntityBindings(entity, lua)
luaMovement.init(lua)
provideEffectBindings() provideEffectBindings()
if (animator != null) { if (animator != null) {
@ -502,84 +516,103 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
provideAnimatorBindings(animator.animator, lua) provideAnimatorBindings(animator.animator, lua)
} }
provideConfigBindings(lua) { path, default -> provideConfigBinding(lua) { path ->
path.find(effect.json) ?: default 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() { private fun provideEffectBindings() {
val callbacks = lua.newTable() lua.pushTable()
lua.globals["effect"] = callbacks lua.dup()
lua.storeGlobal("effect")
callbacks["duration"] = luaFunction { lua.setTableValue("duration", ::duration)
returnBuffer.setTo(metadata.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 -> lua.push("getParameter")
val value = duration.toDouble() createConfigBinding(lua) { path -> path.find(effect.json) }
check(value.isFinite()) { "Infinite duration provided" } lua.setTableValue()
check(!value.isNaN()) { "NaN duration provided" }
if (!isPersistent) { lua.pop()
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")
}
}
} }
fun promoteToPersistent() { fun promoteToPersistent() {
@ -591,7 +624,7 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
// remove an ephemeral effect. // remove an ephemeral effect.
// Original engine only updates the duration (setting it to null), // 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 // persistent effect will be sourced to whoever put ephemeral effect
metadata.duration = 0.0 metadata.duration = 0.0
metadata.sourceEntity = null metadata.sourceEntity = null
@ -599,7 +632,9 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
} }
fun remove() { fun remove() {
lua.invokeGlobal("onExpire") if (entity.isLocal)
lua.invokeGlobal("onExpire", 0)
uniqueEffectMetadata.remove(metadataNetworkID) uniqueEffectMetadata.remove(metadataNetworkID)
for (group in modifierGroups) { 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.JsonArray
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.google.gson.JsonNull
import ru.dbotthepony.kstarbound.lua.LuaThread
interface ScriptedEntity { interface ScriptedEntity {
val lua: LuaThread
// Call a script function directly with the given arguments, should return // Call a script function directly with the given arguments, should return
// nothing only on failure. // 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 // Execute the given code directly in the underlying context, return nothing
// on failure. // 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.defs.actor.player.PlayerGamemode
import ru.dbotthepony.kstarbound.io.readInternedString import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable 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.AABB
import ru.dbotthepony.kstarbound.math.Interpolator import ru.dbotthepony.kstarbound.math.Interpolator
import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.math.vector.Vector2d
@ -101,7 +101,7 @@ class PlayerEntity() : HumanoidActorEntity() {
val newChatMessage = networkGroup.upstream.add(networkedEventCounter()) val newChatMessage = networkGroup.upstream.add(networkedEventCounter())
var emote by networkGroup.upstream.add(networkedEnumExtraStupid(HumanoidEmote.IDLE)) var emote by networkGroup.upstream.add(networkedEnumExtraStupid(HumanoidEmote.IDLE))
override val lua: LuaEnvironment override val lua: LuaThread
get() = TODO("Not yet implemented") get() = TODO("Not yet implemented")
override val emoteCooldownTimer: GameTimer = GameTimer() override val emoteCooldownTimer: GameTimer = GameTimer()
override val danceTimer: GameTimer? override val danceTimer: GameTimer?

View File

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