378 lines
10 KiB
Kotlin
378 lines
10 KiB
Kotlin
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.Conversions
|
|
import org.classdump.luna.Table
|
|
import org.classdump.luna.TableFactory
|
|
import org.classdump.luna.impl.NonsuspendableFunctionException
|
|
import org.classdump.luna.runtime.AbstractFunction3
|
|
import org.classdump.luna.runtime.ExecutionContext
|
|
import ru.dbotthepony.kommons.gson.set
|
|
import ru.dbotthepony.kommons.math.RGBAColor
|
|
import ru.dbotthepony.kommons.util.AABB
|
|
import ru.dbotthepony.kommons.util.IStruct2d
|
|
import ru.dbotthepony.kommons.util.IStruct2i
|
|
import ru.dbotthepony.kommons.util.IStruct3i
|
|
import ru.dbotthepony.kommons.util.IStruct4i
|
|
import ru.dbotthepony.kommons.vector.Vector2d
|
|
import ru.dbotthepony.kommons.vector.Vector2f
|
|
import ru.dbotthepony.kommons.vector.Vector2i
|
|
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
|
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
|
|
|
fun ExecutionContext.toVector2i(table: Any): Vector2i {
|
|
val x = indexNoYield(table, 1L)
|
|
val y = indexNoYield(table, 2L)
|
|
|
|
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)
|
|
|
|
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.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)
|
|
|
|
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
|
|
|
|
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)
|
|
|
|
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 toJsonFromLua(value: Any?): JsonElement {
|
|
return when (value) {
|
|
null, is JsonNull -> JsonNull.INSTANCE
|
|
is String -> JsonPrimitive(value)
|
|
is ByteString -> JsonPrimitive(value.decode())
|
|
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 value.asDouble
|
|
} 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: 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 {
|
|
return newTable(2, 0).apply {
|
|
this[1L] = value.component1()
|
|
this[2L] = value.component2()
|
|
}
|
|
}
|
|
|
|
fun TableFactory.from(value: Poly): Table {
|
|
return newTable(value.vertices.size, 0).apply {
|
|
value.vertices.withIndex().forEach { (i, v) -> this[i + 1L] = from(v) }
|
|
}
|
|
}
|
|
|
|
fun TableFactory.fromCollection(value: Collection<JsonElement?>): Table {
|
|
val table = newTable(value.size, 0)
|
|
|
|
for ((k, v) in value.withIndex()) {
|
|
table.rawset(Conversions.normaliseKey(k + 1), from(v))
|
|
}
|
|
|
|
return table
|
|
}
|
|
|
|
fun TableFactory.from(value: IStruct2i): Table {
|
|
return newTable(2, 0).also {
|
|
it.rawset(1L, value.component1())
|
|
it.rawset(2L, value.component2())
|
|
}
|
|
}
|
|
|
|
fun TableFactory.from(value: IStruct3i): Table {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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)
|
|
}
|
|
}
|