352 lines
13 KiB
Kotlin
352 lines
13 KiB
Kotlin
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 <T : Any> lookup(registry: Registry<T>, key: Any?): Registry.Entry<T>? {
|
|
if (key is ByteString) {
|
|
return registry[key.decode()]
|
|
} else if (key is Number) {
|
|
return registry[key.toInt()]
|
|
} else {
|
|
return null
|
|
}
|
|
}
|
|
|
|
private fun <T : Any> lookupStrict(registry: Registry<T>, key: Any?): Registry.Entry<T> {
|
|
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<ByteString, *, *, *, *> {
|
|
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<Any?, *, *, *, *> {
|
|
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<ByteString, *, *, *, *> {
|
|
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<String>()
|
|
|
|
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()) }
|
|
}
|