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.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.io.writeSignedVarLong import ru.dbotthepony.kommons.math.RGBAColor import ru.dbotthepony.kstarbound.math.AABB import ru.dbotthepony.kommons.util.IStruct2d import ru.dbotthepony.kommons.util.IStruct2f import ru.dbotthepony.kommons.util.IStruct2i import ru.dbotthepony.kommons.util.IStruct3d import ru.dbotthepony.kommons.util.IStruct3f import ru.dbotthepony.kommons.util.IStruct3i import ru.dbotthepony.kommons.util.IStruct4d import ru.dbotthepony.kommons.util.IStruct4f import ru.dbotthepony.kommons.util.IStruct4i import ru.dbotthepony.kstarbound.json.BinaryJsonReader import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.math.vector.Vector2f import ru.dbotthepony.kstarbound.math.vector.Vector2i import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter import ru.dbotthepony.kstarbound.math.AABBi import ru.dbotthepony.kstarbound.math.Line2d import ru.dbotthepony.kstarbound.util.sbIntern import ru.dbotthepony.kstarbound.world.physics.Poly fun ExecutionContext.toVector2i(table: Any): Vector2i { val x = indexNoYield(table, 1L) val y = indexNoYield(table, 2L) returnBuffer.setTo() if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x") if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y") return Vector2i(x.toInt(), y.toInt()) } fun ExecutionContext.toVector2d(table: Any): Vector2d { val x = indexNoYield(table, 1L) val y = indexNoYield(table, 2L) returnBuffer.setTo() if (x !is Number) throw ClassCastException("Expected table representing a vector, but value at [1] is not a number: $x") if (y !is Number) throw ClassCastException("Expected table representing a vector, but value at [2] is not a number: $y") return Vector2d(x.toDouble(), y.toDouble()) } fun ExecutionContext.toLine2d(table: Any): Line2d { val p0 = toVector2d(indexNoYield(table, 1L) ?: throw LuaRuntimeException("Invalid line: $table")) val p1 = toVector2d(indexNoYield(table, 2L) ?: throw LuaRuntimeException("Invalid line: $table")) return Line2d(p0, p1) } fun String?.toByteString(): ByteString? { return if (this == null) null else ByteString.of(this) } fun ExecutionContext.toPoly(table: Table): Poly { val vertices = ArrayList() 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() val hashValues = HashMap() 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() { 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): 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): Table { val alloc = newTable(collection.size, 0) for ((i, v) in collection.withIndex()) alloc[i + 1L] = v return alloc } fun LuaState.getLine2d(stackIndex: Int = -1): Line2d? { val abs = this.absStackIndex(stackIndex) if (!this.isTable(abs)) return null push(1) loadTableValue(abs) val x = getVector2d(abs + 1) pop() x ?: return null push(2) loadTableValue(abs) val y = getVector2d(abs + 1) pop() y ?: return null return Line2d(x, y) } fun LuaState.ArgStack.getLine2d(position: Int = this.position++): Line2d { if (position !in 1 ..this.top) throw IllegalArgumentException("Bad argument #$position: Line2d expected, got nil") return lua.getLine2d(position) ?: throw IllegalArgumentException("Lua code error: Bad argument #$position: Line2d expected, got ${lua.typeAt(position)}") } fun LuaState.getVector2d(stackIndex: Int = -1): Vector2d? { val abs = this.absStackIndex(stackIndex) if (!this.isTable(abs)) return null push(1) loadTableValue(abs) val x = getDouble(abs + 1) pop() x ?: return null push(2) loadTableValue(abs) val y = getDouble(abs + 1) pop() y ?: return null return Vector2d(x, y) } fun LuaState.ArgStack.getVector2d(position: Int = this.position++): Vector2d { if (position !in 1 ..this.top) throw IllegalArgumentException("Bad argument #$position: Vector2d expected, got nil") return lua.getVector2d(position) ?: throw IllegalArgumentException("Lua code error: Bad argument #$position: Vector2d expected, got ${lua.typeAt(position)}") } fun LuaState.getVector2i(stackIndex: Int = -1): Vector2i? { val abs = this.absStackIndex(stackIndex) if (!this.isTable(abs)) return null push(1) loadTableValue(abs) val x = getLong(abs + 1) pop() x ?: return null push(2) loadTableValue(abs) val y = getLong(abs + 1) pop() y ?: return null return Vector2i(x.toInt(), y.toInt()) } fun LuaState.ArgStack.getVector2i(position: Int = this.position++): Vector2i { if (position !in 1 ..this.top) throw IllegalArgumentException("Bad argument #$position: Vector2i expected, got nil") return lua.getVector2i(position) ?: throw IllegalArgumentException("Lua code error: Bad argument #$position: Vector2i expected, got ${lua.typeAt(position)}") } fun LuaState.push(value: IStruct4i) { pushTable(arraySize = 4) val table = stackTop val (x, y, z, w) = value push(1) push(x) setTableValue(table) push(2) push(y) setTableValue(table) push(3) push(z) setTableValue(table) push(4) push(w) setTableValue(table) } fun LuaState.push(value: IStruct3i) { pushTable(arraySize = 3) val table = stackTop val (x, y, z) = value push(1) push(x) setTableValue(table) push(2) push(y) setTableValue(table) push(3) push(z) setTableValue(table) } fun LuaState.push(value: IStruct2i) { pushTable(arraySize = 2) val table = stackTop val (x, y) = value push(1) push(x) setTableValue(table) push(2) push(y) setTableValue(table) } fun LuaState.push(value: IStruct4f) { pushTable(arraySize = 4) val table = stackTop val (x, y, z, w) = value push(1) push(x) setTableValue(table) push(2) push(y) setTableValue(table) push(3) push(z) setTableValue(table) push(4) push(w) setTableValue(table) } fun LuaState.push(value: IStruct3f) { pushTable(arraySize = 3) val table = stackTop val (x, y, z) = value push(1) push(x) setTableValue(table) push(2) push(y) setTableValue(table) push(3) push(z) setTableValue(table) } fun LuaState.push(value: IStruct2f) { pushTable(arraySize = 2) val table = stackTop val (x, y) = value push(1) push(x) setTableValue(table) push(2) push(y) setTableValue(table) } fun LuaState.push(value: IStruct4d) { pushTable(arraySize = 4) val table = stackTop val (x, y, z, w) = value push(1) push(x) setTableValue(table) push(2) push(y) setTableValue(table) push(3) push(z) setTableValue(table) push(4) push(w) setTableValue(table) } fun LuaState.push(value: IStruct3d) { pushTable(arraySize = 3) val table = stackTop val (x, y, z) = value push(1) push(x) setTableValue(table) push(2) push(y) setTableValue(table) push(3) push(z) setTableValue(table) } fun LuaState.push(value: IStruct2d) { pushTable(arraySize = 2) val table = stackTop val (x, y) = value push(1) push(x) setTableValue(table) push(2) push(y) setTableValue(table) }