Move remaining bindings to PUC Lua

This commit is contained in:
DBotThePony 2024-12-28 13:56:08 +07:00
parent 20b1a7b5e5
commit e1d1531e0a
Signed by: DBot
GPG Key ID: DCC23B5715498507
40 changed files with 3436 additions and 2354 deletions

View File

@ -155,6 +155,7 @@ In addition to `add`, `multiply`, `merge` and `override` new merge methods are a
* Added `animator.effects(): List<string>` * Added `animator.effects(): List<string>`
* Added `animator.hasEffect(effect: string): boolean` * Added `animator.hasEffect(effect: string): boolean`
* Added `animator.parts(): List<string>` * Added `animator.parts(): List<string>`
* Added `animator.hasPart(part: String): boolean`
## mcontroller ## mcontroller
@ -185,6 +186,10 @@ In addition to `add`, `multiply`, `merge` and `override` new merge methods are a
* Added `status.minimumLiquidStatusEffectPercentage(): Double` * Added `status.minimumLiquidStatusEffectPercentage(): Double`
* Added `status.setMinimumLiquidStatusEffectPercentage(value: Double)` * Added `status.setMinimumLiquidStatusEffectPercentage(value: Double)`
## objec
* Added `object.worldSpaces(): List<Vector2i>`, similar to `object.spaces()`, but returns coordinates in world-space grid instead of local coordinates
## Path Finder ## Path Finder
In new engine, pathfinder returned by `world.platformerPathStart()`, if unable find path to goal inside `finder:explore()` due to budget constraints, launches In new engine, pathfinder returned by `world.platformerPathStart()`, if unable find path to goal inside `finder:explore()` due to budget constraints, launches

View File

@ -116,6 +116,8 @@ object Starbound : BlockableEventLoop("Multiverse Thread"), Scheduler, ISBFileLo
// compile flags. uuuugh // compile flags. uuuugh
const val DEDUP_CELL_STATES = true const val DEDUP_CELL_STATES = true
// Debug option. Caffeine is almost 3x times slower than specialized interner,
// and saves only 200 mib on big modpack vs specialized's 300 mib
const val USE_CAFFEINE_INTERNER = false const val USE_CAFFEINE_INTERNER = false
const val USE_INTERNER = true const val USE_INTERNER = true

View File

@ -1,19 +1,11 @@
package ru.dbotthepony.kstarbound.defs.actor.behavior package ru.dbotthepony.kstarbound.defs.actor.behavior
import com.google.common.collect.ImmutableList
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.world.entities.behavior.AbstractBehaviorNode
import ru.dbotthepony.kstarbound.world.entities.behavior.BehaviorTree
import ru.dbotthepony.kstarbound.world.entities.behavior.Blackboard
import ru.dbotthepony.kstarbound.world.entities.behavior.DynamicNode
import ru.dbotthepony.kstarbound.world.entities.behavior.ParallelNode
import ru.dbotthepony.kstarbound.world.entities.behavior.RandomizeNode
import ru.dbotthepony.kstarbound.world.entities.behavior.SequenceNode
enum class CompositeNodeType(override val jsonName: String, val factory: (Map<String, NodeParameter>, ImmutableList<AbstractBehaviorNode>) -> AbstractBehaviorNode) : IStringSerializable { enum class CompositeNodeType(override val jsonName: String, val fnName: String) : IStringSerializable {
SEQUENCE("Sequence", { _, c -> SequenceNode(c, isSelector = false) }), SEQUENCE("Sequence", "SequenceNode"),
SELECTOR("Selector", { _, c -> SequenceNode(c, isSelector = true) }), SELECTOR("Selector", "SelectorNode"),
PARALLEL("Parallel", { p, c -> ParallelNode(p, c) }), PARALLEL("Parallel", "ParallelNode"),
DYNAMIC("Dynamic", { _, c -> DynamicNode(c) }), DYNAMIC("Dynamic", "DynamicNode"),
RANDOMIZE("Randomize", { _, c -> RandomizeNode(c) }); RANDOMIZE("Randomize", "RandomNode");
} }

View File

@ -6,12 +6,23 @@ import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.get import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kstarbound.json.popObject import ru.dbotthepony.kstarbound.json.popObject
import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.util.asStringOrNull import ru.dbotthepony.kstarbound.util.asStringOrNull
import ru.dbotthepony.kstarbound.util.valueOf import ru.dbotthepony.kstarbound.util.valueOf
import ru.dbotthepony.kstarbound.world.entities.behavior.NodeParameterType import ru.dbotthepony.kstarbound.lua.userdata.NodeParameterType
@JsonAdapter(NodeOutput.Adapter::class) @JsonAdapter(NodeOutput.Adapter::class)
data class NodeOutput(val type: NodeParameterType, val key: String? = null, val ephemeral: Boolean = false) { data class NodeOutput(val type: NodeParameterType, val key: String? = null, val ephemeral: Boolean = false) {
fun push(lua: LuaThread) {
lua.pushTable(hashSize = 3)
lua.setTableValue("type", type.ordinal)
if (key != null)
lua.setTableValue("key", key)
lua.setTableValue("ephemeral", ephemeral)
}
class Adapter : TypeAdapter<NodeOutput>() { class Adapter : TypeAdapter<NodeOutput>() {
override fun write(out: JsonWriter, value: NodeOutput) { override fun write(out: JsonWriter, value: NodeOutput) {
out.beginObject() out.beginObject()

View File

@ -8,8 +8,9 @@ import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.contains import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.value import ru.dbotthepony.kommons.gson.value
import ru.dbotthepony.kstarbound.json.popObject import ru.dbotthepony.kstarbound.json.popObject
import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.util.valueOf import ru.dbotthepony.kstarbound.util.valueOf
import ru.dbotthepony.kstarbound.world.entities.behavior.NodeParameterType import ru.dbotthepony.kstarbound.lua.userdata.NodeParameterType
@JsonAdapter(NodeParameter.Adapter::class) @JsonAdapter(NodeParameter.Adapter::class)
data class NodeParameter(val type: NodeParameterType, val value: NodeParameterValue) { data class NodeParameter(val type: NodeParameterType, val value: NodeParameterValue) {
@ -17,6 +18,17 @@ data class NodeParameter(val type: NodeParameterType, val value: NodeParameterVa
return "[$type as $value]" return "[$type as $value]"
} }
fun push(lua: LuaThread) {
lua.pushTable(hashSize = 3)
lua.setTableValue("type", type.ordinal)
if (value.key != null) {
lua.setTableValue("key", value.key)
} else {
lua.setTableValue("value", value.value)
}
}
class Adapter : TypeAdapter<NodeParameter>() { class Adapter : TypeAdapter<NodeParameter>() {
override fun write(out: JsonWriter, value: NodeParameter) { override fun write(out: JsonWriter, value: NodeParameter) {
out.beginObject() out.beginObject()

View File

@ -17,6 +17,10 @@ import ru.dbotthepony.kstarbound.json.popObject
*/ */
@JsonAdapter(NodeParameterValue.Adapter::class) @JsonAdapter(NodeParameterValue.Adapter::class)
data class NodeParameterValue(val key: String?, val value: JsonElement?) { data class NodeParameterValue(val key: String?, val value: JsonElement?) {
init {
require(key != null || value != null) { "Both key and value are nulls" }
}
override fun toString(): String { override fun toString(): String {
if (key != null) if (key != null)
return "key=$key" return "key=$key"

View File

@ -26,6 +26,7 @@ import ru.dbotthepony.kommons.util.IStruct3i
import ru.dbotthepony.kommons.util.IStruct4d 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.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
@ -557,6 +558,44 @@ fun LuaThread.ArgStack.nextOptionalVector2d(position: Int = this.position++): Ve
return lua.getVector2d(position) return lua.getVector2d(position)
} }
fun LuaThread.getVector2f(stackIndex: Int = -1): Vector2f? {
val abs = this.absStackIndex(stackIndex)
if (!this.isTable(abs))
return null
push(1)
loadTableValue(abs)
val x = getFloat(abs + 1)
pop()
x ?: return null
push(2)
loadTableValue(abs)
val y = getFloat(abs + 1)
pop()
y ?: return null
return Vector2f(x, y)
}
fun LuaThread.ArgStack.nextVector2f(position: Int = this.position++): Vector2f {
if (position !in 1 ..this.top)
throw IllegalArgumentException("bad argument #$position: Vector2f expected, got nil")
return lua.getVector2f(position)
?: throw IllegalArgumentException("bad argument #$position: Vector2f expected, got ${lua.typeAt(position)}")
}
fun LuaThread.ArgStack.nextOptionalVector2f(position: Int = this.position++): Vector2f? {
if (position !in 1 ..this.top)
return null
return lua.getVector2f(position)
}
fun LuaThread.getVector2iOrAABB(stackIndex: Int = -1): Either<Vector2i, AABB>? { fun LuaThread.getVector2iOrAABB(stackIndex: Int = -1): Either<Vector2i, AABB>? {
val abs = this.absStackIndex(stackIndex) val abs = this.absStackIndex(stackIndex)
@ -1062,3 +1101,28 @@ fun LuaThread.push(value: AABBi?) {
push(w.toLong()) push(w.toLong())
setTableValue(table) setTableValue(table)
} }
fun LuaThread.push(value: Poly?) {
value ?: return push()
pushTable(value.vertices.size)
for ((i, vertex) in value.vertices.withIndex())
setTableValue(i + 1L, vertex)
}
fun LuaThread.setTableValue(key: String, value: Poly?) { push(key); push(value); setTableValue() }
fun LuaThread.setTableValue(key: Int, value: Poly?) { push(key.toLong()); push(value); setTableValue() }
fun LuaThread.setTableValue(key: Long, value: Poly?) { push(key); push(value); setTableValue() }
fun LuaThread.push(value: Registry.Ref<*>, preferStringsIDs: Boolean = true) {
if (value.isPresent) {
if (preferStringsIDs || value.entry!!.id == null) {
push(value.entry!!.key)
} else {
push(value.entry!!.id!!.toLong())
}
} else {
value.key.map({ push(it) }, { push(it.toLong()) })
}
}

View File

@ -1,61 +1,66 @@
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 com.google.gson.JsonNull
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.classdump.luna.ByteString
import org.classdump.luna.exec.CallPausedException
import org.classdump.luna.runtime.LuaFunction
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kstarbound.util.ActionPacer import ru.dbotthepony.kstarbound.util.ActionPacer
import ru.dbotthepony.kstarbound.util.sbIntern import ru.dbotthepony.kstarbound.util.sbIntern
import ru.dbotthepony.kstarbound.world.World
class LuaMessageHandlerComponent(val lua: LuaEnvironment, val nameProvider: () -> String) { class LuaMessageHandlerComponent(lua: LuaThread, val nameProvider: () -> String) {
private val handlers = HashMap<String, LuaFunction<*, *, *, *, *>>() private val handlers = HashMap<String, LuaHandle>()
init { private fun setHandler(args: LuaThread.ArgStack): Int {
val table = lua.newTable() val name = args.nextString()
lua.globals["message"] = table val peek = args.peek()
table["setHandler"] = luaFunction { message: ByteString, handler: LuaFunction<*, *, *, *, *>? -> if (peek.isNothing) {
if (handler == null) { handlers.remove(name)?.close()
handlers.remove(message.decode()) } else if (peek == LuaType.FUNCTION) {
} else { args.lua.dup(2)
handlers[message.decode().sbIntern()] = handler val handle = args.lua.createHandle()
} handlers.put(name.sbIntern(), handle)?.close()
} }
return 0
} }
private val logPacer = ActionPacer(1, 5) init {
lua.pushTable()
lua.dup()
lua.storeGlobal("message")
lua.setTableValue("setHandler", ::setHandler)
lua.pop()
}
fun handle(message: String, isLocal: Boolean, arguments: JsonArray): JsonElement? { val logPacer = ActionPacer(1, 5)
val handler = handlers[message] ?: return null
fun lookupHandler(name: String): LuaHandle? {
return handlers[name]
}
inline fun handle(lua: LuaThread, message: String, isLocal: Boolean, arguments: LuaThread.() -> Int): JsonElement? {
val handler = lookupHandler(message) ?: return null
val top = lua.stackTop
try { try {
val unpack = arguments.map { lua.from(it) }.toTypedArray() lua.push(handler)
val result = lua.executor.call(lua, handler, isLocal, *unpack) lua.push(isLocal)
val amountOfArguments = arguments(lua)
check(amountOfArguments >= 0) { "Invalid amount of arguments to pass to Lua handler: $amountOfArguments" }
if (result.isEmpty()) { lua.call(amountOfArguments + 1, 1)
return null
} else {
return toJsonFromLua(result[0])
}
} catch (err: CallPausedException) {
if (logPacer.consumeAndReturnDeadline() <= 0L)
LOGGER.error("${nameProvider.invoke()}: '$message' handler attempted to yield across C boundary", err)
throw World.MessageCallException("$message handler attempted to yield across C boundary") return lua.getJson()
} catch (err: Throwable) { } catch (err: Throwable) {
if (logPacer.consumeAndReturnDeadline() <= 0L) if (logPacer.consumeAndReturnDeadline() <= 0L)
LOGGER.error("${nameProvider.invoke()}: Exception while handling message '$message'", err) LOGGER.error("${nameProvider.invoke()}: Exception while handling message '$message'", err)
throw err throw err
} finally {
lua.setTop(top)
} }
} }
companion object { companion object {
private val LOGGER = LogManager.getLogger() val LOGGER = LogManager.getLogger()
} }
} }

View File

@ -460,10 +460,22 @@ class LuaThread private constructor(
val b = status[0] > 0 val b = status[0] > 0
stack.close() stack.close()
if (!b) if (!b) return null
return value
}
fun getFloat(stackIndex: Int = -1): Float? {
if (!this.isNumber(stackIndex))
return null return null
return value val stack = MemoryStack.stackPush()
val status = stack.mallocInt(1)
val value = LuaJNR.INSTANCE.lua_tonumberx(this.pointer, stackIndex, MemoryUtil.memAddress(status))
val b = status[0] > 0
stack.close()
if (!b) return null
return value.toFloat()
} }
private fun getDoubleRaw(stackIndex: Int = -1): Double { private fun getDoubleRaw(stackIndex: Int = -1): Double {
@ -499,7 +511,7 @@ class LuaThread private constructor(
LuaType.NIL -> JsonNull.INSTANCE LuaType.NIL -> JsonNull.INSTANCE
LuaType.BOOLEAN -> InternedJsonElementAdapter.of(this.getBooleanRaw(abs)) LuaType.BOOLEAN -> InternedJsonElementAdapter.of(this.getBooleanRaw(abs))
LuaType.NUMBER -> JsonPrimitive(if (this.isInteger(abs)) this.getLongRaw(abs) else this.getDoubleRaw(abs)) LuaType.NUMBER -> JsonPrimitive(if (this.isInteger(abs)) this.getLongRaw(abs) else this.getDoubleRaw(abs))
LuaType.STRING -> JsonPrimitive(this.getStringRaw(abs, limit = limit)) LuaType.STRING -> JsonPrimitive(stringInterner.intern(getStringRaw(abs, limit = limit)))
LuaType.TABLE -> { LuaType.TABLE -> {
val values = HashMap<Any, JsonElement>() val values = HashMap<Any, JsonElement>()
@ -826,10 +838,26 @@ class LuaThread private constructor(
return LuaType.valueOf(LuaJNR.INSTANCE.lua_getglobal(this.pointer, name)) return LuaType.valueOf(LuaJNR.INSTANCE.lua_getglobal(this.pointer, name))
} }
fun setGlobal(name: String, value: JsonElement?) {
push(value)
storeGlobal(name)
}
fun getGlobal(name: String): JsonElement? {
loadGlobal(name)
return getJson()
}
inner class ArgStack(val top: Int) { inner class ArgStack(val top: Int) {
val lua get() = this@LuaThread val lua get() = this@LuaThread
var position = 1 var position = 1
val isEmpty: Boolean
get() = top == 0
val isNotEmpty: Boolean
get() = top != 0
init { init {
if (top >= 10) { if (top >= 10) {
this@LuaThread.ensureExtraCapacity(10) this@LuaThread.ensureExtraCapacity(10)
@ -849,7 +877,7 @@ class LuaThread private constructor(
return peek return peek
} }
fun hasNext(): Boolean { val hasNext: Boolean get() {
return position <= top return position <= top
} }
@ -951,7 +979,7 @@ class LuaThread private constructor(
fun nextJson(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonElement { fun nextJson(position: Int = this.position++, limit: Long = DEFAULT_STRING_LIMIT): JsonElement {
if (position !in 1 ..this.top) if (position !in 1 ..this.top)
throw IllegalArgumentException("bad argument #$position: json expected, got nil") throw IllegalArgumentException("bad argument #$position: json expected, got nothing")
val value = this@LuaThread.getJson(position, limit = limit) val value = this@LuaThread.getJson(position, limit = limit)
return value ?: throw IllegalArgumentException("bad argument #$position: anything expected, got ${this@LuaThread.typeAt(position)}") return value ?: throw IllegalArgumentException("bad argument #$position: anything expected, got ${this@LuaThread.typeAt(position)}")
@ -988,6 +1016,14 @@ class LuaThread private constructor(
?: throw IllegalArgumentException("bad argument #$position: number expected, got ${this@LuaThread.typeAt(position)}") ?: throw IllegalArgumentException("bad argument #$position: number expected, got ${this@LuaThread.typeAt(position)}")
} }
fun nextFloat(position: Int = this.position++): Float {
if (position !in 1 ..this.top)
throw IllegalArgumentException("bad argument #$position: number expected, got nil")
return this@LuaThread.getFloat(position)
?: throw IllegalArgumentException("bad argument #$position: number expected, got ${this@LuaThread.typeAt(position)}")
}
fun nextOptionalDouble(position: Int = this.position++): Double? { fun nextOptionalDouble(position: Int = this.position++): Double? {
val type = this@LuaThread.typeAt(position) val type = this@LuaThread.typeAt(position)
@ -997,6 +1033,15 @@ class LuaThread private constructor(
return this@LuaThread.getDouble(position) return this@LuaThread.getDouble(position)
} }
fun nextOptionalFloat(position: Int = this.position++): Float? {
val type = this@LuaThread.typeAt(position)
if (type != LuaType.NUMBER && type != LuaType.NIL && type != LuaType.NONE)
throw IllegalArgumentException("bad argument #$position: double expected, got $type")
return this@LuaThread.getFloat(position)
}
fun nextOptionalLong(position: Int = this.position++): Long? { fun nextOptionalLong(position: Int = this.position++): Long? {
val type = this@LuaThread.typeAt(position) val type = this@LuaThread.typeAt(position)
@ -1136,6 +1181,12 @@ class LuaThread private constructor(
setTableValue() setTableValue()
} }
fun <T> pushBinding(self: T, name: String, function: (T) -> Unit) {
push(name)
push { function.invoke(self); 0 }
setTableValue()
}
fun ensureExtraCapacity(maxSize: Int): Boolean { fun ensureExtraCapacity(maxSize: Int): Boolean {
return LuaJNR.INSTANCE.lua_checkstack(pointer, maxSize) return LuaJNR.INSTANCE.lua_checkstack(pointer, maxSize)
} }
@ -1376,6 +1427,40 @@ class LuaThread private constructor(
return setTableValue(key.toLong(), value) return setTableValue(key.toLong(), value)
} }
fun setTableValue(key: String, value: Boolean?) {
value ?: return
this.push(key)
this.push(value)
this.setTableValue()
}
fun setTableValue(key: String, value: Boolean) {
this.push(key)
this.push(value)
this.setTableValue()
}
fun setTableValue(key: Int, value: Boolean?) {
return setTableValue(key.toLong(), value)
}
fun setTableValue(key: Int, value: Boolean) {
return setTableValue(key.toLong(), value)
}
fun setTableValue(key: Long, value: Boolean?) {
value ?: return
this.push(key)
this.push(value)
this.setTableValue()
}
fun setTableValue(key: Long, value: Boolean) {
this.push(key)
this.push(value)
this.setTableValue()
}
fun setTableValue(key: Long, value: JsonElement?) { fun setTableValue(key: Long, value: JsonElement?) {
this.push(key) this.push(key)
this.push(value) this.push(value)

View File

@ -1,22 +1,32 @@
package ru.dbotthepony.kstarbound.lua package ru.dbotthepony.kstarbound.lua
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
class LuaUpdateComponent(val lua: LuaEnvironment) { class LuaUpdateComponent(val lua: LuaThread, val name: Any) {
var stepCount = 1.0 var stepCount = 1.0
private var steps = 0.0 private var steps = 0.0
private var lastType = LuaType.FUNCTION
private fun updateDt(args: LuaThread.ArgStack): Int {
args.lua.push(stepCount * Starbound.TIMESTEP)
return 1
}
private fun setUpdateDelta(args: LuaThread.ArgStack): Int {
stepCount = args.nextDouble()
return 0
}
init { init {
val script = lua.newTable() lua.pushTable()
lua.globals["script"] = script lua.dup()
lua.storeGlobal("script")
script["updateDt"] = luaFunction { lua.setTableValue("updateDt", ::updateDt)
returnBuffer.setTo(stepCount * Starbound.TIMESTEP) lua.setTableValue("setUpdateDelta", ::setUpdateDelta)
}
script["setUpdateDelta"] = luaFunction { ticks: Number -> lua.pop()
stepCount = ticks.toDouble()
}
} }
fun update(delta: Double, preRun: () -> Unit) { fun update(delta: Double, preRun: () -> Unit) {
@ -27,20 +37,32 @@ class LuaUpdateComponent(val lua: LuaEnvironment) {
if (steps >= stepCount) { if (steps >= stepCount) {
steps %= stepCount steps %= stepCount
preRun()
lua.invokeGlobal("update", stepCount * Starbound.TIMESTEP) val type = lua.loadGlobal("update")
if (type != lastType) {
lastType = type
if (type != LuaType.FUNCTION) {
LOGGER.warn("Lua environment for $name has $type as global 'update', script update wasn't called")
}
}
if (type == LuaType.FUNCTION) {
preRun()
lua.push(stepCount * Starbound.TIMESTEP)
lua.call(1)
} else {
lua.pop()
}
} }
} }
fun update(delta: Double, vararg arguments: Any?) { fun update(delta: Double) {
if (stepCount == 0.0) return update(delta) {}
return }
steps += delta / Starbound.TIMESTEP companion object {
private val LOGGER = LogManager.getLogger()
if (steps >= stepCount) {
steps %= stepCount
lua.invokeGlobal("update", stepCount * Starbound.TIMESTEP, *arguments)
}
} }
} }

View File

@ -1,261 +1,318 @@
package ru.dbotthepony.kstarbound.lua.bindings package ru.dbotthepony.kstarbound.lua.bindings
import org.classdump.luna.ByteString import ru.dbotthepony.kstarbound.lua.LuaThread
import org.classdump.luna.Table import ru.dbotthepony.kstarbound.lua.LuaType
import ru.dbotthepony.kommons.collect.map import ru.dbotthepony.kstarbound.lua.nextAABB
import ru.dbotthepony.kommons.collect.toList import ru.dbotthepony.kstarbound.lua.nextColor
import ru.dbotthepony.kstarbound.lua.nextOptionalVector2f
import ru.dbotthepony.kstarbound.lua.nextPoly
import ru.dbotthepony.kstarbound.lua.nextVector2d
import ru.dbotthepony.kstarbound.lua.nextVector2f
import ru.dbotthepony.kstarbound.lua.push
import ru.dbotthepony.kstarbound.math.vector.Vector2f import ru.dbotthepony.kstarbound.math.vector.Vector2f
import ru.dbotthepony.kstarbound.lua.LuaEnvironment import ru.dbotthepony.kstarbound.util.sbIntern
import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.iterator
import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.luaFunctionArray
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.toAABB
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.lua.toColor
import ru.dbotthepony.kstarbound.lua.toPoly
import ru.dbotthepony.kstarbound.lua.toVector2d
import ru.dbotthepony.kstarbound.lua.toVector2f
import ru.dbotthepony.kstarbound.world.entities.Animator import ru.dbotthepony.kstarbound.world.entities.Animator
fun provideAnimatorBindings(self: Animator, lua: LuaEnvironment) { private fun setAnimationState(self: Animator, args: LuaThread.ArgStack): Int {
val callbacks = lua.newTable() val type = args.nextString()
lua.globals["animator"] = callbacks val state = args.nextString()
val alwaysStart = args.nextOptionalBoolean() == true
callbacks["setAnimationState"] = luaFunction { type: ByteString, state: ByteString, alwaysStart: Boolean? -> self.setActiveState(type, state, alwaysStart)
returnBuffer.setTo(self.setActiveState(type.decode(), state.decode(), alwaysStart ?: false)) return 0
} }
callbacks["animationState"] = luaFunction { type: ByteString -> private fun animationState(self: Animator, args: LuaThread.ArgStack): Int {
returnBuffer.setTo(self.animationState(type.decode())) val type = args.nextString()
} args.lua.push(self.animationState(type))
return 1
callbacks["animationStateProperty"] = luaFunction { type: ByteString, key: ByteString -> }
returnBuffer.setTo(self.stateProperty(type.decode(), key.decode()))
} private fun animationStateProperty(self: Animator, args: LuaThread.ArgStack): Int {
val key = args.nextString()
callbacks["setGlobalTag"] = luaFunction { key: ByteString, value: ByteString -> val value = args.nextString()
self.setGlobalTag(key.decode(), value.decode()) args.lua.push(self.stateProperty(key, value))
} return 1
}
callbacks["setPartTag"] = luaFunction { part: ByteString, key: ByteString, value: ByteString ->
self.setPartTag(part.decode(), key.decode(), value.decode()) private fun setGlobalTag(self: Animator, args: LuaThread.ArgStack): Int {
} val key = args.nextString().sbIntern()
val value = args.nextString().sbIntern()
callbacks["setFlipped"] = luaFunction { isFlipped: Boolean, centerLine: Double? -> self.setGlobalTag(key, value)
self.isFlipped = isFlipped return 0
self.flippedRelativeCenterLine = centerLine ?: 0.0 }
}
private fun setPartTag(self: Animator, args: LuaThread.ArgStack): Int {
callbacks["setAnimationRate"] = luaFunction { rate: Double -> val part = args.nextString().sbIntern()
self.animationRate = rate val key = args.nextString().sbIntern()
} val value = args.nextString().sbIntern()
self.setPartTag(part, key, value)
callbacks["rotateGroup"] = luaFunction { group: ByteString, rotation: Number, immediate: Boolean? -> return 0
self.rotateGroup(group.decode(), rotation.toDouble(), immediate ?: false) }
}
private fun setFlipped(self: Animator, args: LuaThread.ArgStack): Int {
callbacks["currentRotationAngle"] = luaFunction { group: ByteString -> self.isFlipped = args.nextBoolean()
returnBuffer.setTo(self.currentRotationAngle(group.decode())) self.flippedRelativeCenterLine = args.nextOptionalDouble() ?: 0.0
} return 0
}
callbacks["targetRotationAngle"] = luaFunction { group: ByteString ->
returnBuffer.setTo(self.targetRotationAngle(group.decode())) private fun setAnimationRate(self: Animator, args: LuaThread.ArgStack): Int {
} self.animationRate = args.nextDouble()
return 0
callbacks["hasRotationGroup"] = luaFunction { group: ByteString -> }
returnBuffer.setTo(self.hasRotationGroup(group.decode()))
} private fun rotateGroup(self: Animator, args: LuaThread.ArgStack): Int {
val group = args.nextString()
callbacks["rotationGroups"] = luaFunction { val rotation = args.nextDouble()
val groups = self.rotationGroups() val immediate = args.nextOptionalBoolean() == true
val keys = newTable(groups.size, 0) self.rotateGroup(group, rotation, immediate)
groups.withIndex().forEach { (i, v) -> keys[i + 1L] = v.toByteString() } return 0
returnBuffer.setTo(keys) }
}
private fun currentRotationAngle(self: Animator, args: LuaThread.ArgStack): Int {
callbacks["hasTransformationGroup"] = luaFunction { group: ByteString -> val group = args.nextString()
returnBuffer.setTo(self.hasTransformationGroup(group.decode())) args.lua.push(self.currentRotationAngle(group))
} return 1
}
callbacks["transformationGroups"] = luaFunction {
val groups = self.transformationGroups() private fun targetRotationAngle(self: Animator, args: LuaThread.ArgStack): Int {
val keys = newTable(groups.size, 0) args.lua.push(self.targetRotationAngle(args.nextString()))
groups.withIndex().forEach { (i, v) -> keys[i + 1L] = v } return 1
returnBuffer.setTo(keys) }
}
private fun has(self: Animator, getter: (Animator, String) -> Boolean, args: LuaThread.ArgStack): Int {
callbacks["translateTransformGroup"] = luaFunction { group: ByteString, translation: Table -> args.lua.push(getter(self, args.nextString()))
self.translateTransformGroup(group.decode(), toVector2f(translation)) return 1
} }
callbacks["rotateTransformGroup"] = luaFunction { group: ByteString, rotation: Double, center: Table? -> private fun pushNames(self: Animator, getter: (Animator) -> Collection<String>, args: LuaThread.ArgStack): Int {
self.rotateTransformGroup(group.decode(), rotation.toFloat(), if (center == null) Vector2f.ZERO else toVector2f(center)) val values = getter(self)
} args.lua.pushTable(values.size)
callbacks["scaleTransformationGroup"] = luaFunction { group: ByteString, scale: Any, center: Table? -> for ((i, v) in values.withIndex())
if (scale is Number) { args.lua.setTableValue(i + 1L, v)
self.scaleTransformationGroup(group.decode(), Vector2f(scale.toFloat(), scale.toFloat()), if (center == null) Vector2f.ZERO else toVector2f(center))
} else { return 1
self.scaleTransformationGroup(group.decode(), toVector2f(scale), if (center == null) Vector2f.ZERO else toVector2f(center)) }
}
} private fun translateTransformGroup(self: Animator, args: LuaThread.ArgStack): Int {
val group = args.nextString()
callbacks["transformTransformationGroup"] = luaFunctionArray { arguments: Array<out Any?> -> val translation = args.nextVector2f()
val group = arguments[0] as ByteString self.translateTransformGroup(group, translation)
val r00 = arguments[1] as Number return 0
val r01 = arguments[2] as Number }
val r02 = arguments[3] as Number
val r10 = arguments[4] as Number private fun rotateTransformGroup(self: Animator, args: LuaThread.ArgStack): Int {
val r11 = arguments[5] as Number val group = args.nextString()
val r12 = arguments[6] as Number val rotation = args.nextFloat()
val center = args.nextOptionalVector2f() ?: Vector2f.ZERO
self.transformTransformationGroup(group.decode(), r00.toFloat(), self.rotateTransformGroup(group, rotation, center)
r01.toFloat(), return 0
r02.toFloat(), }
r10.toFloat(),
r11.toFloat(), private fun scaleTransformationGroup(self: Animator, args: LuaThread.ArgStack): Int {
r12.toFloat()) val group = args.nextString()
}
if (args.peek() == LuaType.NUMBER) {
callbacks["resetTransformationGroup"] = luaFunction { group: ByteString -> val value = args.nextFloat()
self.resetTransformationGroup(group.decode()) self.scaleTransformationGroup(group, Vector2f(value, value), args.nextOptionalVector2f() ?: Vector2f.ZERO)
} } else {
self.scaleTransformationGroup(group, args.nextVector2f(), args.nextOptionalVector2f() ?: Vector2f.ZERO)
callbacks["particleEmitters"] = luaFunction { }
val groups = self.particleEmitters()
val keys = newTable(groups.size, 0) return 0
groups.withIndex().forEach { (i, v) -> keys[i + 1L] = v } }
returnBuffer.setTo(keys)
} private fun transformTransformationGroup(self: Animator, args: LuaThread.ArgStack): Int {
val group = args.nextString()
callbacks["hasParticleEmitter"] = luaFunction { group: ByteString -> val r00 = args.nextFloat()
returnBuffer.setTo(self.hasParticleEmitter(group.decode())) val r01 = args.nextFloat()
} val r02 = args.nextFloat()
val r10 = args.nextFloat()
callbacks["setParticleEmitterActive"] = luaFunction { emitter: ByteString, state: Boolean -> val r11 = args.nextFloat()
self.setParticleEmitterActive(emitter.decode(), state) val r12 = args.nextFloat()
}
self.transformTransformationGroup(
callbacks["setParticleEmitterEmissionRate"] = luaFunction { emitter: ByteString, rate: Number -> group,
self.setParticleEmitterEmissionRate(emitter.decode(), rate.toDouble()) r00,
} r01,
r02,
callbacks["setParticleEmitterBurstCount"] = luaFunction { emitter: ByteString, count: Number -> r10,
self.setParticleEmitterBurstCount(emitter.decode(), count.toInt()) r11,
} r12)
callbacks["setParticleEmitterOffsetRegion"] = luaFunction { emitter: ByteString, region: Table -> return 0
self.setParticleEmitterOffsetRegion(emitter.decode(), toAABB(region)) }
}
private fun resetTransformationGroup(self: Animator, args: LuaThread.ArgStack): Int {
callbacks["burstParticleEmitter"] = luaFunction { emitter: ByteString -> val group = args.nextString()
self.burstParticleEmitter(emitter.decode()) self.resetTransformationGroup(group)
} return 0
}
callbacks["lights"] = luaFunction {
val groups = self.lights() private fun setParticleEmitterActive(self: Animator, args: LuaThread.ArgStack): Int {
val keys = newTable(groups.size, 0) self.setParticleEmitterActive(args.nextString(), args.nextBoolean())
groups.withIndex().forEach { (i, v) -> keys[i + 1L] = v } return 0
returnBuffer.setTo(keys) }
}
private fun setParticleEmitterEmissionRate(self: Animator, args: LuaThread.ArgStack): Int {
callbacks["hasLight"] = luaFunction { light: ByteString -> self.setParticleEmitterEmissionRate(args.nextString(), args.nextDouble())
returnBuffer.setTo(self.hasLight(light.decode())) return 0
} }
callbacks["setLightActive"] = luaFunction { light: ByteString, state: Boolean -> private fun setParticleEmitterBurstCount(self: Animator, args: LuaThread.ArgStack): Int {
self.setLightActive(light.decode(), state) self.setParticleEmitterBurstCount(args.nextString(), args.nextInt())
} return 0
}
callbacks["setLightPosition"] = luaFunction { light: ByteString, position: Table ->
self.setLightPosition(light.decode(), toVector2d(position)) private fun setParticleEmitterOffsetRegion(self: Animator, args: LuaThread.ArgStack): Int {
} self.setParticleEmitterOffsetRegion(args.nextString(), args.nextAABB())
return 0
callbacks["setLightColor"] = luaFunction { light: ByteString, color: Table -> }
self.setLightColor(light.decode(), toColor(color))
} private fun burstParticleEmitter(self: Animator, args: LuaThread.ArgStack): Int {
self.burstParticleEmitter(args.nextString())
callbacks["setLightPointAngle"] = luaFunction { light: ByteString, angle: Number -> return 0
self.setLightPointAngle(light.decode(), angle.toDouble()) }
}
private fun setLightActive(self: Animator, args: LuaThread.ArgStack): Int {
callbacks["sounds"] = luaFunction { self.setLightActive(args.nextString(), args.nextBoolean())
val groups = self.sounds() return 0
val keys = newTable(groups.size, 0) }
groups.withIndex().forEach { (i, v) -> keys[i + 1L] = v }
returnBuffer.setTo(keys) private fun setLightPosition(self: Animator, args: LuaThread.ArgStack): Int {
} self.setLightPosition(args.nextString(), args.nextVector2d())
return 0
callbacks["hasSound"] = luaFunction { sound: ByteString -> }
returnBuffer.setTo(self.hasSound(sound.decode()))
} private fun setLightColor(self: Animator, args: LuaThread.ArgStack): Int {
self.setLightColor(args.nextString(), args.nextColor())
callbacks["setSoundPool"] = luaFunction { sound: ByteString, sounds: Table -> return 0
self.setSoundPool(sound.decode(), sounds.iterator().map { (it.value as ByteString).decode() }.toList()) }
}
private fun setLightPointAngle(self: Animator, args: LuaThread.ArgStack): Int {
callbacks["setSoundPosition"] = luaFunction { sound: ByteString, position: Table -> self.setLightPointAngle(args.nextString(), args.nextDouble())
self.setSoundPosition(sound.decode(), toVector2d(position)) return 0
} }
callbacks["playSound"] = luaFunction { sound: ByteString, loops: Number? -> private fun setSoundPool(self: Animator, args: LuaThread.ArgStack): Int {
self.playSound(sound.decode(), loops?.toInt() ?: 0) val pool = args.nextString()
} val sounds = args.readTableValues { getString(it)?.sbIntern() ?: throw IllegalArgumentException("invalid values in sound pool table") }
self.setSoundPool(pool, sounds)
callbacks["setSoundVolume"] = luaFunction { sound: ByteString, volume: Number, rampTime: Number? -> return 0
self.setSoundVolume(sound.decode(), volume.toDouble(), rampTime?.toDouble() ?: 0.0) }
}
private fun setSoundPosition(self: Animator, args: LuaThread.ArgStack): Int {
callbacks["setSoundPitch"] = luaFunction { sound: ByteString, pitch: Number, rampTime: Number? -> self.setSoundPosition(args.nextString(), args.nextVector2d())
self.setSoundPitch(sound.decode(), pitch.toDouble(), rampTime?.toDouble() ?: 0.0) return 0
} }
callbacks["stopAllSounds"] = luaFunction { sound: ByteString, rampTime: Number? -> private fun playSound(self: Animator, args: LuaThread.ArgStack): Int {
self.stopAllSounds(sound.decode(), rampTime?.toDouble() ?: 0.0) self.playSound(args.nextString(), args.nextOptionalInt() ?: 0)
} return 0
}
callbacks["effects"] = luaFunction {
val groups = self.effects() private fun setSoundVolume(self: Animator, args: LuaThread.ArgStack): Int {
val keys = newTable(groups.size, 0) self.setSoundVolume(args.nextString(), args.nextDouble(), args.nextOptionalDouble() ?: 0.0)
groups.withIndex().forEach { (i, v) -> keys[i + 1L] = v } return 0
returnBuffer.setTo(keys) }
}
private fun setSoundPitch(self: Animator, args: LuaThread.ArgStack): Int {
callbacks["hasEffect"] = luaFunction { effect: ByteString -> self.setSoundPitch(args.nextString(), args.nextDouble(), args.nextOptionalDouble() ?: 0.0)
returnBuffer.setTo(self.hasEffect(effect.decode())) return 0
} }
callbacks["setEffectActive"] = luaFunction { effect: ByteString, state: Boolean -> private fun stopAllSounds(self: Animator, args: LuaThread.ArgStack): Int {
self.setEffectActive(effect.decode(), state) self.stopAllSounds(args.nextString(), args.nextOptionalDouble() ?: 0.0)
} return 0
}
callbacks["parts"] = luaFunction {
val groups = self.parts() private fun setEffectActive(self: Animator, args: LuaThread.ArgStack): Int {
val keys = newTable(groups.size, 0) self.setEffectActive(args.nextString(), args.nextBoolean())
groups.withIndex().forEach { (i, v) -> keys[i + 1L] = v } return 0
returnBuffer.setTo(keys) }
}
private fun partPoint(self: Animator, args: LuaThread.ArgStack): Int {
callbacks["partPoint"] = luaFunction { part: ByteString, property: ByteString -> args.lua.push(self.partPoint(args.nextString(), args.nextString()))
returnBuffer.setTo(self.partPoint(part.decode(), property.decode())?.let { from(it) }) return 1
} }
callbacks["partPoly"] = luaFunction { part: ByteString, property: ByteString -> private fun partPoly(self: Animator, args: LuaThread.ArgStack): Int {
returnBuffer.setTo(self.partPoly(part.decode(), property.decode())?.let { from(it) }) args.lua.push(self.partPoly(args.nextString(), args.nextString()))
} return 1
}
callbacks["partProperty"] = luaFunction { part: ByteString, property: ByteString ->
returnBuffer.setTo(from(self.partProperty(part.decode(), property.decode()))) private fun partProperty(self: Animator, args: LuaThread.ArgStack): Int {
} args.lua.push(self.partProperty(args.nextString(), args.nextString()))
return 1
callbacks["transformPoint"] = luaFunction { point: Table, part: ByteString -> }
returnBuffer.setTo(from(toVector2d(point).times(self.partTransformation(part.decode()))))
} private fun transformPoint(self: Animator, args: LuaThread.ArgStack): Int {
args.lua.push(args.nextVector2d() * self.partTransformation(args.nextString()))
callbacks["transformPoly"] = luaFunction { poly: Table, part: ByteString -> return 1
returnBuffer.setTo(from(toPoly(poly).transform(self.partTransformation(part.decode())))) }
}
private fun transformPoly(self: Animator, args: LuaThread.ArgStack): Int {
args.lua.push(args.nextPoly().transform(self.partTransformation(args.nextString())))
return 1
}
fun provideAnimatorBindings(self: Animator, lua: LuaThread) {
lua.pushTable()
lua.dup()
lua.storeGlobal("animator")
lua.pushBinding(self, "setAnimationState", ::setAnimationState)
lua.pushBinding(self, "animationState", ::animationState)
lua.pushBinding(self, "animationStateProperty", ::animationStateProperty)
lua.pushBinding(self, "setGlobalTag", ::setGlobalTag)
lua.pushBinding(self, "setPartTag", ::setPartTag)
lua.pushBinding(self, "setFlipped", ::setFlipped)
lua.pushBinding(self, "setAnimationRate", ::setAnimationRate)
lua.pushBinding(self, "rotateGroup", ::rotateGroup)
lua.pushBinding(self, "currentRotationAngle", ::currentRotationAngle)
lua.pushBinding(self, "targetRotationAngle", ::targetRotationAngle)
lua.pushBinding(self, "translateTransformGroup", ::translateTransformGroup)
lua.pushBinding(self, "rotateTransformGroup", ::rotateTransformGroup)
lua.pushBinding(self, "scaleTransformationGroup", ::scaleTransformationGroup)
lua.pushBinding(self, "transformTransformationGroup", ::transformTransformationGroup)
lua.pushBinding(self, "resetTransformationGroup", ::resetTransformationGroup)
lua.pushBinding(self, "setParticleEmitterActive", ::setParticleEmitterActive)
lua.pushBinding(self, "setParticleEmitterEmissionRate", ::setParticleEmitterEmissionRate)
lua.pushBinding(self, "setParticleEmitterBurstCount", ::setParticleEmitterBurstCount)
lua.pushBinding(self, "setParticleEmitterOffsetRegion", ::setParticleEmitterOffsetRegion)
lua.pushBinding(self, "burstParticleEmitter", ::burstParticleEmitter)
lua.pushBinding(self, "setLightActive", ::setLightActive)
lua.pushBinding(self, "setLightPosition", ::setLightPosition)
lua.pushBinding(self, "setLightColor", ::setLightColor)
lua.pushBinding(self, "setLightPointAngle", ::setLightPointAngle)
lua.pushBinding(self, "setSoundPool", ::setSoundPool)
lua.pushBinding(self, "setSoundPosition", ::setSoundPosition)
lua.pushBinding(self, "playSound", ::playSound)
lua.pushBinding(self, "setSoundVolume", ::setSoundVolume)
lua.pushBinding(self, "setSoundPitch", ::setSoundPitch)
lua.pushBinding(self, "stopAllSounds", ::stopAllSounds)
lua.pushBinding(self, "setEffectActive", ::setEffectActive)
lua.pushBinding(self, "partPoint", ::partPoint)
lua.pushBinding(self, "partPoly", ::partPoly)
lua.pushBinding(self, "partProperty", ::partProperty)
lua.pushBinding(self, "transformPoint", ::transformPoint)
lua.pushBinding(self, "transformPoly", ::transformPoly)
lua.pushBinding(self, Animator::hasRotationGroup, "hasRotationGroup", ::has)
lua.pushBinding(self, Animator::hasTransformationGroup, "hasTransformationGroup", ::has)
lua.pushBinding(self, Animator::hasParticleEmitter, "hasParticleEmitter", ::has)
lua.pushBinding(self, Animator::hasLight, "hasLight", ::has)
lua.pushBinding(self, Animator::hasSound, "hasSound", ::has)
lua.pushBinding(self, Animator::hasEffect, "hasEffect", ::has)
lua.pushBinding(self, Animator::hasPart, "hasPart", ::has)
lua.pushBinding(self, Animator::rotationGroups, "rotationGroups", ::pushNames)
lua.pushBinding(self, Animator::transformationGroups, "transformationGroups", ::pushNames)
lua.pushBinding(self, Animator::particleEmitters, "particleEmitters", ::pushNames)
lua.pushBinding(self, Animator::lights, "lights", ::pushNames)
lua.pushBinding(self, Animator::sounds, "sounds", ::pushNames)
lua.pushBinding(self, Animator::effects, "effects", ::pushNames)
lua.pushBinding(self, Animator::parts, "parts", ::pushNames)
lua.pop()
} }

View File

@ -0,0 +1,41 @@
package ru.dbotthepony.kstarbound.lua.bindings
import com.google.common.collect.ImmutableList
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.quest.QuestArcDescriptor
import ru.dbotthepony.kstarbound.defs.quest.QuestDescriptor
import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.LuaType
import ru.dbotthepony.kstarbound.util.sbIntern
fun <T : Any> setOfferedQuests(self: T, prop: (T) -> MutableList<QuestArcDescriptor>, args: LuaThread.ArgStack): Int {
val offeredQuests = prop(self)
offeredQuests.clear()
if (args.hasNext) {
offeredQuests.addAll(args.readTableValues {
val peek = typeAt(it)
if (peek == LuaType.TABLE) {
Starbound.gson.fromJson(getJson(it)!!, QuestArcDescriptor::class.java)
} else if (peek == LuaType.STRING) {
QuestArcDescriptor(ImmutableList.of(QuestDescriptor(getString(it)!!.sbIntern())))
} else {
throw IllegalArgumentException("Invalid value in provided quests table: $peek")
}
})
}
return 0
}
fun <T : Any> setTurnInQuests(self: T, prop: (T) -> MutableList<String>, args: LuaThread.ArgStack): Int {
val turnInQuests = prop(self)
turnInQuests.clear()
if (args.hasNext) {
turnInQuests.addAll(args.readTableValues { getString(it)?.sbIntern() ?: throw IllegalArgumentException("Turn-in quests table contains non-string values") })
}
return 0
}

View File

@ -1,13 +1,9 @@
package ru.dbotthepony.kstarbound.lua.bindings package ru.dbotthepony.kstarbound.lua.bindings
import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.LuaEnvironment import ru.dbotthepony.kstarbound.lua.push
import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.math.Line2d import ru.dbotthepony.kstarbound.math.Line2d
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
import ru.dbotthepony.kstarbound.world.entities.ActorEntity import ru.dbotthepony.kstarbound.world.entities.ActorEntity
import ru.dbotthepony.kstarbound.world.entities.MonsterEntity import ru.dbotthepony.kstarbound.world.entities.MonsterEntity
@ -15,7 +11,84 @@ import ru.dbotthepony.kstarbound.world.entities.NPCEntity
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
fun provideEntityBindings(self: AbstractEntity, lua: LuaEnvironment) { private fun id(self: AbstractEntity, args: LuaThread.ArgStack): Int {
args.lua.push(self.entityID.toLong())
return 1
}
private fun position(self: AbstractEntity, args: LuaThread.ArgStack): Int {
args.lua.push(self.position)
return 1
}
private fun entityType(self: AbstractEntity, args: LuaThread.ArgStack): Int {
args.lua.push(self.type.jsonName)
return 1
}
private fun uniqueId(self: AbstractEntity, args: LuaThread.ArgStack): Int {
args.lua.push(self.uniqueID.get())
return 1
}
private fun persistent(self: AbstractEntity, args: LuaThread.ArgStack): Int {
args.lua.push(self.isPersistent)
return 1
}
private fun entityInSight(self: AbstractEntity, args: LuaThread.ArgStack): Int {
val target = self.world.entities[args.nextInt()]
if (target == null) {
args.lua.push(false)
} else {
args.lua.push(self.world.chunkMap.collide(Line2d(self.position, target.position)) { it.type.isSolidCollision } == null)
}
return 1
}
private fun isValidTarget(self: AbstractEntity, args: LuaThread.ArgStack): Int {
val target = self.world.entities[args.nextInt()]
if (target == null) {
args.lua.push(false)
} else {
if (!self.team.get().canDamage(target.team.get(), self == target)) // original engine always passes `false` to `isSelfDamage`
args.lua.push(false)
else if (target is MonsterEntity)
args.lua.push(target.isAggressive)
else
args.lua.push(target is PlayerEntity)
// TODO: NPC handling here
}
return 1
}
private fun damageTeam(self: AbstractEntity, args: LuaThread.ArgStack): Int {
args.lua.pushTable(hashSize = 2)
args.lua.setTableValue("team", self.team.get().team)
args.lua.setTableValue("type", self.team.get().type.jsonName)
return 1
}
private fun distanceToEntity(self: AbstractEntity, args: LuaThread.ArgStack): Int {
val target = self.world.entities[args.nextInt()]
if (target != null) {
args.lua.push(self.world.geometry.diff(target.position, self.position))
} else {
args.lua.push(Vector2d.ZERO)
}
return 1
}
fun provideEntityBindings(self: AbstractEntity, lua: LuaThread) {
provideWorldBindings(self.world, lua)
if (self is WorldObject) if (self is WorldObject)
provideWorldObjectBindings(self, lua) provideWorldObjectBindings(self, lua)
@ -28,52 +101,19 @@ fun provideEntityBindings(self: AbstractEntity, lua: LuaEnvironment) {
if (self is NPCEntity) if (self is NPCEntity)
provideNPCBindings(self, lua) provideNPCBindings(self, lua)
//provideWorldBindings(self.world, lua) lua.pushTable()
lua.dup()
lua.storeGlobal("entity")
val table = lua.newTable() lua.pushBinding(self, "id", ::id)
lua.globals["entity"] = table lua.pushBinding(self, "position", ::position)
lua.pushBinding(self, "entityType", ::entityType)
lua.pushBinding(self, "uniqueId", ::uniqueId)
lua.pushBinding(self, "persistent", ::persistent)
lua.pushBinding(self, "entityInSight", ::entityInSight)
lua.pushBinding(self, "isValidTarget", ::isValidTarget)
lua.pushBinding(self, "damageTeam", ::damageTeam)
lua.pushBinding(self, "distanceToEntity", ::distanceToEntity)
table["id"] = luaFunction { returnBuffer.setTo(self.entityID.toLong()) } lua.pop()
table["position"] = luaFunction { returnBuffer.setTo(from(self.position)) }
table["entityType"] = luaFunction { returnBuffer.setTo(self.type.jsonName.toByteString()) }
table["uniqueId"] = luaFunction { returnBuffer.setTo(self.uniqueID.get().toByteString()) }
table["persistent"] = luaFunction { returnBuffer.setTo(self.isPersistent) }
table["entityInSight"] = luaFunction { target: Number ->
val entity = self.world.entities[target.toInt()] ?: return@luaFunction returnBuffer.setTo(false)
returnBuffer.setTo(self.world.chunkMap.collide(Line2d(self.position, entity.position)) { it.type.isSolidCollision } == null)
}
table["isValidTarget"] = luaFunction { target: Number ->
val entity = self.world.entities[target.toInt()] ?: return@luaFunction returnBuffer.setTo(false)
if (!self.team.get().canDamage(entity.team.get(), self == entity)) // original engine always passes `false` to `isSelfDamage`
return@luaFunction returnBuffer.setTo(false)
if (entity is MonsterEntity)
return@luaFunction returnBuffer.setTo(entity.isAggressive)
// TODO: NPC handling here
returnBuffer.setTo(entity is PlayerEntity)
}
table["damageTeam"] = luaFunction {
val result = newTable()
result["team"] = self.team.get().team.toLong()
result["type"] = self.type.jsonName.toByteString()
returnBuffer.setTo(result)
}
table["distanceToEntity"] = luaFunction { entity: Number ->
val find = self.world.entities[entity.toInt()]
if (find != null) {
returnBuffer.setTo(from(self.world.geometry.diff(find.position, self.position)))
} else {
returnBuffer.setTo(from(Vector2d.ZERO))
}
}
} }

View File

@ -1,186 +1,227 @@
package ru.dbotthepony.kstarbound.lua.bindings package ru.dbotthepony.kstarbound.lua.bindings
import com.google.common.collect.ImmutableMap
import com.google.common.collect.ImmutableSet import com.google.common.collect.ImmutableSet
import org.classdump.luna.ByteString import com.google.gson.JsonNull
import org.classdump.luna.Table import com.google.gson.reflect.TypeToken
import ru.dbotthepony.kommons.collect.collect import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kommons.collect.map import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.DamageSource import ru.dbotthepony.kstarbound.defs.DamageSource
import ru.dbotthepony.kstarbound.defs.EntityDamageTeam import ru.dbotthepony.kstarbound.defs.EntityDamageTeam
import ru.dbotthepony.kstarbound.defs.PhysicsForceRegion import ru.dbotthepony.kstarbound.defs.PhysicsForceRegion
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
import ru.dbotthepony.kstarbound.fromJsonFast import ru.dbotthepony.kstarbound.fromJsonFast
import ru.dbotthepony.kstarbound.lua.LuaEnvironment import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.from import ru.dbotthepony.kstarbound.lua.nextVector2d
import ru.dbotthepony.kstarbound.lua.get import ru.dbotthepony.kstarbound.lua.push
import ru.dbotthepony.kstarbound.lua.iterator
import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.stream
import ru.dbotthepony.kstarbound.lua.toJson
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.lua.toVector2d
import ru.dbotthepony.kstarbound.util.SBPattern import ru.dbotthepony.kstarbound.util.SBPattern
import ru.dbotthepony.kstarbound.util.sbIntern import ru.dbotthepony.kstarbound.util.sbIntern
import ru.dbotthepony.kstarbound.util.valueOf import ru.dbotthepony.kstarbound.util.valueOf
import ru.dbotthepony.kstarbound.world.entities.ActorEntity import ru.dbotthepony.kstarbound.world.entities.ActorEntity
import ru.dbotthepony.kstarbound.world.entities.MonsterEntity import ru.dbotthepony.kstarbound.world.entities.MonsterEntity
fun provideMonsterBindings(self: MonsterEntity, lua: LuaEnvironment) { private object DropPoolToken : TypeToken<Either<ImmutableMap<String, Registry.Ref<TreasurePoolDefinition>>, Registry.Ref<TreasurePoolDefinition>>>()
val callbacks = lua.newTable()
lua.globals["monster"] = callbacks
callbacks["type"] = luaFunction { private fun type(self: MonsterEntity, args: LuaThread.ArgStack): Int {
returnBuffer.setTo(self.variant.type) args.lua.push(self.variant.type)
} return 1
}
callbacks["seed"] = luaFunction {
// what the fuck. private fun seed(self: MonsterEntity, args: LuaThread.ArgStack): Int {
returnBuffer.setTo(self.variant.seed.toString()) // FIXME: what the fuck.
} args.lua.push(self.variant.seed.toString())
return 1
callbacks["seedNumber"] = luaFunction { }
returnBuffer.setTo(self.variant.seed)
} private fun seedNumber(self: MonsterEntity, args: LuaThread.ArgStack): Int {
args.lua.push(self.variant.seed)
callbacks["uniqueParameters"] = luaFunction { return 1
returnBuffer.setTo(from(self.variant.uniqueParameters)) }
}
private fun uniqueParameters(self: MonsterEntity, args: LuaThread.ArgStack): Int {
callbacks["level"] = luaFunction { args.lua.push(self.variant.uniqueParameters)
// TODO: this makes half sense return 1
returnBuffer.setTo(self.monsterLevel ?: 0.0) }
}
private fun level(self: MonsterEntity, args: LuaThread.ArgStack): Int {
callbacks["setDamageOnTouch"] = luaFunction { damage: Boolean -> // FIXME: this makes half sense
self.damageOnTouch = damage args.lua.push(self.monsterLevel ?: 0.0)
} return 1
}
callbacks["setDamageSources"] = luaFunction { sources: Table? ->
self.customDamageSources.clear() private fun setDamageOnTouch(self: MonsterEntity, args: LuaThread.ArgStack): Int {
self.damageOnTouch = args.nextBoolean()
if (sources != null) { return 0
for ((_, v) in sources) { }
self.customDamageSources.add(Starbound.gson.fromJson((v as Table).toJson(), DamageSource::class.java))
} private fun setDamageSources(self: MonsterEntity, args: LuaThread.ArgStack): Int {
} self.customDamageSources.clear()
}
if (args.isNotEmpty) {
callbacks["setDamageParts"] = luaFunction { parts: Table? -> val sources = args.readTableValues { Starbound.gson.fromJsonFast(getJson(it)!!, DamageSource::class.java) }
if (parts == null) { self.customDamageSources.addAll(sources)
self.animationDamageParts.clear() }
} else {
val strings = parts.stream().map { (_, v) -> v.toString() }.collect(ImmutableSet.toImmutableSet()) return 0
self.animationDamageParts.removeIf { it !in strings } }
for (v in strings) { private fun setDamageParts(self: MonsterEntity, args: LuaThread.ArgStack): Int {
if (v !in self.animationDamageParts) { if (args.isEmpty) {
self.animationDamageParts.add(v) self.animationDamageParts.clear()
} } else {
} val strings = ImmutableSet.copyOf(args.readTableValues { getString(it) ?: throw IllegalArgumentException("Invalid values in damage parts table") })
} self.animationDamageParts.removeIf { it !in strings }
}
for (v in strings) {
callbacks["setAggressive"] = luaFunction { isAggressive: Boolean -> if (v !in self.animationDamageParts) {
self.isAggressive = isAggressive self.animationDamageParts.add(v)
} }
}
callbacks["setActiveSkillName"] = luaFunction { name: ByteString -> }
self.activeSkillName = name.decode().sbIntern()
} return 0
}
callbacks["setDropPool"] = luaFunction { pool: Any ->
self.dropPool = Starbound.gson.fromJsonFast(toJsonFromLua(pool)) private fun setAggressive(self: MonsterEntity, args: LuaThread.ArgStack): Int {
} self.isAggressive = args.nextBoolean()
return 0
callbacks["toAbsolutePosition"] = luaFunction { position: Table -> }
returnBuffer.setTo(from(self.movement.getAbsolutePosition(toVector2d(position))))
} private fun setActiveSkillName(self: MonsterEntity, args: LuaThread.ArgStack): Int {
self.activeSkillName = args.nextString().sbIntern()
callbacks["mouthPosition"] = luaFunction { return 0
returnBuffer.setTo(from(self.mouthPosition)) }
}
private fun setDropPool(self: MonsterEntity, args: LuaThread.ArgStack): Int {
// This callback is registered here rather than in self.dropPool = Starbound.gson.fromJsonFast(args.nextOptionalJson() ?: JsonNull.INSTANCE, DropPoolToken)
// makeActorMovementControllerCallbacks return 0
// because it requires access to world }
callbacks["flyTo"] = luaFunction { position: Table ->
self.movement.controlFly = self.world.geometry.diff(toVector2d(position), self.movement.position) private fun toAbsolutePosition(self: MonsterEntity, args: LuaThread.ArgStack): Int {
} args.lua.push(self.movement.getAbsolutePosition(args.nextVector2d()))
return 0
callbacks["setDeathParticleBurst"] = luaFunction { value: ByteString? -> }
self.deathParticlesBurst = value?.decode()?.sbIntern() ?: ""
} private fun mouthPosition(self: MonsterEntity, args: LuaThread.ArgStack): Int {
args.lua.push(self.mouthPosition)
callbacks["setDeathSound"] = luaFunction { value: ByteString? -> return 0
self.deathSound = value?.decode()?.sbIntern() ?: "" }
}
// This callback is registered here rather than in
callbacks["setPhysicsForces"] = luaFunction { forces: Table? -> // makeActorMovementControllerCallbacks
if (forces == null) { // because it requires access to world
self.forceRegions.clear() private fun flyTo(self: MonsterEntity, args: LuaThread.ArgStack): Int {
} else { self.movement.controlFly = self.world.geometry.diff(args.nextVector2d(), self.movement.position)
self.forceRegions.clear() return 0
}
for ((_, v) in forces) {
self.forceRegions.add(Starbound.gson.fromJsonFast(toJsonFromLua(v), PhysicsForceRegion::class.java)) private fun setDeathParticleBurst(self: MonsterEntity, args: LuaThread.ArgStack): Int {
} self.deathParticlesBurst = args.nextOptionalString()?.sbIntern() ?: ""
} return 0
} }
callbacks["setName"] = luaFunction { name: ByteString? -> private fun setDeathSound(self: MonsterEntity, args: LuaThread.ArgStack): Int {
self.networkName = name?.decode() self.deathSound = args.nextOptionalString()?.sbIntern() ?: ""
} return 0
}
callbacks["setDisplayNametag"] = luaFunction { shouldDisplay: Boolean ->
self.displayNameTag = shouldDisplay private fun setPhysicsForces(self: MonsterEntity, args: LuaThread.ArgStack): Int {
} self.forceRegions.clear()
callbacks["say"] = luaFunction { line: ByteString, tags: Table? -> if (args.isNotEmpty) {
var actualLine = line.decode() val forces = args.readTableValues { Starbound.gson.fromJsonFast(getJson(it)!!, PhysicsForceRegion::class.java) }
self.forceRegions.addAll(forces)
if (tags != null) { }
actualLine = SBPattern.of(actualLine).resolveOrSkip({ tags[it]?.toString() })
} return 0
}
if (actualLine.isNotBlank()) {
self.addChatMessage(actualLine.sbIntern()) private fun setName(self: MonsterEntity, args: LuaThread.ArgStack): Int {
} self.networkName = args.nextOptionalString()?.sbIntern() ?: ""
return 0
returnBuffer.setTo(actualLine.isNotBlank()) }
}
private fun setDisplayNametag(self: MonsterEntity, args: LuaThread.ArgStack): Int {
callbacks["sayPortrait"] = luaFunction { line: ByteString, portrait: ByteString, tags: Table? -> self.displayNameTag = args.nextBoolean()
var actualLine = line.decode() return 0
}
if (tags != null) {
actualLine = SBPattern.of(actualLine).resolveOrSkip({ tags[it]?.toString() }) private fun say(self: MonsterEntity, hasPortrait: Boolean, args: LuaThread.ArgStack): Int {
} var line = args.nextString()
val portrait = if (hasPortrait) args.nextString() else null
if (actualLine.isNotBlank()) {
self.addChatMessage(actualLine.sbIntern(), portrait.decode().sbIntern()) if (args.hasNext) {
} val tags = args.readTable(keyVisitor = { getString(it) ?: throw IllegalArgumentException("Pattern tag replacement table contains non-string keys") }, valueVisitor = { getString(it) ?: throw IllegalArgumentException("Pattern tag replacement table contains non-string values") })
line = SBPattern.of(line).resolveOrSkip({ t -> tags.firstOrNull { it.first == t }?.second })
returnBuffer.setTo(actualLine.isNotBlank()) }
}
if (line.isNotBlank()) {
callbacks["setDamageTeam"] = luaFunction { team: Any -> self.addChatMessage(line.sbIntern(), portrait = portrait)
self.team.accept(Starbound.gson.fromJsonFast(toJsonFromLua(team), EntityDamageTeam::class.java)) args.lua.push(true)
} } else {
args.lua.push(false)
callbacks["setUniqueId"] = luaFunction { name: ByteString? -> }
self.uniqueID.accept(name?.decode()?.sbIntern())
} return 1
}
callbacks["setDamageBar"] = luaFunction { type: ByteString ->
self.damageBarType = ActorEntity.DamageBarType.entries.valueOf(type.decode()) private fun setDamageTeam(self: MonsterEntity, args: LuaThread.ArgStack): Int {
} self.team.accept(Starbound.gson.fromJsonFast(args.nextJson(), EntityDamageTeam::class.java))
return 0
callbacks["setInteractive"] = luaFunction { isInteractive: Boolean -> }
self.isInteractive = isInteractive
} private fun setUniqueId(self: MonsterEntity, args: LuaThread.ArgStack): Int {
self.uniqueID.accept(args.nextOptionalString()?.sbIntern())
callbacks["setAnimationParameter"] = luaFunction { name: ByteString, value: Any? -> return 0
self.scriptedAnimationParameters[name.decode().sbIntern()] = toJsonFromLua(value) }
}
private fun setDamageBar(self: MonsterEntity, args: LuaThread.ArgStack): Int {
self.damageBarType = ActorEntity.DamageBarType.entries.valueOf(args.nextString())
return 0
}
private fun setInteractive(self: MonsterEntity, args: LuaThread.ArgStack): Int {
self.isInteractive = args.nextBoolean()
return 0
}
private fun setAnimationParameter(self: MonsterEntity, args: LuaThread.ArgStack): Int {
self.scriptedAnimationParameters[args.nextString().sbIntern()] = args.nextJson()
return 0
}
fun provideMonsterBindings(self: MonsterEntity, lua: LuaThread) {
lua.pushTable()
lua.dup()
lua.storeGlobal("monster")
lua.pushBinding(self, "type", ::type)
lua.pushBinding(self, "seed", ::seed)
lua.pushBinding(self, "seedNumber", ::seedNumber)
lua.pushBinding(self, "uniqueParameters", ::uniqueParameters)
lua.pushBinding(self, "level", ::level)
lua.pushBinding(self, "setDamageOnTouch", ::setDamageOnTouch)
lua.pushBinding(self, "setDamageSources", ::setDamageSources)
lua.pushBinding(self, "setDamageParts", ::setDamageParts)
lua.pushBinding(self, "setAggressive", ::setAggressive)
lua.pushBinding(self, "setActiveSkillName", ::setActiveSkillName)
lua.pushBinding(self, "setDropPool", ::setDropPool)
lua.pushBinding(self, "toAbsolutePosition", ::toAbsolutePosition)
lua.pushBinding(self, "mouthPosition", ::mouthPosition)
lua.pushBinding(self, "flyTo", ::flyTo)
lua.pushBinding(self, "setDeathParticleBurst", ::setDeathParticleBurst)
lua.pushBinding(self, "setDeathSound", ::setDeathSound)
lua.pushBinding(self, "setPhysicsForces", ::setPhysicsForces)
lua.pushBinding(self, "setName", ::setName)
lua.pushBinding(self, "setDisplayNametag", ::setDisplayNametag)
lua.pushBinding(self, false, "say", ::say)
lua.pushBinding(self, true, "sayPortrait", ::say)
lua.pushBinding(self, "setDamageTeam", ::setDamageTeam)
lua.pushBinding(self, "setUniqueId", ::setUniqueId)
lua.pushBinding(self, "setDamageBar", ::setDamageBar)
lua.pushBinding(self, "setInteractive", ::setInteractive)
lua.pushBinding(self, "setAnimationParameter", ::setAnimationParameter)
lua.pop()
} }

View File

@ -1,20 +1,14 @@
package ru.dbotthepony.kstarbound.lua.bindings package ru.dbotthepony.kstarbound.lua.bindings
import org.classdump.luna.Table import com.google.gson.JsonNull
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.actor.ActorMovementModifiers
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
import ru.dbotthepony.kstarbound.defs.actor.ActorMovementModifiers
import ru.dbotthepony.kstarbound.fromJsonFast import ru.dbotthepony.kstarbound.fromJsonFast
import ru.dbotthepony.kstarbound.lua.LuaEnvironment import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.from import ru.dbotthepony.kstarbound.lua.nextVector2d
import ru.dbotthepony.kstarbound.lua.luaFunction import ru.dbotthepony.kstarbound.lua.push
import ru.dbotthepony.kstarbound.lua.set import ru.dbotthepony.kstarbound.lua.setTableValue
import ru.dbotthepony.kstarbound.lua.tableFrom
import ru.dbotthepony.kstarbound.lua.tableOf
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.lua.toJson
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.lua.toVector2d
import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.world.Direction import ru.dbotthepony.kstarbound.world.Direction
import ru.dbotthepony.kstarbound.world.entities.ActorMovementController import ru.dbotthepony.kstarbound.world.entities.ActorMovementController
@ -23,230 +17,369 @@ 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) {
fun init(lua: LuaEnvironment) { private fun mass(args: LuaThread.ArgStack): Int {
val callbacks = lua.newTable() args.lua.push(self.mass)
lua.globals["mcontroller"] = callbacks return 1
}
// pass-through private fun localBoundBox(args: LuaThread.ArgStack): Int {
callbacks["mass"] = luaFunction { args.lua.push(self.computeLocalCollisionAABB())
returnBuffer.setTo(self.mass) return 1
}
// TODO
private fun boundBox(args: LuaThread.ArgStack): Int {
args.lua.push(self.computeLocalCollisionAABB())
return 1
}
private fun collisionBoundBox(args: LuaThread.ArgStack): Int {
args.lua.push(self.computeGlobalCollisionAABB())
return 1
}
private fun collisionPoly(args: LuaThread.ArgStack): Int {
args.lua.push(self.movementParameters.collisionPoly?.map({ it }, { it.firstOrNull() }) ?: Poly.EMPTY)
return 1
}
private fun collisionPolies(args: LuaThread.ArgStack): Int {
val result = self.movementParameters.collisionPoly?.map({ listOf(it) }, { it }) ?: listOf(Poly.EMPTY)
args.lua.pushTable(result.size)
for ((i, v) in result.withIndex())
args.lua.setTableValue(i + 1L, v)
return 1
}
private fun collisionBody(args: LuaThread.ArgStack): Int {
args.lua.push(self.computeGlobalHitboxes().firstOrNull() ?: Poly.EMPTY)
return 1
}
private fun collisionBodies(args: LuaThread.ArgStack): Int {
val result = self.computeGlobalHitboxes()
args.lua.pushTable(result.size)
for ((i, v) in result.withIndex())
args.lua.setTableValue(i + 1L, v)
return 1
}
private fun position(args: LuaThread.ArgStack): Int { args.lua.push(self.position); return 1 }
private fun xPosition(args: LuaThread.ArgStack): Int { args.lua.push(self.xPosition); return 1 }
private fun yPosition(args: LuaThread.ArgStack): Int { args.lua.push(self.yPosition); return 1 }
private fun velocity(args: LuaThread.ArgStack): Int { args.lua.push(self.velocity); return 1 }
private fun xVelocity(args: LuaThread.ArgStack): Int { args.lua.push(self.xVelocity); return 1 }
private fun yVelocity(args: LuaThread.ArgStack): Int { args.lua.push(self.yVelocity); return 1 }
private fun rotation(args: LuaThread.ArgStack): Int { args.lua.push(self.rotation); return 1 }
private fun isColliding(args: LuaThread.ArgStack): Int { args.lua.push(self.isColliding); return 1 }
private fun isNullColliding(args: LuaThread.ArgStack): Int { args.lua.push(self.isCollidingWithNull); return 1 }
private fun isCollisionStuck(args: LuaThread.ArgStack): Int { args.lua.push(self.isCollisionStuck); return 1 }
private fun stickingDirection(args: LuaThread.ArgStack): Int { args.lua.push(self.stickingDirection); return 1 }
private fun liquidPercentage(args: LuaThread.ArgStack): Int { args.lua.push(self.liquidPercentage); return 1 }
private fun liquidId(args: LuaThread.ArgStack): Int { args.lua.push(self.liquid?.id?.toLong() ?: 0L); return 1 }
private fun liquidName(args: LuaThread.ArgStack): Int { args.lua.push(self.liquid?.key); return 1 }
private fun onGround(args: LuaThread.ArgStack): Int { args.lua.push(self.isOnGround); return 1 }
private fun zeroG(args: LuaThread.ArgStack): Int { args.lua.push(self.isZeroGravity); return 1 }
private fun atWorldLimit(args: LuaThread.ArgStack): Int {
args.lua.push(self.isAtWorldLimit(args.nextBoolean()))
return 1
}
private fun setAnchorState(args: LuaThread.ArgStack): Int {
self.anchorNetworkState = AnchorNetworkState(args.nextInt(), args.nextInt())
return 0
}
private fun resetAnchorState(args: LuaThread.ArgStack): Int {
self.anchorNetworkState = null
return 0
}
private fun anchorState(args: LuaThread.ArgStack): Int {
val anchorState = self.anchorNetworkState
if (anchorState != null) {
args.lua.push(anchorState.entityID.toLong())
args.lua.push(anchorState.positionIndex.toLong())
return 2
} else {
return 0
}
}
private fun setPosition(args: LuaThread.ArgStack): Int {
resetPathMove = true
self.position = args.nextVector2d()
return 0
}
private fun translate(args: LuaThread.ArgStack): Int {
resetPathMove = true
self.position += args.nextVector2d()
return 0
}
private fun setXPosition(args: LuaThread.ArgStack): Int {
resetPathMove = true
self.xPosition = args.nextDouble()
return 0
}
private fun setYPosition(args: LuaThread.ArgStack): Int {
resetPathMove = true
self.yPosition = args.nextDouble()
return 0
}
private fun setVelocity(args: LuaThread.ArgStack): Int {
resetPathMove = true
self.velocity = args.nextVector2d()
return 0
}
private fun setXVelocity(args: LuaThread.ArgStack): Int {
resetPathMove = true
self.xVelocity = args.nextDouble()
return 0
}
private fun setYVelocity(args: LuaThread.ArgStack): Int {
resetPathMove = true
self.yVelocity = args.nextDouble()
return 0
}
private fun addMomentum(args: LuaThread.ArgStack): Int {
resetPathMove = true
if (self.mass != 0.0) // let's not collapse into black hole
self.velocity += args.nextVector2d() / self.mass
return 0
}
private fun setRotation(args: LuaThread.ArgStack): Int {
resetPathMove = true
self.rotation = args.nextDouble()
return 0
}
private fun baseParameters(args: LuaThread.ArgStack): Int { args.lua.push(Starbound.gson.toJsonTree(self.actorMovementParameters)); return 1 }
private fun walking(args: LuaThread.ArgStack): Int { args.lua.push(self.isWalking); return 1 }
private fun running(args: LuaThread.ArgStack): Int { args.lua.push(self.isRunning); return 1 }
private fun movingDirection(args: LuaThread.ArgStack): Int { args.lua.push(self.movingDirection.numericalValue); return 1 }
private fun facingDirection(args: LuaThread.ArgStack): Int { args.lua.push(self.facingDirection.numericalValue); return 1 }
private fun crouching(args: LuaThread.ArgStack): Int { args.lua.push(self.isCrouching); return 1 }
private fun flying(args: LuaThread.ArgStack): Int { args.lua.push(self.isFlying); return 1 }
private fun falling(args: LuaThread.ArgStack): Int { args.lua.push(self.isFalling); return 1 }
private fun canJump(args: LuaThread.ArgStack): Int { args.lua.push(self.canJump); return 1 }
private fun jumping(args: LuaThread.ArgStack): Int { args.lua.push(self.isJumping); return 1 }
private fun groundMovement(args: LuaThread.ArgStack): Int { args.lua.push(self.isGroundMovement); return 1 }
private fun liquidMovement(args: LuaThread.ArgStack): Int { args.lua.push(self.isLiquidMovement); return 1 }
// controls, stored locally, reset automatically or are persistent
private fun controlRotation(args: LuaThread.ArgStack): Int {
controlRotationRate += args.nextDouble()
return 0
}
private fun controlAcceleration(args: LuaThread.ArgStack): Int {
controlAcceleration += args.nextVector2d()
return 0
}
private fun controlForce(args: LuaThread.ArgStack): Int {
controlForce += args.nextVector2d()
return 0
}
private fun controlApproachVelocity(args: LuaThread.ArgStack): Int {
controlApproachVelocity = ActorMovementController.ApproachVelocityCommand(args.nextVector2d(), args.nextDouble())
return 0
}
private fun controlApproachVelocityAlongAngle(args: LuaThread.ArgStack): Int {
controlApproachVelocityAlongAngle = ActorMovementController.ApproachVelocityAngleCommand(args.nextDouble(), args.nextDouble(), args.nextDouble(), args.nextBoolean())
return 0
}
private fun controlApproachXVelocity(args: LuaThread.ArgStack): Int {
controlApproachVelocityAlongAngle = ActorMovementController.ApproachVelocityAngleCommand(0.0, args.nextDouble(), args.nextDouble(), false)
return 0
}
private fun controlApproachYVelocity(args: LuaThread.ArgStack): Int {
controlApproachVelocityAlongAngle = ActorMovementController.ApproachVelocityAngleCommand(PI / 2.0, args.nextDouble(), args.nextDouble(), false)
return 0
}
private fun controlParameters(args: LuaThread.ArgStack): Int {
controlParameters = controlParameters.merge(Starbound.gson.fromJsonFast(args.nextJson(), ActorMovementParameters::class.java))
return 0
}
private fun controlModifiers(args: LuaThread.ArgStack): Int {
controlModifiers = controlModifiers.merge(Starbound.gson.fromJsonFast(args.nextJson(), ActorMovementModifiers::class.java))
return 0
}
private fun controlMove(args: LuaThread.ArgStack): Int {
// why?
val new = Direction.valueOf(args.nextDouble())
val shouldRun = args.nextOptionalBoolean()
if (new != null) {
controlMove = new
controlShouldRun = shouldRun ?: false
} }
callbacks["localBoundBox"] = luaFunction { return 0
returnBuffer.setTo(from(self.computeLocalCollisionAABB())) }
}
callbacks["boundBox"] = luaFunction { private fun controlFace(args: LuaThread.ArgStack): Int {
returnBuffer.setTo(from(self.computeLocalCollisionAABB())) // why?
} val new = Direction.valueOf(args.nextDouble())
callbacks["collisionBoundBox"] = luaFunction { if (new != null)
returnBuffer.setTo(from(self.computeGlobalCollisionAABB())) controlFace = new
}
callbacks["collisionPoly"] = luaFunction { return 0
returnBuffer.setTo(from(self.movementParameters.collisionPoly?.map({ it }, { it.firstOrNull() }) ?: Poly.EMPTY)) }
}
callbacks["collisionPolies"] = luaFunction { private fun controlDown(args: LuaThread.ArgStack): Int { controlDown = true; return 0 }
returnBuffer.setTo(tableFrom((self.movementParameters.collisionPoly?.map({ listOf(it) }, { it }) ?: listOf(Poly.EMPTY)).map { from(it) })) private fun controlCrouch(args: LuaThread.ArgStack): Int { controlCrouch = true; return 0 }
} private fun controlHoldJump(args: LuaThread.ArgStack): Int { controlHoldJump = true; return 0 }
private fun controlJump(args: LuaThread.ArgStack): Int { controlJump = args.nextBoolean(); return 0 }
private fun controlFly(args: LuaThread.ArgStack): Int { controlFly = args.nextVector2d(); return 0 }
callbacks["collisionBody"] = luaFunction { private fun controlPathMove(args: LuaThread.ArgStack): Int {
returnBuffer.setTo(from(self.computeGlobalHitboxes().firstOrNull() ?: Poly.EMPTY)) val position = args.nextVector2d()
}
callbacks["collisionBodies"] = luaFunction { if (pathMoveResult?.first == position) {
returnBuffer.setTo(tableFrom(self.computeGlobalHitboxes().map { from(it) })) val pathMoveResult = this.pathMoveResult!!
} this.pathMoveResult = null
args.lua.push(pathMoveResult.second)
} else {
pathMoveResult = null
callbacks["position"] = luaFunction { returnBuffer.setTo(tableOf(self.xPosition, self.yPosition)) } val run = args.nextOptionalBoolean() == true
callbacks["xPosition"] = luaFunction { returnBuffer.setTo(self.xPosition) } val parameters = args.nextOptionalJson()
callbacks["yPosition"] = luaFunction { returnBuffer.setTo(self.yPosition) }
callbacks["velocity"] = luaFunction { returnBuffer.setTo(tableOf(self.xVelocity, self.yVelocity)) }
callbacks["xVelocity"] = luaFunction { returnBuffer.setTo(self.xVelocity) }
callbacks["yVelocity"] = luaFunction { returnBuffer.setTo(self.yVelocity) }
callbacks["rotation"] = luaFunction { returnBuffer.setTo(self.rotation) }
callbacks["isColliding"] = luaFunction { returnBuffer.setTo(self.isColliding) }
callbacks["isNullColliding"] = luaFunction { returnBuffer.setTo(self.isCollidingWithNull) }
callbacks["isCollisionStuck"] = luaFunction { returnBuffer.setTo(self.isCollisionStuck) }
callbacks["stickingDirection"] = luaFunction { returnBuffer.setTo(self.stickingDirection) }
callbacks["liquidPercentage"] = luaFunction { returnBuffer.setTo(self.liquidPercentage) }
callbacks["liquidId"] = luaFunction { returnBuffer.setTo(self.liquid?.id ?: 0) }
callbacks["liquidName"] = luaFunction { returnBuffer.setTo(self.liquid?.key.toByteString()) }
callbacks["onGround"] = luaFunction { returnBuffer.setTo(self.isOnGround) }
callbacks["zeroG"] = luaFunction { returnBuffer.setTo(self.isZeroGravity) }
callbacks["atWorldLimit"] = luaFunction { bottomOnly: Boolean -> val result = self.pathMove(position, run, Starbound.gson.fromJsonFast(parameters ?: JsonNull.INSTANCE))
returnBuffer.setTo(self.isAtWorldLimit(bottomOnly))
}
callbacks["setAnchorState"] = luaFunction { anchor: Number, index: Number -> if (result == null) {
self.anchorNetworkState = AnchorNetworkState(anchor.toInt(), index.toInt()) controlPathMove = position to false
}
callbacks["resetAnchorState"] = luaFunction {
self.anchorNetworkState = null
}
callbacks["anchorState"] = luaFunction {
val anchorState = self.anchorNetworkState
if (anchorState != null) {
returnBuffer.setTo(anchorState.entityID, anchorState.positionIndex)
} }
args.lua.push(result?.second)
} }
callbacks["setPosition"] = luaFunction { value: Table -> return 1
resetPathMove = true }
self.position = toVector2d(value)
}
callbacks["setXPosition"] = luaFunction { value: Number -> private fun pathfinding(args: LuaThread.ArgStack): Int {
resetPathMove = true args.lua.push(self.pathController.isPathfinding)
self.xPosition = value.toDouble() return 1
} }
callbacks["setYPosition"] = luaFunction { value: Number -> private fun autoClearControls(args: LuaThread.ArgStack): Int {
resetPathMove = true args.lua.push(autoClearControls)
self.yPosition = value.toDouble() return 1
} }
callbacks["translate"] = luaFunction { value: Table -> private fun setAutoClearControls(args: LuaThread.ArgStack): Int {
resetPathMove = true autoClearControls = args.nextBoolean()
self.position += toVector2d(value) return 0
} }
callbacks["setVelocity"] = luaFunction { value: Table -> private fun clearControls(args: LuaThread.ArgStack): Int {
resetPathMove = true clearControls()
self.velocity = toVector2d(value) return 0
} }
callbacks["setXVelocity"] = luaFunction { value: Number -> fun init(lua: LuaThread) {
resetPathMove = true lua.pushTable()
self.xVelocity = value.toDouble() lua.dup()
} lua.storeGlobal("mcontroller")
callbacks["setYVelocity"] = luaFunction { value: Number -> lua.setTableValue("mass", ::mass)
resetPathMove = true lua.setTableValue("localBoundBox", ::localBoundBox)
self.yVelocity = value.toDouble() lua.setTableValue("boundBox", ::boundBox)
} lua.setTableValue("collisionBoundBox", ::collisionBoundBox)
lua.setTableValue("collisionPoly", ::collisionPoly)
lua.setTableValue("collisionPolies", ::collisionPolies)
lua.setTableValue("collisionBody", ::collisionBody)
lua.setTableValue("collisionBodies", ::collisionBodies)
lua.setTableValue("position", ::position)
lua.setTableValue("xPosition", ::xPosition)
lua.setTableValue("yPosition", ::yPosition)
lua.setTableValue("velocity", ::velocity)
lua.setTableValue("xVelocity", ::xVelocity)
lua.setTableValue("yVelocity", ::yVelocity)
lua.setTableValue("rotation", ::rotation)
lua.setTableValue("isColliding", ::isColliding)
lua.setTableValue("isNullColliding", ::isNullColliding)
lua.setTableValue("isCollisionStuck", ::isCollisionStuck)
lua.setTableValue("stickingDirection", ::stickingDirection)
lua.setTableValue("liquidPercentage", ::liquidPercentage)
lua.setTableValue("liquidId", ::liquidId)
lua.setTableValue("liquidName", ::liquidName)
lua.setTableValue("onGround", ::onGround)
lua.setTableValue("zeroG", ::zeroG)
lua.setTableValue("atWorldLimit", ::atWorldLimit)
lua.setTableValue("setAnchorState", ::setAnchorState)
lua.setTableValue("resetAnchorState", ::resetAnchorState)
lua.setTableValue("anchorState", ::anchorState)
lua.setTableValue("setPosition", ::setPosition)
lua.setTableValue("setXPosition", ::setXPosition)
lua.setTableValue("setYPosition", ::setYPosition)
lua.setTableValue("translate", ::translate)
lua.setTableValue("setVelocity", ::setVelocity)
lua.setTableValue("setXVelocity", ::setXVelocity)
lua.setTableValue("setYVelocity", ::setYVelocity)
lua.setTableValue("addMomentum", ::addMomentum)
lua.setTableValue("setRotation", ::setRotation)
lua.setTableValue("baseParameters", ::baseParameters)
lua.setTableValue("walking", ::walking)
lua.setTableValue("running", ::running)
lua.setTableValue("movingDirection", ::movingDirection)
lua.setTableValue("facingDirection", ::facingDirection)
lua.setTableValue("crouching", ::crouching)
lua.setTableValue("flying", ::flying)
lua.setTableValue("falling", ::falling)
lua.setTableValue("canJump", ::canJump)
lua.setTableValue("jumping", ::jumping)
lua.setTableValue("groundMovement", ::groundMovement)
lua.setTableValue("liquidMovement", ::liquidMovement)
lua.setTableValue("controlRotation", ::controlRotation)
lua.setTableValue("controlAcceleration", ::controlAcceleration)
lua.setTableValue("controlForce", ::controlForce)
lua.setTableValue("controlApproachVelocity", ::controlApproachVelocity)
lua.setTableValue("controlApproachVelocityAlongAngle", ::controlApproachVelocityAlongAngle)
lua.setTableValue("controlApproachXVelocity", ::controlApproachXVelocity)
lua.setTableValue("controlApproachYVelocity", ::controlApproachYVelocity)
lua.setTableValue("controlParameters", ::controlParameters)
lua.setTableValue("controlModifiers", ::controlModifiers)
lua.setTableValue("controlMove", ::controlMove)
lua.setTableValue("controlFace", ::controlFace)
lua.setTableValue("controlDown", ::controlDown)
lua.setTableValue("controlCrouch", ::controlCrouch)
lua.setTableValue("controlHoldJump", ::controlHoldJump)
lua.setTableValue("controlJump", ::controlJump)
lua.setTableValue("controlFly", ::controlFly)
lua.setTableValue("controlPathMove", ::controlPathMove)
lua.setTableValue("pathfinding", ::pathfinding)
lua.setTableValue("autoClearControls", ::autoClearControls)
lua.setTableValue("setAutoClearControls", ::setAutoClearControls)
lua.setTableValue("clearControls", ::clearControls)
callbacks["addMomentum"] = luaFunction { value: Table -> lua.pop()
resetPathMove = true
if (self.mass != 0.0) // let's not collapse into black hole
self.velocity += toVector2d(value) / self.mass
}
callbacks["setRotation"] = luaFunction { value: Number ->
resetPathMove = true
self.rotation = value.toDouble()
}
callbacks["baseParameters"] = luaFunction { returnBuffer.setTo(from(Starbound.gson.toJsonTree(self.actorMovementParameters))) }
callbacks["walking"] = luaFunction { returnBuffer.setTo(self.isWalking) }
callbacks["running"] = luaFunction { returnBuffer.setTo(self.isRunning) }
callbacks["movingDirection"] = luaFunction { returnBuffer.setTo(self.movingDirection.numericalValue) }
callbacks["facingDirection"] = luaFunction { returnBuffer.setTo(self.facingDirection.numericalValue) }
callbacks["crouching"] = luaFunction { returnBuffer.setTo(self.isCrouching) }
callbacks["flying"] = luaFunction { returnBuffer.setTo(self.isFlying) }
callbacks["falling"] = luaFunction { returnBuffer.setTo(self.isFalling) }
callbacks["canJump"] = luaFunction { returnBuffer.setTo(self.canJump) }
callbacks["jumping"] = luaFunction { returnBuffer.setTo(self.isJumping) }
callbacks["groundMovement"] = luaFunction { returnBuffer.setTo(self.isGroundMovement) }
callbacks["liquidMovement"] = luaFunction { returnBuffer.setTo(self.isLiquidMovement) }
// controls, stored locally, reset automatically or are persistent
callbacks["controlRotation"] = luaFunction { value: Number ->
controlRotationRate += value.toDouble()
}
callbacks["controlAcceleration"] = luaFunction { value: Table ->
controlAcceleration += toVector2d(value)
}
callbacks["controlForce"] = luaFunction { value: Table ->
controlForce += toVector2d(value)
}
callbacks["controlApproachVelocity"] = luaFunction { value: Table, rate: Number ->
controlApproachVelocity = ActorMovementController.ApproachVelocityCommand(toVector2d(value), rate.toDouble())
}
callbacks["controlApproachVelocityAlongAngle"] = luaFunction { angle: Number, targetVelocity: Number, maxControlForce: Number, positiveOnly: Boolean ->
controlApproachVelocityAlongAngle = ActorMovementController.ApproachVelocityAngleCommand(angle.toDouble(), targetVelocity.toDouble(), maxControlForce.toDouble(), positiveOnly)
}
callbacks["controlApproachXVelocity"] = luaFunction { targetVelocity: Number, maxControlForce: Number ->
controlApproachVelocityAlongAngle = ActorMovementController.ApproachVelocityAngleCommand(0.0, targetVelocity.toDouble(), maxControlForce.toDouble(), false)
}
callbacks["controlApproachYVelocity"] = luaFunction { targetVelocity: Number, maxControlForce: Number ->
controlApproachVelocityAlongAngle = ActorMovementController.ApproachVelocityAngleCommand(PI / 2.0, targetVelocity.toDouble(), maxControlForce.toDouble(), false)
}
callbacks["controlParameters"] = luaFunction { data: Table ->
controlParameters = controlParameters.merge(Starbound.gson.fromJsonFast(data.toJson(true), ActorMovementParameters::class.java))
}
callbacks["controlModifiers"] = luaFunction { data: Table ->
controlModifiers = controlModifiers.merge(Starbound.gson.fromJsonFast(data.toJson(true), ActorMovementModifiers::class.java))
}
callbacks["controlMove"] = luaFunction { direction: Number, shouldRun: Boolean? ->
// why?
val new = Direction.valueOf(direction)
if (new != null) {
controlMove = new
controlShouldRun = shouldRun ?: false
}
}
callbacks["controlFace"] = luaFunction { direction: Number ->
// why?
val new = Direction.valueOf(direction)
if (new != null)
controlFace = new
}
callbacks["controlDown"] = luaFunction { controlDown = true }
callbacks["controlCrouch"] = luaFunction { controlCrouch = true }
callbacks["controlJump"] = luaFunction { should: Boolean -> controlJump = should }
callbacks["controlHoldJump"] = luaFunction { controlHoldJump = true }
callbacks["controlFly"] = luaFunction { value: Table -> controlFly = toVector2d(value) }
callbacks["controlPathMove"] = luaFunction { position: Table, run: Boolean?, parameters: Table? ->
if (pathMoveResult?.first == toVector2d(position)) {
val pathMoveResult = pathMoveResult!!
this@MovementControllerBindings.pathMoveResult = null
returnBuffer.setTo(pathMoveResult.second)
} else {
pathMoveResult = null
val result = self.pathMove(toVector2d(position), run == true, Starbound.gson.fromJsonFast(toJsonFromLua(parameters)))
if (result == null) {
controlPathMove = toVector2d(position) to (run == true)
}
returnBuffer.setTo(result?.second)
}
}
callbacks["pathfinding"] = luaFunction {
returnBuffer.setTo(self.pathController.isPathfinding)
}
callbacks["autoClearControls"] = luaFunction {
returnBuffer.setTo(autoClearControls)
}
callbacks["setAutoClearControls"] = luaFunction { should: Boolean ->
autoClearControls = should
}
callbacks["clearControls"] = luaFunction { clearControls() }
} }
private var autoClearControls = true private var autoClearControls = true

View File

@ -1,222 +1,364 @@
package ru.dbotthepony.kstarbound.lua.bindings package ru.dbotthepony.kstarbound.lua.bindings
import org.classdump.luna.ByteString import com.google.gson.JsonNull
import org.classdump.luna.Table import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import ru.dbotthepony.kstarbound.Registries import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.EntityDamageTeam import ru.dbotthepony.kstarbound.defs.EntityDamageTeam
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
import ru.dbotthepony.kstarbound.defs.quest.QuestArcDescriptor
import ru.dbotthepony.kstarbound.fromJsonFast import ru.dbotthepony.kstarbound.fromJsonFast
import ru.dbotthepony.kstarbound.lua.LuaEnvironment import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.from import ru.dbotthepony.kstarbound.lua.LuaType
import ru.dbotthepony.kstarbound.lua.get import ru.dbotthepony.kstarbound.lua.nextVector2d
import ru.dbotthepony.kstarbound.lua.iterator import ru.dbotthepony.kstarbound.lua.push
import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.tableOf
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.lua.toVector2d
import ru.dbotthepony.kstarbound.util.SBPattern import ru.dbotthepony.kstarbound.util.SBPattern
import ru.dbotthepony.kstarbound.util.sbIntern import ru.dbotthepony.kstarbound.util.sbIntern
import ru.dbotthepony.kstarbound.util.valueOfOrNull import ru.dbotthepony.kstarbound.util.valueOfOrNull
import ru.dbotthepony.kstarbound.world.entities.AnchorNetworkState import ru.dbotthepony.kstarbound.world.entities.AnchorNetworkState
import ru.dbotthepony.kstarbound.world.entities.HumanoidActorEntity import ru.dbotthepony.kstarbound.world.entities.HumanoidActorEntity
import ru.dbotthepony.kstarbound.world.entities.LoungeAnchorState
import ru.dbotthepony.kstarbound.world.entities.NPCEntity import ru.dbotthepony.kstarbound.world.entities.NPCEntity
import ru.dbotthepony.kstarbound.world.entities.api.LoungeableEntity import ru.dbotthepony.kstarbound.world.entities.api.LoungeableEntity
import kotlin.collections.contains
import kotlin.collections.indices
import kotlin.collections.isNotEmpty
import kotlin.collections.set
import kotlin.collections.withIndex
fun provideNPCBindings(self: NPCEntity, lua: LuaEnvironment) { private fun toAbsolutePosition(self: NPCEntity, args: LuaThread.ArgStack): Int {
val callbacks = lua.newTable() args.lua.push(self.toAbsolutePosition(args.nextVector2d()))
lua.globals["npc"] = callbacks return 1
}
callbacks["toAbsolutePosition"] = luaFunction { pos: Table -> private fun species(self: NPCEntity, args: LuaThread.ArgStack): Int {
returnBuffer.setTo(from(self.toAbsolutePosition(toVector2d(pos)))) args.lua.push(self.variant.species.key)
return 1
}
private fun gender(self: NPCEntity, args: LuaThread.ArgStack): Int {
args.lua.push(self.variant.humanoidIdentity.gender.jsonName)
return 1
}
private fun humanoidIdentity(self: NPCEntity, args: LuaThread.ArgStack): Int {
args.lua.push(Starbound.gson.toJsonTree(self.variant.humanoidIdentity))
return 1
}
private fun npcType(self: NPCEntity, args: LuaThread.ArgStack): Int {
args.lua.push(self.variant.typeName)
return 1
}
private fun seed(self: NPCEntity, args: LuaThread.ArgStack): Int {
args.lua.push(self.variant.seed)
return 1
}
private fun level(self: NPCEntity, args: LuaThread.ArgStack): Int {
args.lua.push(self.variant.level)
return 1
}
private fun dropPools(self: NPCEntity, args: LuaThread.ArgStack): Int {
args.lua.pushTable(self.dropPools.size)
for ((i, entry) in self.dropPools.withIndex()) {
args.lua.setTableValue(i + 1L, entry.key.left())
} }
callbacks["species"] = luaFunction { returnBuffer.setTo(self.variant.species.key.toByteString()) } return 1
callbacks["gender"] = luaFunction { returnBuffer.setTo(self.variant.humanoidIdentity.gender.jsonName.toByteString()) } }
callbacks["humanoidIdentity"] = luaFunction { returnBuffer.setTo(from(Starbound.gson.toJsonTree(self.variant.humanoidIdentity))) }
callbacks["npcType"] = luaFunction { returnBuffer.setTo(self.variant.typeName.toByteString()) }
callbacks["seed"] = luaFunction { returnBuffer.setTo(self.variant.seed) }
callbacks["level"] = luaFunction { returnBuffer.setTo(self.variant.level) }
callbacks["dropPools"] = luaFunction { returnBuffer.setTo(tableOf(*self.dropPools.map { it.key.left().toByteString() }.toTypedArray())) }
callbacks["setDropPools"] = luaFunction { dropPools: Table? -> private fun setDropPools(self: NPCEntity, args: LuaThread.ArgStack): Int {
self.dropPools.clear() self.dropPools.clear()
if (dropPools != null) { if (args.hasNext) {
for ((_, pool) in dropPools) { val values = args.readTableValues { Registries.treasurePools.ref(getString(it) ?: throw IllegalArgumentException("Drop pools table contains non-string values")) }
self.dropPools.add(Registries.treasurePools.ref(pool.toString())) self.dropPools.addAll(values)
} }
return 0
}
private fun energy(self: NPCEntity, args: LuaThread.ArgStack): Int {
args.lua.push(self.energy)
return 1
}
private fun maxEnergy(self: NPCEntity, args: LuaThread.ArgStack): Int {
args.lua.push(self.maxEnergy)
return 1
}
private fun say(self: NPCEntity, hasPortrait: Boolean, args: LuaThread.ArgStack): Int {
val line = args.nextString()
val portrait = if (hasPortrait) args.nextString() else ""
val tags = Object2ObjectArrayMap<String, String>()
val tagsType = args.peek()
if (tagsType == LuaType.TABLE) {
val pairs = args.readTable(
keyVisitor = { getString(it) ?: throw IllegalArgumentException("Tag replacement table contains non-strings as keys") },
valueVisitor = { getString(it) ?: throw IllegalArgumentException("Tag replacement table contains non-strings as keys") },
)
for ((k, v) in pairs) {
tags[k] = v
} }
} else if (!tagsType.isNothing) {
throw IllegalArgumentException("bad argument #2 to object.say: table expected, got $tagsType")
} else {
args.skip()
} }
// lol why val sayConfig = args.nextOptionalJson() ?: JsonNull.INSTANCE
callbacks["energy"] = luaFunction { returnBuffer.setTo(self.energy) }
callbacks["maxEnergy"] = luaFunction { returnBuffer.setTo(self.maxEnergy) }
callbacks["say"] = luaFunction { line: ByteString, tags: Table?, config: Any? -> if (line.isBlank()) {
val actualLine = if (tags != null) { args.lua.push(false)
SBPattern.of(line.decode()).resolveOrSkip({ tags[it]?.toString() }) } else if (tags.isEmpty()) {
self.addChatMessage(line, sayConfig, portrait = portrait)
args.lua.push(true)
} else {
self.addChatMessage(SBPattern.of(line).resolveOrSkip(tags::get), sayConfig, portrait = portrait)
args.lua.push(true)
}
return 1
}
private fun emote(self: NPCEntity, args: LuaThread.ArgStack): Int {
self.addEmote(args.nextString())
return 0
}
private fun dance(self: NPCEntity, args: LuaThread.ArgStack): Int {
self.setDance(args.nextString())
return 0
}
private fun setInteractive(self: NPCEntity, args: LuaThread.ArgStack): Int {
self.isInteractive = args.nextBoolean()
return 0
}
private fun setLounging(self: NPCEntity, args: LuaThread.ArgStack): Int {
val loungeable = args.nextInt()
val anchorIndex = args.nextOptionalInt() ?: 0
val entity = self.world.entities[loungeable] as? LoungeableEntity
if (entity == null || anchorIndex !in entity.anchors.indices || entity.entitiesLoungingIn(anchorIndex).isNotEmpty()) {
args.lua.push(false)
} else {
self.movement.anchorNetworkState = AnchorNetworkState(loungeable, anchorIndex)
args.lua.push(true)
}
return 1
}
private fun resetLounging(self: NPCEntity, args: LuaThread.ArgStack): Int {
self.movement.anchorNetworkState = null
return 0
}
private fun isLounging(self: NPCEntity, args: LuaThread.ArgStack): Int {
args.lua.push(self.movement.anchorState is LoungeAnchorState)
return 1
}
private fun loungingIn(self: NPCEntity, args: LuaThread.ArgStack): Int {
args.lua.push(self.movement.anchorNetworkState?.entityID?.toLong())
return 1
}
private fun setItemSlot(self: NPCEntity, args: LuaThread.ArgStack): Int {
val slot = args.nextString()
val descriptor = ItemDescriptor(args)
args.lua.push(self.setItem(slot, descriptor))
return 1
}
private fun getItemSlot(self: NPCEntity, args: LuaThread.ArgStack): Int {
val slot = args.nextString()
val slotType = HumanoidActorEntity.ItemSlot.entries.valueOfOrNull(slot.lowercase())
if (slotType == null) {
if (slot in self.variant.items) {
self.variant.items[slot]!!.store(args.lua)
} else { } else {
line.decode() return 0
} }
} else {
val isNotEmpty = actualLine.isNotEmpty() self.getItem(slotType).createDescriptor().store(args.lua)
if (isNotEmpty)
self.addChatMessage(actualLine, toJsonFromLua(config))
returnBuffer.setTo(isNotEmpty)
} }
callbacks["sayPortrait"] = luaFunction { line: ByteString, portrait: ByteString, tags: Table?, config: Any? -> return 1
val actualLine = if (tags != null) { }
SBPattern.of(line.decode()).resolveOrSkip({ tags[it]?.toString() })
} else {
line.decode()
}
val isNotEmpty = actualLine.isNotEmpty() private fun disableWornArmor(self: NPCEntity, args: LuaThread.ArgStack): Int {
self.disableWornArmor = args.nextBoolean()
return 0
}
if (isNotEmpty) private fun getDisableWornArmor(self: NPCEntity, args: LuaThread.ArgStack): Int {
self.addChatMessage(actualLine, toJsonFromLua(config), portrait.decode()) args.lua.push(self.disableWornArmor)
return 1
}
returnBuffer.setTo(isNotEmpty) private fun setShifting(self: NPCEntity, args: LuaThread.ArgStack): Int {
} self.isShifting = args.nextBoolean()
return 0
}
callbacks["emote"] = luaFunction { emote: ByteString -> private fun isShifting(self: NPCEntity, args: LuaThread.ArgStack): Int {
self.addEmote(emote.decode()) args.lua.push(self.isShifting)
} return 1
}
callbacks["dance"] = luaFunction { dance: ByteString -> private fun getDamageOnTouch(self: NPCEntity, args: LuaThread.ArgStack): Int {
self.setDance(dance.decode()) args.lua.push(self.damageOnTouch)
} return 1
}
callbacks["setInteractive"] = luaFunction { isInteractive: Boolean -> private fun setDamageOnTouch(self: NPCEntity, args: LuaThread.ArgStack): Int {
self.isInteractive = isInteractive self.damageOnTouch = args.nextBoolean()
} return 0
}
callbacks["setLounging"] = luaFunction { loungeable: Number, oAnchorIndex: Number? -> private fun aimPosition(self: NPCEntity, args: LuaThread.ArgStack): Int {
val anchorIndex = oAnchorIndex?.toInt() ?: 0 args.lua.push(self.aimPosition)
val entity = self.world.entities[loungeable.toInt()] as? LoungeableEntity return 1
}
if (entity == null || anchorIndex !in 0 until entity.anchors.size || entity.entitiesLoungingIn(anchorIndex).isNotEmpty()) { private fun setAimPosition(self: NPCEntity, args: LuaThread.ArgStack): Int {
returnBuffer.setTo(false) self.aimPosition = self.world.geometry.diff(args.nextVector2d(), self.position)
} else { return 0
self.movement.anchorNetworkState = AnchorNetworkState(loungeable.toInt(), anchorIndex) }
returnBuffer.setTo(true)
}
}
callbacks["resetLounging"] = luaFunction { private fun getDeathParticleBurst(self: NPCEntity, args: LuaThread.ArgStack): Int {
self.movement.anchorNetworkState = null args.lua.push(self.deathParticleBurst)
} return 1
}
callbacks["isLounging"] = luaFunction { private fun setDeathParticleBurst(self: NPCEntity, args: LuaThread.ArgStack): Int {
returnBuffer.setTo(self.movement.anchorNetworkState != null) self.deathParticleBurst = args.nextOptionalString()?.sbIntern()
} return 0
}
callbacks["loungingIn"] = luaFunction { private fun getStatusText(self: NPCEntity, args: LuaThread.ArgStack): Int {
returnBuffer.setTo(self.movement.anchorNetworkState?.entityID) args.lua.push(self.statusText)
} return 1
}
callbacks["setOfferedQuests"] = luaFunction { values: Table? -> private fun setStatusText(self: NPCEntity, args: LuaThread.ArgStack): Int {
self.offeredQuests.clear() self.statusText = args.nextOptionalString()?.sbIntern()
return 0
}
if (values != null) { private fun getDisplayNametag(self: NPCEntity, args: LuaThread.ArgStack): Int {
for ((_, quest) in values) { args.lua.push(self.displayNametag)
self.offeredQuests.add(Starbound.gson.fromJsonFast(toJsonFromLua(quest), QuestArcDescriptor::class.java)) return 1
} }
}
}
callbacks["setTurnInQuests"] = luaFunction { values: Table? -> private fun setDisplayNametag(self: NPCEntity, args: LuaThread.ArgStack): Int {
self.turnInQuests.clear() self.displayNametag = args.nextBoolean()
return 0
}
if (values != null) { private fun getKeepAlive(self: NPCEntity, args: LuaThread.ArgStack): Int {
for ((_, value) in values) { args.lua.push(self.keepAlive)
self.turnInQuests.add(value.toString()) return 1
} }
}
}
callbacks["setItemSlot"] = luaFunction { slot: ByteString, descriptor: Any -> private fun setKeepAlive(self: NPCEntity, args: LuaThread.ArgStack): Int {
returnBuffer.setTo(self.setItem(slot.decode(), ItemDescriptor(descriptor))) self.keepAlive = args.nextBoolean()
} return 0
}
callbacks["getItemSlot"] = luaFunction { slot: ByteString -> private fun getAggressive(self: NPCEntity, args: LuaThread.ArgStack): Int {
val decoded = slot.decode() args.lua.push(self.isAggressive)
val slotType = HumanoidActorEntity.ItemSlot.entries.valueOfOrNull(decoded.lowercase()) return 1
}
if (slotType == null) { private fun setAggressive(self: NPCEntity, args: LuaThread.ArgStack): Int {
if (decoded in self.variant.items) { self.isAggressive = args.nextBoolean()
returnBuffer.setTo(self.variant.items[decoded]!!.toTable(this)) return 0
} else { }
returnBuffer.setTo()
}
} else {
returnBuffer.setTo(self.getItem(slotType).toTable(this))
}
}
callbacks["disableWornArmor"] = luaFunction { disable: Boolean -> private fun setPersistent(self: NPCEntity, args: LuaThread.ArgStack): Int {
self.disableWornArmor = disable self.isPersistent = args.nextBoolean()
} return 0
}
callbacks["beginPrimaryFire"] = luaFunction { self.beginPrimaryFire() } private fun setDamageTeam(self: NPCEntity, args: LuaThread.ArgStack): Int {
callbacks["beginAltFire"] = luaFunction { self.beginSecondaryFire() } self.team.accept(Starbound.gson.fromJsonFast(args.nextJson(), EntityDamageTeam::class.java))
callbacks["beginSecondaryFire"] = luaFunction { self.beginSecondaryFire() } return 0
callbacks["endPrimaryFire"] = luaFunction { self.endPrimaryFire() } }
callbacks["endAltFire"] = luaFunction { self.endSecondaryFire() }
callbacks["endSecondaryFire"] = luaFunction { self.endSecondaryFire() }
callbacks["setShifting"] = luaFunction { value: Boolean -> private fun setUniqueId(self: NPCEntity, args: LuaThread.ArgStack): Int {
self.isShifting = value self.uniqueID.accept(args.nextOptionalString()?.sbIntern())
} return 0
}
callbacks["setDamageOnTouch"] = luaFunction { value: Boolean -> fun provideNPCBindings(self: NPCEntity, lua: LuaThread) {
self.damageOnTouch = value lua.pushTable()
} lua.dup()
lua.storeGlobal("npc")
callbacks["aimPosition"] = luaFunction { lua.pushBinding(self, "toAbsolutePosition", ::toAbsolutePosition)
returnBuffer.setTo(from(self.aimPosition)) lua.pushBinding(self, "species", ::species)
} lua.pushBinding(self, "gender", ::gender)
lua.pushBinding(self, "humanoidIdentity", ::humanoidIdentity)
lua.pushBinding(self, "npcType", ::npcType)
lua.pushBinding(self, "seed", ::seed)
lua.pushBinding(self, "level", ::level)
lua.pushBinding(self, "dropPools", ::dropPools)
lua.pushBinding(self, "setDropPools", ::setDropPools)
lua.pushBinding(self, "energy", ::energy)
lua.pushBinding(self, "maxEnergy", ::maxEnergy)
lua.pushBinding(self, false, "say", ::say)
lua.pushBinding(self, true, "sayPortrait", ::say)
lua.pushBinding(self, "emote", ::emote)
lua.pushBinding(self, "dance", ::dance)
lua.pushBinding(self, "setInteractive", ::setInteractive)
lua.pushBinding(self, "setLounging", ::setLounging)
lua.pushBinding(self, "resetLounging", ::resetLounging)
lua.pushBinding(self, "isLounging", ::isLounging)
lua.pushBinding(self, "loungingIn", ::loungingIn)
lua.pushBinding(self, NPCEntity::offeredQuests, "setOfferedQuests", ::setOfferedQuests)
lua.pushBinding(self, NPCEntity::turnInQuests, "setTurnInQuests", ::setTurnInQuests)
lua.pushBinding(self, "setItemSlot", ::setItemSlot)
lua.pushBinding(self, "getItemSlot", ::getItemSlot)
lua.pushBinding(self, "disableWornArmor", ::disableWornArmor)
lua.pushBinding(self, "getDisableWornArmor", ::getDisableWornArmor)
lua.pushBinding(self, "setShifting", ::setShifting)
lua.pushBinding(self, "isShifting", ::isShifting)
lua.pushBinding(self, "setDamageOnTouch", ::setDamageOnTouch)
lua.pushBinding(self, "getDamageOnTouch", ::getDamageOnTouch)
lua.pushBinding(self, "aimPosition", ::aimPosition)
lua.pushBinding(self, "setAimPosition", ::setAimPosition)
lua.pushBinding(self, "getDeathParticleBurst", ::getDeathParticleBurst)
lua.pushBinding(self, "setDeathParticleBurst", ::setDeathParticleBurst)
lua.pushBinding(self, "getStatusText", ::getStatusText)
lua.pushBinding(self, "setStatusText", ::setStatusText)
lua.pushBinding(self, "getDisplayNametag", ::getDisplayNametag)
lua.pushBinding(self, "setDisplayNametag", ::setDisplayNametag)
lua.pushBinding(self, "getKeepAlive", ::getKeepAlive)
lua.pushBinding(self, "setKeepAlive", ::setKeepAlive)
lua.pushBinding(self, "getAggressive", ::getAggressive)
lua.pushBinding(self, "setAggressive", ::setAggressive)
lua.pushBinding(self, "setPersistent", ::setPersistent)
lua.pushBinding(self, "setDamageTeam", ::setDamageTeam)
lua.pushBinding(self, "setUniqueId", ::setUniqueId)
callbacks["setAimPosition"] = luaFunction { pos: Table -> lua.pushBinding(self, "beginPrimaryFire", NPCEntity::beginPrimaryFire)
self.aimPosition = self.world.geometry.diff(toVector2d(pos), self.position) lua.pushBinding(self, "beginAltFire", NPCEntity::beginSecondaryFire)
} lua.pushBinding(self, "beginSecondaryFire", NPCEntity::beginSecondaryFire)
lua.pushBinding(self, "endPrimaryFire", NPCEntity::endPrimaryFire)
lua.pushBinding(self, "endAltFire", NPCEntity::endSecondaryFire)
lua.pushBinding(self, "endSecondaryFire", NPCEntity::endSecondaryFire)
callbacks["setDeathParticleBurst"] = luaFunction { value: ByteString? -> lua.pop()
self.deathParticleBurst = value?.decode()?.sbIntern() }
}
callbacks["setStatusText"] = luaFunction { value: ByteString? ->
self.statusText = value?.decode()?.sbIntern()
}
callbacks["setDisplayNametag"] = luaFunction { value: Boolean ->
self.displayNametag = value
}
callbacks["setPersistent"] = luaFunction { value: Boolean ->
self.isPersistent = value
}
callbacks["setKeepAlive"] = luaFunction { value: Boolean ->
self.keepAlive = value
}
callbacks["setAggressive"] = luaFunction { value: Boolean ->
self.isAggressive = value
}
callbacks["setDamageTeam"] = luaFunction { value: Table ->
self.team.accept(Starbound.gson.fromJsonFast(toJsonFromLua(value), EntityDamageTeam::class.java))
}
callbacks["setUniqueId"] = luaFunction { value: ByteString? ->
self.uniqueID.accept(value?.decode()?.sbIntern())
}
}

View File

@ -1,249 +1,397 @@
package ru.dbotthepony.kstarbound.lua.bindings package ru.dbotthepony.kstarbound.lua.bindings
import com.google.gson.JsonObject import com.google.gson.JsonNull
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import org.classdump.luna.ByteString
import org.classdump.luna.LuaRuntimeException
import org.classdump.luna.Table
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kstarbound.Registries import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.DamageData import ru.dbotthepony.kstarbound.defs.DamageData
import ru.dbotthepony.kstarbound.defs.EphemeralStatusEffect import ru.dbotthepony.kstarbound.defs.EphemeralStatusEffect
import ru.dbotthepony.kstarbound.defs.actor.PersistentStatusEffect import ru.dbotthepony.kstarbound.defs.actor.PersistentStatusEffect
import ru.dbotthepony.kstarbound.fromJsonFast import ru.dbotthepony.kstarbound.fromJsonFast
import ru.dbotthepony.kstarbound.lua.LuaEnvironment import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.from import ru.dbotthepony.kstarbound.lua.push
import ru.dbotthepony.kstarbound.lua.iterator
import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.tableOf
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.util.sbIntern import ru.dbotthepony.kstarbound.util.sbIntern
import ru.dbotthepony.kstarbound.world.entities.StatusController import ru.dbotthepony.kstarbound.world.entities.StatusController
private object PersistentStatusEffectToken : TypeToken<PersistentStatusEffect>() private object PersistentStatusEffectToken : TypeToken<PersistentStatusEffect>()
private object CPersistentStatusEffectToken : TypeToken<ArrayList<PersistentStatusEffect>>() private object CPersistentStatusEffectToken : TypeToken<ArrayList<PersistentStatusEffect>>()
fun provideStatusControllerBindings(self: StatusController, lua: LuaEnvironment) { private fun statusProperty(self: StatusController, args: LuaThread.ArgStack): Int {
val callbacks = lua.newTable() val get = self.getProperty(args.nextString())
lua.globals["status"] = callbacks
callbacks["statusProperty"] = luaFunction { key: ByteString, ifMissing: Any? -> if (get == null) {
val get = self.getProperty(key.decode()) if (args.top == 1) {
args.lua.push()
if (get == null) { } else if (args.top > 3) {
returnBuffer.setTo(ifMissing) args.lua.dup(2)
} else {
returnBuffer.setTo(from(get))
} }
} else {
args.lua.push(get)
} }
callbacks["setStatusProperty"] = luaFunction { key: ByteString, value: Any? -> return 1
self.setProperty(key.decode().sbIntern(), toJsonFromLua(value)) }
}
private fun setStatusProperty(self: StatusController, args: LuaThread.ArgStack): Int {
callbacks["stat"] = luaFunction { name: ByteString -> self.setProperty(args.nextString().sbIntern(), args.nextOptionalJson() ?: JsonNull.INSTANCE)
returnBuffer.setTo(self.effectiveStats[name.decode()]?.effectiveModifiedValue ?: 0.0) return 0
} }
callbacks["statPositive"] = luaFunction { name: ByteString -> private fun stat(self: StatusController, args: LuaThread.ArgStack): Int {
returnBuffer.setTo(self.statPositive(name.decode())) args.lua.push(self.effectiveStats[args.nextString()]?.effectiveModifiedValue ?: 0.0)
} return 1
}
callbacks["resourceNames"] = luaFunction {
returnBuffer.setTo(tableOf(self.resources.keys.toTypedArray())) private fun statPositive(self: StatusController, args: LuaThread.ArgStack): Int {
} args.lua.push(self.statPositive(args.nextString()))
return 1
callbacks["isResource"] = luaFunction { name: ByteString -> }
returnBuffer.setTo(name.decode() in self.resources.keys)
} private fun resourceNames(self: StatusController, args: LuaThread.ArgStack): Int {
args.lua.pushTable(self.resources.size)
callbacks["resource"] = luaFunction { name: ByteString ->
val resource = self.resources[name.decode()] ?: return@luaFunction returnBuffer.setTo(0.0) for ((i, value) in self.resources.keys.withIndex())
returnBuffer.setTo(resource.value) args.lua.setTableValue(i + 1, value)
}
return 1
callbacks["resourcePositive"] = luaFunction { name: ByteString -> }
val resource = self.resources[name.decode()] ?: return@luaFunction returnBuffer.setTo(false)
returnBuffer.setTo(resource.value > 0.0) private fun isResource(self: StatusController, args: LuaThread.ArgStack): Int {
} args.lua.push(args.nextString() in self.resources)
return 1
callbacks["setResource"] = luaFunction { name: ByteString, value: Number -> }
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name")
resource.value = value.toDouble() private fun resource(self: StatusController, args: LuaThread.ArgStack): Int {
} val resource = self.resources[args.nextString()] ?: return 0
args.lua.push(resource.value)
callbacks["modifyResource"] = luaFunction { name: ByteString, value: Number -> return 1
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name") }
resource.value += value.toDouble()
} private fun resourcePositive(self: StatusController, args: LuaThread.ArgStack): Int {
val resource = self.resources[args.nextString()]
callbacks["giveResource"] = luaFunction { name: ByteString, value: Number ->
// while other functions throw an exception if resource does not exist if (resource == null)
// this one returns 0 args.lua.push(false)
// Consistency is my first, second, and last name else
val resource = self.resources[name.decode()] ?: return@luaFunction returnBuffer.setTo(0.0) args.lua.push(resource.isPositive)
returnBuffer.setTo(resource.give(value.toDouble()))
} return 1
}
callbacks["consumeResource"] = luaFunction { name: ByteString, value: Number ->
// while other functions throw an exception if resource does not exist private fun setResource(self: StatusController, args: LuaThread.ArgStack): Int {
// this one returns 0 val name = args.nextString()
// Consistency is my first, second, and last name val resource = self.resources[name] ?: throw NoSuchElementException("No such resource $name")
val resource = self.resources[name.decode()] ?: return@luaFunction returnBuffer.setTo(false) resource.value = args.nextDouble()
returnBuffer.setTo(resource.consume(value.toDouble())) return 0
} }
callbacks["overConsumeResource"] = luaFunction { name: ByteString, value: Number -> private fun modifyResource(self: StatusController, args: LuaThread.ArgStack): Int {
// while other functions throw an exception if resource does not exist val name = args.nextString()
// this one returns 0 val resource = self.resources[name] ?: throw NoSuchElementException("No such resource $name")
// Consistency is my first, second, and last name resource.value += args.nextDouble()
val resource = self.resources[name.decode()] ?: return@luaFunction returnBuffer.setTo(false) return 0
returnBuffer.setTo(resource.consume(value.toDouble(), allowOverdraw = true)) }
}
private fun giveResource(self: StatusController, args: LuaThread.ArgStack): Int {
callbacks["resourceLocked"] = luaFunction { name: ByteString -> val name = args.nextString()
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name") val resource = self.resources[name]
returnBuffer.setTo(resource.isLocked) val amount = args.nextDouble()
}
if (resource == null) {
callbacks["setResourceLocked"] = luaFunction { name: ByteString, isLocked: Boolean -> args.lua.push(0.0)
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name") } else {
resource.isLocked = isLocked args.lua.push(resource.give(amount))
} }
callbacks["resetResource"] = luaFunction { name: ByteString, isLocked: Boolean -> return 1
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name") }
resource.reset()
} private fun consumeResource(self: StatusController, overdraw: Boolean, args: LuaThread.ArgStack): Int {
val name = args.nextString()
callbacks["resetAllResources"] = luaFunction { name: ByteString, isLocked: Boolean -> val resource = self.resources[name]
self.resetResources() val amount = args.nextDouble()
}
if (resource == null) {
callbacks["resourceMax"] = luaFunction { name: ByteString -> args.lua.push(false)
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name") } else {
returnBuffer.setTo(resource.maxValue) args.lua.push(resource.consume(amount, overdraw))
} }
callbacks["resourcePercentage"] = luaFunction { name: ByteString -> return 1
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name") }
returnBuffer.setTo(resource.percentage)
} private fun resourceLocked(self: StatusController, args: LuaThread.ArgStack): Int {
val name = args.nextString()
callbacks["setResourcePercentage"] = luaFunction { name: ByteString, value: Number -> val resource = self.resources[name] ?: throw NoSuchElementException("No such resource $name")
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name") args.lua.push(resource.isLocked)
resource.setAsPercentage(value.toDouble()) return 1
} }
callbacks["modifyResourcePercentage"] = luaFunction { name: ByteString, value: Number -> private fun setResourceLocked(self: StatusController, args: LuaThread.ArgStack): Int {
val resource = self.resources[name.decode()] ?: throw LuaRuntimeException("No such resource $name") val name = args.nextString()
resource.modifyPercentage(value.toDouble()) val resource = self.resources[name] ?: throw NoSuchElementException("No such resource $name")
} resource.isLocked = args.nextBoolean()
return 0
callbacks["getPersistentEffects"] = luaFunction { category: ByteString -> }
returnBuffer.setTo(tableOf(self.getPersistentEffects(category.decode()).map { it.map({ from(Starbound.gson.toJsonTree(it)) }, { it }) }))
} private fun resetResource(self: StatusController, args: LuaThread.ArgStack): Int {
val name = args.nextString()
callbacks["addPersistentEffect"] = luaFunction { category: ByteString, effect: Any -> val resource = self.resources[name] ?: throw NoSuchElementException("No such resource $name")
self.addPersistentEffect(category.decode().sbIntern(), Starbound.gson.fromJsonFast(toJsonFromLua(effect), PersistentStatusEffectToken)) resource.reset()
} return 0
}
callbacks["addPersistentEffects"] = luaFunction { category: ByteString, effect: Any ->
self.addPersistentEffects(category.decode().sbIntern(), Starbound.gson.fromJsonFast(toJsonFromLua(effect), CPersistentStatusEffectToken)) private fun resetAllResources(self: StatusController, args: LuaThread.ArgStack): Int {
} self.resetResources()
return 0
callbacks["setPersistentEffects"] = luaFunction { category: ByteString, effect: Any -> }
self.setPersistentEffects(category.decode().sbIntern(), Starbound.gson.fromJsonFast(toJsonFromLua(effect), CPersistentStatusEffectToken))
} private fun resourceMax(self: StatusController, args: LuaThread.ArgStack): Int {
val name = args.nextString()
callbacks["clearPersistentEffects"] = luaFunction { category: ByteString -> val resource = self.resources[name] ?: throw NoSuchElementException("No such resource $name")
self.removePersistentEffects(category.decode()) args.lua.push(resource.maxValue)
} return 1
}
callbacks["clearAllPersistentEffects"] = luaFunction {
self.removeAllPersistentEffects() private fun resourcePercentage(self: StatusController, args: LuaThread.ArgStack): Int {
} val name = args.nextString()
val resource = self.resources[name] ?: throw NoSuchElementException("No such resource $name")
callbacks["addEphemeralEffect"] = luaFunction { name: ByteString, duration: Number?, source: Number? -> args.lua.push(resource.percentage)
self.addEphemeralEffect(EphemeralStatusEffect(Registries.statusEffects.ref(name.decode().sbIntern()), duration?.toDouble()), source?.toInt()) return 1
} }
callbacks["addEphemeralEffects"] = luaFunction { effects: Table, source: Number? -> private fun setResourcePercentage(self: StatusController, args: LuaThread.ArgStack): Int {
for ((_, effect) in effects) { val name = args.nextString()
self.addEphemeralEffect(Starbound.gson.fromJsonFast(toJsonFromLua(effect), EphemeralStatusEffect::class.java), source?.toInt()) val resource = self.resources[name] ?: throw NoSuchElementException("No such resource $name")
} resource.setAsPercentage(args.nextDouble())
} return 0
}
callbacks["removeEphemeralEffect"] = luaFunction { name: ByteString ->
self.removeEphemeralEffect(name.decode()) private fun modifyResourcePercentage(self: StatusController, args: LuaThread.ArgStack): Int {
} val name = args.nextString()
val resource = self.resources[name] ?: throw NoSuchElementException("No such resource $name")
callbacks["clearEphemeralEffects"] = luaFunction { resource.modifyPercentage(args.nextDouble())
self.removeEphemeralEffects() return 0
} }
callbacks["damageTakenSince"] = luaFunction { since: Number? -> private fun getPersistentEffects(self: StatusController, args: LuaThread.ArgStack): Int {
val (list, newSince) = self.recentDamageReceived(since?.toLong() ?: 0L) val effects = self.getPersistentEffects(args.nextString())
returnBuffer.setTo(tableOf(*list.map { from(Starbound.gson.toJsonTree(it)) }.toTypedArray()), newSince) args.lua.pushTable(effects.size)
}
for ((i, effect) in effects.withIndex()) {
callbacks["inflictedHitsSince"] = luaFunction { since: Number? -> args.lua.push(i + 1L)
val (list, newSince) = self.recentHitsDealt(since?.toLong() ?: 0L) effect.map({ args.lua.push(Starbound.gson.toJsonTree(it)) }, { args.lua.push(it) })
args.lua.setTableValue()
returnBuffer.setTo(tableOf(*list.map { p -> }
from(Starbound.gson.toJsonTree(p.second).also { it as JsonObject; it["targetEntityId"] = p.first })
}.toTypedArray()), newSince) return 1
} }
callbacks["inflictedDamageSince"] = luaFunction { since: Number? -> private fun addPersistentEffect(self: StatusController, args: LuaThread.ArgStack): Int {
val (list, newSince) = self.recentDamageDealt(since?.toLong() ?: 0L) val category = args.nextString().sbIntern()
returnBuffer.setTo(tableOf(*list.map { from(Starbound.gson.toJsonTree(it)) }.toTypedArray()), newSince) val effect = Starbound.gson.fromJsonFast(args.nextJson(), PersistentStatusEffectToken)
} self.addPersistentEffect(category, effect)
return 0
callbacks["activeUniqueStatusEffectSummary"] = luaFunction { }
returnBuffer.setTo(tableOf(*self.activeUniqueStatusEffectSummary().map { tableOf(it.first.key, it.second) }.toTypedArray()))
} private fun addPersistentEffects(self: StatusController, args: LuaThread.ArgStack): Int {
val category = args.nextString().sbIntern()
callbacks["uniqueStatusEffectActive"] = luaFunction { name: ByteString -> val effects = Starbound.gson.fromJsonFast(args.nextJson(), CPersistentStatusEffectToken)
returnBuffer.setTo(self.uniqueStatusEffectActive(name.decode())) self.addPersistentEffects(category, effects)
} return 0
}
callbacks["primaryDirectives"] = luaFunction {
returnBuffer.setTo(self.primaryDirectives.toByteString()) private fun setPersistentEffects(self: StatusController, args: LuaThread.ArgStack): Int {
} val category = args.nextString().sbIntern()
val effects = Starbound.gson.fromJsonFast(args.nextJson(), CPersistentStatusEffectToken)
callbacks["setPrimaryDirectives"] = luaFunction { directives: ByteString? -> self.setPersistentEffects(category, effects)
self.primaryDirectives = directives?.decode()?.sbIntern() ?: "" return 0
} }
callbacks["applySelfDamageRequest"] = luaFunction { damage: Table -> private fun clearPersistentEffects(self: StatusController, args: LuaThread.ArgStack): Int {
self.inflictSelfDamage(Starbound.gson.fromJsonFast(toJsonFromLua(damage), DamageData::class.java)) val category = args.nextString()
} self.removePersistentEffects(category)
return 0
callbacks["appliesEnvironmentStatusEffects"] = luaFunction { }
returnBuffer.setTo(self.appliesEnvironmentStatusEffects)
} private fun clearAllPersistentEffects(self: StatusController, args: LuaThread.ArgStack): Int {
self.removeAllPersistentEffects()
callbacks["appliesWeatherStatusEffects"] = luaFunction { return 0
returnBuffer.setTo(self.appliesWeatherStatusEffects) }
}
private fun addEphemeralEffect(self: StatusController, args: LuaThread.ArgStack): Int {
callbacks["minimumLiquidStatusEffectPercentage"] = luaFunction { val name = args.nextString()
returnBuffer.setTo(self.minimumLiquidStatusEffectPercentage) val duration = args.nextOptionalDouble()
} val sourceEntity = args.nextOptionalInt()
callbacks["setAppliesEnvironmentStatusEffects"] = luaFunction { should: Boolean -> self.addEphemeralEffect(EphemeralStatusEffect(Registries.statusEffects.ref(name), duration), sourceEntity)
self.appliesEnvironmentStatusEffects = should return 0
} }
callbacks["setAppliesWeatherStatusEffects"] = luaFunction { should: Boolean -> private fun addEphemeralEffects(self: StatusController, args: LuaThread.ArgStack): Int {
self.appliesWeatherStatusEffects = should val effects = args.readTableValues { Starbound.gson.fromJsonFast(getJson() ?: throw IllegalArgumentException("invalid values in effects table"), EphemeralStatusEffect::class.java) }
} val sourceEntity = args.nextOptionalInt()
callbacks["setMinimumLiquidStatusEffectPercentage"] = luaFunction { value: Number -> for (effect in effects) {
self.minimumLiquidStatusEffectPercentage = value.toDouble() self.addEphemeralEffect(effect, sourceEntity)
} }
return 0
}
private fun removeEphemeralEffect(self: StatusController, args: LuaThread.ArgStack): Int {
self.removeEphemeralEffect(args.nextString())
return 0
}
private fun removeEphemeralEffects(self: StatusController, args: LuaThread.ArgStack): Int {
self.removeEphemeralEffects()
return 0
}
private fun damageTakenSince(self: StatusController, args: LuaThread.ArgStack): Int {
val since = args.nextOptionalLong() ?: 0L
val (list, newSince) = self.recentDamageReceived(since)
args.lua.pushTable(list.size)
for ((i, v) in list.withIndex()) {
args.lua.setTableValue(i + 1L, Starbound.gson.toJsonTree(v))
}
args.lua.push(newSince)
return 2
}
private fun inflictedHitsSince(self: StatusController, args: LuaThread.ArgStack): Int {
val since = args.nextOptionalLong() ?: 0L
val (list, newSince) = self.recentHitsDealt(since)
args.lua.pushTable(list.size)
for ((i, v) in list.withIndex()) {
val (target, data) = v
args.lua.push(i + 1L)
args.lua.push(Starbound.gson.toJsonTree(data))
args.lua.setTableValue("targetEntityId", target)
args.lua.setTableValue()
}
args.lua.push(newSince)
return 2
}
private fun inflictedDamageSince(self: StatusController, args: LuaThread.ArgStack): Int {
val since = args.nextOptionalLong() ?: 0L
val (list, newSince) = self.recentHitsDealt(since)
args.lua.pushTable(list.size)
for ((i, v) in list.withIndex()) {
args.lua.setTableValue(i + 1L, Starbound.gson.toJsonTree(v))
}
args.lua.push(newSince)
return 2
}
private fun activeUniqueStatusEffectSummary(self: StatusController, args: LuaThread.ArgStack): Int {
val effects = self.activeUniqueStatusEffectSummary()
args.lua.pushTable(effects.size)
for ((i, data) in effects.withIndex()) {
args.lua.push(i + 1L)
args.lua.pushTable(2)
args.lua.setTableValue(1L, data.first.key)
args.lua.setTableValue(2L, data.second)
args.lua.setTableValue()
}
return 1
}
private fun uniqueStatusEffectActive(self: StatusController, args: LuaThread.ArgStack): Int {
args.lua.push(self.uniqueStatusEffectActive(args.nextString()))
return 1
}
private fun primaryDirectives(self: StatusController, args: LuaThread.ArgStack): Int {
args.lua.push(self.primaryDirectives)
return 1
}
private fun setPrimaryDirectives(self: StatusController, args: LuaThread.ArgStack): Int {
self.primaryDirectives = args.nextOptionalString()?.sbIntern() ?: ""
return 0
}
private fun applySelfDamageRequest(self: StatusController, args: LuaThread.ArgStack): Int {
self.inflictSelfDamage(Starbound.gson.fromJsonFast(args.nextJson(), DamageData::class.java))
return 1
}
private fun appliesEnvironmentStatusEffects(self: StatusController, args: LuaThread.ArgStack): Int { args.lua.push(self.appliesEnvironmentStatusEffects); return 1 }
private fun appliesWeatherStatusEffects(self: StatusController, args: LuaThread.ArgStack): Int { args.lua.push(self.appliesWeatherStatusEffects); return 1 }
private fun minimumLiquidStatusEffectPercentage(self: StatusController, args: LuaThread.ArgStack): Int { args.lua.push(self.minimumLiquidStatusEffectPercentage); return 1 }
private fun setAppliesEnvironmentStatusEffects(self: StatusController, args: LuaThread.ArgStack): Int { self.appliesEnvironmentStatusEffects = args.nextBoolean(); return 0 }
private fun setAppliesWeatherStatusEffects(self: StatusController, args: LuaThread.ArgStack): Int { self.appliesWeatherStatusEffects = args.nextBoolean(); return 0 }
private fun setMinimumLiquidStatusEffectPercentage(self: StatusController, args: LuaThread.ArgStack): Int { self.minimumLiquidStatusEffectPercentage = args.nextDouble(); return 0 }
fun provideStatusControllerBindings(self: StatusController, lua: LuaThread) {
lua.pushTable()
lua.dup()
lua.storeGlobal("status")
lua.pushBinding(self, "statusProperty", ::statusProperty)
lua.pushBinding(self, "setStatusProperty", ::setStatusProperty)
lua.pushBinding(self, "stat", ::stat)
lua.pushBinding(self, "statPositive", ::statPositive)
lua.pushBinding(self, "resourceNames", ::resourceNames)
lua.pushBinding(self, "isResource", ::isResource)
lua.pushBinding(self, "resource", ::resource)
lua.pushBinding(self, "resourcePositive", ::resourcePositive)
lua.pushBinding(self, "setResource", ::setResource)
lua.pushBinding(self, "modifyResource", ::modifyResource)
lua.pushBinding(self, "giveResource", ::giveResource)
lua.pushBinding(self, false, "consumeResource", ::consumeResource)
lua.pushBinding(self, true, "overConsumeResource", ::consumeResource)
lua.pushBinding(self, "resourceLocked", ::resourceLocked)
lua.pushBinding(self, "setResourceLocked", ::setResourceLocked)
lua.pushBinding(self, "resetResource", ::resetResource)
lua.pushBinding(self, "resetAllResources", ::resetAllResources)
lua.pushBinding(self, "resourceMax", ::resourceMax)
lua.pushBinding(self, "resourcePercentage", ::resourcePercentage)
lua.pushBinding(self, "setResourcePercentage", ::setResourcePercentage)
lua.pushBinding(self, "modifyResourcePercentage", ::modifyResourcePercentage)
lua.pushBinding(self, "getPersistentEffects", ::getPersistentEffects)
lua.pushBinding(self, "addPersistentEffect", ::addPersistentEffect)
lua.pushBinding(self, "addPersistentEffects", ::addPersistentEffects)
lua.pushBinding(self, "setPersistentEffects", ::setPersistentEffects)
lua.pushBinding(self, "clearPersistentEffects", ::clearPersistentEffects)
lua.pushBinding(self, "clearAllPersistentEffects", ::clearAllPersistentEffects)
lua.pushBinding(self, "addEphemeralEffect", ::addEphemeralEffect)
lua.pushBinding(self, "addEphemeralEffects", ::addEphemeralEffects)
lua.pushBinding(self, "removeEphemeralEffect", ::removeEphemeralEffect)
lua.pushBinding(self, "removeEphemeralEffects", ::removeEphemeralEffects)
lua.pushBinding(self, "damageTakenSince", ::damageTakenSince)
lua.pushBinding(self, "inflictedHitsSince", ::inflictedHitsSince)
lua.pushBinding(self, "inflictedDamageSince", ::inflictedDamageSince)
lua.pushBinding(self, "activeUniqueStatusEffectSummary", ::activeUniqueStatusEffectSummary)
lua.pushBinding(self, "uniqueStatusEffectActive", ::uniqueStatusEffectActive)
lua.pushBinding(self, "primaryDirectives", ::primaryDirectives)
lua.pushBinding(self, "setPrimaryDirectives", ::setPrimaryDirectives)
lua.pushBinding(self, "applySelfDamageRequest", ::applySelfDamageRequest)
lua.pushBinding(self, "appliesEnvironmentStatusEffects", ::appliesEnvironmentStatusEffects)
lua.pushBinding(self, "appliesWeatherStatusEffects", ::appliesWeatherStatusEffects)
lua.pushBinding(self, "minimumLiquidStatusEffectPercentage", ::minimumLiquidStatusEffectPercentage)
lua.pushBinding(self, "setAppliesEnvironmentStatusEffects", ::setAppliesEnvironmentStatusEffects)
lua.pushBinding(self, "setAppliesWeatherStatusEffects", ::setAppliesWeatherStatusEffects)
lua.pushBinding(self, "setMinimumLiquidStatusEffectPercentage", ::setMinimumLiquidStatusEffectPercentage)
lua.pop()
} }

View File

@ -4,19 +4,13 @@ import com.google.gson.JsonElement
import com.google.gson.JsonNull import com.google.gson.JsonNull
import com.google.gson.internal.bind.TypeAdapters import com.google.gson.internal.bind.TypeAdapters
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import org.classdump.luna.ByteString
import org.classdump.luna.runtime.ExecutionContext
import ru.dbotthepony.kommons.util.XXHash32 import ru.dbotthepony.kommons.util.XXHash32
import ru.dbotthepony.kommons.util.XXHash64 import ru.dbotthepony.kommons.util.XXHash64
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
import ru.dbotthepony.kstarbound.json.JsonPath import ru.dbotthepony.kstarbound.json.JsonPath
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.from
import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.userdata.LuaPerlinNoise import ru.dbotthepony.kstarbound.lua.userdata.LuaPerlinNoise
import ru.dbotthepony.kstarbound.lua.userdata.LuaRandom import ru.dbotthepony.kstarbound.lua.userdata.LuaRandom
import ru.dbotthepony.kstarbound.util.SBPattern import ru.dbotthepony.kstarbound.util.SBPattern
@ -47,7 +41,7 @@ private fun replaceTags(args: LuaThread.ArgStack): Int {
private fun hash32(args: LuaThread.ArgStack): Int { private fun hash32(args: LuaThread.ArgStack): Int {
val digest = XXHash32(2938728349.toInt()) val digest = XXHash32(2938728349.toInt())
while (args.hasNext()) { while (args.hasNext) {
when (args.peek()) { when (args.peek()) {
LuaType.BOOLEAN -> digest.update(if (args.nextBoolean()) 1 else 0) LuaType.BOOLEAN -> digest.update(if (args.nextBoolean()) 1 else 0)
LuaType.NUMBER -> toBytes(digest::update, args.nextDouble()) LuaType.NUMBER -> toBytes(digest::update, args.nextDouble())
@ -62,7 +56,7 @@ private fun hash32(args: LuaThread.ArgStack): Int {
private fun hash64(args: LuaThread.ArgStack): Long { private fun hash64(args: LuaThread.ArgStack): Long {
val digest = XXHash64(1997293021376312589L) val digest = XXHash64(1997293021376312589L)
while (args.hasNext()) { while (args.hasNext) {
when (args.peek()) { when (args.peek()) {
LuaType.BOOLEAN -> digest.update(if (args.nextBoolean()) 1 else 0) LuaType.BOOLEAN -> digest.update(if (args.nextBoolean()) 1 else 0)
LuaType.NUMBER -> toBytes(digest::update, args.nextDouble()) LuaType.NUMBER -> toBytes(digest::update, args.nextDouble())
@ -329,17 +323,34 @@ fun provideUtilityBindings(lua: LuaThread) {
lua.pop() lua.pop()
} }
fun provideConfigBindings(lua: LuaEnvironment, lookup: ExecutionContext.(path: JsonPath, ifMissing: Any?) -> Any?) { private typealias ConfigLookup = (path: JsonPath) -> JsonElement?
val config = lua.newTable()
lua.globals["config"] = config private fun lookupConfigValue(self: ConfigLookup, args: LuaThread.ArgStack): Int {
config["getParameter"] = createConfigBinding(lookup) val parameter = args.nextString()
val lookup = self(JsonPath.query(parameter))
if (lookup == null) {
// TODO: while this is considerably faster, it does not correspond to original engine behavior where "default value" is always copied (lua -> json -> lua)
if (args.top == 1) {
args.lua.push()
} else if (args.top > 2) {
args.lua.dup(2)
}
} else {
args.lua.push(lookup)
}
return 1
} }
fun createConfigBinding(lookup: ExecutionContext.(path: JsonPath, ifMissing: Any?) -> Any?) = luaFunction { name: ByteString, default: Any? -> fun provideConfigBinding(lua: LuaThread, lookup: ConfigLookup) {
val get = lookup(this, JsonPath.query(name.decode()), default) lua.pushTable()
lua.dup()
if (get is JsonElement) lua.storeGlobal("config")
returnBuffer.setTo(from(get)) lua.pushBinding(lookup, "getParameter", ::lookupConfigValue)
else lua.pop()
returnBuffer.setTo(get) }
fun createConfigBinding(lua: LuaThread, lookup: ConfigLookup) {
lua.push { lookupConfigValue(lookup, it) }
} }

View File

@ -522,10 +522,10 @@ private fun getObjectParameter(self: World<*, *>, args: LuaThread.ArgStack): Int
// FIXME: this is stupid (defaultValue is ignored when we lookup parameter on non existing entity), // FIXME: this is stupid (defaultValue is ignored when we lookup parameter on non existing entity),
// but we must retain original behavior // but we must retain original behavior
val entity = self.entities[id] as? WorldObject ?: return 0 val entity = self.entities[id] as? WorldObject ?: return 0
val result = entity.lookupProperty(parameter) val result = entity.lookupPropertyOrNull(parameter)
if (result.isJsonNull) { if (result == null) {
// TODO: while this is faster, it does not correspond to original engine behavior where "default value" is always copied (lua -> json -> lua) // TODO: while this is considerably faster, it does not correspond to original engine behavior where "default value" is always copied (lua -> json -> lua)
if (args.top == 2) { if (args.top == 2) {
args.lua.push() args.lua.push()
} else if (args.top != 3) { } else if (args.top != 3) {

View File

@ -24,7 +24,7 @@ import ru.dbotthepony.kstarbound.world.tileAreaBrush
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
private fun damageTilesImpl(self: World<*, *>, args: LuaThread.ArgStack): CompletableFuture<TileDamageResult> { private fun damageTilesImpl(self: World<*, *>, args: LuaThread.ArgStack): CompletableFuture<TileDamageResult> {
val positions = args.readTableValues { getVector2i() ?: throw IllegalArgumentException("Positions table contains invalid positions") } val positions = args.readTableValues { getVector2i(it) ?: throw IllegalArgumentException("Positions table contains invalid positions") }
val isBackground = args.nextBoolean() val isBackground = args.nextBoolean()
val sourcePosition = args.nextVector2d() val sourcePosition = args.nextVector2d()

View File

@ -1,255 +1,363 @@
package ru.dbotthepony.kstarbound.lua.bindings package ru.dbotthepony.kstarbound.lua.bindings
import com.google.common.collect.ImmutableList
import com.google.gson.JsonNull import com.google.gson.JsonNull
import com.google.gson.JsonPrimitive import com.google.gson.JsonPrimitive
import org.classdump.luna.ByteString import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import org.classdump.luna.LuaRuntimeException
import org.classdump.luna.Table
import ru.dbotthepony.kstarbound.Registries import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.DamageSource import ru.dbotthepony.kstarbound.defs.DamageSource
import ru.dbotthepony.kstarbound.defs.quest.QuestArcDescriptor import ru.dbotthepony.kstarbound.fromJsonFast
import ru.dbotthepony.kstarbound.defs.quest.QuestDescriptor
import ru.dbotthepony.kstarbound.json.JsonPath import ru.dbotthepony.kstarbound.json.JsonPath
import ru.dbotthepony.kstarbound.lua.LuaEnvironment import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.from import ru.dbotthepony.kstarbound.lua.LuaType
import ru.dbotthepony.kstarbound.lua.get import ru.dbotthepony.kstarbound.lua.getVector2i
import ru.dbotthepony.kstarbound.lua.indexNoYield import ru.dbotthepony.kstarbound.lua.nextColor
import ru.dbotthepony.kstarbound.lua.iterator import ru.dbotthepony.kstarbound.lua.nextVector2d
import ru.dbotthepony.kstarbound.lua.luaFunction import ru.dbotthepony.kstarbound.lua.push
import ru.dbotthepony.kstarbound.lua.set import ru.dbotthepony.kstarbound.lua.setTableValue
import ru.dbotthepony.kstarbound.lua.tableOf
import ru.dbotthepony.kstarbound.lua.toByteString
import ru.dbotthepony.kstarbound.lua.toColor
import ru.dbotthepony.kstarbound.lua.toJson
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.lua.toVector2d
import ru.dbotthepony.kstarbound.lua.toVector2i
import ru.dbotthepony.kstarbound.util.SBPattern import ru.dbotthepony.kstarbound.util.SBPattern
import ru.dbotthepony.kstarbound.util.sbIntern import ru.dbotthepony.kstarbound.util.sbIntern
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
import kotlin.collections.forEach
import kotlin.collections.isNotEmpty
import kotlin.collections.set
import kotlin.collections.withIndex
fun provideWorldObjectBindings(self: WorldObject, lua: LuaEnvironment) { private fun name(self: WorldObject, args: LuaThread.ArgStack): Int {
val config = lua.newTable() args.lua.push(self.config.key)
lua.globals["config"] = config return 1
}
config["getParameter"] = luaFunction { name: ByteString, default: Any? ->
val path = JsonPath.query(name.decode()) private fun directions(self: WorldObject, args: LuaThread.ArgStack): Int {
val find = self.lookupProperty(path) { JsonNull.INSTANCE } args.lua.push(self.direction.numericalValue)
return 1
if (find.isJsonNull) { }
returnBuffer.setTo(default)
} else { private fun position(self: WorldObject, args: LuaThread.ArgStack): Int {
returnBuffer.setTo(from(find)) args.lua.push(self.tilePosition)
} return 1
} }
val table = lua.newTable() private fun setInteractive(self: WorldObject, args: LuaThread.ArgStack): Int {
lua.globals["object"] = table self.isInteractive = args.nextBoolean()
return 0
table["name"] = luaFunction { returnBuffer.setTo(self.config.key.toByteString()) } }
table["direction"] = luaFunction { returnBuffer.setTo(self.direction.numericalValue) }
table["position"] = luaFunction { returnBuffer.setTo(from(self.tilePosition)) } private fun uniqueId(self: WorldObject, args: LuaThread.ArgStack): Int {
table["setInteractive"] = luaFunction { interactive: Boolean -> self.isInteractive = interactive } args.lua.push(self.uniqueID.get())
table["uniqueId"] = luaFunction { returnBuffer.setTo(self.uniqueID.get().toByteString()) } return 1
table["setUniqueId"] = luaFunction { id: ByteString? -> self.uniqueID.accept(id?.decode()?.sbIntern()) } }
table["boundBox"] = luaFunction { returnBuffer.setTo(from(self.metaBoundingBox)) }
private fun setUniqueId(self: WorldObject, args: LuaThread.ArgStack): Int {
// original engine parity, it returns occupied spaces in local coordinates self.uniqueID.accept(args.nextOptionalString()?.sbIntern())
table["spaces"] = luaFunction { returnBuffer.setTo(tableOf(*self.occupySpaces.map { from(it - self.tilePosition) }.toTypedArray())) } return 0
}
table["setProcessingDirectives"] = luaFunction { directives: ByteString -> self.animator.processingDirectives = directives.decode() }
table["setSoundEffectEnabled"] = luaFunction { state: Boolean -> self.soundEffectEnabled = state } private fun boundBox(self: WorldObject, args: LuaThread.ArgStack): Int {
args.lua.push(self.metaBoundingBox)
table["smash"] = luaFunction { smash: Boolean? -> return 1
if (smash == true) { }
self.health = 0.0
} // original engine parity, it returns occupied spaces in local coordinates
private fun spaces(self: WorldObject, local: Boolean, args: LuaThread.ArgStack): Int {
self.remove(AbstractEntity.RemovalReason.DYING) val spaces = self.occupySpaces
} val spos = self.tilePosition
table["level"] = luaFunction { returnBuffer.setTo(self.lookupProperty(JsonPath("level")) { JsonPrimitive(self.world.template.threatLevel) }.asDouble) } args.lua.pushTable(spaces.size)
table["toAbsolutePosition"] = luaFunction { pos: Table -> returnBuffer.setTo(from(toVector2d(pos) + self.position)) }
if (local) {
table["say"] = luaFunction { line: ByteString, tags: Table?, sayConfig: Table? -> for ((i, pos) in spaces.withIndex()) {
if (tags == null) { args.lua.setTableValue(i + 1, pos - spos)
if (line.isEmpty) { }
returnBuffer.setTo(false) } else {
} else { for ((i, pos) in spaces.withIndex()) {
self.addChatMessage(line.decode(), sayConfig?.toJson() ?: JsonNull.INSTANCE) args.lua.setTableValue(i + 1, pos)
returnBuffer.setTo(true) }
} }
} else {
if (line.isEmpty) { return 1
returnBuffer.setTo(false) }
} else {
self.addChatMessage(SBPattern.of(line.decode()).resolveOrSkip({ tags[it]?.toString() }), sayConfig?.toJson() ?: JsonNull.INSTANCE) private fun setProcessingDirectives(self: WorldObject, args: LuaThread.ArgStack): Int {
returnBuffer.setTo(true) self.animator.processingDirectives = args.nextString().sbIntern()
} return 0
} }
}
private fun setSoundEffectEnabled(self: WorldObject, args: LuaThread.ArgStack): Int {
table["sayPortrait"] = luaFunction { line: ByteString, portrait: ByteString, tags: Table?, config: Table -> self.soundEffectEnabled = args.nextBoolean()
if (tags == null) { return 0
if (line.isEmpty) { }
returnBuffer.setTo(false)
} else { private fun smash(self: WorldObject, args: LuaThread.ArgStack): Int {
self.addChatMessage(line.decode(), config.toJson(), portrait.decode()) if (args.nextOptionalBoolean() == true) {
returnBuffer.setTo(true) self.health = 0.0
} }
} else {
if (line.isEmpty) { self.remove(AbstractEntity.RemovalReason.DYING)
returnBuffer.setTo(false) return 0
} else { }
self.addChatMessage(SBPattern.of(line.decode()).resolveOrSkip({ tags[it]?.toString() }), config.toJson(), portrait.decode())
returnBuffer.setTo(true) private fun level(self: WorldObject, args: LuaThread.ArgStack): Int {
} args.lua.push(self.lookupProperty(JsonPath("level")) { JsonPrimitive(self.world.template.threatLevel) }.asDouble)
} return 1
} }
table["isTouching"] = luaFunction { entity: Number -> private fun toAbsolutePosition(self: WorldObject, args: LuaThread.ArgStack): Int {
val find = self.world.entities[entity] args.lua.push(args.nextVector2d() + self.position)
return 1
if (find != null) { }
returnBuffer.setTo(find.collisionArea.intersect(self.volumeBoundingBox))
} else { private fun say(self: WorldObject, hasPortrait: Boolean, args: LuaThread.ArgStack): Int {
returnBuffer.setTo(false) val line = args.nextString()
} val portrait = if (hasPortrait) args.nextString() else null
} val tags = Object2ObjectArrayMap<String, String>()
val tagsType = args.peek()
table["setLightColor"] = luaFunction { color: Table ->
self.lightSourceColor = toColor(color) if (tagsType == LuaType.TABLE) {
} val pairs = args.readTable(
keyVisitor = { getString(it) ?: throw IllegalArgumentException("Tag replacement table contains non-strings as keys") },
table["getLightColor"] = luaFunction { valueVisitor = { getString(it) ?: throw IllegalArgumentException("Tag replacement table contains non-strings as keys") },
returnBuffer.setTo(from(self.lightSourceColor)) )
}
for ((k, v) in pairs) {
table["inputNodeCount"] = luaFunction { returnBuffer.setTo(self.inputNodes.size.toLong()) } tags[k] = v
table["outputNodeCount"] = luaFunction { returnBuffer.setTo(self.outputNodes.size.toLong()) } }
} else if (!tagsType.isNothing) {
table["getInputNodePosition"] = luaFunction { index: Long -> throw IllegalArgumentException("bad argument #2 to object.say: table expected, got $tagsType")
returnBuffer.setTo(from(self.inputNodes[index.toInt()].position)) } else {
} args.skip()
}
table["getOutputNodePosition"] = luaFunction { index: Long ->
returnBuffer.setTo(from(self.outputNodes[index.toInt()].position)) val sayConfig = args.nextOptionalJson() ?: JsonNull.INSTANCE
}
if (line.isBlank()) {
table["getInputNodeLevel"] = luaFunction { index: Long -> args.lua.push(false)
returnBuffer.setTo(self.inputNodes[index.toInt()].state) } else if (tags.isEmpty()) {
} self.addChatMessage(line, sayConfig, portrait = portrait)
args.lua.push(true)
table["getOutputNodeLevel"] = luaFunction { index: Long -> } else {
returnBuffer.setTo(self.outputNodes[index.toInt()].state) self.addChatMessage(SBPattern.of(line).resolveOrSkip(tags::get), sayConfig, portrait = portrait)
} args.lua.push(true)
}
table["isInputNodeConnected"] = luaFunction { index: Long ->
returnBuffer.setTo(self.inputNodes[index.toInt()].connections.isNotEmpty()) return 1
} }
table["isOutputNodeConnected"] = luaFunction { index: Long -> private fun isTouching(self: WorldObject, args: LuaThread.ArgStack): Int {
returnBuffer.setTo(self.outputNodes[index.toInt()].connections.isNotEmpty()) val id = args.nextInt()
} val entity = self.world.entities[id]
table["getInputNodeIds"] = luaFunction { index: Long -> if (entity == null) {
val results = newTable() args.lua.push(false)
} else {
for (connection in self.inputNodes[index.toInt()].connections) { args.lua.push(entity.collisionArea.intersect(self.volumeBoundingBox))
val entity = self.world.entityIndex.tileEntityAt(connection.entityLocation, WorldObject::class) }
if (entity != null) { return 1
results[entity.entityID] = connection.index }
}
} private fun setLightColor(self: WorldObject, args: LuaThread.ArgStack): Int {
self.lightSourceColor = args.nextColor()
returnBuffer.setTo(results) return 0
} }
table["getOutputNodeIds"] = luaFunction { index: Long -> private fun getLightColor(self: WorldObject, args: LuaThread.ArgStack): Int {
val results = newTable() args.lua.push(self.lightSourceColor)
return 1
for (connection in self.outputNodes[index.toInt()].connections) { }
val entity = self.world.entityIndex.tileEntityAt(connection.entityLocation, WorldObject::class)
private fun inputNodeCount(self: WorldObject, args: LuaThread.ArgStack): Int {
if (entity != null) { args.lua.push(self.inputNodes.size.toLong())
results[entity.entityID] = connection.index return 1
} }
}
private fun outputNodeCount(self: WorldObject, args: LuaThread.ArgStack): Int {
returnBuffer.setTo(results) args.lua.push(self.outputNodes.size.toLong())
} return 1
}
table["setOutputNodeLevel"] = luaFunction { index: Long, state: Boolean ->
self.outputNodes[index.toInt()].state = state private fun getInputNodePosition(self: WorldObject, args: LuaThread.ArgStack): Int {
} args.lua.push(self.inputNodes[args.nextInt()].position)
return 1
table["setAllOutputNodes"] = luaFunction { state: Boolean -> }
self.outputNodes.forEach { it.state = state }
} private fun getOutputNodePosition(self: WorldObject, args: LuaThread.ArgStack): Int {
args.lua.push(self.outputNodes[args.nextInt()].position)
table["setOfferedQuests"] = luaFunction { quests: Table? -> return 1
self.offeredQuests.clear() }
if (quests != null) { private fun getInputNodeLevel(self: WorldObject, args: LuaThread.ArgStack): Int {
for ((_, v) in quests) { args.lua.push(self.inputNodes[args.nextInt()].state)
if (v is Table) { return 1
self.offeredQuests.add(Starbound.gson.fromJson(v.toJson(), QuestArcDescriptor::class.java)) }
} else if (v is ByteString) {
self.offeredQuests.add(QuestArcDescriptor(ImmutableList.of(QuestDescriptor(v.decode())))) private fun getOutputNodeLevel(self: WorldObject, args: LuaThread.ArgStack): Int {
} else { args.lua.push(self.outputNodes[args.nextInt()].state)
throw LuaRuntimeException("Unknown quest arc descriptor type: $v") return 1
} }
}
} private fun isInputNodeConnected(self: WorldObject, args: LuaThread.ArgStack): Int {
} args.lua.push(self.inputNodes[args.nextInt()].connections.isNotEmpty())
return 1
table["setTurnInQuests"] = luaFunction { quests: Table? -> }
self.turnInQuests.clear()
private fun isOutputNodeConnected(self: WorldObject, args: LuaThread.ArgStack): Int {
if (quests != null) { args.lua.push(self.outputNodes[args.nextInt()].connections.isNotEmpty())
for ((_, v) in quests) { return 1
self.turnInQuests.add((v as ByteString).decode()) }
}
} private fun getInputNodeIds(self: WorldObject, args: LuaThread.ArgStack): Int {
} val connections = self.inputNodes[args.nextInt()].connections
table["setConfigParameter"] = luaFunction { key: ByteString, value: Any? -> args.lua.pushTable(connections.size)
self.parameters[key.decode()] = toJsonFromLua(value)
} for (connection in connections) {
val entity = self.world.entityIndex.tileEntityAt(connection.entityLocation, WorldObject::class)
table["setAnimationParameter"] = luaFunction { key: ByteString, value: Any? ->
self.scriptedAnimationParameters[key.decode()] = toJsonFromLua(value) if (entity != null) {
} args.lua.setTableValue(entity.entityID, connection.index)
}
table["setMaterialSpaces"] = luaFunction { spaces: Table? -> }
self.declaredMaterialSpaces.clear()
return 1
if (spaces != null) { }
for ((i, pair) in spaces) {
pair as Table private fun getOutputNodeIds(self: WorldObject, args: LuaThread.ArgStack): Int {
val connections = self.outputNodes[args.nextInt()].connections
val position = toVector2i(indexNoYield(pair, 1L) ?: throw NullPointerException("invalid space at $i"))
val material = indexNoYield(pair, 2L) as? ByteString ?: throw NullPointerException("invalid space at $i") args.lua.pushTable(connections.size)
self.declaredMaterialSpaces.add(position to Registries.tiles.ref(material.decode())) for (connection in connections) {
} val entity = self.world.entityIndex.tileEntityAt(connection.entityLocation, WorldObject::class)
}
} if (entity != null) {
args.lua.setTableValue(entity.entityID, connection.index)
table["setDamageSources"] = luaFunction { sources: Table? -> }
self.customDamageSources.clear() }
if (sources != null) { return 1
for ((_, v) in sources) { }
self.customDamageSources.add(Starbound.gson.fromJson((v as Table).toJson(), DamageSource::class.java))
} private fun setOutputNodeLevel(self: WorldObject, args: LuaThread.ArgStack): Int {
} val index = args.nextInt()
} val state = args.nextBoolean()
self.outputNodes[index].state = state
table["health"] = luaFunction { returnBuffer.setTo(self.health) } return 0
table["setHealth"] = luaFunction { health: Double -> self.health = health } }
private fun setAllOutputNodes(self: WorldObject, args: LuaThread.ArgStack): Int {
val state = args.nextBoolean()
self.outputNodes.forEach { it.state = state }
return 0
}
private fun setConfigParameter(self: WorldObject, args: LuaThread.ArgStack): Int {
val key = args.nextString().sbIntern()
val value = args.nextJson()
self.parameters[key] = value
return 0
}
private fun setAnimationParameter(self: WorldObject, args: LuaThread.ArgStack): Int {
val key = args.nextString().sbIntern()
val value = args.nextJson()
self.scriptedAnimationParameters[key] = value
return 0
}
private fun setMaterialSpaces(self: WorldObject, args: LuaThread.ArgStack): Int {
self.declaredMaterialSpaces.clear()
if (args.hasNext) {
val pairs = args.readTableValues { tI ->
push(1L)
val vType = loadTableValue(tI)
val position = getVector2i() ?: throw IllegalArgumentException("invalid space pair, first value is not a vector: $vType")
push(2L)
val mType = loadTableValue(tI)
val material = getString() ?: throw IllegalArgumentException("invalid space pair, second value is not a string: $mType")
position to Registries.tiles.ref(material)
}
self.declaredMaterialSpaces.addAll(pairs)
}
return 0
}
private fun setDamageSources(self: WorldObject, args: LuaThread.ArgStack): Int {
self.customDamageSources.clear()
if (args.hasNext) {
val sources = args.readTableValues {
Starbound.gson.fromJsonFast(getJson(it)!!, DamageSource::class.java)
}
self.customDamageSources.addAll(sources)
}
return 0
}
private fun health(self: WorldObject, args: LuaThread.ArgStack): Int {
args.lua.push(self.health)
return 1
}
private fun setHealth(self: WorldObject, args: LuaThread.ArgStack): Int {
self.health = args.nextDouble()
return 0
}
fun provideWorldObjectBindings(self: WorldObject, lua: LuaThread) {
provideConfigBinding(lua, self::lookupPropertyOrNull)
lua.pushTable()
lua.dup()
lua.storeGlobal("object")
lua.pushBinding(self, "name", ::name)
lua.pushBinding(self, "directions", ::directions)
lua.pushBinding(self, "position", ::position)
lua.pushBinding(self, "setInteractive", ::setInteractive)
lua.pushBinding(self, "uniqueId", ::uniqueId)
lua.pushBinding(self, "setUniqueId", ::setUniqueId)
lua.pushBinding(self, "boundBox", ::boundBox)
lua.pushBinding(self, true, "spaces", ::spaces)
lua.pushBinding(self, false, "worldSpaces", ::spaces)
lua.pushBinding(self, "setProcessingDirectives", ::setProcessingDirectives)
lua.pushBinding(self, "setSoundEffectEnabled", ::setSoundEffectEnabled)
lua.pushBinding(self, "smash", ::smash)
lua.pushBinding(self, "level", ::level)
lua.pushBinding(self, "toAbsolutePosition", ::toAbsolutePosition)
lua.pushBinding(self, false, "say", ::say)
lua.pushBinding(self, true, "sayPortrait", ::say)
lua.pushBinding(self, "isTouching", ::isTouching)
lua.pushBinding(self, "setLightColor", ::setLightColor)
lua.pushBinding(self, "getLightColor", ::getLightColor)
lua.pushBinding(self, "inputNodeCount", ::inputNodeCount)
lua.pushBinding(self, "outputNodeCount", ::outputNodeCount)
lua.pushBinding(self, "getInputNodePosition", ::getInputNodePosition)
lua.pushBinding(self, "getOutputNodePosition", ::getOutputNodePosition)
lua.pushBinding(self, "getInputNodeLevel", ::getInputNodeLevel)
lua.pushBinding(self, "getOutputNodeLevel", ::getOutputNodeLevel)
lua.pushBinding(self, "isInputNodeConnected", ::isInputNodeConnected)
lua.pushBinding(self, "isOutputNodeConnected", ::isOutputNodeConnected)
lua.pushBinding(self, "getInputNodeIds", ::getInputNodeIds)
lua.pushBinding(self, "getOutputNodeIds", ::getOutputNodeIds)
lua.pushBinding(self, "setOutputNodeLevel", ::setOutputNodeLevel)
lua.pushBinding(self, "setAllOutputNodes", ::setAllOutputNodes)
lua.pushBinding(self, WorldObject::offeredQuests, "setOfferedQuests", ::setOfferedQuests)
lua.pushBinding(self, WorldObject::turnInQuests, "setTurnInQuests", ::setTurnInQuests)
lua.pushBinding(self, "setConfigParameter", ::setConfigParameter)
lua.pushBinding(self, "setAnimationParameter", ::setAnimationParameter)
lua.pushBinding(self, "setMaterialSpaces", ::setMaterialSpaces)
lua.pushBinding(self, "setDamageSources", ::setDamageSources)
lua.pushBinding(self, "health", ::health)
lua.pushBinding(self, "setHealth", ::setHealth)
lua.pop()
} }

View File

@ -1,4 +1,4 @@
package ru.dbotthepony.kstarbound.world.entities.behavior package ru.dbotthepony.kstarbound.lua.userdata
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable import ru.dbotthepony.kstarbound.json.builder.IStringSerializable

View File

@ -1,126 +1,303 @@
package ru.dbotthepony.kstarbound.lua.userdata package ru.dbotthepony.kstarbound.lua.userdata
import com.google.common.collect.ImmutableList
import com.google.gson.JsonArray
import com.google.gson.JsonNull import com.google.gson.JsonNull
import com.google.gson.JsonObject import com.google.gson.JsonObject
import org.classdump.luna.ByteString import com.google.gson.JsonPrimitive
import org.classdump.luna.LuaRuntimeException import ru.dbotthepony.kommons.gson.contains
import org.classdump.luna.Table import ru.dbotthepony.kommons.gson.get
import org.classdump.luna.Userdata
import org.classdump.luna.impl.ImmutableTable
import org.classdump.luna.runtime.LuaFunction
import ru.dbotthepony.kommons.gson.set import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.gson.stream
import ru.dbotthepony.kstarbound.Registries import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.actor.behavior.BehaviorDefinition import ru.dbotthepony.kstarbound.defs.actor.behavior.BehaviorDefinition
import ru.dbotthepony.kstarbound.defs.actor.behavior.CompositeNodeType
import ru.dbotthepony.kstarbound.defs.actor.behavior.NodeOutput
import ru.dbotthepony.kstarbound.defs.actor.behavior.NodeParameter
import ru.dbotthepony.kstarbound.defs.actor.behavior.NodeParameterValue
import ru.dbotthepony.kstarbound.fromJsonFast import ru.dbotthepony.kstarbound.fromJsonFast
import ru.dbotthepony.kstarbound.json.mergeJson import ru.dbotthepony.kstarbound.json.mergeJson
import ru.dbotthepony.kstarbound.lua.LuaEnvironment import ru.dbotthepony.kstarbound.lua.LuaHandle
import ru.dbotthepony.kstarbound.lua.get import ru.dbotthepony.kstarbound.lua.LuaThread
import ru.dbotthepony.kstarbound.lua.iterator import ru.dbotthepony.kstarbound.lua.LuaType
import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.tableMapOf
import ru.dbotthepony.kstarbound.lua.toJson
import ru.dbotthepony.kstarbound.lua.toJsonFromLua import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.world.entities.behavior.AbstractBehaviorNode import ru.dbotthepony.kstarbound.util.valueOf
import ru.dbotthepony.kstarbound.world.entities.behavior.BehaviorTree
import ru.dbotthepony.kstarbound.world.entities.behavior.Blackboard
import java.util.concurrent.atomic.AtomicLong
class BehaviorState(val tree: BehaviorTree) : Userdata<BehaviorState>() { private fun replaceBehaviorTag(parameter: NodeParameterValue, treeParameters: Map<String, NodeParameterValue>): NodeParameterValue {
val blackboard get() = tree.blackboard var str: String? = null
val lua get() = blackboard.lua
val functions = HashMap<String, LuaFunction<*, *, *, *, *>>()
init { if (parameter.key != null)
lua.attach(tree.scripts) str = parameter.key
for (name in tree.functions) { // original engine does this, and i don't know why this make any sense
val get = lua.globals[name] else if (parameter.value is JsonPrimitive && parameter.value.isString)
str = parameter.value.asString
if (get == null) { if (!str.isNullOrEmpty()) {
throw LuaRuntimeException("No such function for behavior: $name") if (str.first() == '<' && str.last() == '>') {
} else if (get !is LuaFunction<*, *, *, *, *>) { val treeKey = str.substring(1, str.length - 1)
throw LuaRuntimeException("Not a Lua function for behavior: $name") val param = treeParameters[treeKey]
if (param != null) {
return param
} else { } else {
this.functions[name] = get throw NoSuchElementException("No parameter specified for tag '$treeKey'")
} }
} }
} }
fun run(delta: Double): AbstractBehaviorNode.Status { return parameter
val ephemerals = blackboard.takeEphemerals() }
val status = tree.runAndReset(delta, this)
blackboard.clearEphemerals(ephemerals)
return status
}
override fun getMetatable(): Table { private fun replaceOutputBehaviorTag(output: String?, treeParameters: Map<String, NodeParameterValue>): String? {
return Companion.metatable if (output == null) {
} return null
} else if (output.first() == '<' && output.last() == '>') {
val replacement = treeParameters[output.substring(1, output.length - 1)] ?: throw NoSuchElementException("No parameter specified for tag '$output'")
override fun setMetatable(mt: Table?): Table { if (replacement.key != null)
throw UnsupportedOperationException() return replacement.key
} else if (replacement.value is JsonPrimitive && replacement.value.isString)
return replacement.value.asString
override fun getUserValue(): BehaviorState { else
return this return null
} } else {
return output
override fun setUserValue(value: BehaviorState?): BehaviorState? {
throw UnsupportedOperationException()
}
companion object {
// required for Lua code
// in original engine, passes directly 32/64 bit pointer of *Node struct
val NODE_GARBAGE_INDEX = AtomicLong()
private fun __index(): Table {
return metatable
}
private val metatable = ImmutableTable.Builder()
.add("__index", luaFunction { _: Any?, index: Any -> returnBuffer.setTo(__index()[index]) })
.add("run", luaFunction { self: BehaviorState, delta: Number ->
returnBuffer.setTo(self.run(delta.toDouble()))
})
.add("clear", luaFunction { self: BehaviorState ->
self.tree.reset()
})
.add("blackboard", luaFunction { self: BehaviorState ->
returnBuffer.setTo(self.blackboard)
})
.build()
fun provideBindings(lua: LuaEnvironment) {
lua.globals["behavior"] = lua.tableMapOf(
"behavior" to luaFunction { config: Any, parameters: Table, _: Any?, blackboard: Blackboard? ->
val tree: BehaviorTree
if (config is ByteString) {
val base = Registries.behavior.getOrThrow(config.decode())
if (!parameters.iterator().hasNext()) {
tree = BehaviorTree(blackboard ?: Blackboard(lua), base.value)
} else {
tree = BehaviorTree(blackboard ?: Blackboard(lua), Starbound.gson.fromJsonFast(base.json.deepCopy().also {
it as JsonObject
it["parameters"] = mergeJson(it["parameters"] ?: JsonNull.INSTANCE, toJsonFromLua(parameters))
}, BehaviorDefinition::class.java))
}
} else {
val cast = (config as Table).toJson(true) as JsonObject
if (parameters.iterator().hasNext())
cast["parameters"] = mergeJson(cast["parameters"] ?: JsonNull.INSTANCE, toJsonFromLua(parameters))
tree = BehaviorTree(blackboard ?: Blackboard(lua), Starbound.gson.fromJsonFast(cast, BehaviorDefinition::class.java))
}
returnBuffer.setTo(BehaviorState(tree))
}
)
}
} }
} }
@JvmName("pushInputs")
private fun push(lua: LuaThread, parameters: Map<String, NodeParameter>) {
lua.pushTable(hashSize = parameters.size)
for ((k, v) in parameters) {
lua.push(k)
v.push(lua)
lua.setTableValue()
}
}
@JvmName("pushOutputs")
private fun push(lua: LuaThread, parameters: Map<String, NodeOutput>) {
lua.pushTable(hashSize = parameters.size)
for ((k, v) in parameters) {
lua.push(k)
v.push(lua)
lua.setTableValue()
}
}
private fun createNode(
lua: LuaThread,
data: JsonObject,
treeParameters: Map<String, NodeParameterValue>,
blackboard: LuaHandle,
scripts: MutableSet<String>,
functions: MutableSet<String>,
handles: MutableList<LuaHandle>
): LuaHandle {
val type = BehaviorNodeType.entries.valueOf(data["type"].asString)
val name = data["name"].asString
val parameterConfig = data.get("parameters") { JsonObject() }
if (type == BehaviorNodeType.MODULE) {
val base = Registries.behavior.getOrThrow(name).value
base.scripts.forEach { scripts.add(it.fullPath) }
// merge in module parameters to a copy of the treeParameters to propagate
// tree parameters into the subtree, but allow modules to override
val mergedParameters = LinkedHashMap(base.mappedParameters)
mergedParameters.putAll(treeParameters)
for ((k, v) in parameterConfig.entrySet()) {
mergedParameters[k] = replaceBehaviorTag(
Starbound.gson.fromJsonFast(v, NodeParameterValue::class.java),
treeParameters
)
}
return createNode(lua, base.root, mergedParameters, blackboard, scripts, functions, handles)
}
val parameters = LinkedHashMap(Registries.behaviorNodes.getOrThrow(name).value.properties)
for ((k, v) in parameters.entries) {
if (k in parameterConfig) {
parameters[k] = v.copy(value = replaceBehaviorTag(
Starbound.gson.fromJsonFast(
parameterConfig[k],
NodeParameterValue::class.java
), treeParameters)
)
} else {
val replaced = replaceBehaviorTag(v.value, treeParameters)
if (replaced != v.value) {
parameters[k] = v.copy(value = replaced)
}
}
}
when (type) {
BehaviorNodeType.ACTION -> {
functions.add(name)
val outputConfig = data.get("output") { JsonObject() }
val output = LinkedHashMap(Registries.behaviorNodes.getOrThrow(name).value.output)
for ((k, v) in output.entries) {
val replaced = replaceOutputBehaviorTag(outputConfig[k]?.asString ?: v.key, treeParameters)
if (replaced != v.key) {
output[k] = v.copy(key = replaced)
}
}
check(lua.loadGlobal("ActionNode") == LuaType.FUNCTION) { "Global ActionNode is not a Lua function" }
lua.push(name)
push(lua, parameters)
push(lua, output)
lua.call(3, 1)
val handle = lua.createHandle()
handles.add(handle)
lua.pop()
return handle
}
BehaviorNodeType.DECORATOR -> {
functions.add(name)
val sacrifice = createNode(lua, data["child"] as JsonObject, treeParameters, blackboard, scripts, functions, handles)
check(lua.loadGlobal("DecoratorNode") == LuaType.FUNCTION) { "Global DecoratorNode is not a Lua function" }
lua.push(name)
push(lua, parameters)
lua.push(sacrifice)
lua.call(3, 1)
val handle = lua.createHandle()
handles.add(handle)
lua.pop()
return handle
}
BehaviorNodeType.COMPOSITE -> {
val children = data.get("children") { JsonArray() }
.stream()
.map { createNode(lua, it as JsonObject, treeParameters, blackboard, scripts, functions, handles) }
.collect(ImmutableList.toImmutableList())
val factory = CompositeNodeType.entries.valueOf(name)
check(lua.loadGlobal(factory.fnName) == LuaType.FUNCTION) { "Global ${factory.fnName} is not a Lua function" }
push(lua, parameters)
lua.pushTable(children.size)
for ((i, child) in children.withIndex()) {
lua.push(i + 1L)
lua.push(child)
lua.setTableValue()
}
lua.call(2, 1)
val handle = lua.createHandle()
handles.add(handle)
lua.pop()
return handle
}
BehaviorNodeType.MODULE -> throw RuntimeException()
}
}
private fun createBlackboard(lua: LuaThread): LuaHandle {
lua.loadGlobal("Blackboard")
lua.call(numResults = 1)
val handle = lua.createHandle()
lua.pop()
return handle
}
private fun createBehaviorTree(args: LuaThread.ArgStack): Int {
val functions = HashSet<String>()
val scripts = HashSet<String>()
val mergedParams: BehaviorDefinition
val blackboard: LuaHandle
val handles = ArrayList<LuaHandle>()
if (args.peek() == LuaType.STRING) {
val base = Registries.behavior.getOrThrow(args.nextString())
val parameters = args.nextJson() as JsonObject
args.skip()
if (args.peek().isNothing) {
blackboard = createBlackboard(args.lua)
} else {
// assume proper blackboard was given
args.lua.dup(args.position++)
blackboard = args.lua.createHandle()
args.lua.pop()
}
if (parameters.size() == 0) {
mergedParams = base.value
} else {
mergedParams = Starbound.gson.fromJsonFast(base.json.deepCopy().also {
it as JsonObject
it["parameters"] = mergeJson(it["parameters"] ?: JsonNull.INSTANCE, toJsonFromLua(parameters))
}, BehaviorDefinition::class.java)
}
} else {
val config = args.nextJson() as JsonObject
val parameters = args.nextJson() as JsonObject
args.skip()
if (args.peek().isNothing) {
blackboard = createBlackboard(args.lua)
} else {
// assume proper blackboard was given
args.lua.dup(args.position++)
blackboard = args.lua.createHandle()
args.lua.pop()
}
if (parameters.size() != 0)
config["parameters"] = mergeJson(config["parameters"] ?: JsonNull.INSTANCE, parameters)
mergedParams = Starbound.gson.fromJsonFast(config, BehaviorDefinition::class.java)
}
handles.add(blackboard)
args.lua.ensureExtraCapacity(40)
val root = createNode(args.lua, mergedParams.root, mergedParams.mappedParameters, blackboard, scripts, functions, handles)
handles.add(root)
check(args.lua.loadGlobal("BehaviorState") == LuaType.FUNCTION) { "Global BehaviorState is not a Lua function" }
args.lua.push(blackboard)
args.lua.push(root)
args.lua.call(2, 1)
handles.forEach { it.close() }
return 1
}
private val script by lazy { LuaThread.loadInternalScript("behavior") }
fun provideBehaviorBindings(lua: LuaThread) {
lua.pushTable()
lua.dup()
lua.storeGlobal("behavior")
lua.setTableValue("behavior", ::createBehaviorTree)
lua.pop()
lua.load(script, "@/internal/behavior.lua")
lua.call()
}

View File

@ -1,4 +1,4 @@
package ru.dbotthepony.kstarbound.world.entities.behavior package ru.dbotthepony.kstarbound.lua.userdata
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable import ru.dbotthepony.kstarbound.json.builder.IStringSerializable

View File

@ -37,5 +37,25 @@ enum class Direction(val normal: Vector2d, override val jsonName: String, val nu
return RIGHT return RIGHT
} }
} }
fun valueOf(value: Long): Direction? {
if (value == 0L) {
return null
} else if (value < 0L) {
return LEFT
} else {
return RIGHT
}
}
fun valueOf(value: Double): Direction? {
if (value == 0.0) {
return null
} else if (value < 0.0) {
return LEFT
} else {
return RIGHT
}
}
} }
} }

View File

@ -693,12 +693,12 @@ class Animator() {
return group in rotationGroups return group in rotationGroups
} }
fun rotationGroups(): Collection<String> { fun rotationGroups(): Set<String> {
return Collections.unmodifiableCollection(rotationGroups.keys) return Collections.unmodifiableSet(rotationGroups.keys)
} }
fun transformationGroups(): Collection<String> { fun transformationGroups(): Set<String> {
return Collections.unmodifiableCollection(transformationGroups.keys) return Collections.unmodifiableSet(transformationGroups.keys)
} }
fun hasTransformationGroup(group: String): Boolean { fun hasTransformationGroup(group: String): Boolean {
@ -757,8 +757,8 @@ class Animator() {
return emitter in particleEmitters return emitter in particleEmitters
} }
fun particleEmitters(): Collection<String> { fun particleEmitters(): Set<String> {
return Collections.unmodifiableCollection(particleEmitters.keys) return Collections.unmodifiableSet(particleEmitters.keys)
} }
fun setParticleEmitterActive(emitter: String, state: Boolean = true) { fun setParticleEmitterActive(emitter: String, state: Boolean = true) {
@ -792,8 +792,8 @@ class Animator() {
get.burstEvent.trigger() get.burstEvent.trigger()
} }
fun lights(): Collection<String> { fun lights(): Set<String> {
return Collections.unmodifiableCollection(lights.keys) return Collections.unmodifiableSet(lights.keys)
} }
fun hasLight(light: String): Boolean { fun hasLight(light: String): Boolean {
@ -827,8 +827,8 @@ class Animator() {
get.pointAngle = angle get.pointAngle = angle
} }
fun sounds(): Collection<String> { fun sounds(): Set<String> {
return Collections.unmodifiableCollection(sounds.keys) return Collections.unmodifiableSet(sounds.keys)
} }
fun hasSound(sound: String): Boolean { fun hasSound(sound: String): Boolean {
@ -881,8 +881,8 @@ class Animator() {
get.signals.push(SoundSignal.STOP_ALL) get.signals.push(SoundSignal.STOP_ALL)
} }
fun effects(): Collection<String> { fun effects(): Set<String> {
return Collections.unmodifiableCollection(effects.keys) return Collections.unmodifiableSet(effects.keys)
} }
fun hasEffect(effect: String): Boolean { fun hasEffect(effect: String): Boolean {
@ -894,8 +894,12 @@ class Animator() {
get.enabled.accept(state) get.enabled.accept(state)
} }
fun parts(): Collection<String> { fun parts(): Set<String> {
return Collections.unmodifiableCollection(parts.keys) return Collections.unmodifiableSet(parts.keys)
}
fun hasPart(part: String): Boolean {
return part in parts
} }
fun partPoint(part: String, property: String): Vector2d? { fun partPoint(part: String, property: String): Vector2d? {

View File

@ -1,156 +0,0 @@
package ru.dbotthepony.kstarbound.world.entities.behavior
import com.google.common.collect.ImmutableList
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.stream
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.actor.behavior.CompositeNodeType
import ru.dbotthepony.kstarbound.defs.actor.behavior.NodeParameterValue
import ru.dbotthepony.kstarbound.fromJsonFast
import ru.dbotthepony.kstarbound.lua.userdata.BehaviorState
import ru.dbotthepony.kstarbound.util.valueOf
abstract class AbstractBehaviorNode {
enum class Status(val asBoolean: Boolean?) {
INVALID(null),
SUCCESS(true),
FAILURE(false),
RUNNING(null)
}
var calls = 0
protected set
abstract fun run(delta: Double, state: BehaviorState): Status
abstract fun reset()
fun runAndReset(delta: Double, state: BehaviorState): Status {
val status = run(delta, state)
if (status != Status.RUNNING)
reset()
return status
}
companion object {
fun replaceBehaviorTag(parameter: NodeParameterValue, treeParameters: Map<String, NodeParameterValue>): NodeParameterValue {
var str: String? = null
if (parameter.key != null)
str = parameter.key
// original engine does this, and i don't know why this make any sense
else if (parameter.value is JsonPrimitive && parameter.value.isString)
str = parameter.value.asString
if (!str.isNullOrEmpty()) {
if (str.first() == '<' && str.last() == '>') {
val treeKey = str.substring(1, str.length - 1)
val param = treeParameters[treeKey]
if (param != null) {
return param
} else {
throw NoSuchElementException("No parameter specified for tag '$treeKey'")
}
}
}
return parameter
}
fun replaceOutputBehaviorTag(output: String?, treeParameters: Map<String, NodeParameterValue>): String? {
if (output == null) {
return null
} else if (output.first() == '<' && output.last() == '>') {
val replacement = treeParameters[output.substring(1, output.length - 1)] ?: throw NoSuchElementException("No parameter specified for tag '$output'")
if (replacement.key != null)
return replacement.key
else if (replacement.value is JsonPrimitive && replacement.value.isString)
return replacement.value.asString
else
return null
} else {
return output
}
}
fun create(data: JsonObject, treeParameters: Map<String, NodeParameterValue>, tree: BehaviorTree): AbstractBehaviorNode {
val type = BehaviorNodeType.entries.valueOf(data["type"].asString)
val name = data["name"].asString
val parameterConfig = data.get("parameters") { JsonObject() }
if (type == BehaviorNodeType.MODULE) {
// merge in module parameters to a copy of the treeParameters to propagate
// tree parameters into the sub-tree, but allow modules to override
val moduleParameters = LinkedHashMap(treeParameters)
for ((k, v) in parameterConfig.entrySet()) {
moduleParameters[k] = replaceBehaviorTag(Starbound.gson.fromJsonFast(v, NodeParameterValue::class.java), treeParameters)
}
val module = BehaviorTree(tree.blackboard, Registries.behavior.getOrThrow(name).value, moduleParameters)
tree.scripts.addAll(module.scripts)
tree.functions.addAll(module.functions)
return module.root
}
val parameters = LinkedHashMap(Registries.behaviorNodes.getOrThrow(name).value.properties)
for ((k, v) in parameters.entries) {
if (k in parameterConfig) {
parameters[k] = v.copy(value = replaceBehaviorTag(Starbound.gson.fromJsonFast(parameterConfig[k], NodeParameterValue::class.java), treeParameters))
} else {
val replaced = replaceBehaviorTag(v.value, treeParameters)
if (replaced != v.value) {
parameters[k] = v.copy(value = replaced)
}
}
}
when (type) {
BehaviorNodeType.ACTION -> {
tree.functions.add(name)
val outputConfig = data.get("output") { JsonObject() }
val output = LinkedHashMap(Registries.behaviorNodes.getOrThrow(name).value.output)
for ((k, v) in output.entries) {
val replaced = replaceOutputBehaviorTag(outputConfig[k]?.asString ?: v.key, treeParameters)
if (replaced != v.key) {
output[k] = v.copy(key = replaced)
}
}
return ActionNode(name, parameters, output)
}
BehaviorNodeType.DECORATOR -> {
tree.functions.add(name)
val sacrifice = create(data["child"] as JsonObject, treeParameters, tree)
return DecoratorNode(name, parameters, sacrifice)
}
BehaviorNodeType.COMPOSITE -> {
val children = data.get("children") { JsonArray() }
.stream()
.map { create(it as JsonObject, treeParameters, tree) }
.collect(ImmutableList.toImmutableList())
return CompositeNodeType.entries.valueOf(name).factory(parameters, children)
}
BehaviorNodeType.MODULE -> throw RuntimeException()
}
}
}
}

View File

@ -1,77 +0,0 @@
package ru.dbotthepony.kstarbound.world.entities.behavior
import org.apache.logging.log4j.LogManager
import org.classdump.luna.Table
import org.classdump.luna.exec.CallPausedException
import org.classdump.luna.lib.CoroutineLib
import org.classdump.luna.runtime.Coroutine
import ru.dbotthepony.kstarbound.defs.actor.behavior.NodeOutput
import ru.dbotthepony.kstarbound.defs.actor.behavior.NodeParameter
import ru.dbotthepony.kstarbound.lua.userdata.BehaviorState
class ActionNode(val name: String, val parameters: Map<String, NodeParameter>, val outputs: Map<String, NodeOutput>) : AbstractBehaviorNode() {
private var coroutine: Coroutine? = null
private val nodeID = BehaviorState.NODE_GARBAGE_INDEX.getAndIncrement()
override fun run(delta: Double, state: BehaviorState): Status {
calls++
var coroutine = coroutine
var firstTime = false
if (coroutine == null) {
firstTime = true
val fn = state.functions[name] ?: throw RuntimeException("How? $name")
coroutine = state.lua.call(CoroutineLib.create(), fn)[0] as Coroutine
}
try {
val result = if (firstTime) {
val parameters = state.blackboard.parameters(parameters, this)
state.lua.call(CoroutineLib.resume(), coroutine, parameters, state.blackboard, nodeID, delta)
} else {
state.lua.call(CoroutineLib.resume(), coroutine, delta)
}
val coroutineStatus = result[0] as Boolean
if (!coroutineStatus) {
LOGGER.warn("Behavior ActionNode '$name' failed: ${result[1]}")
return Status.FAILURE
}
if (result.size == 1) {
return Status.RUNNING
}
val nodeStatus = result.getOrNull(1) as? Boolean ?: false
val nodeExtra = result.getOrNull(2) as? Table
if (nodeExtra != null) {
state.blackboard.setOutput(this, nodeExtra)
}
if (!nodeStatus) {
return Status.FAILURE
} else {
return Status.SUCCESS
}
} catch (err: CallPausedException) {
LOGGER.error("Behavior ActionNode '$name' called blocking code, which initiated pause. This is not supported.")
return Status.FAILURE
}
}
override fun reset() {
coroutine = null
}
override fun toString(): String {
return "ActionNode[$calls / $name, parameters=$parameters, outputs=$outputs, coroutine=$coroutine]"
}
companion object {
private val LOGGER = LogManager.getLogger()
}
}

View File

@ -1,31 +0,0 @@
package ru.dbotthepony.kstarbound.world.entities.behavior
import ru.dbotthepony.kstarbound.defs.actor.behavior.BehaviorDefinition
import ru.dbotthepony.kstarbound.defs.actor.behavior.NodeParameterValue
import ru.dbotthepony.kstarbound.lua.userdata.BehaviorState
class BehaviorTree(val blackboard: Blackboard, val data: BehaviorDefinition, overrides: Map<String, NodeParameterValue> = mapOf()) : AbstractBehaviorNode() {
val scripts = HashSet(data.scripts)
val functions = HashSet<String>()
val root: AbstractBehaviorNode
init {
if (overrides.isEmpty()) {
root = create(data.root, data.mappedParameters, this)
} else {
val parameters = LinkedHashMap(data.mappedParameters)
parameters.putAll(overrides)
root = create(data.root, parameters, this)
}
}
override fun run(delta: Double, state: BehaviorState): Status {
return root.run(delta, state)
}
override fun reset() {
root.reset()
}
}

View File

@ -1,194 +0,0 @@
package ru.dbotthepony.kstarbound.world.entities.behavior
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonPrimitive
import org.classdump.luna.ByteString
import org.classdump.luna.Table
import org.classdump.luna.Userdata
import org.classdump.luna.impl.ImmutableTable
import ru.dbotthepony.kstarbound.defs.actor.behavior.NodeParameter
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.luaFunction
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.util.valueOf
import java.util.EnumMap
import java.util.HashSet
import kotlin.collections.HashMap
/**
* Blackboard (Greenboard, if you will) for tracking inputs and outputs of function nodes in behavior tree.
*
* While this is obviously flawed, and inputs/outputs should _generally_ be stored inside nodes themselves
* (since we create/clone behavior tree each time we make AI actor), this class is exposed to Lua scripts,
* so we must keep this class
*
* On side note, this architecture also employs global inputs/outputs of functions, hence you can't make
* an actual functional tree out of behavior nodes (because variable flow is global, and not local, so you
* can't have, per say, function "calculate sin out of x" because both input "x" and "sin result" are
* defined as global variables).
* Bravo, Chucklefish.
*/
class Blackboard(val lua: LuaEnvironment) : Userdata<Blackboard>() {
private val board = EnumMap<NodeParameterType, HashMap<String, Any>>(NodeParameterType::class.java)
private val input = EnumMap<NodeParameterType, HashMap<String, ArrayList<Pair<String, Table>>>>(NodeParameterType::class.java)
private val parameters = HashMap<Any, Table>()
// key -> list of Lua tables and their indices
private val vectorNumberInput = HashMap<String, HashSet<Pair<Any, Table>>>()
private var ephemeral = HashSet<Pair<NodeParameterType, String>>()
init {
for (v in NodeParameterType.entries) {
board[v] = HashMap()
input[v] = HashMap()
}
}
operator fun set(type: NodeParameterType, key: String, value: Any?) {
if (value == null) {
board[type]!!.remove(key)
} else {
board[type]!![key] = value
}
val input = input[type]!![key]
if (input != null) {
for ((index, table) in input) {
table[index] = value
}
}
// dumb special case for setting number outputs to vec2 inputs
if (type == NodeParameterType.NUMBER) {
val mappings = vectorNumberInput[key]
if (mappings != null) {
for ((index, table) in mappings) {
table[index] = value
}
}
}
}
operator fun get(type: NodeParameterType, key: String): Any? {
return board[type]!![key]
}
fun parameters(parameters: Map<String, NodeParameter>, nodeID: Any): Table {
val get = this.parameters[nodeID]
if (get != null)
return get
val table = lua.newTable()
for ((name, parameter) in parameters.entries) {
if (parameter.value.key != null) {
val typeInput = input[parameter.type]!!.computeIfAbsent(parameter.value.key) { ArrayList() }
typeInput.add(name to table)
table[name] = this[parameter.type, parameter.value.key]
} else {
val value = parameter.value.value ?: JsonNull.INSTANCE
if (value.isJsonNull)
continue
// dumb special case for allowing a vec2 of blackboard number keys
if (parameter.type == NodeParameterType.VEC2) {
if (value !is JsonArray)
throw IllegalArgumentException("Expected '$name' to be VEC2 array, got $value")
val vector = lua.newTable(value.size(), 0)
for ((i, element) in value.withIndex()) {
if (element is JsonPrimitive && element.isString) {
vectorNumberInput.computeIfAbsent(element.asString) { HashSet() }.add(i + 1L to vector)
vector[i + 1L] = this[NodeParameterType.NUMBER, element.asString]
} else {
vector[i + 1L] = lua.from(element)
}
}
table[name] = vector
} else {
table[name] = lua.from(value)
}
}
}
this.parameters[nodeID] = table
return table
}
fun setOutput(node: ActionNode, output: Table) {
for ((tableKey, out) in node.outputs) {
if (out.key != null) {
this[out.type, out.key] = output[tableKey]
if (out.ephemeral) {
ephemeral.add(out.type to out.key)
}
}
}
}
fun takeEphemerals(): Set<Pair<NodeParameterType, String>> {
val ephemeral = ephemeral
this.ephemeral = HashSet()
return ephemeral
}
fun clearEphemerals(ephemerals: Collection<Pair<NodeParameterType, String>>) {
for ((type, key) in ephemerals) {
this[type, key] = null
}
}
override fun getMetatable(): Table {
return Companion.metatable
}
override fun setMetatable(mt: Table?): Table {
throw UnsupportedOperationException()
}
override fun getUserValue(): Blackboard {
return this
}
override fun setUserValue(value: Blackboard?): Blackboard {
throw UnsupportedOperationException()
}
companion object {
private val metatable: ImmutableTable
private fun __index() = metatable
init {
val builder = ImmutableTable.Builder()
.add("get", luaFunction { self: Blackboard, type: ByteString, key: ByteString ->
returnBuffer.setTo(self[NodeParameterType.entries.valueOf(type.decode()), key.decode()])
})
.add("set", luaFunction { self: Blackboard, type: ByteString, key: ByteString, value: Any? ->
self[NodeParameterType.entries.valueOf(type.decode()), key.decode()] = value
})
for (type in NodeParameterType.entries) {
builder.add("get${type.funcName}", luaFunction { self: Blackboard, key: ByteString ->
returnBuffer.setTo(self[type, key.decode()])
})
builder.add("set${type.funcName}", luaFunction { self: Blackboard, key: ByteString, value: Any? ->
self[type, key.decode()] = value
})
}
builder.add("__index", luaFunction { _: Any?, index: Any -> returnBuffer.setTo(__index()[index]) })
metatable = builder.build()
}
}
}

View File

@ -1,110 +0,0 @@
package ru.dbotthepony.kstarbound.world.entities.behavior
import org.apache.logging.log4j.LogManager
import org.classdump.luna.ByteString
import org.classdump.luna.Table
import org.classdump.luna.exec.CallPausedException
import org.classdump.luna.lib.CoroutineLib
import org.classdump.luna.runtime.Coroutine
import ru.dbotthepony.kstarbound.defs.actor.behavior.NodeParameter
import ru.dbotthepony.kstarbound.lua.userdata.BehaviorState
import java.util.concurrent.atomic.AtomicLong
class DecoratorNode(val name: String, val parameters: Map<String, NodeParameter>, val child: AbstractBehaviorNode) : AbstractBehaviorNode() {
private var coroutine: Coroutine? = null
private val nodeID = BehaviorState.NODE_GARBAGE_INDEX.getAndIncrement()
override fun run(delta: Double, state: BehaviorState): Status {
calls++
var coroutine = coroutine
if (coroutine == null) {
val parameters = state.blackboard.parameters(parameters, this)
val fn = state.functions[name] ?: throw RuntimeException("How? $name")
try {
coroutine = state.lua.call(CoroutineLib.create(), fn)[0] as Coroutine
val result = state.lua.call(CoroutineLib.resume(), coroutine, parameters, state.blackboard, nodeID)
val status = result[0] as Boolean
if (!status) {
LOGGER.warn("Behavior DecoratorNode '$name' failed: ${result[1]}")
return Status.FAILURE
}
if (result.size == 1) {
val coroutineStatus = state.lua.call(CoroutineLib.status(), coroutine)[0] as String
if (coroutineStatus == "dead") {
// quite unexpected, but whatever
return Status.SUCCESS
} else {
this.coroutine = coroutine
}
} else {
val nodeStatus = result.getOrNull(1) as? Boolean ?: false
// val nodeExtra = result.getOrNull(3) as? Table
return if (nodeStatus) Status.SUCCESS else Status.FAILURE
}
} catch (err: CallPausedException) {
LOGGER.error("Behavior DecoratorNode '$name' called blocking code, which initiated pause. This is not supported.")
return Status.FAILURE
}
}
// decorator runs its child on yield and is resumed with the child's status on success or failure
var status = Status.RUNNING
while (status == Status.RUNNING) {
val childStatus = child.runAndReset(delta, state)
if (childStatus == Status.SUCCESS || childStatus == Status.FAILURE) {
try {
val result = state.lua.call(CoroutineLib.resume(), coroutine, childStatus.asBoolean)
val execStatus = result[0] as Boolean
if (!execStatus && result.size >= 2) {
LOGGER.warn("Behavior DecoratorNode '$name' failed: ${result[1]}")
}
if (result.size == 1) {
// another yield OR unexpected return?
val coroutineStatus = state.lua.call(CoroutineLib.status(), coroutine)[0] as String
if (coroutineStatus == "dead") {
this.coroutine = null
status = Status.SUCCESS
} else
status = Status.RUNNING
} else {
// yield or return with status
val nodeStatus = result.getOrNull(1) as? Boolean ?: false
// val nodeExtra = result.getOrNull(3) as? Table
status = if (nodeStatus) Status.SUCCESS else Status.FAILURE
this.coroutine = null
}
} catch (err: CallPausedException) {
LOGGER.error("Behavior DecoratorNode '$name' called blocking code on children return, which initiated pause. This is not supported.")
status = Status.FAILURE
}
} else {
return Status.RUNNING
}
}
return status
}
override fun reset() {
coroutine = null
}
override fun toString(): String {
return "DecoratorNode[$calls / $name, coroutine=$coroutine, parameters=$parameters, children=$child]"
}
companion object {
private val LOGGER = LogManager.getLogger()
}
}

View File

@ -1,38 +0,0 @@
package ru.dbotthepony.kstarbound.world.entities.behavior
import com.google.common.collect.ImmutableList
import org.classdump.luna.runtime.ExecutionContext
import ru.dbotthepony.kstarbound.lua.userdata.BehaviorState
class DynamicNode(val children: ImmutableList<AbstractBehaviorNode>) : AbstractBehaviorNode() {
private var index = 0
override fun run(delta: Double, state: BehaviorState): Status {
calls++
for ((i, node) in children.withIndex()) {
val status = node.runAndReset(delta, state)
if (status == Status.FAILURE && index == i)
index++
else if (i < index && (status == Status.SUCCESS || status == Status.FAILURE)) {
node.reset()
index = i
}
if (status == Status.SUCCESS || index >= children.size) {
return status
}
}
return Status.RUNNING
}
override fun toString(): String {
return "DynamicNode[$calls / ${children.size}, index=$index, current=${children.getOrNull(index)}]"
}
override fun reset() {
children.forEach { it.reset() }
index = 0
}
}

View File

@ -1,70 +0,0 @@
package ru.dbotthepony.kstarbound.world.entities.behavior
import com.google.common.collect.ImmutableList
import com.google.gson.JsonPrimitive
import org.classdump.luna.runtime.ExecutionContext
import ru.dbotthepony.kstarbound.defs.actor.behavior.NodeParameter
import ru.dbotthepony.kstarbound.lua.userdata.BehaviorState
class ParallelNode(parameters: Map<String, NodeParameter>, val children: ImmutableList<AbstractBehaviorNode>) : AbstractBehaviorNode() {
val successLimit: Int
val failLimit: Int
init {
val value = (parameters["success"]?.value?.value as? JsonPrimitive)?.asInt ?: -1
if (value == -1) {
successLimit = children.size
} else {
successLimit = value
}
}
init {
val value = (parameters["fail"]?.value?.value as? JsonPrimitive)?.asInt ?: -1
if (value == -1) {
failLimit = children.size
} else {
failLimit = value
}
}
private var lastFailed = -1
private var lastSucceeded = -1
override fun run(delta: Double, state: BehaviorState): Status {
calls++
var failed = 0
var succeeded = 0
for (node in children) {
val status = node.runAndReset(delta, state)
if (status == Status.SUCCESS)
succeeded++
else if (status == Status.FAILURE)
failed++
if (succeeded >= successLimit || failed >= failLimit) {
lastFailed = failed
lastSucceeded = succeeded
return if (succeeded >= successLimit) Status.SUCCESS else Status.FAILURE
}
}
lastFailed = failed
lastSucceeded = succeeded
return Status.RUNNING
}
override fun toString(): String {
return "ParallelNode[$calls / children=${children.size}, successLimit=$successLimit, failLimit=$failLimit, lastFailed=$lastFailed, lastSucceeded=$lastSucceeded]"
}
override fun reset() {
children.forEach { it.reset() }
lastSucceeded = -1
lastFailed = -1
}
}

View File

@ -1,33 +0,0 @@
package ru.dbotthepony.kstarbound.world.entities.behavior
import com.google.common.collect.ImmutableList
import org.classdump.luna.runtime.ExecutionContext
import ru.dbotthepony.kstarbound.lua.userdata.BehaviorState
class RandomizeNode(val children: ImmutableList<AbstractBehaviorNode>) : AbstractBehaviorNode() {
var index = -1
override fun run(delta: Double, state: BehaviorState): Status {
calls++
if (index == -1 && children.isNotEmpty())
index = state.lua.random.nextInt(children.size)
if (index == -1)
return Status.FAILURE
return children[index].runAndReset(delta, state)
}
override fun toString(): String {
if (index == -1)
return "RandomizeNode[$calls / ${children.size}, not chosen]"
else
return "RandomizeNode[$calls / ${children.size}, ${children[index]}]"
}
override fun reset() {
children.forEach { it.reset() }
index = -1
}
}

View File

@ -1,37 +0,0 @@
package ru.dbotthepony.kstarbound.world.entities.behavior
import com.google.common.collect.ImmutableList
import org.classdump.luna.runtime.ExecutionContext
import ru.dbotthepony.kstarbound.lua.userdata.BehaviorState
class SequenceNode(val children: ImmutableList<AbstractBehaviorNode>, val isSelector: Boolean) : AbstractBehaviorNode() {
private var index = 0
override fun run(delta: Double, state: BehaviorState): Status {
calls++
while (index < children.size) {
val child = children[index]
val status = child.runAndReset(delta, state)
if ((isSelector && status == Status.SUCCESS || !isSelector && status == Status.FAILURE) || status == Status.RUNNING)
return status
index++
}
return if (isSelector) Status.FAILURE else Status.SUCCESS
}
override fun toString(): String {
if (isSelector)
return "SelectorNode[$calls / ${children.size}, index=$index, current=${children.getOrNull(index)}]"
else
return "SequenceNode[$calls / ${children.size}, index=$index, current=${children.getOrNull(index)}]"
}
override fun reset() {
children.forEach { it.reset() }
index = 0
}
}

View File

@ -226,6 +226,10 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
return path.get(mergedJson.value, orElse) return path.get(mergedJson.value, orElse)
} }
fun lookupPropertyOrNull(path: JsonPath): JsonElement? {
return path.find(mergedJson.value)
}
fun lookupProperty(path: String, orElse: () -> JsonElement): JsonElement { fun lookupProperty(path: String, orElse: () -> JsonElement): JsonElement {
return mergedJson.value[path] ?: orElse.invoke() return mergedJson.value[path] ?: orElse.invoke()
} }
@ -234,6 +238,10 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
return mergedJson.value[path] ?: JsonNull.INSTANCE return mergedJson.value[path] ?: JsonNull.INSTANCE
} }
fun lookupPropertyOrNull(path: String): JsonElement? {
return mergedJson.value[path]
}
init { init {
networkGroup.upstream.add(uniqueID) networkGroup.upstream.add(uniqueID)
} }

View File

@ -0,0 +1,674 @@
-- TODO: Incomplete.
-- Blackboard
local blackboardPrototype = {}
blackboardPrototype.__index = blackboardPrototype
local nodeParametersTypes = {
{'json', 'Json'},
{'entity', 'Entity'},
{'position', 'Position'},
{'vec2', 'Vec2'},
{'number', 'Number'},
{'bool', 'Bool'},
{'list', 'List'},
{'table', 'Table'},
{'string', 'String'},
}
local mappedParameterTypes = {}
for i, data in ipairs(nodeParametersTypes) do
mappedParameterTypes[i] = i
mappedParameterTypes[data[1]] = i
end
function blackboardPrototype:ctor()
self.board = {}
self.input = {}
self._parameters = {}
self.vectorNumberInput = {}
self.ephemeral = {}
for i, data in ipairs(nodeParametersTypes) do
self.board[data[1]] = {}
self.input[data[1]] = {}
self.ephemeral[i] = {}
self.board[i] = self.board[data[1]]
self.input[i] = self.input[data[1]]
end
return self
end
local function blackboardSet(self, t, key, value)
self.board[t][key] = value
local input = self.input[t][key]
if input then
for _, pair in ipairs(input) do
local index = pair[1]
local tab = pair[2]
tab[index] = value
end
end
-- dumb special case for setting number outputs to vec2 inputs
if t == 5 then
local mappings = self.vectorNumberInput[key]
if mappings then
for _, pair in pairs(input) do
local index = pair[1]
local tab = pair[2]
tab[index] = value
end
end
end
end
function blackboardPrototype:set(t, key, value)
blackboardSet(self, mappedParameterTypes[t], key, value)
end
function blackboardPrototype:setRaw(t, key, value)
blackboardSet(self, t, key, value)
end
function blackboardPrototype:get(t, key)
return self.board[t][key]
end
for i, data in ipairs(nodeParametersTypes) do
blackboardPrototype['get' .. data[2]] = function(self, key)
return self.board[i][key]
end
blackboardPrototype['set' .. data[2]] = function(self, key, value)
blackboardSet(self, i, key, value)
end
end
function blackboardPrototype:parameters(parameters, nodeID)
local tab = self._parameters[nodeID]
if tab then return tab end
tab = {}
for parameterName, parameter in pairs(parameters) do
local t = assert(mappedParameterTypes[parameter.type])
local pKey = parameter.key
local pValue = parameter.value
if pKey then
local typeInput = self.input[t][pKey]
if not typeInput then
typeInput = {}
self.input[i][pKey] = typeInput
end
table.insert(typeInput, {parameterName, tab})
tab[parameterName] = self.board[t][pKey]
elseif pValue then
if t == 4 then -- vec2
-- dumb special case for allowing a vec2 of blackboard number keys
if type(pValue) ~= 'table' then
error(string.format('parameter %s of type %s for node %s has has non-table value: %s', parameterName, parameter.type, nodeID, type(pValue)))
end
local vector = {}
for i, vValue in ipairs(pValue) do
if type(vValue) == 'string' then
-- vector part with symbolic reference
local typeInput = self.vectorNumberInput[vValue]
if not typeInput then
typeInput = {}
self.vectorNumberInput[vValue] = typeInput
end
table.insert(typeInput, {i, vector})
vector[i] = self.board[5][key] -- number
else
vector[i] = vValue
end
end
tab[parameterName] = vector
else
tab[parameterName] = pValue
end
else
error(string.format('parameter %s of type %s for node %s has no key nor value', parameterName, parameter.type, nodeID))
end
end
self._parameters[nodeID] = tab
return tab
end
function blackboardPrototype:setOutput(node, output)
for key, out in pairs(node.outputs) do
if out.key then
local t = out.type
blackboardSet(self, t, out.key, output[key])
if out.ephemeral then
self.ephemeral[t][out.key] = true
end
end
end
end
function blackboardPrototype:takeEphemerals()
local value = self.ephemeral
self.ephemeral = {}
for i, _ in ipairs(nodeParametersTypes) do
self.ephemeral[i] = {}
end
return value
end
function blackboardPrototype:clearEphemerals(ephemerals)
for i, keys in ipairs(ephemerals) do
for key in pairs(keys) do
blackboardSet(self, i, key, nil)
end
end
end
local function Blackboard()
return setmetatable({}, blackboardPrototype):ctor()
end
-- //// Blackboard
local SUCCESS = true
local FAILURE = false
local RUNNING = nil
local nextNodeID = 0
local function runAndReset(self, ...)
local status = self:run(...)
if status ~= RUNNING then
self:reset()
end
return status
end
-- ActionNode
local actionNode = {}
actionNode.__index = actionNode
function actionNode:ctor(name, parameters, outputs)
self.name = name
self.parameters = parameters
self.outputs = outputs
self.calls = 0
self.nodeID = nextNodeID
nextNodeID = nextNodeID + 1
return self
end
function actionNode:bake()
self.callable = _G[self.name]
if type(callable) ~= 'function' then
error('expected global ' .. self.name .. ' to be a function, but got ' .. type(callable))
end
end
do
local create = coroutine.create
local resume = coroutine.resume
local status = coroutine.status
function actionNode:run(delta, blackboard)
self.calls = self.calls + 1
local status, nodeStatus, nodeExtra
if not self.coroutine then
self.coroutine = create(self.callable)
local parameters = blackboard:parameters(self.parameters, self)
status, nodeStatus, nodeExtra = resume(self.coroutine, parameters, blackboard, self.nodeID, delta)
else
status, nodeStatus, nodeExtra = resume(self.coroutine, delta)
end
if not status then
sb.logError('Behavior ActionNode %q failed: %s', self.name, nodeStatus)
return FAILURE
end
if result == nil then
return RUNNING
end
if nodeExtra ~= nil then
blackboard:setOutput(self, nodeExtra)
end
if nodeStatus then
return SUCCESS
else
return FAILURE
end
end
end
function actionNode:reset()
self.coroutine = nil
end
function actionNode:__tostring()
return string.format('ActionNode[%q / %d / %s]', self.name, self.calls, tostring(self.coroutine or 'none'))
end
function ActionNode(...)
return setmetatable({}, actionNode):ctor(...)
end
-- //// ActionNode
-- DecoratorNode
local decoratorNode = {}
decoratorNode.__index = decoratorNode
function decoratorNode:ctor(name, parameters, child)
self.name = name
self.parameters = parameters
self.child = child
self.calls = 0
self.nodeID = nextNodeID
nextNodeID = nextNodeID + 1
return self
end
function decoratorNode:bake()
self.callable = _G[self.name]
if type(callable) ~= 'function' then
error('expected global ' .. self.name .. ' to be a function, but got ' .. type(callable))
end
self.child:bake()
end
do
local create = coroutine.create
local resume = coroutine.resume
local coroutine_status = coroutine.status
function decoratorNode:run(delta, blackboard)
self.calls = self.calls + 1
if not self.coroutine then
local parameters = blackboard:parameters(self.parameters, self)
local coroutine = create(self.callable)
local status, nodeStatus = resume(coroutine, parameters, blackboard, self.nodeID, delta)
if not status then
sb.logError('Behavior DecoratorNode %q failed: %s', self.name, nodeStatus)
return FAILURE
end
if nodeStatus == nil then
local s = coroutine_status(coroutine)
if s == 'dead' then
-- quite unexpected, but whatever
return SUCCESS
else
self.coroutine = coroutine
end
elseif nodeStatus then
return SUCCESS
else
return FAILURE
end
end
while true do
local childStatus = runAndReset(self.child, delta, blackboard)
if childStatus == RUNNING then
return RUNNING
end
local status, nodeStatus = resume(self.coroutine, childStatus)
if not status then
sb.logError('Behavior DecoratorNode %q failed: %s', self.name, nodeStatus)
return FAILURE
end
if nodeStatus == nil then
-- another yield OR unexpected return?
local s = coroutine_status(coroutine)
if s == 'dead' then
self.coroutine = nil
return SUCCESS
end
else
-- yield or return with status
self.coroutine = nil
if nodeStatus then
return SUCCESS
else
return FAILURE
end
end
end
end
end
function decoratorNode:reset()
self.coroutine = nil
self.child:reset()
end
function actionNode:__tostring()
return string.format('DecoratorNode[%q / %d / %s; %s]', self.name, self.calls, tostring(self.coroutine or 'none'), tostring(self.child))
end
function DecoratorNode(...)
return setmetatable({}, decoratorNode):ctor(...)
end
-- //// DecoratorNode
-- Composite nodes
local seqNode = {}
seqNode.__index = seqNode
function seqNode:ctor(children, isSelector)
self.children = children
self.isSelector = isSelector
self.calls = 0
self.index = 1
self.size = #children
return self
end
function seqNode:run(delta, blackboard)
self.calls = self.calls + 1
local size = self.size
local isSelector = self.isSelector
`
while self.index <= size do
local child = self.children[self.index]
local status = runAndReset(child, delta, blackboard)
if status == RUNNING then
return RUNNING
elseif isSelector and status == SUCCESS then
return SUCCESS
elseif not isSelector and status == FAILURE then
return FAILURE
end
self.index = self.index + 1
end
end
function seqNode:reset()
self.index = 1
for _, child in ipairs(self.children) do
child:reset()
end
end
function seqNode:bake()
for _, child in ipairs(self.children) do
child:bake()
end
end
function seqNode:__tostring()
if self.isSelector then
return string.format('SelectorNode[%d / %d, index=%d, current=%s]', self.calls, self.size, self.index, tostring(self.children[self.index]))
else
return string.format('SequenceNode[%d / %d, index=%d, current=%s]', self.calls, self.size, self.index, tostring(self.children[self.index]))
end
end
function SequenceNode(p, c)
return setmetatable({}, seqNode):ctor(c, false)
end
function SelectorNode(p, c)
return setmetatable({}, seqNode):ctor(c, true)
end
local parallelNode = {}
parallelNode.__index = parallelNode
function parallelNode:ctor(parameters, children)
self.children = children
if type(parameters.success) == 'number' then
self.successLimit = parameters.success
else
self.successLimit = #children
end
if type(parameters.fail) == 'number' then
self.failLimit = parameters.fail
else
self.failLimit = #children
end
self.lastFailed = -1
self.lastSucceed = -1
self.calls = 0
return self
end
function parallelNode:run(delta, blackboard)
self.calls = self.calls + 1
local failed = 0
local succeeded = 0
local failLimit = self.failLimit
local successLimit = self.successLimit
for _, node in ipairs(self.children) do
local status = runAndReset(node, delta, blackboard)
if status == SUCCESS then
succeeded = succeeded + 1
elseif status == FAILURE then
failed = failed + 1
end
if failed >= failLimit then
self.lastFailed = failed
self.lastSucceed = succeeded
return FAILURE
elseif succeeded >= successLimit then
self.lastFailed = failed
self.lastSucceed = succeeded
return SUCCESS
end
end
self.lastFailed = failed
self.lastSucceed = succeeded
return RUNNING
end
function parallelNode:bake()
for _, child in ipairs(self.children) do
child:bake()
end
end
function parallelNode:reset()
self.lastFailed = -1
self.lastSucceed = -1
for _, child in ipairs(self.children) do
child:reset()
end
end
function parallelNode:__tostring()
return string.format('ParallelNode[%d / %d, limits=%d / %d, last=%d / %d]', self.calls, #self.children, self.successLimit, self.failLimit, self.lastSucceed, self.lastFailed)
end
function ParallelNode(p, c)
return setmetatable({}, parallelNode):ctor(p, c)
end
local dynNode = {}
dynNode.__index = dynNode
function dynNode:ctor(children)
self.children = children
self.calls = 0
self.index = 1
self.size = #children
return self
end
function dynNode:run(delta, blackboard)
self.calls = self.calls + 1
for i, node in ipairs(self.children) do
local status = runAndReset(node, delta, blackboard)
if stauts == FAILURE and self.index == i then
self.index = self.index + 1
elseif status ~= RUNNING and i < self.index then
node:reset()
self.index = i
end
if status == SUCCESS or self.index > self.size then
return status
end
end
return RUNNING
end
function dynNode:bake()
for _, child in ipairs(self.children) do
child:bake()
end
end
function dynNode:reset()
self.index = 1
for _, child in ipairs(self.children) do
child:reset()
end
end
function dynNode:__tostring()
return string.format('DynamicNode[%d / %d, index=%d, current=%s]', self.calls, self.size, self.index, tostring(self.children[self.index]))
end
function DynamicNode(p, c)
return setmetatable({}, dynNode):ctor(c)
end
local randNode = {}
randNode.__index = randNode
function randNode:ctor(children)
self.children = children
self.index = -1
self.calls = 0
self.size = #children
return self
end
function randNode:run(delta, blackboard)
self.calls = self.calls + 1
if self.index == -1 and self.size ~= 0 then
self.index = math.random(1, self.size)
end
if self.index == -1 then
return FAILURE
else
return runAndReset(self.children[self.index], delta, blackboard)
end
end
function randNode:bake()
for _, child in ipairs(self.children) do
child:bake()
end
end
function randNode:reset()
self.index = -1
for _, child in ipairs(self.children) do
child:reset()
end
end
function randNode:__tostring()
return string.format('RandomNode[%d / %d, index=%d, current=%s]', self.calls, self.size, self.index, tostring(self.children[self.index]))
end
function RandomNode(p, c)
return setmetatable({}, randNode):ctor(c)
end
-- //// Composite nodes
local statePrototype = {}
statePrototype.__index = statePrototype
function statePrototype:ctor(blackboard, root)
self.root = root
self._blackboard = blackboard
return self
end
function statePrototype:run(delta)
local ephemerals = self._blackboard:takeEphemerals()
local status = runAndReset(self.root, delta, self._blackboard)
self._blackboard:clearEphemerals(ephemerals)
return status
end
function statePrototype:clear()
self.tree:reset()
end
function statePrototype:blackboard()
return self._blackboard
end
function BehaviorState(...)
return setmetatable({}, statePrototype):ctor(...)
end

View File

@ -1,17 +0,0 @@
config = {}
local config = config
function config.getParameter(name, default)
if type(name) ~= 'string' then
error('config.getParameter: name must be a string, got ' .. type(name), 2)
end
local get = config._get(name)
if get == nil then
return default
else
return get
end
end

View File

@ -18,7 +18,7 @@ local format = string.format
function checkarg(value, index, expected, fnName, overrideExpected) function checkarg(value, index, expected, fnName, overrideExpected)
if type(value) ~= expected then if type(value) ~= expected then
error(string.format('bad argument #%d to %s: %s expected, got %s', index, fnName, overrideExpected or expected, type(value)), 3) error(format('bad argument #%d to %s: %s expected, got %s', index, fnName, overrideExpected or expected, type(value)), 3)
end end
end end
@ -286,22 +286,6 @@ do
end end
end end
do
local min = math.min
local max = math.max
function math.clamp(value, _min, _max)
if _min > _max then
error(format('interval is empty: %d <= %d', _min, _max), 2)
end
return max(min(value, _min), _max)
end
end
function math.lerp(t, a, b)
return t * (b - a) + a
end
do do
local lerp = math.lerp local lerp = math.lerp
@ -336,3 +320,85 @@ end
-- why is this even a thing. -- why is this even a thing.
sb.print = tostring sb.print = tostring
-- KStarbound global Lua functions and extensions
do
local min = math.min
local max = math.max
function math.clamp(value, _min, _max)
if _min > _max then
error(format('interval is empty: %d <= %d', _min, _max), 2)
end
return max(min(value, _min), _max)
end
end
function math.lerp(t, a, b)
return t * (b - a) + a
end
function table.copy(source)
local copy = {}
for k, v in pairs(source) do
if type(v) == 'table' then
copy[k] = table.copy(v)
else
copy[k] = v
end
end
return copy
end
function jsonType(value)
if type(value) ~= 'table' then
return LUA_HINT_NONE
else
local meta = getmetatable(value)
if not meta then
return LUA_HINT_NONE
else
return meta.__typehint or LUA_HINT_NONE
end
end
end
function mergeJson(base, with)
local bType = jsonType(base)
local wType = jsonType(with)
if bType == LUA_HINT_OBJECT and wType == LUA_HINT_OBJECT or bType == LUA_HINT_NONE and wType == LUA_HINT_NONE and type(base) == 'table' and type(with) == 'table' then
-- merge as json objects as long as they are both "json objects" or plain tables
for k, v in pairs(with) do
base[k] = mergeJson(base[k], v)
end
return base
elseif wType == nil then
return base
else
return with
end
end
string.__index = string
do
local sub = string.sub
function string:__index(key)
if type(key) == 'number' then
return sub(self, key, key)
else
return string[key]
end
end
end
setmetatable('', string)

View File

@ -1,27 +0,0 @@
message = {
handlers = {}
}
local message = message
function message.setHandler(name, handler)
if type(name) ~= 'string' then
error('message.setHandler: Handler name must be a string, got ' .. type(name), 2)
end
if type(handler) ~= 'function' then
error('message.setHandler: Handler itself must be a function, got ' .. type(handler), 2)
end
message.subscribe(name)
message.handlers[name] = handler
end
function message.call(name, ...)
local handler = message.handlers[name]
if handler ~= nil then
return handler(...)
end
end