package ru.dbotthepony.kstarbound.lua.bindings import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap import org.classdump.luna.ByteString import org.classdump.luna.LuaRuntimeException import org.classdump.luna.Table import org.classdump.luna.lib.ArgumentIterator import org.classdump.luna.runtime.ExecutionContext import org.classdump.luna.runtime.LuaFunction import ru.dbotthepony.kstarbound.Globals import ru.dbotthepony.kstarbound.RecipeRegistry import ru.dbotthepony.kstarbound.Registries import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.image.Image import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor import ru.dbotthepony.kstarbound.lua.AssetJsonFunction import ru.dbotthepony.kstarbound.lua.NewLuaState import ru.dbotthepony.kstarbound.lua.StateMachine import ru.dbotthepony.kstarbound.lua.from import ru.dbotthepony.kstarbound.lua.iterator import ru.dbotthepony.kstarbound.lua.luaFunction import ru.dbotthepony.kstarbound.lua.luaFunctionN import ru.dbotthepony.kstarbound.lua.luaFunctionNS import ru.dbotthepony.kstarbound.lua.luaStub import ru.dbotthepony.kstarbound.lua.nextOptionalFloat import ru.dbotthepony.kstarbound.lua.nextOptionalInteger import ru.dbotthepony.kstarbound.lua.set import kotlin.collections.component1 import kotlin.collections.component2 import kotlin.collections.isNotEmpty import kotlin.collections.random import kotlin.collections.set import kotlin.collections.withIndex private fun lookup(registry: Registry, key: Any?): Registry.Entry? { if (key is ByteString) { return registry[key.decode()] } else if (key is Number) { return registry[key.toInt()] } else { return null } } private fun lookupStrict(registry: Registry, key: Any?): Registry.Entry { if (key is ByteString) { return registry[key.decode()] ?: throw LuaRuntimeException("No such ${registry.name}: $key") } else if (key is Number) { return registry[key.toInt()] ?: throw LuaRuntimeException("No such ${registry.name}: $key") } else { throw LuaRuntimeException("No such ${registry.name}: $key") } } private fun evalFunction(context: ExecutionContext, name: ByteString, value: Double) { val fn = Registries.jsonFunctions[name.decode()] ?: throw LuaRuntimeException("No such function $name") context.returnBuffer.setTo(fn.value.evaluate(value)) } private fun evalFunction2(context: ExecutionContext, name: ByteString, value: Double, value2: Double) { val fn = Registries.json2Functions[name.decode()] ?: throw LuaRuntimeException("No such function $name") context.returnBuffer.setTo(fn.value.evaluate(value, value2)) } private fun imageSize(context: ExecutionContext, name: ByteString) { context.returnBuffer.setTo(Image.get(name.decode())?.size ?: throw LuaRuntimeException("No such image $name")) } private fun imageSpaces(context: ExecutionContext, arguments: ArgumentIterator): StateMachine { val machine = StateMachine() val name = arguments.nextString() val image = Image.get(name.decode()) ?: throw LuaRuntimeException("No such image $name") val pixelOffset = machine.loadVector2i(arguments.nextTable()) val fillFactor = arguments.nextFloat() val flip = arguments.nextOptionalBoolean(false) return machine .add { val values = image.worldSpaces(pixelOffset.get(), fillFactor, flip) val table = context.newTable(values.size, 0) for ((i, value) in values.withIndex()) { table.rawset(i.toLong() + 1, value) } } } private fun nonEmptyRegion(context: ExecutionContext, name: ByteString) { val image = Image.get(name.decode()) ?: throw LuaRuntimeException("No such image $name") context.returnBuffer.setTo(context.from(image.nonEmptyRegion)) } private fun registryDef(registry: Registry<*>): LuaFunction { return luaFunction { context, name -> val value = registry[name.decode()] ?: throw LuaRuntimeException("No such NPC type $name") context.returnBuffer.setTo(context.from(value.json)) } } private fun registryDef2(registry: Registry<*>): LuaFunction { return luaFunction { context, name -> val def = lookup(registry, name) if (def != null) { context.returnBuffer.setTo(context.newTable(0, 2).also { it["path"] = def.file?.computeFullPath() it["config"] = context.from(def.json) }) } else { context.returnBuffer.setTo() } } } private fun registryDefExists(registry: Registry<*>): LuaFunction { return luaFunction { context, name -> context.returnBuffer.setTo(name.decode() in registry) } } private fun recipesForItem(context: ExecutionContext, name: ByteString) { val list = RecipeRegistry.output2recipes[name.decode()] if (list == null) { context.returnBuffer.setTo(context.newTable()) } else { context.returnBuffer.setTo(context.newTable(list.size, 0).also { for ((i, v) in list.withIndex()) { it.rawset(i + 1L, context.from(v.json)) } }) } } private fun itemType(context: ExecutionContext, name: ByteString) { context.returnBuffer.setTo(Registries.items[name.decode()]?.value?.itemType ?: throw NoSuchElementException("No such item $name")) } private fun itemTags(context: ExecutionContext, name: ByteString) { context.returnBuffer.setTo(context.from(Registries.items[name.decode()]?.value?.itemTags ?: throw NoSuchElementException("No such item $name"))) } private fun itemHasTag(context: ExecutionContext, name: ByteString, tag: ByteString) { context.returnBuffer.setTo(Registries.items[name.decode()]?.value?.itemTags?.contains(tag.decode()) ?: throw NoSuchElementException("No such item $name")) } private fun itemConfig(context: ExecutionContext, args: ArgumentIterator): StateMachine { val name = args.nextString().decode() val state = StateMachine() if (name !in Registries.items) { context.returnBuffer.setTo() return state } val desc = ItemDescriptor(args.nextTable(), state) val level = args.nextOptionalFloat() val seed = args.nextOptionalInteger() state.add { val build = Starbound.itemConfig(desc.get().name, desc.get().parameters, level, seed) context.returnBuffer.setTo(context.newTable(0, 3).also { it["directory"] = build.directory it["config"] = context.from(build.config) it["parameters"] = context.from(build.parameters) }) } return state } private fun getMatchingTenants(context: ExecutionContext, tags: Table) { val actualTags = Object2IntOpenHashMap() for ((k, v) in tags) { if (k is ByteString && v is Number) { actualTags[k.decode()] = v.toInt() } } val result = Registries.tenants.keys.values .stream() .filter { it.value.test(actualTags) } .sorted { a, b -> b.value.compareTo(a.value) } .map { context.from(it.json) } .toList() context.returnBuffer.setTo(context.newTable(result.size, 0).also { for ((k, v) in result.withIndex()) { it[k + 1] = v } }) } private fun liquidStatusEffects(context: ExecutionContext, id: Any) { val liquid = lookup(Registries.liquid, id) if (liquid == null) { context.returnBuffer.setTo(context.newTable()) } else { context.returnBuffer.setTo(context.newTable(liquid.value.statusEffects.size, 0).also { for ((k, v) in liquid.value.statusEffects.withIndex()) { it[k + 1] = v.key.map({ it }, { it }) } }) } } private fun createTreasure(context: ExecutionContext, arguments: ArgumentIterator) { val pool = arguments.nextString().decode() val level = arguments.nextFloat() val seed = arguments.nextOptionalInteger() val get = Registries.treasurePools[pool] ?: throw LuaRuntimeException("No such treasure pool $pool") val result = get.value.evaluate(seed ?: System.nanoTime(), level) .stream().filter { it.isNotEmpty }.map { it.toTable(context)!! }.toList() context.returnBuffer.setTo(context.from(result)) } private fun materialMiningSound(context: ExecutionContext, arguments: ArgumentIterator) { val tile = lookup(Registries.tiles, arguments.nextAny()) val mod = lookup(Registries.tiles, arguments.nextOptionalAny(null)) if (mod != null && mod.value.miningSounds.map({ it.isNotEmpty() }, { true })) { context.returnBuffer.setTo(mod.value.miningSounds.map({ it.random() }, { it })) return } if (tile != null && tile.value.miningSounds.map({ it.isNotEmpty() }, { true })) { context.returnBuffer.setTo(tile.value.miningSounds.map({ it.random() }, { it })) return } // original engine parity context.returnBuffer.setTo("") } private fun materialFootstepSound(context: ExecutionContext, arguments: ArgumentIterator) { val tile = lookup(Registries.tiles, arguments.nextAny()) val mod = lookup(Registries.tiles, arguments.nextOptionalAny(null)) if (mod != null && mod.value.footstepSound.map({ it.isNotEmpty() }, { true })) { context.returnBuffer.setTo(mod.value.footstepSound.map({ it.random() }, { it })) return } if (tile != null && tile.value.footstepSound.map({ it.isNotEmpty() }, { true })) { context.returnBuffer.setTo(tile.value.footstepSound.map({ it.random() }, { it })) return } context.returnBuffer.setTo(Globals.client.defaultFootstepSound.map({ it }, { it.random() })) } private fun materialHealth(context: ExecutionContext, arguments: ArgumentIterator) { context.returnBuffer.setTo(lookupStrict(Registries.tiles, arguments.nextAny()).value.actualDamageTable.totalHealth) } private fun liquidName(context: ExecutionContext, arguments: ArgumentIterator) { context.returnBuffer.setTo(lookupStrict(Registries.liquid, arguments.nextAny()).key) } private fun liquidId(context: ExecutionContext, arguments: ArgumentIterator) { context.returnBuffer.setTo(lookupStrict(Registries.liquid, arguments.nextAny()).id) } private fun techType(context: ExecutionContext, arguments: ArgumentIterator) { context.returnBuffer.setTo(lookupStrict(Registries.techs, arguments.nextAny()).value.type) } private fun techConfig(context: ExecutionContext, arguments: ArgumentIterator) { context.returnBuffer.setTo(lookupStrict(Registries.techs, arguments.nextAny()).json) } fun provideRootBindings(state: NewLuaState) { val table = state.state.newTable() state.env["root"] = table table["assetJson"] = AssetJsonFunction(state.state) table["makeCurrentVersionedJson"] = luaStub("makeCurrentVersionedJson") table["loadVersionedJson"] = luaStub("loadVersionedJson") table["evalFunction"] = luaFunction(::evalFunction) table["evalFunction2"] = luaFunction(::evalFunction2) table["imageSize"] = luaFunction(::imageSize) table["imageSpaces"] = luaFunctionNS("imageSpaces", ::imageSpaces) table["nonEmptyRegion"] = luaFunction(::nonEmptyRegion) table["npcConfig"] = registryDef(Registries.npcTypes) table["npcVariant"] = luaStub("npcVariant") table["projectileGravityMultiplier"] = luaStub("projectileGravityMultiplier") table["projectileConfig"] = registryDef(Registries.projectiles) table["recipesForItem"] = luaFunction(::recipesForItem) table["itemType"] = luaFunction(::itemType) table["itemTags"] = luaFunction(::itemTags) table["itemHasTag"] = luaFunction(::itemHasTag) table["itemConfig"] = luaFunctionNS("itemConfig", ::itemConfig) table["createItem"] = luaStub("createItem") table["tenantConfig"] = registryDef(Registries.tenants) table["getMatchingTenants"] = luaFunction(::getMatchingTenants) table["liquidStatusEffects"] = luaFunction(::liquidStatusEffects) table["generateName"] = luaStub("generateName") table["questConfig"] = registryDef(Registries.questTemplates) table["npcPortrait"] = luaStub("npcPortrait") table["monsterPortrait"] = luaStub("monsterPortrait") table["npcPortrait"] = luaStub("npcPortrait") table["isTreasurePool"] = registryDefExists(Registries.treasurePools) table["createTreasure"] = luaFunctionN("createTreasure", ::createTreasure) table["materialMiningSound"] = luaFunctionN("materialMiningSound", ::materialMiningSound) table["materialFootstepSound"] = luaFunctionN("materialFootstepSound", ::materialFootstepSound) table["materialHealth"] = luaFunctionN("materialHealth", ::materialHealth) table["materialConfig"] = registryDef2(Registries.tiles) table["modConfig"] = registryDef2(Registries.tileModifiers) table["liquidName"] = luaFunctionN("liquidName", ::liquidName) table["liquidId"] = luaFunctionN("liquidId", ::liquidId) table["createBiome"] = luaStub("createBiome") table["monsterSkillParameter"] = luaStub("monsterSkillParameter") table["monsterParameters"] = luaStub("monsterParameters") table["monsterMovementSettings"] = luaStub("monsterMovementSettings") table["hasTech"] = registryDefExists(Registries.techs) table["techType"] = luaFunctionN("techType", ::techType) table["techConfig"] = luaFunctionN("techConfig", ::techConfig) table["treeStemDirectory"] = luaStub("treeStemDirectory") table["treeFoliageDirectory"] = luaStub("treeFoliageDirectory") table["collection"] = luaStub("collection") table["collectables"] = luaStub("collectables") table["elementalResistance"] = luaStub("elementalResistance") table["dungeonMetadata"] = luaStub("dungeonMetadata") table["behavior"] = luaStub("behavior") state.env["jobject"] = luaFunction { executionContext -> executionContext.returnBuffer.setTo(executionContext.newTable()) } state.env["jarray"] = luaFunction { executionContext -> executionContext.returnBuffer.setTo(executionContext.newTable()) } }