Compare commits
10 Commits
918b6ff95f
...
3b454374ec
Author | SHA1 | Date | |
---|---|---|---|
3b454374ec | |||
aeca7836cd | |||
11e4efb2af | |||
c5fa4e4b59 | |||
de34b8ac5f | |||
3efedd7d22 | |||
45e95f7f71 | |||
885bccb2d2 | |||
2b698be166 | |||
9947138eac |
@ -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")
|
||||
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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)))
|
||||
|
@ -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)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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")))
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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])!!
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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()))
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -318,7 +318,7 @@ class StatusController(val entity: ActorEntity, val config: StatusControllerConf
|
||||
}
|
||||
|
||||
fun deserialize(data: SerializedData) {
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
// ----- Persistent status effects
|
||||
|
@ -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?
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user