Stagehands, they don't work because of Luna (:

This commit is contained in:
DBotThePony 2024-12-12 22:29:54 +07:00
parent aeca7836cd
commit 3b454374ec
Signed by: DBot
GPG Key ID: DCC23B5715498507
11 changed files with 293 additions and 40 deletions

View File

@ -50,6 +50,7 @@ operator fun <K : Any, V : Any> ImmutableMap.Builder<K, V>.set(key: K, value: V)
inline fun <reified T> Gson.fromJson(reader: JsonReader): T? = fromJson<T>(reader, T::class.java) inline fun <reified T> Gson.fromJson(reader: JsonReader): T? = fromJson<T>(reader, T::class.java)
inline fun <reified T> Gson.fromJson(reader: JsonElement): T? = getAdapter(T::class.java).read(FastJsonTreeReader(reader)) inline fun <reified T> Gson.fromJson(reader: JsonElement): T? = getAdapter(T::class.java).read(FastJsonTreeReader(reader))
// TODO: mark these nullable
fun <T> Gson.fromJsonFast(reader: JsonElement, type: Class<T>): T = getAdapter(type).read(FastJsonTreeReader(reader)) fun <T> Gson.fromJsonFast(reader: JsonElement, type: Class<T>): T = getAdapter(type).read(FastJsonTreeReader(reader))
fun <T> Gson.fromJsonFast(reader: JsonElement, type: TypeToken<T>): T = getAdapter(type).read(FastJsonTreeReader(reader)) fun <T> Gson.fromJsonFast(reader: JsonElement, type: TypeToken<T>): T = getAdapter(type).read(FastJsonTreeReader(reader))
inline fun <reified T> Gson.fromJsonFast(reader: JsonElement): T = getAdapter(object : TypeToken<T>() {}).read(FastJsonTreeReader(reader)) inline fun <reified T> Gson.fromJsonFast(reader: JsonElement): T = getAdapter(object : TypeToken<T>() {}).read(FastJsonTreeReader(reader))

View File

@ -117,6 +117,33 @@ object Registries {
private val monsterParts = HashMap<Pair<String, String>, HashMap<String, Pair<MonsterPartDefinition, IStarboundFile>>>() private val monsterParts = HashMap<Pair<String, String>, HashMap<String, Pair<MonsterPartDefinition, IStarboundFile>>>()
private val loggedMonsterPartMisses = Collections.synchronizedSet(ObjectOpenHashSet<Pair<String, String>>()) private val loggedMonsterPartMisses = Collections.synchronizedSet(ObjectOpenHashSet<Pair<String, String>>())
private val stagehands = HashMap<String, Pair<JsonObject, IStarboundFile>>()
fun makeStagehandConfig(type: String, overrides: JsonElement? = JsonNull.INSTANCE): JsonObject {
val (data) = stagehands[type] ?: throw NoSuchElementException("No such stagehand: $type")
return mergeJson(data.deepCopy(), overrides ?: JsonNull.INSTANCE)
}
private fun loadStagehands(files: Collection<IStarboundFile>, patches: Map<String, Collection<IStarboundFile>>): List<Future<*>> {
return files.map { listedFile ->
Starbound.GLOBAL_SCOPE.launch {
try {
val elem = JsonPatch.applyAsync(listedFile.asyncJsonReader(), patches[listedFile.computeFullPath()]).asJsonObject
val type = elem["type"].asString
Starbound.submit {
val existing = stagehands.put(type, elem to listedFile)
if (existing != null) {
LOGGER.warn("Redefining stagehand prototype $type (new def originate from $listedFile, existing originate from ${existing.second})")
}
}
} catch (err: Throwable) {
LOGGER.error("Loading stagehand definition file $listedFile", err)
}
}.asCompletableFuture()
}
}
fun selectMonsterPart(category: String, type: String, random: RandomGenerator): MonsterPartDefinition? { fun selectMonsterPart(category: String, type: String, random: RandomGenerator): MonsterPartDefinition? {
val key = category to type val key = category to type
@ -275,6 +302,7 @@ object Registries {
tasks.addAll(loadRegistry(dungeons, patchTree, fileTree["dungeon"] ?: listOf(), key(DungeonDefinition::name))) tasks.addAll(loadRegistry(dungeons, patchTree, fileTree["dungeon"] ?: listOf(), key(DungeonDefinition::name)))
tasks.addAll(loadMonsterParts(fileTree["monsterpart"] ?: listOf(), patchTree)) tasks.addAll(loadMonsterParts(fileTree["monsterpart"] ?: listOf(), patchTree))
tasks.addAll(loadStagehands(fileTree["stagehand"] ?: listOf(), patchTree))
tasks.addAll(loadRegistry(worldObjects, patchTree, fileTree["object"] ?: listOf(), key(ObjectDefinition::objectName))) tasks.addAll(loadRegistry(worldObjects, patchTree, fileTree["object"] ?: listOf(), key(ObjectDefinition::objectName)))
tasks.addAll(loadRegistry(monsterTypes, patchTree, fileTree["monstertype"] ?: listOf(), key(MonsterTypeDefinition::type))) tasks.addAll(loadRegistry(monsterTypes, patchTree, fileTree["monstertype"] ?: listOf(), key(MonsterTypeDefinition::type)))

View File

@ -16,6 +16,7 @@ import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
import ru.dbotthepony.kstarbound.world.entities.MonsterEntity import ru.dbotthepony.kstarbound.world.entities.MonsterEntity
import ru.dbotthepony.kstarbound.world.entities.NPCEntity import ru.dbotthepony.kstarbound.world.entities.NPCEntity
import ru.dbotthepony.kstarbound.world.entities.ProjectileEntity import ru.dbotthepony.kstarbound.world.entities.ProjectileEntity
import ru.dbotthepony.kstarbound.world.entities.StagehandEntity
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
import ru.dbotthepony.kstarbound.world.entities.tile.ContainerObject import ru.dbotthepony.kstarbound.world.entities.tile.ContainerObject
import ru.dbotthepony.kstarbound.world.entities.tile.LoungeableObject import ru.dbotthepony.kstarbound.world.entities.tile.LoungeableObject
@ -102,11 +103,11 @@ enum class EntityType(override val jsonName: String, val storeName: String, val
STAGEHAND("stagehand", "StagehandEntity", true, true) { STAGEHAND("stagehand", "StagehandEntity", true, true) {
override suspend fun fromNetwork(stream: DataInputStream, isLegacy: Boolean): AbstractEntity { override suspend fun fromNetwork(stream: DataInputStream, isLegacy: Boolean): AbstractEntity {
TODO("STAGEHAND") return StagehandEntity(stream, isLegacy)
} }
override fun fromStorage(data: JsonObject): AbstractEntity { override fun fromStorage(data: JsonObject): AbstractEntity {
TODO("STAGEHAND") return StagehandEntity(data)
} }
}, },

View File

@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.lua.bindings
import com.google.gson.JsonObject import com.google.gson.JsonObject
import it.unimi.dsi.fastutil.ints.IntArrayList import it.unimi.dsi.fastutil.ints.IntArrayList
import it.unimi.dsi.fastutil.objects.ObjectArrayList import it.unimi.dsi.fastutil.objects.ObjectArrayList
import kotlinx.coroutines.runBlocking
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.classdump.luna.ByteString import org.classdump.luna.ByteString
import org.classdump.luna.LuaRuntimeException import org.classdump.luna.LuaRuntimeException
@ -11,17 +12,22 @@ import org.classdump.luna.runtime.ExecutionContext
import org.classdump.luna.runtime.LuaFunction import org.classdump.luna.runtime.LuaFunction
import ru.dbotthepony.kommons.collect.map import ru.dbotthepony.kommons.collect.map
import ru.dbotthepony.kommons.collect.toList import ru.dbotthepony.kommons.collect.toList
import ru.dbotthepony.kommons.gson.contains
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.ActorMovementParameters import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
import ru.dbotthepony.kstarbound.defs.EntityType import ru.dbotthepony.kstarbound.defs.EntityType
import ru.dbotthepony.kstarbound.defs.actor.NPCVariant
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
import ru.dbotthepony.kstarbound.defs.monster.MonsterVariant
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile
import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldParameters import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldParameters
import ru.dbotthepony.kstarbound.fromJsonFast import ru.dbotthepony.kstarbound.fromJsonFast
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.json.mergeJson
import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.lua.LuaEnvironment import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.contains import ru.dbotthepony.kstarbound.lua.contains
@ -63,7 +69,10 @@ import ru.dbotthepony.kstarbound.world.api.AbstractLiquidState
import ru.dbotthepony.kstarbound.world.castRay import ru.dbotthepony.kstarbound.world.castRay
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
import ru.dbotthepony.kstarbound.world.entities.MonsterEntity
import ru.dbotthepony.kstarbound.world.entities.NPCEntity
import ru.dbotthepony.kstarbound.world.entities.PathFinder import ru.dbotthepony.kstarbound.world.entities.PathFinder
import ru.dbotthepony.kstarbound.world.entities.StagehandEntity
import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
import ru.dbotthepony.kstarbound.world.physics.CollisionType import ru.dbotthepony.kstarbound.world.physics.CollisionType
@ -430,7 +439,7 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
LOGGER.debug("Lua script tried to create non existing item {} at {}", itemType, pos) LOGGER.debug("Lua script tried to create non existing item {} at {}", itemType, pos)
returnBuffer.setTo() returnBuffer.setTo()
} else { } else {
val create = ItemDropEntity(descriptor) val create = ItemDropEntity(descriptor, lua.random)
create.movement.velocity = initialVelocity create.movement.velocity = initialVelocity
if (intangibleTime is Number) { if (intangibleTime is Number) {
@ -453,8 +462,7 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
val items = Registries.treasurePools val items = Registries.treasurePools
.getOrThrow(pool.decode()) .getOrThrow(pool.decode())
.value .value
// not using lua.random because we are, well, world's bindings .evaluate(if (seed != null) random(seed.toLong()) else lua.random, level.toDouble())
.evaluate(if (seed != null) random(seed.toLong()) else self.random, level.toDouble())
val pos = toVector2d(position) val pos = toVector2d(position)
@ -465,18 +473,79 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
entities.add(entity.entityID) entities.add(entity.entityID)
} }
} catch (err: Throwable) { } catch (err: Throwable) {
LOGGER.error("Exception while spawning treasure from $pool at $position", err) LOGGER.error("Exception while spawning treasure from pool '$pool' at $position", err)
} }
returnBuffer.setTo(tableOf(*entities.toTypedArray())) returnBuffer.setTo(tableOf(*entities.toTypedArray()))
} }
callbacks["spawnMonster"] = luaFunction { callbacks["spawnMonster"] = luaFunction { type: ByteString, position: Table, overrides: Any? ->
// TODO try {
returnBuffer.setTo(0L) val parameters = JsonObject()
parameters["aggressive"] = lua.random.nextBoolean()
if (overrides != null) {
mergeJson(parameters, toJsonFromLua(overrides))
}
val level = parameters["level"]?.asDouble ?: 1.0
val seed: Long
if ("seed" !in parameters || !parameters["seed"].asJsonPrimitive.isNumber) {
seed = lua.random.nextLong()
} else {
seed = parameters["seed"].asLong
}
val variant = Registries.monsterTypes.getOrThrow(type.decode()).value.create(seed, parameters)
val monster = MonsterEntity(variant, level)
monster.position = toVector2d(position)
monster.joinWorld(self)
returnBuffer.setTo(monster.entityID)
} catch (err: Throwable) {
LOGGER.error("Exception caught while spawning Monster type $type", err)
}
} }
callbacks["spawnNpc"] = luaStub("spawnNpc")
callbacks["spawnStagehand"] = luaStub("spawnStagehand") callbacks["spawnNpc"] = luaFunctionN("spawnNpc") {
val position = it.nextTable()
val species = it.nextString().decode()
val type = it.nextString().decode()
val level = it.nextFloat()
val seed = it.nextOptionalInteger() ?: lua.random.nextLong()
val overrides = toJsonFromLua(it.nextOptionalAny(null))
try {
// TODO: this blocks world thread
val npc = runBlocking {
NPCEntity(NPCVariant.create(
Registries.species.getOrThrow(species),
type,
level,
seed,
overrides
))
}
npc.position = toVector2d(position)
npc.joinWorld(self)
returnBuffer.setTo(npc.entityID)
} catch (err: Throwable) {
LOGGER.error("Exception caught while spawning NPC $type with species $species", err)
}
}
callbacks["spawnStagehand"] = luaFunction { position: Table, type: ByteString, overrides: Any? ->
try {
val stagehand = StagehandEntity(type.decode(), toJsonFromLua(overrides))
stagehand.position = toVector2d(position)
stagehand.joinWorld(self)
returnBuffer.setTo(stagehand.entityID)
} catch (err: Throwable) {
LOGGER.error("Exception caught while spawning stagehand of type '$type'", err)
}
}
callbacks["spawnProjectile"] = luaStub("spawnProjectile") callbacks["spawnProjectile"] = luaStub("spawnProjectile")
callbacks["spawnVehicle"] = luaStub("spawnVehicle") callbacks["spawnVehicle"] = luaStub("spawnVehicle")

View File

@ -72,7 +72,7 @@ private val centerStr = ByteString.of("center")
private val boundModeStr = ByteString.of("boundMode") private val boundModeStr = ByteString.of("boundMode")
private val orderStr = ByteString.of("order") private val orderStr = ByteString.of("order")
private fun ExecutionContext.entityQueryImpl(self: World<*, *>, options: Table, predicate: Predicate<AbstractEntity> = Predicate { true }): Table { private fun ExecutionContext.entityQueryImpl(self: World<*, *>, lua: LuaEnvironment, options: Table, predicate: Predicate<AbstractEntity> = Predicate { true }): Table {
val withoutEntityId = (indexNoYield(options, withoutEntityIdStr) as Number?)?.toInt() val withoutEntityId = (indexNoYield(options, withoutEntityIdStr) as Number?)?.toInt()
val includedTypes = EnumSet.allOf(EntityType::class.java) val includedTypes = EnumSet.allOf(EntityType::class.java)
@ -200,7 +200,7 @@ private fun ExecutionContext.entityQueryImpl(self: World<*, *>, options: Table,
when (val order = (indexNoYield(options, orderStr) as ByteString?)?.decode()?.lowercase()) { when (val order = (indexNoYield(options, orderStr) as ByteString?)?.decode()?.lowercase()) {
null -> {} // do nothing null -> {} // do nothing
"random" -> entitites.shuffle(self.random) "random" -> entitites.shuffle(lua.random)
"nearest" -> { "nearest" -> {
val nearestPosition = lineQuery?.p0 ?: polyQuery?.centre ?: rectQuery?.centre ?: radiusQuery?.first ?: Vector2d.ZERO val nearestPosition = lineQuery?.p0 ?: polyQuery?.centre ?: rectQuery?.centre ?: radiusQuery?.first ?: Vector2d.ZERO
@ -213,7 +213,7 @@ private fun ExecutionContext.entityQueryImpl(self: World<*, *>, options: Table,
return tableOf(*entitites.map { it.entityID.toLong() }.toTypedArray()) return tableOf(*entitites.map { it.entityID.toLong() }.toTypedArray())
} }
private fun ExecutionContext.intermediateQueryFunction(self: World<*, *>, pos1: Table, pos2OrRadius: Any, options: Table?, predicate: Predicate<AbstractEntity>) { private fun ExecutionContext.intermediateQueryFunction(self: World<*, *>, lua: LuaEnvironment, pos1: Table, pos2OrRadius: Any, options: Table?, predicate: Predicate<AbstractEntity>) {
val actualOptions = options ?: tableOf() val actualOptions = options ?: tableOf()
if (pos2OrRadius is Number) { if (pos2OrRadius is Number) {
@ -224,35 +224,35 @@ private fun ExecutionContext.intermediateQueryFunction(self: World<*, *>, pos1:
actualOptions[rectStr] = tableOf(pos1[1L], pos1[2L], pos2OrRadius[1L], pos2OrRadius[2L]) actualOptions[rectStr] = tableOf(pos1[1L], pos1[2L], pos2OrRadius[1L], pos2OrRadius[2L])
} }
returnBuffer.setTo(entityQueryImpl(self, actualOptions, predicate)) returnBuffer.setTo(entityQueryImpl(self, lua, actualOptions, predicate))
} }
private fun ExecutionContext.intermediateLineQueryFunction(self: World<*, *>, pos1: Table, pos2: Table, options: Table?, predicate: Predicate<AbstractEntity>) { private fun ExecutionContext.intermediateLineQueryFunction(self: World<*, *>, lua: LuaEnvironment, pos1: Table, pos2: Table, options: Table?, predicate: Predicate<AbstractEntity>) {
val actualOptions = options ?: tableOf() val actualOptions = options ?: tableOf()
actualOptions[lineStr] = tableOf(pos1, pos2) actualOptions[lineStr] = tableOf(pos1, pos2)
returnBuffer.setTo(entityQueryImpl(self, actualOptions, predicate)) returnBuffer.setTo(entityQueryImpl(self, lua, actualOptions, predicate))
} }
private inline fun <reified T : AbstractEntity> createQueryFunction(self: World<*, *>) = luaFunction { pos1: Table, pos2OrRadius: Any, options: Table? -> private inline fun <reified T : AbstractEntity> createQueryFunction(self: World<*, *>, lua: LuaEnvironment) = luaFunction { pos1: Table, pos2OrRadius: Any, options: Table? ->
intermediateQueryFunction(self, pos1, pos2OrRadius, options, Predicate { it is T }) intermediateQueryFunction(self, lua, pos1, pos2OrRadius, options, Predicate { it is T })
} }
private inline fun <reified T : AbstractEntity> createLineQueryFunction(self: World<*, *>) = luaFunction { pos1: Table, pos2: Table, options: Table? -> private inline fun <reified T : AbstractEntity> createLineQueryFunction(self: World<*, *>, lua: LuaEnvironment) = luaFunction { pos1: Table, pos2: Table, options: Table? ->
intermediateLineQueryFunction(self, pos1, pos2, options, Predicate { it is T }) intermediateLineQueryFunction(self, lua, pos1, pos2, options, Predicate { it is T })
} }
fun provideWorldEntitiesBindings(self: World<*, *>, callbacks: Table, lua: LuaEnvironment) { fun provideWorldEntitiesBindings(self: World<*, *>, callbacks: Table, lua: LuaEnvironment) {
callbacks["entityQuery"] = createQueryFunction<AbstractEntity>(self) callbacks["entityQuery"] = createQueryFunction<AbstractEntity>(self, lua)
callbacks["monsterQuery"] = createQueryFunction<AbstractEntity>(self) // TODO callbacks["monsterQuery"] = createQueryFunction<AbstractEntity>(self, lua) // TODO
callbacks["npcQuery"] = createQueryFunction<AbstractEntity>(self) // TODO callbacks["npcQuery"] = createQueryFunction<AbstractEntity>(self, lua) // TODO
callbacks["itemDropQuery"] = createQueryFunction<ItemDropEntity>(self) callbacks["itemDropQuery"] = createQueryFunction<ItemDropEntity>(self, lua)
callbacks["playerQuery"] = createQueryFunction<PlayerEntity>(self) callbacks["playerQuery"] = createQueryFunction<PlayerEntity>(self, lua)
callbacks["entityLineQuery"] = createLineQueryFunction<AbstractEntity>(self) callbacks["entityLineQuery"] = createLineQueryFunction<AbstractEntity>(self, lua)
callbacks["monsterLineQuery"] = createLineQueryFunction<AbstractEntity>(self) // TODO callbacks["monsterLineQuery"] = createLineQueryFunction<AbstractEntity>(self, lua) // TODO
callbacks["npcLineQuery"] = createLineQueryFunction<AbstractEntity>(self) // TODO callbacks["npcLineQuery"] = createLineQueryFunction<AbstractEntity>(self, lua) // TODO
callbacks["itemDropLineQuery"] = createLineQueryFunction<ItemDropEntity>(self) callbacks["itemDropLineQuery"] = createLineQueryFunction<ItemDropEntity>(self, lua)
callbacks["playerLineQuery"] = createLineQueryFunction<PlayerEntity>(self) callbacks["playerLineQuery"] = createLineQueryFunction<PlayerEntity>(self, lua)
callbacks["objectQuery"] = luaFunction { pos1: Table, pos2OrRadius: Any, options: Table? -> callbacks["objectQuery"] = luaFunction { pos1: Table, pos2OrRadius: Any, options: Table? ->
var objectName: String? = null var objectName: String? = null
@ -260,7 +260,7 @@ fun provideWorldEntitiesBindings(self: World<*, *>, callbacks: Table, lua: LuaEn
if (options != null) if (options != null)
objectName = (indexNoYield(options, "name") as ByteString?)?.decode() objectName = (indexNoYield(options, "name") as ByteString?)?.decode()
intermediateQueryFunction(self, pos1, pos2OrRadius, options, Predicate { intermediateQueryFunction(self, lua, pos1, pos2OrRadius, options, Predicate {
it is WorldObject && (objectName == null || it.config.key == objectName) it is WorldObject && (objectName == null || it.config.key == objectName)
}) })
} }
@ -271,7 +271,7 @@ fun provideWorldEntitiesBindings(self: World<*, *>, callbacks: Table, lua: LuaEn
if (options != null) if (options != null)
objectName = (indexNoYield(options, "name") as ByteString?)?.decode() objectName = (indexNoYield(options, "name") as ByteString?)?.decode()
intermediateLineQueryFunction(self, pos1, pos2, options, Predicate { intermediateLineQueryFunction(self, lua, pos1, pos2, options, Predicate {
it is WorldObject && (objectName == null || it.config.key == objectName) it is WorldObject && (objectName == null || it.config.key == objectName)
}) })
} }
@ -287,7 +287,7 @@ fun provideWorldEntitiesBindings(self: World<*, *>, callbacks: Table, lua: LuaEn
else -> LoungeOrientation.entries.valueOf(orientationName) else -> LoungeOrientation.entries.valueOf(orientationName)
} }
intermediateQueryFunction(self, pos1, pos2OrRadius, options, Predicate { intermediateQueryFunction(self, lua, pos1, pos2OrRadius, options, Predicate {
it is LoungeableObject && (orientation == LoungeOrientation.NONE || it.sitOrientation == orientation) it is LoungeableObject && (orientation == LoungeOrientation.NONE || it.sitOrientation == orientation)
}) })
} }
@ -303,7 +303,7 @@ fun provideWorldEntitiesBindings(self: World<*, *>, callbacks: Table, lua: LuaEn
else -> LoungeOrientation.entries.valueOf(orientationName!!) else -> LoungeOrientation.entries.valueOf(orientationName!!)
} }
intermediateLineQueryFunction(self, pos1, pos2, options, Predicate { intermediateLineQueryFunction(self, lua, pos1, pos2, options, Predicate {
it is LoungeableObject && (orientation == LoungeOrientation.NONE || it.sitOrientation == orientation) it is LoungeableObject && (orientation == LoungeOrientation.NONE || it.sitOrientation == orientation)
}) })
} }

View File

@ -748,7 +748,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
enqueueWarp(WarpAlias.OwnShip) enqueueWarp(WarpAlias.OwnShip)
} }
//enqueueWarp(WarpAction.World(WorldID.Instance("outpost"))) enqueueWarp(WarpAction.World(WorldID.Instance("outpost")))
scope.launch { shipFlightEventLoop(context.shipCoordinate, context.systemLocation) } scope.launch { shipFlightEventLoop(context.shipCoordinate, context.systemLocation) }
} }

View File

@ -670,6 +670,8 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
ticks++ ticks++
simulationTime += delta simulationTime += delta
// TODO: Zombie entity handling (don't tick entities ending up in partially loaded chunks,
// or chunks which border with unloaded/not sufficiently loaded regions)
if (entityList.size < 128) { if (entityList.size < 128) {
entityList.forEach { it.tickParallel(delta) } entityList.forEach { it.tickParallel(delta) }
} else { } else {

View File

@ -30,6 +30,7 @@ import ru.dbotthepony.kstarbound.world.physics.Poly
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
import java.util.function.Predicate import java.util.function.Predicate
import java.util.random.RandomGenerator
import kotlin.math.min import kotlin.math.min
class ItemDropEntity() : DynamicEntity() { class ItemDropEntity() : DynamicEntity() {
@ -70,8 +71,8 @@ class ItemDropEntity() : DynamicEntity() {
movement.applyParameters(MovementParameters(collisionPoly = Either.left(Poly(AABB(Vector2d(-0.5, -0.5), Vector2d(0.5, 0.5)))))) movement.applyParameters(MovementParameters(collisionPoly = Either.left(Poly(AABB(Vector2d(-0.5, -0.5), Vector2d(0.5, 0.5))))))
} }
constructor(item: ItemDescriptor) : this() { constructor(item: ItemDescriptor, random: RandomGenerator? = null) : this() {
this.item = item.build() this.item = item.build(random = random)
this.owningEntity = 0 this.owningEntity = 0
this.state = State.AVAILABLE this.state = State.AVAILABLE
} }

View File

@ -0,0 +1,152 @@
package ru.dbotthepony.kstarbound.world.entities
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.kommons.util.setValue
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.AssetPath
import ru.dbotthepony.kstarbound.defs.EntityType
import ru.dbotthepony.kstarbound.fromJsonFast
import ru.dbotthepony.kstarbound.json.jsonArrayOf
import ru.dbotthepony.kstarbound.json.putAll
import ru.dbotthepony.kstarbound.json.readJsonElement
import ru.dbotthepony.kstarbound.json.writeJsonElement
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.LuaUpdateComponent
import ru.dbotthepony.kstarbound.lua.from
import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.set
import ru.dbotthepony.kstarbound.lua.tableOf
import ru.dbotthepony.kstarbound.lua.toJsonFromLua
import ru.dbotthepony.kstarbound.math.AABB
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.network.syncher.networkedFloat
import ru.dbotthepony.kstarbound.world.World
import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
import java.io.DataInputStream
import java.io.DataOutputStream
class StagehandEntity(isRemote: Boolean = false) : AbstractEntity(), ScriptedEntity {
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readJsonElement().asJsonObject, true)
constructor(data: JsonObject, isRemote: Boolean = false) : this(isRemote) {
deserialize(data)
}
constructor(type: String, overrides: JsonElement? = JsonNull.INSTANCE) : this(Registries.makeStagehandConfig(type, overrides))
var xPosition by networkedFloat().also { networkGroup.upstream.add(it) }
var yPosition by networkedFloat().also { networkGroup.upstream.add(it) }
init {
networkGroup.upstream.add(uniqueID)
this.isRemote = isRemote
}
override var position: Vector2d
get() = Vector2d(xPosition, yPosition)
set(value) {
xPosition = value.x
yPosition = value.y
}
override val type: EntityType
get() = EntityType.STAGEHAND
private var config: JsonObject = JsonObject()
var isScripted = false
private set
val lua = LuaEnvironment()
val luaUpdate = LuaUpdateComponent(lua)
init {
lua.globals["storage"] = lua.tableOf()
}
override fun deserialize(data: JsonObject) {
super.deserialize(data)
config = data.deepCopy()
isScripted = config["scripts"] is JsonArray
if ("position" in config) {
val pos = config["position"].asJsonArray
xPosition = pos[0].asDouble
yPosition = pos[1].asDouble
}
var broadcastArea: AABB? = null
if ("broadcastArea" in config) {
broadcastArea = Starbound.gson.fromJsonFast(config["broadcastArena"], AABB::class.java)
if (broadcastArea.width < 0.0 || broadcastArea.height > 0.0)
broadcastArea = null
}
boundingBox = broadcastArea ?: AABB.withSide(Vector2d.ZERO, 5.0)
if (isScripted && isLocal) {
lua.attach(config["scripts"].asJsonArray.map { AssetPath(it.asString) })
luaUpdate.stepCount = config.get("scriptDelta", 5.0)
if ("scriptStorage" in config) {
lua.globals["storage"] = lua.from(config["scriptStorage"])
}
}
}
override fun onJoinWorld(world: World<*, *>) {
super.onJoinWorld(world)
if (isLocal && isScripted) {
lua.init()
}
}
override fun serialize(data: JsonObject) {
super.serialize(data)
data.putAll(config, true)
data["position"] = jsonArrayOf(JsonPrimitive(xPosition), JsonPrimitive(yPosition))
if (uniqueID.get() != null)
data["uniqueId"] = uniqueID.get()!!
else
data.remove("uniqueId")
if (isScripted)
data["scriptStorage"] = toJsonFromLua(lua.globals["storage"])
}
override fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeJsonElement(config)
}
private var boundingBox = AABB.withSide(Vector2d.ZERO, 5.0)
override val metaBoundingBox: AABB
get() = boundingBox + position
override val name: String
get() = "Stagehand"
override val description: String
get() = "Technical entity responsible for doing cool stuff"
override fun callScript(fnName: String, vararg arguments: Any?): Array<Any?> {
return lua.call(fnName, *arguments)
}
override fun evalScript(code: String): Array<Any?> {
return lua.eval(code)
}
}

View File

@ -318,7 +318,7 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
} }
fun deserialize(data: SerializedData) { fun deserialize(data: SerializedData) {
// TODO
} }
// ----- Persistent status effects // ----- Persistent status effects

View File

@ -549,7 +549,6 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
provideEntityBindings(this, lua) provideEntityBindings(this, lua)
provideAnimatorBindings(animator, lua) provideAnimatorBindings(animator, lua)
lua.attach(config.value.scripts) lua.attach(config.value.scripts)
lua.random = world.random
luaUpdate.stepCount = lookupProperty(JsonPath("scriptDelta")) { JsonPrimitive(5) }.asDouble luaUpdate.stepCount = lookupProperty(JsonPath("scriptDelta")) { JsonPrimitive(5) }.asDouble
lua.init() lua.init()
} }