Compare commits

...

10 Commits

29 changed files with 596 additions and 223 deletions

View File

@ -22,6 +22,7 @@ val guavaVersion: String by project
val junitVersion: String by project
val sqliteVersion: String by project
val picocliVersion: String by project
val zstdVersion: String by project
repositories {
maven {
@ -59,6 +60,7 @@ dependencies {
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
implementation("com.google.code.gson:gson:$gsonVersion")
implementation("com.github.luben:zstd-jni:$zstdVersion")
implementation("it.unimi.dsi:fastutil:$fastutilVersion")

View File

@ -17,5 +17,6 @@ log4jVersion=2.17.1
guavaVersion=33.0.0-jre
junitVersion=5.8.2
sqliteVersion=3.45.3.0
zstdVersion=1.5.6-8
picocliVersion=4.7.6

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: 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: TypeToken<T>): T = getAdapter(type).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 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? {
val key = category to type
@ -275,6 +302,7 @@ object Registries {
tasks.addAll(loadRegistry(dungeons, patchTree, fileTree["dungeon"] ?: listOf(), key(DungeonDefinition::name)))
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(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.NPCEntity
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.tile.ContainerObject
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) {
override suspend fun fromNetwork(stream: DataInputStream, isLegacy: Boolean): AbstractEntity {
TODO("STAGEHAND")
return StagehandEntity(stream, isLegacy)
}
override fun fromStorage(data: JsonObject): AbstractEntity {
TODO("STAGEHAND")
return StagehandEntity(data)
}
},

View File

@ -130,7 +130,7 @@ class DungeonWorld(
private val boundingBoxes = ArrayList<AABBi>()
fun clearTileEntityAt(x: Int, y: Int) {
clearTileEntitiesAt.add(geometry.wrap(Vector2i(x, y)))
clearTileEntitiesAt.add(geometry.wrap(x, y))
}
fun clearTileEntityAt(position: Vector2i) {
@ -138,7 +138,7 @@ class DungeonWorld(
}
fun isClearingTileEntityAt(x: Int, y: Int): Boolean {
return geometry.wrap(Vector2i(x, y)) in clearTileEntitiesAt
return geometry.wrap(x, y) in clearTileEntitiesAt
}
fun clearTileEntity(entity: TileEntity) {
@ -163,15 +163,15 @@ class DungeonWorld(
}
fun isTouched(x: Int, y: Int): Boolean {
return touchedTiles.contains(geometry.wrap(Vector2i(x, y)))
return touchedTiles.contains(geometry.wrap(x, y))
}
fun touchPlacement(x: Int, y: Int) {
protectTile.add(geometry.wrap(Vector2i(x, y)))
protectTile.add(geometry.wrap(x, y))
}
fun hasPlacement(x: Int, y: Int): Boolean {
return protectTile.contains(geometry.wrap(Vector2i(x, y)))
return protectTile.contains(geometry.wrap(x, y))
}
private val biomeItems = HashSet<Vector2i>(8192, 0.5f)
@ -206,32 +206,32 @@ class DungeonWorld(
}
fun placeObject(x: Int, y: Int, prototype: Registry.Entry<ObjectDefinition>, direction: Direction = Direction.LEFT, parameters: JsonObject = JsonObject()) {
placedObjects[geometry.wrap(Vector2i(x, y))] = PlacedObject(prototype, direction, parameters)
placedObjects[geometry.wrap(x, y)] = PlacedObject(prototype, direction, parameters)
}
fun placeWiring(x: Int, y: Int, group: String, partLocal: Boolean) {
val table = if (partLocal) openLocalWires else globalWires
table.computeIfAbsent(group) { LinkedHashSet() }.add(geometry.wrap(Vector2i(x, y)))
table.computeIfAbsent(group) { LinkedHashSet() }.add(geometry.wrap(x, y))
}
fun placeNPC(x: Int, y: Int, species: Registry.Entry<Species>, type: String, seed: Long, overrides: JsonElement = JsonNull.INSTANCE) {
placedNPCs.computeIfAbsent(geometry.wrap(Vector2i(x, y))) { ArrayList() }.add(NPCData(species, type, seed, overrides))
placedNPCs.computeIfAbsent(geometry.wrap(x, y)) { ArrayList() }.add(NPCData(species, type, seed, overrides))
}
fun placeMonster(x: Int, y: Int, type: Registry.Entry<MonsterTypeDefinition>, seed: Long, overrides: JsonObject = JsonObject()) {
placedMonsters.computeIfAbsent(geometry.wrap(Vector2i(x, y))) { ArrayList() }.add(MonsterData(type, seed, overrides))
placedMonsters.computeIfAbsent(geometry.wrap(x, y)) { ArrayList() }.add(MonsterData(type, seed, overrides))
}
fun requestLiquid(x: Int, y: Int, liquid: AbstractLiquidState) {
pendingLiquids[geometry.wrap(Vector2i(x, y))] = liquid
pendingLiquids[geometry.wrap(x, y)] = liquid
}
fun setDungeonID(x: Int, y: Int, id: Int) {
dungeonIDs[geometry.wrap(Vector2i(x, y))] = id
dungeonIDs[geometry.wrap(x, y)] = id
}
fun setDungeonID(x: Int, y: Int) {
dungeonIDs.remove(geometry.wrap(Vector2i(x, y)))
dungeonIDs.remove(geometry.wrap(x, y))
}
fun setDungeonID(id: Int) {
@ -244,35 +244,35 @@ class DungeonWorld(
}
fun placeBiomeTree(x: Int, y: Int) {
biomeTrees.add(geometry.wrap(Vector2i(x, y)))
biomeTrees.add(geometry.wrap(x, y))
}
fun placeBiomeItems(x: Int, y: Int) {
biomeItems.add(geometry.wrap(Vector2i(x, y)))
biomeItems.add(geometry.wrap(x, y))
}
fun dropItem(x: Int, y: Int, item: ItemDescriptor) {
itemDrops.computeIfAbsent(geometry.wrap(Vector2i(x, y))) { ArrayList() }.add(item)
itemDrops.computeIfAbsent(geometry.wrap(x, y)) { ArrayList() }.add(item)
}
fun dropRandomizedItem(x: Int, y: Int, item: ItemDescriptor) {
randomizedItemDrops.computeIfAbsent(geometry.wrap(Vector2i(x, y))) { ArrayList() }.add(item)
randomizedItemDrops.computeIfAbsent(geometry.wrap(x, y)) { ArrayList() }.add(item)
}
fun setLiquid(x: Int, y: Int, liquid: AbstractLiquidState) {
this.liquid[geometry.wrap(Vector2i(x, y))] = liquid
this.liquid[geometry.wrap(x, y)] = liquid
}
fun getLiquid(x: Int, y: Int): AbstractLiquidState {
return this.liquid[geometry.wrap(Vector2i(x, y))] ?: AbstractLiquidState.EMPTY
return this.liquid[geometry.wrap(x, y)] ?: AbstractLiquidState.EMPTY
}
fun setForeground(x: Int, y: Int, tile: Material) {
this.foregroundMaterial[geometry.wrap(Vector2i(x, y))] = tile
this.foregroundMaterial[geometry.wrap(x, y)] = tile
}
fun setForeground(x: Int, y: Int, tile: Modifier) {
this.foregroundModifier[geometry.wrap(Vector2i(x, y))] = tile
this.foregroundModifier[geometry.wrap(x, y)] = tile
}
fun setForeground(x: Int, y: Int, material: Registry.Entry<TileDefinition>, hueShift: Float = 0f, color: TileColor = TileColor.DEFAULT) {
@ -284,11 +284,11 @@ class DungeonWorld(
}
fun setBackground(x: Int, y: Int, tile: Material) {
this.backgroundMaterial[geometry.wrap(Vector2i(x, y))] = tile
this.backgroundMaterial[geometry.wrap(x, y)] = tile
}
fun setBackground(x: Int, y: Int, tile: Modifier) {
this.backgroundModifier[geometry.wrap(Vector2i(x, y))] = tile
this.backgroundModifier[geometry.wrap(x, y)] = tile
}
fun setBackground(x: Int, y: Int, material: Registry.Entry<TileDefinition>, hueShift: Float = 0f, color: TileColor = TileColor.DEFAULT) {
@ -313,7 +313,7 @@ class DungeonWorld(
setBackgroundMaterial: Boolean = true,
setBackgroundModifier: Boolean = true,
) {
val pos = geometry.wrap(Vector2i(x, y))
val pos = geometry.wrap(x, y)
if (foreground != null) {
if (setForegroundMaterial)
@ -336,7 +336,7 @@ class DungeonWorld(
}
fun clearTile(x: Int, y: Int) {
val pos = geometry.wrap(Vector2i(x, y))
val pos = geometry.wrap(x, y)
this.foregroundMaterial[pos] = emptyMaterial
this.foregroundModifier[pos] = emptyModifier
@ -348,24 +348,24 @@ class DungeonWorld(
}
fun needsForegroundBiomeMod(x: Int, y: Int): Boolean {
val pos = geometry.wrap(Vector2i(x, y))
val pos = geometry.wrap(x, y)
val material = foregroundMaterial[pos] ?: return false
if (material.material !in BuiltinMetaMaterials.BIOME_META_MATERIALS)
return false
val above = geometry.wrap(Vector2i(x, y + 1))
val above = geometry.wrap(x, y + 1)
return foregroundMaterial[above]?.material?.isNotEmptyTile == false
}
fun needsBackgroundBiomeMod(x: Int, y: Int): Boolean {
val pos = geometry.wrap(Vector2i(x, y))
val pos = geometry.wrap(x, y)
val material = backgroundMaterial[pos] ?: return false
if (material.material !in BuiltinMetaMaterials.BIOME_META_MATERIALS)
return false
val above = geometry.wrap(Vector2i(x, y + 1))
val above = geometry.wrap(x, y + 1)
return backgroundMaterial[above]?.material?.isNotEmptyTile == false
}

View File

@ -1,11 +1,12 @@
package ru.dbotthepony.kstarbound.defs.`object`
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.world.entities.HumanoidActorEntity
// int32_t
enum class LoungeOrientation(override val jsonName: String) : IStringSerializable {
NONE("none"),
SIT("sit"),
LAY("lay"),
STAND("stand");
enum class LoungeOrientation(override val jsonName: String, val humanoidState: HumanoidActorEntity.HumanoidState) : IStringSerializable {
NONE("none", HumanoidActorEntity.HumanoidState.IDLE),
SIT("sit", HumanoidActorEntity.HumanoidState.SIT),
LAY("lay", HumanoidActorEntity.HumanoidState.LAY),
STAND("stand", HumanoidActorEntity.HumanoidState.IDLE);
}

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.kstarbound.json
import com.github.luben.zstd.ZstdInputStreamNoFinalizer
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonNull
@ -29,31 +30,55 @@ import java.util.zip.DeflaterOutputStream
import java.util.zip.Inflater
import java.util.zip.InflaterInputStream
private fun <T> ByteArray.callRead(inflate: Boolean, callable: DataInputStream.() -> T): T {
private enum class InflateType {
ZLIB,
ZSTD,
NONE
}
private fun <T> ByteArray.callRead(inflate: InflateType, callable: DataInputStream.() -> T): T {
val stream = FastByteArrayInputStream(this)
if (inflate) {
val inflater = Inflater()
when (inflate) {
InflateType.ZLIB -> {
val inflater = Inflater()
try {
val data = DataInputStream(BufferedInputStream(InflaterInputStream(stream, inflater, 0x4000), 0x10000))
val t = callable(data)
return t
} finally {
inflater.end()
try {
val data = DataInputStream(BufferedInputStream(InflaterInputStream(stream, inflater, 0x4000), 0x10000))
val t = callable(data)
return t
} finally {
inflater.end()
}
}
InflateType.ZSTD -> {
val data = DataInputStream(BufferedInputStream(ZstdInputStreamNoFinalizer(stream), 0x10000))
try {
return callable(data)
} finally {
data.close()
}
}
InflateType.NONE -> {
return callable(DataInputStream(stream))
}
} else {
return callable(DataInputStream(stream))
}
}
fun ByteArray.readJsonElement(): JsonElement = callRead(false) { readJsonElement() }
fun ByteArray.readJsonObject(): JsonObject = callRead(false) { readJsonObject() }
fun ByteArray.readJsonArray(): JsonArray = callRead(false) { readJsonArray() }
fun ByteArray.readJsonElement(): JsonElement = callRead(InflateType.NONE) { readJsonElement() }
fun ByteArray.readJsonObject(): JsonObject = callRead(InflateType.NONE) { readJsonObject() }
fun ByteArray.readJsonArray(): JsonArray = callRead(InflateType.NONE) { readJsonArray() }
fun ByteArray.readJsonElementInflated(): JsonElement = callRead(true) { readJsonElement() }
fun ByteArray.readJsonObjectInflated(): JsonObject = callRead(true) { readJsonObject() }
fun ByteArray.readJsonArrayInflated(): JsonArray = callRead(true) { readJsonArray() }
fun ByteArray.readJsonElementInflated(): JsonElement = callRead(InflateType.ZLIB) { readJsonElement() }
fun ByteArray.readJsonObjectInflated(): JsonObject = callRead(InflateType.ZLIB) { readJsonObject() }
fun ByteArray.readJsonArrayInflated(): JsonArray = callRead(InflateType.ZLIB) { readJsonArray() }
fun ByteArray.readJsonElementZstd(): JsonElement = callRead(InflateType.ZSTD) { readJsonElement() }
fun ByteArray.readJsonObjectZstd(): JsonObject = callRead(InflateType.ZSTD) { readJsonObject() }
fun ByteArray.readJsonArrayZstd(): JsonArray = callRead(InflateType.ZSTD) { readJsonArray() }
/**
* Позволяет читать двоичный JSON прямиком в [JsonElement]

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.kstarbound.json
import com.github.luben.zstd.ZstdOutputStreamNoFinalizer
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonNull
@ -14,31 +15,51 @@ import java.io.DataOutputStream
import java.util.zip.Deflater
import java.util.zip.DeflaterOutputStream
private fun <T> T.callWrite(deflate: Boolean, callable: DataOutputStream.(T) -> Unit): ByteArray {
private enum class DeflateType {
ZLIB,
ZSTD,
NONE
}
private fun <T> T.callWrite(deflate: DeflateType, zstdCompressionLevel: Int = 6, callable: DataOutputStream.(T) -> Unit): ByteArray {
val stream = FastByteArrayOutputStream()
if (deflate) {
val deflater = Deflater()
val data = DataOutputStream(BufferedOutputStream(DeflaterOutputStream(stream, deflater, 0x4000), 0x10000))
when (deflate) {
DeflateType.ZLIB -> {
val deflater = Deflater()
val data = DataOutputStream(BufferedOutputStream(DeflaterOutputStream(stream, deflater, 0x4000), 0x10000))
data.use {
callable(data, this)
data.use {
callable(data, this)
}
}
} else {
callable(DataOutputStream(stream), this)
DeflateType.ZSTD -> {
val s = ZstdOutputStreamNoFinalizer(stream)
s.setLevel(zstdCompressionLevel)
DataOutputStream(BufferedOutputStream(s, 0x10000)).use {
callable(it, this)
}
}
DeflateType.NONE -> callable(DataOutputStream(stream), this)
}
return stream.array.copyOf(stream.length)
}
fun JsonElement.writeJsonElement(): ByteArray = callWrite(false) { writeJsonElement(it) }
fun JsonObject.writeJsonObject(): ByteArray = callWrite(false) { writeJsonObject(it) }
fun JsonArray.writeJsonArray(): ByteArray = callWrite(false) { writeJsonArray(it) }
fun JsonElement.writeJsonElement(): ByteArray = callWrite(DeflateType.NONE) { writeJsonElement(it) }
fun JsonObject.writeJsonObject(): ByteArray = callWrite(DeflateType.NONE) { writeJsonObject(it) }
fun JsonArray.writeJsonArray(): ByteArray = callWrite(DeflateType.NONE) { writeJsonArray(it) }
fun JsonElement.writeJsonElementDeflated(): ByteArray = callWrite(true) { writeJsonElement(it) }
fun JsonObject.writeJsonObjectDeflated(): ByteArray = callWrite(true) { writeJsonObject(it) }
fun JsonArray.writeJsonArrayDeflated(): ByteArray = callWrite(true) { writeJsonArray(it) }
fun JsonElement.writeJsonElementDeflated(): ByteArray = callWrite(DeflateType.ZLIB) { writeJsonElement(it) }
fun JsonObject.writeJsonObjectDeflated(): ByteArray = callWrite(DeflateType.ZLIB) { writeJsonObject(it) }
fun JsonArray.writeJsonArrayDeflated(): ByteArray = callWrite(DeflateType.ZLIB) { writeJsonArray(it) }
fun JsonElement.writeJsonElementZstd(level: Int = 6): ByteArray = callWrite(DeflateType.ZSTD, zstdCompressionLevel = level) { writeJsonElement(it) }
fun JsonObject.writeJsonObjectZstd(level: Int = 6): ByteArray = callWrite(DeflateType.ZSTD, zstdCompressionLevel = level) { writeJsonObject(it) }
fun JsonArray.writeJsonArrayZstd(level: Int = 6): ByteArray = callWrite(DeflateType.ZSTD, zstdCompressionLevel = level) { writeJsonArray(it) }
fun DataOutputStream.writeJsonElement(value: JsonElement) {
when (value) {

View File

@ -102,7 +102,7 @@ fun provideNPCBindings(self: NPCEntity, lua: LuaEnvironment) {
val anchorIndex = oAnchorIndex?.toInt() ?: 0
val entity = self.world.entities[loungeable.toInt()] as? LoungeableEntity
if (entity == null || anchorIndex !in 0 until entity.sitPositions.size || entity.entitiesLoungingIn(anchorIndex).isNotEmpty()) {
if (entity == null || anchorIndex !in 0 until entity.anchors.size || entity.entitiesLoungingIn(anchorIndex).isNotEmpty()) {
returnBuffer.setTo(false)
} else {
self.movement.anchorNetworkState = AnchorNetworkState(loungeable.toInt(), anchorIndex)

View File

@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.lua.bindings
import com.google.gson.JsonObject
import it.unimi.dsi.fastutil.ints.IntArrayList
import it.unimi.dsi.fastutil.objects.ObjectArrayList
import kotlinx.coroutines.runBlocking
import org.apache.logging.log4j.LogManager
import org.classdump.luna.ByteString
import org.classdump.luna.LuaRuntimeException
@ -11,17 +12,22 @@ import org.classdump.luna.runtime.ExecutionContext
import org.classdump.luna.runtime.LuaFunction
import ru.dbotthepony.kommons.collect.map
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.Starbound
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
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.monster.MonsterVariant
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile
import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldParameters
import ru.dbotthepony.kstarbound.fromJsonFast
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.json.mergeJson
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
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.entities.AbstractEntity
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.StagehandEntity
import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
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)
returnBuffer.setTo()
} else {
val create = ItemDropEntity(descriptor)
val create = ItemDropEntity(descriptor, lua.random)
create.movement.velocity = initialVelocity
if (intangibleTime is Number) {
@ -453,8 +462,7 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
val items = Registries.treasurePools
.getOrThrow(pool.decode())
.value
// not using lua.random because we are, well, world's bindings
.evaluate(if (seed != null) random(seed.toLong()) else self.random, level.toDouble())
.evaluate(if (seed != null) random(seed.toLong()) else lua.random, level.toDouble())
val pos = toVector2d(position)
@ -465,18 +473,79 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
entities.add(entity.entityID)
}
} 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()))
}
callbacks["spawnMonster"] = luaFunction {
// TODO
returnBuffer.setTo(0L)
callbacks["spawnMonster"] = luaFunction { type: ByteString, position: Table, overrides: Any? ->
try {
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["spawnVehicle"] = luaStub("spawnVehicle")

View File

@ -72,7 +72,7 @@ private val centerStr = ByteString.of("center")
private val boundModeStr = ByteString.of("boundMode")
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 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()) {
null -> {} // do nothing
"random" -> entitites.shuffle(self.random)
"random" -> entitites.shuffle(lua.random)
"nearest" -> {
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())
}
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()
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])
}
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()
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? ->
intermediateQueryFunction(self, pos1, pos2OrRadius, options, Predicate { it is T })
private inline fun <reified T : AbstractEntity> createQueryFunction(self: World<*, *>, lua: LuaEnvironment) = luaFunction { pos1: Table, pos2OrRadius: Any, options: Table? ->
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? ->
intermediateLineQueryFunction(self, pos1, pos2, options, Predicate { it is T })
private inline fun <reified T : AbstractEntity> createLineQueryFunction(self: World<*, *>, lua: LuaEnvironment) = luaFunction { pos1: Table, pos2: Table, options: Table? ->
intermediateLineQueryFunction(self, lua, pos1, pos2, options, Predicate { it is T })
}
fun provideWorldEntitiesBindings(self: World<*, *>, callbacks: Table, lua: LuaEnvironment) {
callbacks["entityQuery"] = createQueryFunction<AbstractEntity>(self)
callbacks["monsterQuery"] = createQueryFunction<AbstractEntity>(self) // TODO
callbacks["npcQuery"] = createQueryFunction<AbstractEntity>(self) // TODO
callbacks["itemDropQuery"] = createQueryFunction<ItemDropEntity>(self)
callbacks["playerQuery"] = createQueryFunction<PlayerEntity>(self)
callbacks["entityQuery"] = createQueryFunction<AbstractEntity>(self, lua)
callbacks["monsterQuery"] = createQueryFunction<AbstractEntity>(self, lua) // TODO
callbacks["npcQuery"] = createQueryFunction<AbstractEntity>(self, lua) // TODO
callbacks["itemDropQuery"] = createQueryFunction<ItemDropEntity>(self, lua)
callbacks["playerQuery"] = createQueryFunction<PlayerEntity>(self, lua)
callbacks["entityLineQuery"] = createLineQueryFunction<AbstractEntity>(self)
callbacks["monsterLineQuery"] = createLineQueryFunction<AbstractEntity>(self) // TODO
callbacks["npcLineQuery"] = createLineQueryFunction<AbstractEntity>(self) // TODO
callbacks["itemDropLineQuery"] = createLineQueryFunction<ItemDropEntity>(self)
callbacks["playerLineQuery"] = createLineQueryFunction<PlayerEntity>(self)
callbacks["entityLineQuery"] = createLineQueryFunction<AbstractEntity>(self, lua)
callbacks["monsterLineQuery"] = createLineQueryFunction<AbstractEntity>(self, lua) // TODO
callbacks["npcLineQuery"] = createLineQueryFunction<AbstractEntity>(self, lua) // TODO
callbacks["itemDropLineQuery"] = createLineQueryFunction<ItemDropEntity>(self, lua)
callbacks["playerLineQuery"] = createLineQueryFunction<PlayerEntity>(self, lua)
callbacks["objectQuery"] = luaFunction { pos1: Table, pos2OrRadius: Any, options: Table? ->
var objectName: String? = null
@ -260,7 +260,7 @@ fun provideWorldEntitiesBindings(self: World<*, *>, callbacks: Table, lua: LuaEn
if (options != null)
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)
})
}
@ -271,7 +271,7 @@ fun provideWorldEntitiesBindings(self: World<*, *>, callbacks: Table, lua: LuaEn
if (options != null)
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)
})
}
@ -287,7 +287,7 @@ fun provideWorldEntitiesBindings(self: World<*, *>, callbacks: Table, lua: LuaEn
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)
})
}
@ -303,7 +303,7 @@ fun provideWorldEntitiesBindings(self: World<*, *>, callbacks: Table, lua: LuaEn
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)
})
}

View File

@ -121,8 +121,8 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType) :
open fun bind(channel: Channel) {
scope = CoroutineScope(channel.eventLoop().asCoroutineDispatcher() + SupervisorJob() + CoroutineExceptionHandler { coroutineContext, throwable ->
disconnect("Uncaught exception in one of connection' coroutines: $throwable")
LOGGER.fatal("Uncaught exception in one of $this coroutines", throwable)
disconnect("Uncaught exception in one of connection' coroutines: $throwable")
})
channel.config().setOption(ChannelOption.TCP_NODELAY, true)

View File

@ -28,7 +28,7 @@ class EntityInteractPacket(val request: InteractRequest, val id: UUID) : IServer
override suspend fun play(connection: ServerConnection) {
if (request.target >= 0) {
connection.enqueue {
connection.enqueueAndSuspend {
connection.send(EntityInteractResultPacket((entities[request.target] as? InteractiveEntity)?.interact(request) ?: InteractAction.NONE, id, request.source))
}
} else {

View File

@ -742,11 +742,11 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
val startingLocation = findStartingSystem() ?: return
scope.launch { shipFlightEventLoop(startingLocation.location, SystemWorldLocation.Celestial(startingLocation)) }
} else {
/*if (context.returnWarp != null) {
if (context.returnWarp != null) {
enqueueWarp(context.returnWarp, ifFailed = WarpAlias.OwnShip)
} else {
enqueueWarp(WarpAlias.OwnShip)
}*/
}
enqueueWarp(WarpAction.World(WorldID.Instance("outpost")))

View File

@ -25,10 +25,14 @@ import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
import ru.dbotthepony.kstarbound.fromJson
import ru.dbotthepony.kstarbound.json.readJsonElement
import ru.dbotthepony.kstarbound.json.readJsonElementInflated
import ru.dbotthepony.kstarbound.json.readJsonElementZstd
import ru.dbotthepony.kstarbound.json.readJsonObject
import ru.dbotthepony.kstarbound.json.readJsonObjectZstd
import ru.dbotthepony.kstarbound.json.writeJsonElement
import ru.dbotthepony.kstarbound.json.writeJsonElementDeflated
import ru.dbotthepony.kstarbound.json.writeJsonElementZstd
import ru.dbotthepony.kstarbound.json.writeJsonObject
import ru.dbotthepony.kstarbound.json.writeJsonObjectZstd
import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.server.world.LegacyWorldStorage
import ru.dbotthepony.kstarbound.server.world.NativeLocalWorldStorage
@ -159,7 +163,7 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
lookupClientContext.executeQuery().use {
if (it.next()) {
it.getBytes(1).readJsonObject()
it.getBytes(1).readJsonObjectZstd()
} else {
null
}
@ -168,9 +172,10 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
}
fun writeClientContext(uuid: UUID, context: JsonObject): CompletableFuture<*> {
val compressed = context.writeJsonObjectZstd()
return supplyAsync {
writeClientContext.setString(1, uuid.toStarboundString())
writeClientContext.setBytes(2, context.writeJsonObject())
writeClientContext.setBytes(2, compressed)
writeClientContext.execute()
}
}
@ -241,7 +246,7 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
lookupSystemWorld.executeQuery().use {
if (it.next()) {
it.getBytes(1).readJsonElementInflated()
it.getBytes(1).readJsonElementZstd()
} else {
null
}
@ -250,11 +255,13 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
}
fun writeServerWorldData(pos: Vector3i, data: JsonElement) {
val compressed = data.writeJsonElementZstd()
execute {
writeSystemWorld.setInt(1, pos.x)
writeSystemWorld.setInt(2, pos.y)
writeSystemWorld.setInt(3, pos.z)
writeSystemWorld.setBytes(4, data.writeJsonElementDeflated())
writeSystemWorld.setBytes(4, compressed)
writeSystemWorld.execute()
}

View File

@ -1,5 +1,7 @@
package ru.dbotthepony.kstarbound.server.world
import com.github.luben.zstd.ZstdInputStreamNoFinalizer
import com.github.luben.zstd.ZstdOutputStreamNoFinalizer
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
@ -19,9 +21,11 @@ import ru.dbotthepony.kstarbound.defs.EntityType
import ru.dbotthepony.kstarbound.io.SQLSavepoint
import ru.dbotthepony.kstarbound.json.VersionedJson
import ru.dbotthepony.kstarbound.json.readJsonArrayInflated
import ru.dbotthepony.kstarbound.json.readJsonArrayZstd
import ru.dbotthepony.kstarbound.json.readJsonElement
import ru.dbotthepony.kstarbound.json.readJsonObjectInflated
import ru.dbotthepony.kstarbound.json.writeJsonArrayDeflated
import ru.dbotthepony.kstarbound.json.writeJsonArrayZstd
import ru.dbotthepony.kstarbound.json.writeJsonElement
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.math.vector.Vector2i
@ -140,8 +144,7 @@ class NativeLocalWorldStorage(file: File?) : WorldStorage() {
val version = it.getInt(2)
val data = it.getBytes(3)
val inflater = Inflater()
val stream = DataInputStream(BufferedInputStream(InflaterInputStream(FastByteArrayInputStream(data), inflater, 0x10000), 0x40000))
val stream = DataInputStream(BufferedInputStream(ZstdInputStreamNoFinalizer(FastByteArrayInputStream(data)), 0x40000))
try {
val palette = PaletteSet()
@ -159,7 +162,7 @@ class NativeLocalWorldStorage(file: File?) : WorldStorage() {
return@supplyAsync ChunkCells(array as Object2DArray<out AbstractCell>, stage)
} finally {
inflater.end()
stream.close()
}
}
}
@ -178,7 +181,7 @@ class NativeLocalWorldStorage(file: File?) : WorldStorage() {
readEntities.executeQuery().use {
if (it.next()) {
val data = it.getBytes(1).readJsonArrayInflated()
val data = it.getBytes(1).readJsonArrayZstd()
for (entry in data) {
val versioned = VersionedJson.fromJson(entry)
@ -228,16 +231,19 @@ class NativeLocalWorldStorage(file: File?) : WorldStorage() {
return executor.supplyAsync {
val (version, metadata) = readMetadata("metadata") ?: return@supplyAsync null
val stream = DataInputStream(BufferedInputStream(InflaterInputStream(FastByteArrayInputStream(metadata))))
val stream = DataInputStream(BufferedInputStream(ZstdInputStreamNoFinalizer(FastByteArrayInputStream(metadata))))
val width = stream.readInt()
val height = stream.readInt()
val loopX = stream.readBoolean()
val loopY = stream.readBoolean()
val json = VersionedJson("WorldMetadata", version, stream.readJsonElement())
try {
val width = stream.readInt()
val height = stream.readInt()
val loopX = stream.readBoolean()
val loopY = stream.readBoolean()
val json = VersionedJson("WorldMetadata", version, stream.readJsonElement())
stream.close()
Metadata(WorldGeometry(Vector2i(width, height), loopX, loopY), json)
Metadata(WorldGeometry(Vector2i(width, height), loopX, loopY), json)
} finally {
stream.close()
}
}
}
@ -286,7 +292,7 @@ class NativeLocalWorldStorage(file: File?) : WorldStorage() {
writeEntities.setInt(1, pos.x)
writeEntities.setInt(2, pos.y)
writeEntities.setBytes(3, storeData.writeJsonArrayDeflated())
writeEntities.setBytes(3, storeData.writeJsonArrayZstd())
writeEntities.execute()
}
}
@ -380,9 +386,10 @@ class NativeLocalWorldStorage(file: File?) : WorldStorage() {
}
}
val deflater = Deflater()
val buff = FastByteArrayOutputStream()
val stream = DataOutputStream(BufferedOutputStream(DeflaterOutputStream(buff, deflater, 0x10000), 0x40000))
val z = ZstdOutputStreamNoFinalizer(buff)
z.setLevel(6)
val stream = DataOutputStream(BufferedOutputStream(z, 0x40000))
try {
palette.write(stream)
@ -405,7 +412,7 @@ class NativeLocalWorldStorage(file: File?) : WorldStorage() {
writeCells.setBytes(5, buff.array.copyOf(buff.length))
writeCells.execute()
} finally {
deflater.end()
z.close()
}
}
}
@ -413,16 +420,23 @@ class NativeLocalWorldStorage(file: File?) : WorldStorage() {
override fun saveMetadata(data: Metadata) {
executor.execute {
val buff = FastByteArrayOutputStream()
val stream = DataOutputStream(BufferedOutputStream(DeflaterOutputStream(buff, Deflater(), 0x10000), 0x40000))
val z = ZstdOutputStreamNoFinalizer(buff)
z.setLevel(6)
stream.writeInt(data.geometry.size.x)
stream.writeInt(data.geometry.size.y)
stream.writeBoolean(data.geometry.loopX)
stream.writeBoolean(data.geometry.loopY)
stream.writeJsonElement(data.data.content)
try {
val stream = DataOutputStream(BufferedOutputStream(z, 0x40000))
stream.close()
writeMetadata("metadata", data.data.version ?: 0, buff.array.copyOf(buff.length))
stream.writeInt(data.geometry.size.x)
stream.writeInt(data.geometry.size.y)
stream.writeBoolean(data.geometry.loopX)
stream.writeBoolean(data.geometry.loopY)
stream.writeJsonElement(data.data.content)
stream.close()
writeMetadata("metadata", data.data.version ?: 0, buff.array.copyOf(buff.length))
} finally {
z.close()
}
}
}

View File

@ -8,19 +8,15 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.future.await
import ru.dbotthepony.kommons.gson.JsonArrayCollector
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kstarbound.math.AABBi
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.Globals
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.world.CelestialBaseInformation
@ -30,11 +26,13 @@ import ru.dbotthepony.kstarbound.fromJson
import ru.dbotthepony.kstarbound.io.BTreeDB5
import ru.dbotthepony.kstarbound.json.jsonArrayOf
import ru.dbotthepony.kstarbound.json.mergeJson
import ru.dbotthepony.kstarbound.json.readJsonArrayInflated
import ru.dbotthepony.kstarbound.json.readJsonElementInflated
import ru.dbotthepony.kstarbound.json.writeJsonArrayDeflated
import ru.dbotthepony.kstarbound.json.writeJsonElementDeflated
import ru.dbotthepony.kstarbound.json.readJsonArrayZstd
import ru.dbotthepony.kstarbound.json.readJsonElementZstd
import ru.dbotthepony.kstarbound.json.writeJsonArrayZstd
import ru.dbotthepony.kstarbound.json.writeJsonElementZstd
import ru.dbotthepony.kstarbound.math.AABBi
import ru.dbotthepony.kstarbound.math.Line2d
import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.math.vector.Vector3i
import ru.dbotthepony.kstarbound.util.CarriedExecutor
import ru.dbotthepony.kstarbound.util.ScheduledCoroutineExecutor
@ -57,11 +55,9 @@ import java.sql.ResultSet
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
import java.util.function.Consumer
import java.util.function.Function
import java.util.function.Supplier
import java.util.random.RandomGenerator
import kotlin.collections.ArrayList
class ServerUniverse(folder: File? = null) : Universe(), Closeable {
override val baseInformation: CelestialBaseInformation
@ -148,8 +144,8 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
private data class Chunk(val x: Int, val y: Int, val systems: Set<Vector3i>, val constellations: Set<Pair<Vector2i, Vector2i>>) {
constructor(x: Int, y: Int, data: ResultSet) : this(
x, y,
data.getBytes(1).readJsonArrayInflated().map { Vector3i(it.asJsonArray[0].asInt, it.asJsonArray[1].asInt, it.asJsonArray[2].asInt) }.toSet(),
data.getBytes(2).readJsonArrayInflated().map {
data.getBytes(1).readJsonArrayZstd().map { Vector3i(it.asJsonArray[0].asInt, it.asJsonArray[1].asInt, it.asJsonArray[2].asInt) }.toSet(),
data.getBytes(2).readJsonArrayZstd().map {
val a = it.asJsonArray[0].asJsonArray
val b = it.asJsonArray[1].asJsonArray
Vector2i(a[0].asInt, a[1].asInt) to Vector2i(b[0].asInt, b[1].asInt)
@ -162,10 +158,10 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
systems.stream()
.map { jsonArrayOf(it.x, it.y, it.z) }
.collect(JsonArrayCollector)
.writeJsonArrayDeflated(),
.writeJsonArrayZstd(4),
constellations.stream().map {
jsonArrayOf(jsonArrayOf(it.first.x, it.first.y), jsonArrayOf(it.second.x, it.second.y))
}.collect(JsonArrayCollector).writeJsonArrayDeflated()
}.collect(JsonArrayCollector).writeJsonArrayZstd(4)
)
}
@ -198,10 +194,10 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
fun serialize(): SerializedSystem {
return SerializedSystem(
x, y, z,
Starbound.gson.toJsonTree(parameters).writeJsonElementDeflated(),
Starbound.gson.toJsonTree(parameters).writeJsonElementZstd(8),
planets.entries.stream()
.map { jsonArrayOf(it.key.first, it.key.second, it.value) }
.collect(JsonArrayCollector).writeJsonArrayDeflated()
.collect(JsonArrayCollector).writeJsonArrayZstd(8)
)
}
@ -270,9 +266,9 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
// deserialize in off-thread since it involves big json structures
Starbound.EXECUTOR.supplyAsync {
val parameters: CelestialParameters = Starbound.gson.fromJson(parametersBytes.readJsonElementInflated())!!
val parameters: CelestialParameters = Starbound.gson.fromJson(parametersBytes.readJsonElementZstd())!!
val planets: Map<Pair<Int, Int>, CelestialParameters> = planetsBytes.readJsonArrayInflated().associate {
val planets: Map<Pair<Int, Int>, CelestialParameters> = planetsBytes.readJsonArrayZstd().associate {
it as JsonArray
(it[0].asInt to it[1].asInt) to Starbound.gson.fromJson(it[2])!!
}

View File

@ -474,9 +474,9 @@ class ServerWorld private constructor(
if (updated) {
if (breathable != null) {
LOGGER.info("Dungeon ID $id breathable set to: $breathable")
LOGGER.info("Dungeon ID $id 'breathable' set to: $breathable")
} else {
LOGGER.info("Dungeon ID $id no longer has special breathable flag")
LOGGER.info("Dungeon ID $id no longer has special 'breathable' flag")
}
broadcast(UpdateDungeonBreathablePacket(id, breathable))
@ -716,7 +716,7 @@ class ServerWorld private constructor(
var currentDungeonID = 0
// primary dungeons must be generated sequentially since they can get very large
// and to avoid dungeons colliding with each other, we must generate them first, one by one
// and to avoid dungeons colliding with each other, we must generate them one after another
for (dungeon in template.gatherDungeons()) {
var spawnDungeonRetries = Globals.worldServer.spawnDungeonRetries
@ -751,18 +751,12 @@ class ServerWorld private constructor(
protectedDungeonIDsInternal.add(currentDungeonID)
}
if (dungeon.dungeon.value.metadata.gravity != null) {
// TODO: set gravity here
}
if (dungeon.dungeon.value.metadata.breathable != null) {
// TODO: set "breathable" here
}
dungeon.dungeon.value.metadata.gravity?.map({ setDungeonGravity(currentDungeonID, it) }, { setDungeonGravity(currentDungeonID, it) })
setDungeonBreathable(currentDungeonID, dungeon.dungeon.value.metadata.breathable)
currentDungeonID++
break
}
} while (--spawnDungeonRetries > 0)
} while (spawnDungeonRetries-- > 0)
}
}

View File

@ -670,6 +670,8 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
ticks++
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) {
entityList.forEach { it.tickParallel(delta) }
} else {

View File

@ -40,10 +40,40 @@ data class WorldGeometry(val size: Vector2i, val loopX: Boolean = true, val loop
return Vector2i(x.cell(pos.component1()), y.cell(pos.component2()))
}
fun wrap(pos: Vector2i): Vector2i {
val x = this.x.cell(pos.x)
val y = this.y.cell(pos.y)
// avoid allocating heap garbage
if (x == pos.x && y == pos.y)
return pos
return Vector2i(x, y)
}
fun wrap(x: Int, y: Int): Vector2i {
return Vector2i(this.x.cell(x), this.y.cell(y))
}
fun wrap(pos: IStruct2d): Vector2d {
return Vector2d(x.cell(pos.component1()), y.cell(pos.component2()))
}
fun wrap(pos: Vector2d): Vector2d {
val x = this.x.cell(pos.x)
val y = this.y.cell(pos.y)
// avoid allocating heap garbage
if (x == pos.x && y == pos.y)
return pos
return Vector2d(x, y)
}
fun wrap(x: Double, y: Double): Vector2d {
return Vector2d(this.x.cell(x), this.y.cell(y))
}
fun chunkFromCell(pos: IStruct2i): ChunkPos {
return ChunkPos(x.chunkFromCell(pos.component1()), y.chunkFromCell(pos.component2()))
}

View File

@ -69,7 +69,7 @@ class ActorMovementController() : MovementController() {
if (networkState != null) {
val entity = world.entities[networkState.entityID] as? LoungeableEntity ?: throw InvalidArgumentException("${networkState.entityID} is not a valid entity or not a loungeable one")
state = entity.anchor(networkState.positionIndex) ?: throw IllegalArgumentException("Anchor with index ${networkState.positionIndex} of $entity is either invalid or disabled")
state = entity.anchors.getOrNull(networkState.positionIndex) ?: throw IllegalArgumentException("Anchor with index ${networkState.positionIndex} of $entity is either invalid or disabled")
}
if (state == null && this.anchorState?.exitBottomPosition != null) {
@ -344,7 +344,7 @@ class ActorMovementController() : MovementController() {
if (anchorNetworkState == null)
this.anchorState = null
else
this.anchorState = (world.entities[anchorNetworkState.entityID] as? LoungeableEntity)?.anchor(anchorNetworkState.positionIndex)
this.anchorState = (world.entities[anchorNetworkState.entityID] as? LoungeableEntity)?.anchors?.getOrNull(anchorNetworkState.positionIndex)
super.tickRemote(delta)
}
@ -355,7 +355,7 @@ class ActorMovementController() : MovementController() {
val anchorNetworkState = anchorNetworkState
if (anchorNetworkState != null) {
state = (world.entities[anchorNetworkState.entityID] as? LoungeableEntity)?.anchor(anchorNetworkState.positionIndex)
state = (world.entities[anchorNetworkState.entityID] as? LoungeableEntity)?.anchors?.getOrNull(anchorNetworkState.positionIndex)
}
if (state == null) {

View File

@ -1,9 +1,9 @@
package ru.dbotthepony.kstarbound.world.entities
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import ru.dbotthepony.kstarbound.client.render.RenderLayer
import ru.dbotthepony.kstarbound.defs.actor.PersistentStatusEffect
import ru.dbotthepony.kstarbound.defs.`object`.LoungeOrientation
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.world.Direction
@ -21,23 +21,15 @@ data class AnchorState(
override val angle: Double
) : IAnchorState
// int32_t
enum class LoungeOrientation(val humanoidState: HumanoidActorEntity.HumanoidState) {
NONE(HumanoidActorEntity.HumanoidState.IDLE),
SIT(HumanoidActorEntity.HumanoidState.SIT),
LAY(HumanoidActorEntity.HumanoidState.LAY),
STAND(HumanoidActorEntity.HumanoidState.IDLE);
}
data class LoungeAnchorState(
override val position: Vector2d,
override val exitBottomPosition: Vector2d?,
override val direction: Direction,
override val angle: Double,
val orientation: LoungeOrientation = LoungeOrientation.NONE,
val loungeRenderLayer: RenderLayer,
val loungeRenderLayer: RenderLayer.Point,
val controllable: Boolean = false,
val statusEffects: List<PersistentStatusEffect> = listOf(),
val statusEffects: Set<PersistentStatusEffect> = setOf(),
val effectEmitters: Set<String> = setOf(),
val emote: String? = null,
val dance: String? = null,

View File

@ -30,6 +30,7 @@ import ru.dbotthepony.kstarbound.world.physics.Poly
import java.io.DataInputStream
import java.io.DataOutputStream
import java.util.function.Predicate
import java.util.random.RandomGenerator
import kotlin.math.min
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))))))
}
constructor(item: ItemDescriptor) : this() {
this.item = item.build()
constructor(item: ItemDescriptor, random: RandomGenerator? = null) : this() {
this.item = item.build(random = random)
this.owningEntity = 0
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) {
// TODO
}
// ----- Persistent status effects

View File

@ -1,19 +1,18 @@
package ru.dbotthepony.kstarbound.world.entities.api
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.world.entities.ActorEntity
import ru.dbotthepony.kstarbound.world.entities.IAnchorState
interface LoungeableEntity {
val sitPositions: List<Vector2d>
val anchors: List<IAnchorState>
/**
* Determines entities currently lounging in this entity
*/
fun entitiesLoungingIn(): List<ActorEntity>
/**
* Determines entities currently lounging at specified anchor index
*/
fun entitiesLoungingIn(index: Int): List<ActorEntity>
/**
* Returns anchor information with given index, or null if index is invalid
*/
fun anchor(index: Int): IAnchorState?
}

View File

@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.world.entities.tile
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableSet
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.google.gson.reflect.TypeToken
@ -11,6 +12,7 @@ import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.render.RenderLayer
import ru.dbotthepony.kstarbound.defs.InteractAction
import ru.dbotthepony.kstarbound.defs.InteractRequest
import ru.dbotthepony.kstarbound.defs.actor.PersistentStatusEffect
@ -23,6 +25,7 @@ import ru.dbotthepony.kstarbound.world.Direction
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNIT
import ru.dbotthepony.kstarbound.world.entities.ActorEntity
import ru.dbotthepony.kstarbound.world.entities.IAnchorState
import ru.dbotthepony.kstarbound.world.entities.LoungeAnchorState
import ru.dbotthepony.kstarbound.world.entities.api.LoungeableEntity
import java.lang.Math.toRadians
@ -31,67 +34,102 @@ class LoungeableObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(c
isInteractive = true
}
override val sitPositions = ArrayList<Vector2d>()
override val anchors = ArrayList<LoungeAnchorState>()
var sitFlipDirection = false
private set
var sitOrientation = LoungeOrientation.NONE
private set
var sitAngle = 0.0
private set
var sitCoverImage = ""
private set
var sitFlipImages = false
private set
val sitStatusEffects = ObjectArraySet<PersistentStatusEffect>()
val sitEffectEmitters = ObjectArraySet<String>()
var sitEmote: String? = null
private set
var sitDance: String? = null
private set
var sitArmorCosmeticOverrides: JsonObject = JsonObject()
private set
var sitCursorOverride: String? = null
var sitOrientation: LoungeOrientation = LoungeOrientation.NONE
private set
private fun updateSitParams() {
orientation ?: return
anchors.clear()
val orientation = orientation ?: return
val sitPositionsList = ArrayList<Vector2d>()
val sitDir = orientation.directionAffinity ?: Direction.LEFT
sitPositions.clear()
val sitPosition = lookupProperty("sitPosition")
val sitPositions = lookupProperty("sitPositions")
if (!sitPosition.isJsonNull) {
this.sitPositions.add(vectors.fromJsonTree(sitPosition) / PIXELS_IN_STARBOUND_UNIT)
sitPositionsList.add(vectors.fromJsonTree(sitPosition) / PIXELS_IN_STARBOUND_UNIT * sitDir.normal)
} else if (!sitPositions.isJsonNull) {
vectorsList.fromJsonTree(sitPositions).forEach { this.sitPositions.add(it / PIXELS_IN_STARBOUND_UNIT) }
vectorsList.fromJsonTree(sitPositions).forEach { sitPositionsList.add(it / PIXELS_IN_STARBOUND_UNIT * sitDir.normal) }
}
sitFlipDirection = lookupProperty("sitFlipDirection") { JsonPrimitive(false) }.asBoolean
val sitFlipDirection = lookupProperty("sitFlipDirection") { JsonPrimitive(false) }.asBoolean
sitOrientation = LoungeOrientation.entries.valueOf(lookupProperty("sitOrientation") { JsonPrimitive("sit") }.asString)
sitAngle = toRadians(lookupProperty("sitAngle") { JsonPrimitive(0.0) }.asDouble)
var sitAngle = toRadians(lookupProperty("sitAngle") { JsonPrimitive(0.0) }.asDouble)
sitCoverImage = lookupProperty("sitCoverImage") { JsonPrimitive("") }.asString
sitFlipImages = lookupProperty("flipImages") { JsonPrimitive(false) }.asBoolean
sitStatusEffects.clear()
if (sitDir == Direction.LEFT) {
sitAngle *= -1f
}
val sitStatusEffects = ObjectArraySet<PersistentStatusEffect>()
sitStatusEffects.addAll(statusEffects.fromJsonTree(lookupProperty("sitStatusEffects") { JsonArray() }))
sitEffectEmitters.clear()
val sitEffectEmitters = ObjectArraySet<String>()
lookupProperty("sitEffectEmitters") { JsonArray() }.asJsonArray.forEach { sitEffectEmitters.add(it.asString) }
sitEmote = lookupProperty("sitEmote").coalesceNull?.asString
sitDance = lookupProperty("sitDance").coalesceNull?.asString
sitArmorCosmeticOverrides = lookupProperty("sitArmorCosmeticOverrides") { JsonObject() } as JsonObject
sitCursorOverride = lookupProperty("sitCursorOverride").coalesceNull?.asString
val sitEmote = lookupProperty("sitEmote").coalesceNull?.asString
val sitDance = lookupProperty("sitDance").coalesceNull?.asString
val sitArmorCosmeticOverrides = lookupProperty("sitArmorCosmeticOverrides") { JsonObject() } as JsonObject
val sitCursorOverride = lookupProperty("sitCursorOverride").coalesceNull?.asString
for ((i, pos) in sitPositionsList.withIndex()) {
val anchor = LoungeAnchorState(
position = pos,
exitBottomPosition = pos.copy(y = yTilePosition.toDouble() + volumeBoundingBox.mins.y),
direction = if (sitFlipDirection) sitDir.opposite else sitDir,
angle = sitAngle,
orientation = sitOrientation,
// Layer all anchored entities one above the object layer, in top to bottom
// order based on the anchor index.
loungeRenderLayer = RenderLayer.Object.point(i.toLong()),
controllable = false,
emote = sitEmote,
dance = sitDance,
armorCosmeticOverrides = LinkedHashMap<String, JsonElement>().also { for ((k, v) in sitArmorCosmeticOverrides.entrySet()) { it[k] = v } },
cursorOverride = sitCursorOverride,
statusEffects = sitStatusEffects,
effectEmitters = sitEffectEmitters
)
anchors.add(anchor)
}
}
override fun entitiesLoungingIn(index: Int): List<ActorEntity> {
TODO("Not yet implemented")
if (index !in anchors.indices) return listOf()
val result = ArrayList<ActorEntity>()
world.entityIndex.iterate(metaBoundingBox, {
if (it is ActorEntity) {
val state = it.movement.anchorNetworkState
if (state != null && state.entityID == entityID && state.positionIndex == index) {
result.add(it)
}
}
})
return result
}
override fun anchor(index: Int): IAnchorState? {
TODO("Not yet implemented")
override fun entitiesLoungingIn(): List<ActorEntity> {
val result = ArrayList<ActorEntity>()
world.entityIndex.iterate(metaBoundingBox, {
if (it is ActorEntity && it.movement.anchorNetworkState?.entityID == entityID) {
result.add(it)
}
})
return result
}
override fun parametersUpdated() {
@ -107,12 +145,12 @@ class LoungeableObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(c
override fun interact(request: InteractRequest): InteractAction {
val upstream = super.interact(request)
if (upstream.type == InteractAction.Type.NONE && sitPositions.isNotEmpty()) {
if (upstream.type == InteractAction.Type.NONE && anchors.isNotEmpty()) {
val interactOffset = if (direction.isRight) position - request.targetPos else request.targetPos - position
var index = 0
for (i in 1 until sitPositions.size) {
if ((sitPositions[i] + interactOffset).length < (sitPositions[index] + interactOffset).length) {
for (i in 1 until anchors.size) {
if ((anchors[i].position + interactOffset).length < (anchors[index].position + interactOffset).length) {
index = i
}
}

View File

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