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

View File

@ -17,5 +17,6 @@ log4jVersion=2.17.1
guavaVersion=33.0.0-jre guavaVersion=33.0.0-jre
junitVersion=5.8.2 junitVersion=5.8.2
sqliteVersion=3.45.3.0 sqliteVersion=3.45.3.0
zstdVersion=1.5.6-8
picocliVersion=4.7.6 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: 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

@ -130,7 +130,7 @@ class DungeonWorld(
private val boundingBoxes = ArrayList<AABBi>() private val boundingBoxes = ArrayList<AABBi>()
fun clearTileEntityAt(x: Int, y: Int) { fun clearTileEntityAt(x: Int, y: Int) {
clearTileEntitiesAt.add(geometry.wrap(Vector2i(x, y))) clearTileEntitiesAt.add(geometry.wrap(x, y))
} }
fun clearTileEntityAt(position: Vector2i) { fun clearTileEntityAt(position: Vector2i) {
@ -138,7 +138,7 @@ class DungeonWorld(
} }
fun isClearingTileEntityAt(x: Int, y: Int): Boolean { 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) { fun clearTileEntity(entity: TileEntity) {
@ -163,15 +163,15 @@ class DungeonWorld(
} }
fun isTouched(x: Int, y: Int): Boolean { 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) { 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 { 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) 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()) { 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) { fun placeWiring(x: Int, y: Int, group: String, partLocal: Boolean) {
val table = if (partLocal) openLocalWires else globalWires 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) { 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()) { 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) { 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) { 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) { fun setDungeonID(x: Int, y: Int) {
dungeonIDs.remove(geometry.wrap(Vector2i(x, y))) dungeonIDs.remove(geometry.wrap(x, y))
} }
fun setDungeonID(id: Int) { fun setDungeonID(id: Int) {
@ -244,35 +244,35 @@ class DungeonWorld(
} }
fun placeBiomeTree(x: Int, y: Int) { 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) { 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) { 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) { 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) { 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 { 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) { 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) { 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) { 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) { 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) { 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) { 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, setBackgroundMaterial: Boolean = true,
setBackgroundModifier: Boolean = true, setBackgroundModifier: Boolean = true,
) { ) {
val pos = geometry.wrap(Vector2i(x, y)) val pos = geometry.wrap(x, y)
if (foreground != null) { if (foreground != null) {
if (setForegroundMaterial) if (setForegroundMaterial)
@ -336,7 +336,7 @@ class DungeonWorld(
} }
fun clearTile(x: Int, y: Int) { fun clearTile(x: Int, y: Int) {
val pos = geometry.wrap(Vector2i(x, y)) val pos = geometry.wrap(x, y)
this.foregroundMaterial[pos] = emptyMaterial this.foregroundMaterial[pos] = emptyMaterial
this.foregroundModifier[pos] = emptyModifier this.foregroundModifier[pos] = emptyModifier
@ -348,24 +348,24 @@ class DungeonWorld(
} }
fun needsForegroundBiomeMod(x: Int, y: Int): Boolean { 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 val material = foregroundMaterial[pos] ?: return false
if (material.material !in BuiltinMetaMaterials.BIOME_META_MATERIALS) if (material.material !in BuiltinMetaMaterials.BIOME_META_MATERIALS)
return false return false
val above = geometry.wrap(Vector2i(x, y + 1)) val above = geometry.wrap(x, y + 1)
return foregroundMaterial[above]?.material?.isNotEmptyTile == false return foregroundMaterial[above]?.material?.isNotEmptyTile == false
} }
fun needsBackgroundBiomeMod(x: Int, y: Int): Boolean { 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 val material = backgroundMaterial[pos] ?: return false
if (material.material !in BuiltinMetaMaterials.BIOME_META_MATERIALS) if (material.material !in BuiltinMetaMaterials.BIOME_META_MATERIALS)
return false return false
val above = geometry.wrap(Vector2i(x, y + 1)) val above = geometry.wrap(x, y + 1)
return backgroundMaterial[above]?.material?.isNotEmptyTile == false return backgroundMaterial[above]?.material?.isNotEmptyTile == false
} }

View File

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

View File

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

View File

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

View File

@ -102,7 +102,7 @@ fun provideNPCBindings(self: NPCEntity, lua: LuaEnvironment) {
val anchorIndex = oAnchorIndex?.toInt() ?: 0 val anchorIndex = oAnchorIndex?.toInt() ?: 0
val entity = self.world.entities[loungeable.toInt()] as? LoungeableEntity 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) returnBuffer.setTo(false)
} else { } else {
self.movement.anchorNetworkState = AnchorNetworkState(loungeable.toInt(), anchorIndex) 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 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

@ -121,8 +121,8 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType) :
open fun bind(channel: Channel) { open fun bind(channel: Channel) {
scope = CoroutineScope(channel.eventLoop().asCoroutineDispatcher() + SupervisorJob() + CoroutineExceptionHandler { coroutineContext, throwable -> 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) 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) 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) { override suspend fun play(connection: ServerConnection) {
if (request.target >= 0) { if (request.target >= 0) {
connection.enqueue { connection.enqueueAndSuspend {
connection.send(EntityInteractResultPacket((entities[request.target] as? InteractiveEntity)?.interact(request) ?: InteractAction.NONE, id, request.source)) connection.send(EntityInteractResultPacket((entities[request.target] as? InteractiveEntity)?.interact(request) ?: InteractAction.NONE, id, request.source))
} }
} else { } else {

View File

@ -742,11 +742,11 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
val startingLocation = findStartingSystem() ?: return val startingLocation = findStartingSystem() ?: return
scope.launch { shipFlightEventLoop(startingLocation.location, SystemWorldLocation.Celestial(startingLocation)) } scope.launch { shipFlightEventLoop(startingLocation.location, SystemWorldLocation.Celestial(startingLocation)) }
} else { } else {
/*if (context.returnWarp != null) { if (context.returnWarp != null) {
enqueueWarp(context.returnWarp, ifFailed = WarpAlias.OwnShip) enqueueWarp(context.returnWarp, ifFailed = WarpAlias.OwnShip)
} else { } else {
enqueueWarp(WarpAlias.OwnShip) enqueueWarp(WarpAlias.OwnShip)
}*/ }
enqueueWarp(WarpAction.World(WorldID.Instance("outpost"))) 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.fromJson
import ru.dbotthepony.kstarbound.json.readJsonElement import ru.dbotthepony.kstarbound.json.readJsonElement
import ru.dbotthepony.kstarbound.json.readJsonElementInflated import ru.dbotthepony.kstarbound.json.readJsonElementInflated
import ru.dbotthepony.kstarbound.json.readJsonElementZstd
import ru.dbotthepony.kstarbound.json.readJsonObject import ru.dbotthepony.kstarbound.json.readJsonObject
import ru.dbotthepony.kstarbound.json.readJsonObjectZstd
import ru.dbotthepony.kstarbound.json.writeJsonElement import ru.dbotthepony.kstarbound.json.writeJsonElement
import ru.dbotthepony.kstarbound.json.writeJsonElementDeflated import ru.dbotthepony.kstarbound.json.writeJsonElementDeflated
import ru.dbotthepony.kstarbound.json.writeJsonElementZstd
import ru.dbotthepony.kstarbound.json.writeJsonObject import ru.dbotthepony.kstarbound.json.writeJsonObject
import ru.dbotthepony.kstarbound.json.writeJsonObjectZstd
import ru.dbotthepony.kstarbound.math.vector.Vector2i import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.server.world.LegacyWorldStorage import ru.dbotthepony.kstarbound.server.world.LegacyWorldStorage
import ru.dbotthepony.kstarbound.server.world.NativeLocalWorldStorage import ru.dbotthepony.kstarbound.server.world.NativeLocalWorldStorage
@ -159,7 +163,7 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
lookupClientContext.executeQuery().use { lookupClientContext.executeQuery().use {
if (it.next()) { if (it.next()) {
it.getBytes(1).readJsonObject() it.getBytes(1).readJsonObjectZstd()
} else { } else {
null null
} }
@ -168,9 +172,10 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
} }
fun writeClientContext(uuid: UUID, context: JsonObject): CompletableFuture<*> { fun writeClientContext(uuid: UUID, context: JsonObject): CompletableFuture<*> {
val compressed = context.writeJsonObjectZstd()
return supplyAsync { return supplyAsync {
writeClientContext.setString(1, uuid.toStarboundString()) writeClientContext.setString(1, uuid.toStarboundString())
writeClientContext.setBytes(2, context.writeJsonObject()) writeClientContext.setBytes(2, compressed)
writeClientContext.execute() writeClientContext.execute()
} }
} }
@ -241,7 +246,7 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
lookupSystemWorld.executeQuery().use { lookupSystemWorld.executeQuery().use {
if (it.next()) { if (it.next()) {
it.getBytes(1).readJsonElementInflated() it.getBytes(1).readJsonElementZstd()
} else { } else {
null null
} }
@ -250,11 +255,13 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
} }
fun writeServerWorldData(pos: Vector3i, data: JsonElement) { fun writeServerWorldData(pos: Vector3i, data: JsonElement) {
val compressed = data.writeJsonElementZstd()
execute { execute {
writeSystemWorld.setInt(1, pos.x) writeSystemWorld.setInt(1, pos.x)
writeSystemWorld.setInt(2, pos.y) writeSystemWorld.setInt(2, pos.y)
writeSystemWorld.setInt(3, pos.z) writeSystemWorld.setInt(3, pos.z)
writeSystemWorld.setBytes(4, data.writeJsonElementDeflated()) writeSystemWorld.setBytes(4, compressed)
writeSystemWorld.execute() writeSystemWorld.execute()
} }

View File

@ -1,5 +1,7 @@
package ru.dbotthepony.kstarbound.server.world 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.JsonArray
import com.google.gson.JsonObject import com.google.gson.JsonObject
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap 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.io.SQLSavepoint
import ru.dbotthepony.kstarbound.json.VersionedJson import ru.dbotthepony.kstarbound.json.VersionedJson
import ru.dbotthepony.kstarbound.json.readJsonArrayInflated import ru.dbotthepony.kstarbound.json.readJsonArrayInflated
import ru.dbotthepony.kstarbound.json.readJsonArrayZstd
import ru.dbotthepony.kstarbound.json.readJsonElement import ru.dbotthepony.kstarbound.json.readJsonElement
import ru.dbotthepony.kstarbound.json.readJsonObjectInflated import ru.dbotthepony.kstarbound.json.readJsonObjectInflated
import ru.dbotthepony.kstarbound.json.writeJsonArrayDeflated import ru.dbotthepony.kstarbound.json.writeJsonArrayDeflated
import ru.dbotthepony.kstarbound.json.writeJsonArrayZstd
import ru.dbotthepony.kstarbound.json.writeJsonElement import ru.dbotthepony.kstarbound.json.writeJsonElement
import ru.dbotthepony.kstarbound.math.vector.Vector2d import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.math.vector.Vector2i import ru.dbotthepony.kstarbound.math.vector.Vector2i
@ -140,8 +144,7 @@ class NativeLocalWorldStorage(file: File?) : WorldStorage() {
val version = it.getInt(2) val version = it.getInt(2)
val data = it.getBytes(3) val data = it.getBytes(3)
val inflater = Inflater() val stream = DataInputStream(BufferedInputStream(ZstdInputStreamNoFinalizer(FastByteArrayInputStream(data)), 0x40000))
val stream = DataInputStream(BufferedInputStream(InflaterInputStream(FastByteArrayInputStream(data), inflater, 0x10000), 0x40000))
try { try {
val palette = PaletteSet() val palette = PaletteSet()
@ -159,7 +162,7 @@ class NativeLocalWorldStorage(file: File?) : WorldStorage() {
return@supplyAsync ChunkCells(array as Object2DArray<out AbstractCell>, stage) return@supplyAsync ChunkCells(array as Object2DArray<out AbstractCell>, stage)
} finally { } finally {
inflater.end() stream.close()
} }
} }
} }
@ -178,7 +181,7 @@ class NativeLocalWorldStorage(file: File?) : WorldStorage() {
readEntities.executeQuery().use { readEntities.executeQuery().use {
if (it.next()) { if (it.next()) {
val data = it.getBytes(1).readJsonArrayInflated() val data = it.getBytes(1).readJsonArrayZstd()
for (entry in data) { for (entry in data) {
val versioned = VersionedJson.fromJson(entry) val versioned = VersionedJson.fromJson(entry)
@ -228,16 +231,19 @@ class NativeLocalWorldStorage(file: File?) : WorldStorage() {
return executor.supplyAsync { return executor.supplyAsync {
val (version, metadata) = readMetadata("metadata") ?: return@supplyAsync null 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() try {
val height = stream.readInt() val width = stream.readInt()
val loopX = stream.readBoolean() val height = stream.readInt()
val loopY = stream.readBoolean() val loopX = stream.readBoolean()
val json = VersionedJson("WorldMetadata", version, stream.readJsonElement()) 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(1, pos.x)
writeEntities.setInt(2, pos.y) writeEntities.setInt(2, pos.y)
writeEntities.setBytes(3, storeData.writeJsonArrayDeflated()) writeEntities.setBytes(3, storeData.writeJsonArrayZstd())
writeEntities.execute() writeEntities.execute()
} }
} }
@ -380,9 +386,10 @@ class NativeLocalWorldStorage(file: File?) : WorldStorage() {
} }
} }
val deflater = Deflater()
val buff = FastByteArrayOutputStream() 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 { try {
palette.write(stream) palette.write(stream)
@ -405,7 +412,7 @@ class NativeLocalWorldStorage(file: File?) : WorldStorage() {
writeCells.setBytes(5, buff.array.copyOf(buff.length)) writeCells.setBytes(5, buff.array.copyOf(buff.length))
writeCells.execute() writeCells.execute()
} finally { } finally {
deflater.end() z.close()
} }
} }
} }
@ -413,16 +420,23 @@ class NativeLocalWorldStorage(file: File?) : WorldStorage() {
override fun saveMetadata(data: Metadata) { override fun saveMetadata(data: Metadata) {
executor.execute { executor.execute {
val buff = FastByteArrayOutputStream() 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) try {
stream.writeInt(data.geometry.size.y) val stream = DataOutputStream(BufferedOutputStream(z, 0x40000))
stream.writeBoolean(data.geometry.loopX)
stream.writeBoolean(data.geometry.loopY)
stream.writeJsonElement(data.data.content)
stream.close() stream.writeInt(data.geometry.size.x)
writeMetadata("metadata", data.data.version ?: 0, buff.array.copyOf(buff.length)) 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 it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.future.asCompletableFuture import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.future.await import kotlinx.coroutines.future.await
import ru.dbotthepony.kommons.gson.JsonArrayCollector import ru.dbotthepony.kommons.gson.JsonArrayCollector
import ru.dbotthepony.kommons.gson.contains import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.get import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.set import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kstarbound.math.AABBi
import ru.dbotthepony.kommons.util.KOptional import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.Globals import ru.dbotthepony.kstarbound.Globals
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.world.CelestialBaseInformation 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.io.BTreeDB5
import ru.dbotthepony.kstarbound.json.jsonArrayOf import ru.dbotthepony.kstarbound.json.jsonArrayOf
import ru.dbotthepony.kstarbound.json.mergeJson import ru.dbotthepony.kstarbound.json.mergeJson
import ru.dbotthepony.kstarbound.json.readJsonArrayInflated import ru.dbotthepony.kstarbound.json.readJsonArrayZstd
import ru.dbotthepony.kstarbound.json.readJsonElementInflated import ru.dbotthepony.kstarbound.json.readJsonElementZstd
import ru.dbotthepony.kstarbound.json.writeJsonArrayDeflated import ru.dbotthepony.kstarbound.json.writeJsonArrayZstd
import ru.dbotthepony.kstarbound.json.writeJsonElementDeflated import ru.dbotthepony.kstarbound.json.writeJsonElementZstd
import ru.dbotthepony.kstarbound.math.AABBi
import ru.dbotthepony.kstarbound.math.Line2d import ru.dbotthepony.kstarbound.math.Line2d
import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.math.vector.Vector3i import ru.dbotthepony.kstarbound.math.vector.Vector3i
import ru.dbotthepony.kstarbound.util.CarriedExecutor import ru.dbotthepony.kstarbound.util.CarriedExecutor
import ru.dbotthepony.kstarbound.util.ScheduledCoroutineExecutor import ru.dbotthepony.kstarbound.util.ScheduledCoroutineExecutor
@ -57,11 +55,9 @@ import java.sql.ResultSet
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.function.Consumer
import java.util.function.Function import java.util.function.Function
import java.util.function.Supplier import java.util.function.Supplier
import java.util.random.RandomGenerator import java.util.random.RandomGenerator
import kotlin.collections.ArrayList
class ServerUniverse(folder: File? = null) : Universe(), Closeable { class ServerUniverse(folder: File? = null) : Universe(), Closeable {
override val baseInformation: CelestialBaseInformation 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>>) { 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( constructor(x: Int, y: Int, data: ResultSet) : this(
x, y, x, y,
data.getBytes(1).readJsonArrayInflated().map { Vector3i(it.asJsonArray[0].asInt, it.asJsonArray[1].asInt, it.asJsonArray[2].asInt) }.toSet(), data.getBytes(1).readJsonArrayZstd().map { Vector3i(it.asJsonArray[0].asInt, it.asJsonArray[1].asInt, it.asJsonArray[2].asInt) }.toSet(),
data.getBytes(2).readJsonArrayInflated().map { data.getBytes(2).readJsonArrayZstd().map {
val a = it.asJsonArray[0].asJsonArray val a = it.asJsonArray[0].asJsonArray
val b = it.asJsonArray[1].asJsonArray val b = it.asJsonArray[1].asJsonArray
Vector2i(a[0].asInt, a[1].asInt) to Vector2i(b[0].asInt, b[1].asInt) 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() systems.stream()
.map { jsonArrayOf(it.x, it.y, it.z) } .map { jsonArrayOf(it.x, it.y, it.z) }
.collect(JsonArrayCollector) .collect(JsonArrayCollector)
.writeJsonArrayDeflated(), .writeJsonArrayZstd(4),
constellations.stream().map { constellations.stream().map {
jsonArrayOf(jsonArrayOf(it.first.x, it.first.y), jsonArrayOf(it.second.x, it.second.y)) 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 { fun serialize(): SerializedSystem {
return SerializedSystem( return SerializedSystem(
x, y, z, x, y, z,
Starbound.gson.toJsonTree(parameters).writeJsonElementDeflated(), Starbound.gson.toJsonTree(parameters).writeJsonElementZstd(8),
planets.entries.stream() planets.entries.stream()
.map { jsonArrayOf(it.key.first, it.key.second, it.value) } .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 // deserialize in off-thread since it involves big json structures
Starbound.EXECUTOR.supplyAsync { 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 as JsonArray
(it[0].asInt to it[1].asInt) to Starbound.gson.fromJson(it[2])!! (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 (updated) {
if (breathable != null) { if (breathable != null) {
LOGGER.info("Dungeon ID $id breathable set to: $breathable") LOGGER.info("Dungeon ID $id 'breathable' set to: $breathable")
} else { } 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)) broadcast(UpdateDungeonBreathablePacket(id, breathable))
@ -716,7 +716,7 @@ class ServerWorld private constructor(
var currentDungeonID = 0 var currentDungeonID = 0
// primary dungeons must be generated sequentially since they can get very large // 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()) { for (dungeon in template.gatherDungeons()) {
var spawnDungeonRetries = Globals.worldServer.spawnDungeonRetries var spawnDungeonRetries = Globals.worldServer.spawnDungeonRetries
@ -751,18 +751,12 @@ class ServerWorld private constructor(
protectedDungeonIDsInternal.add(currentDungeonID) protectedDungeonIDsInternal.add(currentDungeonID)
} }
if (dungeon.dungeon.value.metadata.gravity != null) { dungeon.dungeon.value.metadata.gravity?.map({ setDungeonGravity(currentDungeonID, it) }, { setDungeonGravity(currentDungeonID, it) })
// TODO: set gravity here setDungeonBreathable(currentDungeonID, dungeon.dungeon.value.metadata.breathable)
}
if (dungeon.dungeon.value.metadata.breathable != null) {
// TODO: set "breathable" here
}
currentDungeonID++ currentDungeonID++
break 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++ 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

@ -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())) 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 { fun wrap(pos: IStruct2d): Vector2d {
return Vector2d(x.cell(pos.component1()), y.cell(pos.component2())) 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 { fun chunkFromCell(pos: IStruct2i): ChunkPos {
return ChunkPos(x.chunkFromCell(pos.component1()), y.chunkFromCell(pos.component2())) return ChunkPos(x.chunkFromCell(pos.component1()), y.chunkFromCell(pos.component2()))
} }

View File

@ -69,7 +69,7 @@ class ActorMovementController() : MovementController() {
if (networkState != null) { 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") 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) { if (state == null && this.anchorState?.exitBottomPosition != null) {
@ -344,7 +344,7 @@ class ActorMovementController() : MovementController() {
if (anchorNetworkState == null) if (anchorNetworkState == null)
this.anchorState = null this.anchorState = null
else 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) super.tickRemote(delta)
} }
@ -355,7 +355,7 @@ class ActorMovementController() : MovementController() {
val anchorNetworkState = anchorNetworkState val anchorNetworkState = anchorNetworkState
if (anchorNetworkState != null) { 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) { if (state == null) {

View File

@ -1,9 +1,9 @@
package ru.dbotthepony.kstarbound.world.entities package ru.dbotthepony.kstarbound.world.entities
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.google.gson.JsonObject
import ru.dbotthepony.kstarbound.client.render.RenderLayer import ru.dbotthepony.kstarbound.client.render.RenderLayer
import ru.dbotthepony.kstarbound.defs.actor.PersistentStatusEffect 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.math.vector.Vector2d
import ru.dbotthepony.kstarbound.world.Direction import ru.dbotthepony.kstarbound.world.Direction
@ -21,23 +21,15 @@ data class AnchorState(
override val angle: Double override val angle: Double
) : IAnchorState ) : 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( data class LoungeAnchorState(
override val position: Vector2d, override val position: Vector2d,
override val exitBottomPosition: Vector2d?, override val exitBottomPosition: Vector2d?,
override val direction: Direction, override val direction: Direction,
override val angle: Double, override val angle: Double,
val orientation: LoungeOrientation = LoungeOrientation.NONE, val orientation: LoungeOrientation = LoungeOrientation.NONE,
val loungeRenderLayer: RenderLayer, val loungeRenderLayer: RenderLayer.Point,
val controllable: Boolean = false, val controllable: Boolean = false,
val statusEffects: List<PersistentStatusEffect> = listOf(), val statusEffects: Set<PersistentStatusEffect> = setOf(),
val effectEmitters: Set<String> = setOf(), val effectEmitters: Set<String> = setOf(),
val emote: String? = null, val emote: String? = null,
val dance: 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.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

@ -1,19 +1,18 @@
package ru.dbotthepony.kstarbound.world.entities.api 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.ActorEntity
import ru.dbotthepony.kstarbound.world.entities.IAnchorState import ru.dbotthepony.kstarbound.world.entities.IAnchorState
interface LoungeableEntity { 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 * Determines entities currently lounging at specified anchor index
*/ */
fun entitiesLoungingIn(index: Int): List<ActorEntity> 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.ImmutableList
import com.google.common.collect.ImmutableSet import com.google.common.collect.ImmutableSet
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive import com.google.gson.JsonPrimitive
import com.google.gson.reflect.TypeToken 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.math.vector.Vector2d
import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.render.RenderLayer
import ru.dbotthepony.kstarbound.defs.InteractAction import ru.dbotthepony.kstarbound.defs.InteractAction
import ru.dbotthepony.kstarbound.defs.InteractRequest import ru.dbotthepony.kstarbound.defs.InteractRequest
import ru.dbotthepony.kstarbound.defs.actor.PersistentStatusEffect 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.PIXELS_IN_STARBOUND_UNIT
import ru.dbotthepony.kstarbound.world.entities.ActorEntity import ru.dbotthepony.kstarbound.world.entities.ActorEntity
import ru.dbotthepony.kstarbound.world.entities.IAnchorState import ru.dbotthepony.kstarbound.world.entities.IAnchorState
import ru.dbotthepony.kstarbound.world.entities.LoungeAnchorState
import ru.dbotthepony.kstarbound.world.entities.api.LoungeableEntity import ru.dbotthepony.kstarbound.world.entities.api.LoungeableEntity
import java.lang.Math.toRadians import java.lang.Math.toRadians
@ -31,67 +34,102 @@ class LoungeableObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(c
isInteractive = true 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 = "" var sitCoverImage = ""
private set private set
var sitFlipImages = false var sitFlipImages = false
private set private set
var sitOrientation: LoungeOrientation = LoungeOrientation.NONE
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
private set private set
private fun updateSitParams() { 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 sitPosition = lookupProperty("sitPosition")
val sitPositions = lookupProperty("sitPositions") val sitPositions = lookupProperty("sitPositions")
if (!sitPosition.isJsonNull) { 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) { } 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) 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 sitCoverImage = lookupProperty("sitCoverImage") { JsonPrimitive("") }.asString
sitFlipImages = lookupProperty("flipImages") { JsonPrimitive(false) }.asBoolean 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() })) sitStatusEffects.addAll(statusEffects.fromJsonTree(lookupProperty("sitStatusEffects") { JsonArray() }))
sitEffectEmitters.clear() val sitEffectEmitters = ObjectArraySet<String>()
lookupProperty("sitEffectEmitters") { JsonArray() }.asJsonArray.forEach { sitEffectEmitters.add(it.asString) } lookupProperty("sitEffectEmitters") { JsonArray() }.asJsonArray.forEach { sitEffectEmitters.add(it.asString) }
sitEmote = lookupProperty("sitEmote").coalesceNull?.asString val sitEmote = lookupProperty("sitEmote").coalesceNull?.asString
sitDance = lookupProperty("sitDance").coalesceNull?.asString val sitDance = lookupProperty("sitDance").coalesceNull?.asString
sitArmorCosmeticOverrides = lookupProperty("sitArmorCosmeticOverrides") { JsonObject() } as JsonObject val sitArmorCosmeticOverrides = lookupProperty("sitArmorCosmeticOverrides") { JsonObject() } as JsonObject
sitCursorOverride = lookupProperty("sitCursorOverride").coalesceNull?.asString 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> { 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? { override fun entitiesLoungingIn(): List<ActorEntity> {
TODO("Not yet implemented") 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() { override fun parametersUpdated() {
@ -107,12 +145,12 @@ class LoungeableObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(c
override fun interact(request: InteractRequest): InteractAction { override fun interact(request: InteractRequest): InteractAction {
val upstream = super.interact(request) 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 val interactOffset = if (direction.isRight) position - request.targetPos else request.targetPos - position
var index = 0 var index = 0
for (i in 1 until sitPositions.size) { for (i in 1 until anchors.size) {
if ((sitPositions[i] + interactOffset).length < (sitPositions[index] + interactOffset).length) { if ((anchors[i].position + interactOffset).length < (anchors[index].position + interactOffset).length) {
index = i index = i
} }
} }

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()
} }