From f9b339c0e487390dc49ea2ea50c9bb16b29c82b5 Mon Sep 17 00:00:00 2001 From: DBotThePony <dbotthepony@yandex.ru> Date: Mon, 30 Dec 2024 12:03:14 +0700 Subject: [PATCH] More native Lua work --- .../actor/behavior/BehaviorNodeDefinition.kt | 2 + .../defs/actor/behavior/NodeOutput.kt | 2 +- .../defs/actor/behavior/NodeParameter.kt | 2 +- .../dbotthepony/kstarbound/lua/Conversions.kt | 30 ++-- .../ru/dbotthepony/kstarbound/lua/Errors.kt | 2 +- .../kstarbound/lua/LuaSharedState.kt | 4 +- .../dbotthepony/kstarbound/lua/LuaThread.kt | 27 ++- .../kstarbound/lua/LuaUpdateComponent.kt | 4 +- .../lua/bindings/MonsterBindings.kt | 4 +- .../lua/bindings/UtilityBindings.kt | 164 +++++++++--------- .../lua/bindings/WorldEntityBindings.kt | 13 +- .../lua/bindings/WorldObjectBindings.kt | 4 +- .../kstarbound/lua/userdata/BehaviorState.kt | 17 +- .../kstarbound/server/world/ServerChunk.kt | 4 +- .../kstarbound/world/SystemWorld.kt | 18 +- .../world/entities/MonsterEntity.kt | 4 +- src/main/resources/scripts/behavior.lua | 115 ++++++++---- src/main/resources/scripts/global.lua | 68 ++++++++ src/main/resources/scripts/world.lua | 2 +- .../dbotthepony/kstarbound/test/LuaTests.kt | 13 +- 20 files changed, 326 insertions(+), 173 deletions(-) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/behavior/BehaviorNodeDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/behavior/BehaviorNodeDefinition.kt index 1493f436..54d6e1d2 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/behavior/BehaviorNodeDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/behavior/BehaviorNodeDefinition.kt @@ -1,6 +1,7 @@ package ru.dbotthepony.kstarbound.defs.actor.behavior import com.google.common.collect.ImmutableMap +import ru.dbotthepony.kstarbound.defs.AssetPath import ru.dbotthepony.kstarbound.json.builder.JsonFactory /** @@ -16,4 +17,5 @@ data class BehaviorNodeDefinition( */ val properties: ImmutableMap<String, NodeParameter> = ImmutableMap.of(), val output: ImmutableMap<String, NodeOutput> = ImmutableMap.of(), + val script: AssetPath? = null, ) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/behavior/NodeOutput.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/behavior/NodeOutput.kt index c253d8d0..4487a824 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/behavior/NodeOutput.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/behavior/NodeOutput.kt @@ -15,7 +15,7 @@ import ru.dbotthepony.kstarbound.lua.userdata.NodeParameterType 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) + lua.setTableValue("type", type.ordinal + 1) if (key != null) lua.setTableValue("key", key) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/behavior/NodeParameter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/behavior/NodeParameter.kt index 25b1eee1..01af9660 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/behavior/NodeParameter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/actor/behavior/NodeParameter.kt @@ -20,7 +20,7 @@ data class NodeParameter(val type: NodeParameterType, val value: NodeParameterVa fun push(lua: LuaThread) { lua.pushTable(hashSize = 3) - lua.setTableValue("type", type.ordinal) + lua.setTableValue("type", type.ordinal + 1) if (value.key != null) { lua.setTableValue("key", value.key) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt index 9b10e4da..d0360c98 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Conversions.kt @@ -18,6 +18,7 @@ import ru.dbotthepony.kstarbound.math.Line2d import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.math.vector.Vector2f import ru.dbotthepony.kstarbound.math.vector.Vector2i +import ru.dbotthepony.kstarbound.util.floorToInt import ru.dbotthepony.kstarbound.world.physics.Poly // TODO: error reporting when argument was provided, but it is malformed @@ -223,18 +224,20 @@ fun LuaThread.getVector2i(stackIndex: Int = -1): Vector2i? { push(1) loadTableValue(abs) - val x = getLong() + // FIXME: original engine parity, where it casts doubles into ints + // while it seems okay, it can cause undesired side effects + val x = getDouble() pop() x ?: return null push(2) loadTableValue(abs) - val y = getLong() + val y = getDouble() pop() y ?: return null - return Vector2i(x.toInt(), y.toInt()) + return Vector2i(x.floorToInt(), y.floorToInt()) } fun LuaThread.ArgStack.nextVector2i(position: Int = this.position++): Vector2i { @@ -262,31 +265,31 @@ fun LuaThread.getColor(stackIndex: Int = -1): RGBAColor? { push(1) loadTableValue(abs) - val x = getLong() + val x = getFloat() pop() x ?: return null push(2) loadTableValue(abs) - val y = getLong() + val y = getFloat() pop() y ?: return null push(3) loadTableValue(abs) - val z = getLong() + val z = getFloat() pop() z ?: return null push(4) loadTableValue(abs) - val w = getLong() ?: 255L + val w = getFloat() ?: 255f pop() - return RGBAColor(x.toInt(), y.toInt(), z.toInt(), w.toInt()) + return RGBAColor(x / 255f, y / 255f, z / 255f, w / 255f) } fun LuaThread.ArgStack.nextColor(position: Int = this.position++): RGBAColor { @@ -365,32 +368,33 @@ fun LuaThread.getAABBi(stackIndex: Int = -1): AABBi? { push(1) loadTableValue(abs) - val x = getLong() + // FIXME: original engine parity + val x = getDouble() pop() x ?: return null push(2) loadTableValue(abs) - val y = getLong() + val y = getDouble() pop() y ?: return null push(3) loadTableValue(abs) - val z = getLong() + val z = getDouble() pop() z ?: return null push(4) loadTableValue(abs) - val w = getLong() + val w = getDouble() pop() w ?: return null - return AABBi(Vector2i(x.toInt(), y.toInt()), Vector2i(z.toInt(), w.toInt())) + return AABBi(Vector2i(x.floorToInt(), y.floorToInt()), Vector2i(z.floorToInt(), w.floorToInt())) } fun LuaThread.ArgStack.nextAABBi(position: Int = this.position++): AABBi { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Errors.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Errors.kt index b37f7d78..54288ec2 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Errors.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/Errors.kt @@ -18,4 +18,4 @@ const val LUA_ERRERR = 5 class InvalidLuaSyntaxException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause) class LuaMemoryAllocException(message: String? = null, cause: Throwable? = null) : Error(message, cause) class LuaException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause) -class LuaRuntimeException(message: String? = null, cause: Throwable? = null) : RuntimeException(message, cause) +class LuaRuntimeException(message: String? = null, cause: Throwable? = null, writeStackTrace: Boolean = true) : RuntimeException(message, cause, true, writeStackTrace) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaSharedState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaSharedState.kt index faaf7b06..e29f7f9f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaSharedState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaSharedState.kt @@ -60,7 +60,7 @@ class LuaSharedState(val handlesThread: LuaThread, private val cleanable: Cleana ) handlesThread.push { - //it.lua.push(it.nextObject<Throwable>().stackTraceToString()) + //it.lua.push(it.nextObject<Throwable?>(-1)?.stackTraceToString() ?: it.nextObject<Any?>(-1).toString()) it.lua.push(it.nextObject<Any?>().toString()) 1 } @@ -79,7 +79,7 @@ class LuaSharedState(val handlesThread: LuaThread, private val cleanable: Cleana if (obj is Throwable && obj !is LuaRuntimeException) { it.lua.traceback(obj.toString(), 1) - val err = LuaRuntimeException(it.lua.getString(), cause = obj) + val err = LuaRuntimeException(it.lua.getString(), cause = obj, writeStackTrace = false) it.lua.push(err) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaThread.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaThread.kt index 7d4bc3f9..9e17ee37 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaThread.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaThread.kt @@ -90,6 +90,10 @@ class LuaThread private constructor( this.storeGlobal("math") LuaJNR.INSTANCE.luaopen_utf8(this.pointer) this.storeGlobal("utf8") + LuaJNR.INSTANCE.luaopen_debug(this.pointer) + this.storeGlobal("debug") + LuaJNR.INSTANCE.luaopen_os(this.pointer) + this.storeGlobal("os") sharedState.initializeHandles(this) @@ -819,8 +823,8 @@ class LuaThread private constructor( try { while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) { - keyVisitor(this, abs + 1) - valueVisitor(this, abs + 2) + keyVisitor(this, top) + valueVisitor(this, top + 1) LuaJNR.INSTANCE.lua_settop(this.pointer, top) } } finally { @@ -843,7 +847,7 @@ class LuaThread private constructor( try { while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) { - values.add(keyVisitor(this, abs + 1)) + values.add(keyVisitor(this, top)) LuaJNR.INSTANCE.lua_settop(this.pointer, top) } } finally { @@ -866,7 +870,7 @@ class LuaThread private constructor( try { while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) { - values.add(valueVisitor(this, abs + 2)) + values.add(valueVisitor(this, top + 1)) LuaJNR.INSTANCE.lua_settop(this.pointer, top) } } finally { @@ -889,7 +893,7 @@ class LuaThread private constructor( try { while (LuaJNR.INSTANCE.lua_next(this.pointer, abs) != 0) { - values.add(keyVisitor(this, abs + 1) to valueVisitor(this, abs + 2)) + values.add(keyVisitor(this, top) to valueVisitor(this, top + 1)) LuaJNR.INSTANCE.lua_settop(this.pointer, top) } } finally { @@ -1233,7 +1237,7 @@ class LuaThread private constructor( check(value >= 0) { "Internal JVM error: ${function::class.qualifiedName} returned incorrect number of arguments to be popped from stack by Lua" } return value } catch (err: Throwable) { - push(err) + realLuaState.push(err) return -1 } } @@ -1436,6 +1440,17 @@ class LuaThread private constructor( LuaJNR.INSTANCE.lua_copy(pointer, fromIndex, toIndex) } + fun swap(indexA: Int, indexB: Int) { + if (indexA == indexB) return + val absA = if (indexA < 0) indexA - 1 else indexA + val absB = if (indexB < 0) indexB - 1 else indexB + push() + copy(absA, -1) + copy(absB, absA) + copy(-1, absB) + pop() + } + fun dup() { push() copy(-2, -1) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaUpdateComponent.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaUpdateComponent.kt index c053fcdd..c07356f1 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaUpdateComponent.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaUpdateComponent.kt @@ -41,13 +41,13 @@ class LuaUpdateComponent(val lua: LuaThread, val name: Any) { lua.callConditional { val type = loadGlobal("update") - if (type != lastType) { + /*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() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/MonsterBindings.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/MonsterBindings.kt index e050dbe8..0da722cf 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/MonsterBindings.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/MonsterBindings.kt @@ -100,12 +100,12 @@ private fun setDropPool(self: MonsterEntity, args: LuaThread.ArgStack): Int { private fun toAbsolutePosition(self: MonsterEntity, args: LuaThread.ArgStack): Int { args.lua.push(self.movement.getAbsolutePosition(args.nextVector2d())) - return 0 + return 1 } private fun mouthPosition(self: MonsterEntity, args: LuaThread.ArgStack): Int { args.lua.push(self.mouthPosition) - return 0 + return 1 } // This callback is registered here rather than in diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/UtilityBindings.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/UtilityBindings.kt index 76a8f80d..330b57c4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/UtilityBindings.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/UtilityBindings.kt @@ -160,91 +160,89 @@ private fun printJson(args: LuaThread.ArgStack): Int { } fun provideUtilityBindings(lua: LuaThread) { - with(lua) { - push { - LuaThread.LOGGER.info(it.nextString()) - 0 - } - - storeGlobal("__print") - - push { - LuaThread.LOGGER.warn(it.nextString()) - 0 - } - - storeGlobal("__print_warn") - - push { - LuaThread.LOGGER.error(it.nextString()) - 0 - } - - storeGlobal("__print_error") - - push { - LuaThread.LOGGER.fatal(it.nextString()) - 0 - } - - storeGlobal("__print_fatal") - - push { - val path = it.nextString() - - try { - load(Starbound.readLuaScript(path).join(), "@$path") - 1 - } catch (err: Exception) { - LuaThread.LOGGER.error("Exception loading Lua script $path", err) - throw err - } - } - - storeGlobal("__require") - - push { - push(random.nextDouble()) - 1 - } - - storeGlobal("__random_double") - - push { - push(random.nextLong(it.nextLong(), it.nextLong())) - 1 - } - - storeGlobal("__random_long") - - push { - random = random(it.nextLong()) - 0 - } - - storeGlobal("__random_seed") - - push { - push(it.lua.getNamedHandle(it.nextString())) - 1 - } - - storeGlobal("gethandle") - - push { - val find = it.lua.findNamedHandle(it.nextString()) - - if (find == null) { - 0 - } else { - push(find) - 1 - } - } - - storeGlobal("findhandle") + lua.push { + LuaThread.LOGGER.info(it.nextString()) + 0 } + lua.storeGlobal("__print") + + lua.push { + LuaThread.LOGGER.warn(it.nextString()) + 0 + } + + lua.storeGlobal("__print_warn") + + lua.push { + LuaThread.LOGGER.error(it.nextString()) + 0 + } + + lua.storeGlobal("__print_error") + + lua.push { + LuaThread.LOGGER.fatal(it.nextString()) + 0 + } + + lua.storeGlobal("__print_fatal") + + lua.push { + val path = it.nextString() + + try { + it.lua.load(Starbound.readLuaScript(path).join(), "@$path") + 1 + } catch (err: Exception) { + LuaThread.LOGGER.error("Exception loading Lua script $path", err) + throw err + } + } + + lua.storeGlobal("__require") + + lua.push { + it.lua.push(it.lua.random.nextDouble()) + 1 + } + + lua.storeGlobal("__random_double") + + lua.push { + it.lua.push(it.lua.random.nextLong(it.nextLong(), it.nextLong())) + 1 + } + + lua.storeGlobal("__random_long") + + lua.push { + it.lua.random = random(it.nextLong()) + 0 + } + + lua.storeGlobal("__random_seed") + + lua.push { + it.lua.push(it.lua.getNamedHandle(it.nextString())) + 1 + } + + lua.storeGlobal("gethandle") + + lua.push { + val find = it.lua.findNamedHandle(it.nextString()) + + if (find == null) { + 0 + } else { + it.lua.push(find) + 1 + } + } + + lua.storeGlobal("findhandle") + lua.pushTable() lua.dup() lua.storeGlobal("sb") diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldEntityBindings.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldEntityBindings.kt index f564374a..330c0280 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldEntityBindings.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldEntityBindings.kt @@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.lua.bindings import com.google.gson.JsonArray import com.google.gson.JsonElement +import com.google.gson.JsonObject import ru.dbotthepony.kommons.util.KOptional import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.defs.EntityType @@ -96,7 +97,17 @@ private data class CallScriptData( private fun LuaThread.ArgStack.getScriptData(): CallScriptData? { if (peek() == LuaType.STRING) { - return CallScriptData(nextString(), nextJson().asJsonArray, nextJson()) + val name = nextString() + val nextJson = nextJson() + val expected = nextJson() + + if (nextJson is JsonObject && nextJson.size() == 0) { + return CallScriptData(name, JsonArray(), expected) + } else if (nextJson is JsonArray) { + return CallScriptData(name, nextJson, expected) + } else { + throw IllegalArgumentException("Invalid script arguments to use (expected to be an array): $nextJson") + } } else { skip(3) return null diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldObjectBindings.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldObjectBindings.kt index 2c7cd805..c5427184 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldObjectBindings.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldObjectBindings.kt @@ -29,7 +29,7 @@ private fun name(self: WorldObject, args: LuaThread.ArgStack): Int { return 1 } -private fun directions(self: WorldObject, args: LuaThread.ArgStack): Int { +private fun direction(self: WorldObject, args: LuaThread.ArgStack): Int { args.lua.push(self.direction.numericalValue) return 1 } @@ -320,7 +320,7 @@ fun provideWorldObjectBindings(self: WorldObject, lua: LuaThread) { lua.storeGlobal("object") lua.pushBinding(self, "name", ::name) - lua.pushBinding(self, "directions", ::directions) + lua.pushBinding(self, "direction", ::direction) lua.pushBinding(self, "position", ::position) lua.pushBinding(self, "setInteractive", ::setInteractive) lua.pushBinding(self, "uniqueId", ::uniqueId) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/userdata/BehaviorState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/userdata/BehaviorState.kt index 42e266c4..b4c3518f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/userdata/BehaviorState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/userdata/BehaviorState.kt @@ -29,7 +29,7 @@ private fun replaceBehaviorTag(parameter: NodeParameterValue, treeParameters: Ma if (parameter.key != null) str = parameter.key - // original engine does this, and i don't know why this make any sense + // FIXME: 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 @@ -144,7 +144,12 @@ private fun createNode( functions.add(name) val outputConfig = data.get("output") { JsonObject() } - val output = LinkedHashMap(Registries.behaviorNodes.getOrThrow(name).value.output) + val node = Registries.behaviorNodes.getOrThrow(name).value + val output = LinkedHashMap(node.output) + + // original engine doesn't do this + if (node.script != null) + scripts.add(node.script.fullPath) for ((k, v) in output.entries) { val replaced = replaceOutputBehaviorTag(outputConfig[k]?.asString ?: v.key, treeParameters) @@ -263,14 +268,12 @@ private fun createBehaviorTree(args: LuaThread.ArgStack): Int { } handles.add(blackboard) - args.lua.ensureExtraCapacity(40) + mergedParams.scripts.forEach { scripts.add(it.fullPath) } val root = createNode(args.lua, mergedParams.root, mergedParams.mappedParameters, blackboard, scripts, functions, handles) handles.add(root) - args.lua.loadGlobal("require") - scripts.forEach { args.lua.call { loadGlobal("require") @@ -289,9 +292,11 @@ private fun createBehaviorTree(args: LuaThread.ArgStack): Int { handle.push(this) push("bake") check(loadTableValue() == LuaType.FUNCTION) { "BehaviorTree.bake is not a Lua function" } - handle.push(this) + swap(-2, -1) } + args.lua.push(handle) + handle.close() handles.forEach { it.close() } return 1 diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt index 1ebad4d8..63a2b7c0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt @@ -51,6 +51,7 @@ import ru.dbotthepony.kstarbound.world.api.MutableTileState import ru.dbotthepony.kstarbound.world.api.TileColor import ru.dbotthepony.kstarbound.world.entities.AbstractEntity import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity +import ru.dbotthepony.kstarbound.world.entities.NPCEntity import java.util.concurrent.CompletableFuture import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.TimeUnit @@ -232,7 +233,8 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server for (obj in world.storage.loadEntities(pos).await()) { try { - obj.joinWorld(world) + if (obj !is NPCEntity) + obj.joinWorld(world) } catch (err: Exception) { LOGGER.error("Exception while spawning entity $obj in world", err) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/SystemWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/SystemWorld.kt index 48a31335..4d372f89 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/SystemWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/SystemWorld.kt @@ -122,18 +122,16 @@ abstract class SystemWorld(val location: Vector3i, val clock: JVMClock, val univ } fun compatCoordinateSeed(coordinate: UniversePos, seedMix: String): Long { - // original code is utterly broken here - - // consider the following: - // auto satellite = coordinate.isSatelliteBody() ? coordinate.orbitNumber() : 0; - // auto planet = coordinate.isSatelliteBody() ? coordinate.parent().orbitNumber() : coordinate.isPlanetaryBody() && coordinate.orbitNumber() || 0; - - // first obvious problem: coordinate.isPlanetaryBody() && coordinate.orbitNumber() || 0 - // this "coalesces" planet orbit into either 0 or 1 - // then, we have coordinate.parent().orbitNumber(), which is correct, but only if we are orbiting a satellite + // FIXME: original code is utterly broken here + // consider the following: + // auto satellite = coordinate.isSatelliteBody() ? coordinate.orbitNumber() : 0; + // auto planet = coordinate.isSatelliteBody() ? coordinate.parent().orbitNumber() : coordinate.isPlanetaryBody() && coordinate.orbitNumber() || 0; + // first obvious problem: coordinate.isPlanetaryBody() && coordinate.orbitNumber() || 0 + // this "coalesces" planet orbit into either 0 or 1 + // then, we have coordinate.parent().orbitNumber(), which is correct, but only if we are orbiting a satellite // TODO: Use correct logic when there are no legacy clients in this system - // Correct logic properly randomizes starting planet orbits, and they feel much more natural + // Correct logic properly randomizes starting planet orbits, and they feel much more natural return staticRandom64(coordinate.location.x, coordinate.location.y, coordinate.location.z, if (coordinate.isPlanet) 1 else coordinate.planetOrbit, coordinate.satelliteOrbit, seedMix) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MonsterEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MonsterEntity.kt index 5c0af90a..e1c555f9 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MonsterEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/MonsterEntity.kt @@ -406,10 +406,10 @@ class MonsterEntity(val variant: MonsterVariant, level: Double? = null) : ActorE } } else { try { - /*luaUpdate.update(delta) { + luaUpdate.update(delta) { luaMovement.clearControlsIfNeeded() forceRegions.clear() - }*/ + } } catch (err: Exception) { LOGGER.error("Exception while ticking $this", err) } diff --git a/src/main/resources/scripts/behavior.lua b/src/main/resources/scripts/behavior.lua index 58af354d..4e9b4b60 100644 --- a/src/main/resources/scripts/behavior.lua +++ b/src/main/resources/scripts/behavior.lua @@ -61,7 +61,7 @@ local function blackboardSet(self, t, key, value) local mappings = self.vectorNumberInput[key] if mappings then - for _, pair in pairs(input) do + for _, pair in pairs(mappings) do local index = pair[1] local tab = pair[2] tab[index] = value @@ -107,12 +107,12 @@ function blackboardPrototype:parameters(parameters, nodeID) if not typeInput then typeInput = {} - self.input[i][pKey] = typeInput + self.input[t][pKey] = typeInput end table.insert(typeInput, {parameterName, tab}) tab[parameterName] = self.board[t][pKey] - elseif pValue then + elseif pValue ~= nil then if t == 4 then -- vec2 -- dumb special case for allowing a vec2 of blackboard number keys if type(pValue) ~= 'table' then @@ -132,7 +132,7 @@ function blackboardPrototype:parameters(parameters, nodeID) end table.insert(typeInput, {i, vector}) - vector[i] = self.board[5][key] -- number + vector[i] = self.board[5][vValue] -- number else vector[i] = vValue end @@ -142,8 +142,6 @@ function blackboardPrototype:parameters(parameters, nodeID) 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 @@ -183,7 +181,7 @@ function blackboardPrototype:clearEphemerals(ephemerals) end end -local function Blackboard() +function Blackboard() return setmetatable({}, blackboardPrototype):ctor() end @@ -204,6 +202,19 @@ local function runAndReset(self, ...) return status end +local function reconstructTree(stack) + local top = #stack + if top == 0 then return '' end + + local result = {'\nbehavior tree traceback:'} + + for i = top, 1, -1 do + table.insert(result, string.format('%s%d. - %q', string.rep(' ', top - i + 1), top - i + 1, stack[i])) + end + + return table.concat(result, '\n') +end + -- ActionNode local actionNode = {} @@ -221,10 +232,10 @@ function actionNode:ctor(name, parameters, outputs) end function actionNode:bake() - self.callable = _G[self.name] + self.callable = _ENV[self.name] - if type(callable) ~= 'function' then - error('expected global ' .. self.name .. ' to be a function, but got ' .. type(callable)) + if type(self.callable) ~= 'function' then + error('expected global ' .. self.name .. ' to be a function, but got ' .. type(self.callable)) end end @@ -233,7 +244,8 @@ do local resume = coroutine.resume local status = coroutine.status - function actionNode:run(delta, blackboard) + function actionNode:run(delta, blackboard, stack) + --table.insert(stack, self.name) self.calls = self.calls + 1 local status, nodeStatus, nodeExtra @@ -246,11 +258,13 @@ do end if not status then - sb.logError('Behavior ActionNode %q failed: %s', self.name, nodeStatus) + sb.logError(debug.traceback(self.coroutine, string.format('Behavior ActionNode %q failed: %s%s', self.name, nodeStatus, reconstructTree(stack)))) + --table.remove(stack) return FAILURE end - if result == nil then + if nodeStatus == nil then + --table.remove(stack) return RUNNING end @@ -258,6 +272,8 @@ do blackboard:setOutput(self, nodeExtra) end + --table.remove(stack) + if nodeStatus then return SUCCESS else @@ -297,10 +313,10 @@ function decoratorNode:ctor(name, parameters, child) end function decoratorNode:bake() - self.callable = _G[self.name] + self.callable = _ENV[self.name] - if type(callable) ~= 'function' then - error('expected global ' .. self.name .. ' to be a function, but got ' .. type(callable)) + if type(self.callable) ~= 'function' then + error('expected global ' .. self.name .. ' to be a function, but got ' .. type(self.callable)) end self.child:bake() @@ -311,7 +327,8 @@ do local resume = coroutine.resume local coroutine_status = coroutine.status - function decoratorNode:run(delta, blackboard) + function decoratorNode:run(delta, blackboard, stack) + --table.insert(stack, self.name) self.calls = self.calls + 1 if not self.coroutine then @@ -320,7 +337,8 @@ do local status, nodeStatus = resume(coroutine, parameters, blackboard, self.nodeID, delta) if not status then - sb.logError('Behavior DecoratorNode %q failed: %s', self.name, nodeStatus) + sb.logError(debug.traceback(coroutine, string.format('Behavior DecoratorNode %q failed: %s%s', self.name, nodeStatus, reconstructTree(stack)))) + --table.remove(stack) return FAILURE end @@ -329,38 +347,44 @@ do if s == 'dead' then -- quite unexpected, but whatever + --table.remove(stack) return SUCCESS else self.coroutine = coroutine end elseif nodeStatus then + --table.remove(stack) return SUCCESS else + --table.remove(stack) return FAILURE end end while true do - local childStatus = runAndReset(self.child, delta, blackboard) + local childStatus = runAndReset(self.child, delta, blackboard, stack) if childStatus == RUNNING then + table.remove(stack) return RUNNING end local status, nodeStatus = resume(self.coroutine, childStatus) if not status then - sb.logError('Behavior DecoratorNode %q failed: %s', self.name, nodeStatus) + sb.logError(debug.traceback(coroutine, string.format('Behavior DecoratorNode %q failed: %s%s', self.name, nodeStatus, reconstructTree(stack)))) + --table.remove(stack) return FAILURE end if nodeStatus == nil then -- another yield OR unexpected return? - local s = coroutine_status(coroutine) + local s = coroutine_status(self.coroutine) if s == 'dead' then self.coroutine = nil + --table.remove(stack) return SUCCESS end else @@ -368,8 +392,10 @@ do self.coroutine = nil if nodeStatus then + --table.remove(stack) return SUCCESS else + --table.remove(stack) return FAILURE end end @@ -407,20 +433,29 @@ function seqNode:ctor(children, isSelector) return self end -function seqNode:run(delta, blackboard) +function seqNode:run(delta, blackboard, stack) self.calls = self.calls + 1 local size = self.size local isSelector = self.isSelector + --[[if isSelector then + table.insert(stack, 'SelectorNode') + else + table.insert(stack, 'SequenceNode') + end]] + while self.index <= size do local child = self.children[self.index] - local status = runAndReset(child, delta, blackboard) + local status = runAndReset(child, delta, blackboard, stack) if status == RUNNING then + --table.remove(stack) return RUNNING elseif isSelector and status == SUCCESS then + --table.remove(stack) return SUCCESS elseif not isSelector and status == FAILURE then + --table.remove(stack) return FAILURE end @@ -464,13 +499,13 @@ parallelNode.__index = parallelNode function parallelNode:ctor(parameters, children) self.children = children - if type(parameters.success) == 'number' then + if type(parameters.success) == 'number' and parameters.success >= 0 then self.successLimit = parameters.success else self.successLimit = #children end - if type(parameters.fail) == 'number' then + if type(parameters.fail) == 'number' and parameters.fail >= 0 then self.failLimit = parameters.fail else self.failLimit = #children @@ -483,15 +518,17 @@ function parallelNode:ctor(parameters, children) return self end -function parallelNode:run(delta, blackboard) +function parallelNode:run(delta, blackboard, stack) self.calls = self.calls + 1 local failed = 0 local succeeded = 0 local failLimit = self.failLimit local successLimit = self.successLimit + --table.insert(stack, 'ParallelNode') + for _, node in ipairs(self.children) do - local status = runAndReset(node, delta, blackboard) + local status = runAndReset(node, delta, blackboard, stack) if status == SUCCESS then succeeded = succeeded + 1 @@ -502,16 +539,19 @@ function parallelNode:run(delta, blackboard) if failed >= failLimit then self.lastFailed = failed self.lastSucceed = succeeded + --table.remove(stack) return FAILURE elseif succeeded >= successLimit then self.lastFailed = failed self.lastSucceed = succeeded + --table.remove(stack) return SUCCESS end end self.lastFailed = failed self.lastSucceed = succeeded + --table.remove(stack) return RUNNING end @@ -550,11 +590,12 @@ function dynNode:ctor(children) return self end -function dynNode:run(delta, blackboard) +function dynNode:run(delta, blackboard, stack) self.calls = self.calls + 1 + --table.insert(stack, 'DynamicNode') for i, node in ipairs(self.children) do - local status = runAndReset(node, delta, blackboard) + local status = runAndReset(node, delta, blackboard, stack) if stauts == FAILURE and self.index == i then self.index = self.index + 1 @@ -564,10 +605,12 @@ function dynNode:run(delta, blackboard) end if status == SUCCESS or self.index > self.size then + --table.remove(stack) return status end end + --table.remove(stack) return RUNNING end @@ -605,7 +648,7 @@ function randNode:ctor(children) return self end -function randNode:run(delta, blackboard) +function randNode:run(delta, blackboard, stack) self.calls = self.calls + 1 if self.index == -1 and self.size ~= 0 then @@ -615,7 +658,10 @@ function randNode:run(delta, blackboard) if self.index == -1 then return FAILURE else - return runAndReset(self.children[self.index], delta, blackboard) + --table.insert(stack, 'RandomNode') + local value = runAndReset(self.children[self.index], delta, blackboard, stack) + --table.remove(stack) + return value end end @@ -654,19 +700,20 @@ function statePrototype:ctor(blackboard, root) end function statePrototype:run(delta) + local stack = {} local ephemerals = self._blackboard:takeEphemerals() - local status = runAndReset(self.root, delta, self._blackboard) + local status = runAndReset(self.root, delta, self._blackboard, stack) self._blackboard:clearEphemerals(ephemerals) return status end function statePrototype:clear() - self.tree:reset() + self.root:reset() end function statePrototype:bake() - self.tree:bake() + self.root:bake() end function statePrototype:blackboard() diff --git a/src/main/resources/scripts/global.lua b/src/main/resources/scripts/global.lua index a7a2be90..b410bed2 100644 --- a/src/main/resources/scripts/global.lua +++ b/src/main/resources/scripts/global.lua @@ -385,4 +385,72 @@ function mergeJson(base, with) end end +do + local line = '' + + local function puts(f, ...) + line = line .. string.format(f, ...) + end + + local function flush() + if line ~= '' then + sb.logInfo(line) + line = '' + end + end + + local function printTable(input, level) + level = level or 0 + + if not next(input) then + puts('{ --[[ empty table ]] }') + if level == 0 then flush() end + else + local prefix = string.rep(' ', level + 1) + + puts('{') + flush() + + for k, v in pairs(input) do + if type(k) == 'string' then + puts('%s[%q] = ', prefix, k) + else + puts('%s[%s] = ', prefix, k) + end + + printValue(v, level + 1) + puts(',') + flush() + end + + puts('%s}', string.rep(' ', level)) + if level == 0 then flush() end + end + end + + function printValue(input, level) + level = level or 0 + + local t = type(input) + + if t == 'nil' then + puts('%s', 'nil') + if level == 0 then flush() end + elseif t == 'number' then + puts('%f', input) + if level == 0 then flush() end + elseif t == 'string' then + puts('%q', tostring(input)) + if level == 0 then flush() end + elseif t == 'boolean' then + puts('%s', tostring(input)) + if level == 0 then flush() end + elseif t == 'table' then + printTable(input, level) + else + puts('unknown value type %q', t) + if level == 0 then flush() end + end + end +end diff --git a/src/main/resources/scripts/world.lua b/src/main/resources/scripts/world.lua index ebf25d2c..0accd486 100644 --- a/src/main/resources/scripts/world.lua +++ b/src/main/resources/scripts/world.lua @@ -99,7 +99,7 @@ local function entityTypeNamesToIntegers(input, fullName) error('invalid entity type ' .. tostring(v) .. ' for ' .. fullName .. ' in types table at index ' .. i, 3) end - entityTypes[i] = lookup + input[i] = lookup end return input diff --git a/src/test/kotlin/ru/dbotthepony/kstarbound/test/LuaTests.kt b/src/test/kotlin/ru/dbotthepony/kstarbound/test/LuaTests.kt index 50a71ea6..a5fea6fc 100644 --- a/src/test/kotlin/ru/dbotthepony/kstarbound/test/LuaTests.kt +++ b/src/test/kotlin/ru/dbotthepony/kstarbound/test/LuaTests.kt @@ -13,19 +13,22 @@ object LuaTests { val lua = LuaThread() lua.push { - throw IllegalArgumentException("test!") + throw IllegalArgumentException("This is error message") } lua.storeGlobal("test") - val results = lua.call(5) { + lua.call { lua.load(""" - return 1, 4, 4.0, 4.1, {a = 71} + local function errornous() + test() + end + + local cor = coroutine.create(errornous) + print(coroutine.resume(cor)) """.trimIndent()) } - println(results) - println(results.last().toJson()) lua.close() } }