Plant entities?
This commit is contained in:
parent
151470f76d
commit
135f8e9728
ADDITIONS.md
src/main/kotlin/ru/dbotthepony/kstarbound
Globals.ktRegistries.ktRegistryTypeAdapterFactory.kt
defs
IThingWithDescription.ktPerlinNoiseParameters.kt
dungeon
image
item
object
tile
BuiltinMetaMaterials.ktTileDamageParameters.ktTileDamageType.ktTileDefinition.ktTileModifierDefinition.kt
world
io
item
json
lua
network/syncher
server/world
util
world
@ -10,7 +10,7 @@ This document briefly documents what have been added (or removed) regarding modd
|
||||
### Worldgen
|
||||
* Where applicable, Perlin noise now can have custom seed specified
|
||||
* Change above allows to explicitly specify universe seed (as `celestial.config:systemTypePerlin:seed`)
|
||||
* Perlin noise now can be of arbitrary scale (defaults to `512`, specified with `scale` key, integer type, 2048>=x>=16)
|
||||
* `treasurechests` now can specify `treasurePool` as array
|
||||
|
||||
#### Terrain
|
||||
* All composing terrain selectors (such as `min`, `displacement`, `rotate`, etc) now can reference other terrain selectors by name (the `.terrain` files) instead of embedding entire config inside them
|
||||
@ -143,6 +143,7 @@ val color: TileColor = TileColor.DEFAULT
|
||||
### Worldgen
|
||||
* Major dungeon placement on planets is now deterministic
|
||||
* Container item population in dungeons is now deterministic and is based on dungeon seed
|
||||
* However, this might backfire, if you specify `seed` inside `/instance_worlds.config`; since that will set dungeon's contents in stone (don't do this, remove seed from your dungeon data, please. Both original and new engines will provide random seed for you on each world generation if you remove your own seed from data)
|
||||
|
||||
#### Dungeons
|
||||
* All brushes are now deterministic
|
||||
|
@ -14,7 +14,7 @@ import ru.dbotthepony.kstarbound.defs.WorldServerConfig
|
||||
import ru.dbotthepony.kstarbound.defs.actor.player.PlayerConfig
|
||||
import ru.dbotthepony.kstarbound.defs.item.ItemDropConfig
|
||||
import ru.dbotthepony.kstarbound.defs.item.ItemGlobalConfig
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageParameters
|
||||
import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldsConfig
|
||||
import ru.dbotthepony.kstarbound.defs.world.AsteroidWorldsConfig
|
||||
import ru.dbotthepony.kstarbound.defs.world.CelestialBaseInformation
|
||||
@ -30,7 +30,6 @@ import ru.dbotthepony.kstarbound.json.listAdapter
|
||||
import ru.dbotthepony.kstarbound.json.mapAdapter
|
||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ForkJoinTask
|
||||
import java.util.concurrent.Future
|
||||
import kotlin.properties.Delegates
|
||||
import kotlin.reflect.KMutableProperty0
|
||||
@ -62,16 +61,16 @@ object Globals {
|
||||
var dungeonWorlds by Delegates.notNull<ImmutableMap<String, DungeonWorldsConfig>>()
|
||||
private set
|
||||
|
||||
var grassDamage by Delegates.notNull<TileDamageConfig>()
|
||||
var grassDamage by Delegates.notNull<TileDamageParameters>()
|
||||
private set
|
||||
|
||||
var treeDamage by Delegates.notNull<TileDamageConfig>()
|
||||
var treeDamage by Delegates.notNull<TileDamageParameters>()
|
||||
private set
|
||||
|
||||
var bushDamage by Delegates.notNull<TileDamageConfig>()
|
||||
var bushDamage by Delegates.notNull<TileDamageParameters>()
|
||||
private set
|
||||
|
||||
var tileDamage by Delegates.notNull<TileDamageConfig>()
|
||||
var tileDamage by Delegates.notNull<TileDamageParameters>()
|
||||
private set
|
||||
|
||||
var sky by Delegates.notNull<SkyGlobalConfig>()
|
||||
|
@ -3,12 +3,10 @@ package ru.dbotthepony.kstarbound
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapterFactory
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.google.gson.stream.JsonReader
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||
@ -28,11 +26,12 @@ import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.actor.player.TechDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.animation.ParticleConfig
|
||||
import ru.dbotthepony.kstarbound.defs.dungeon.DungeonDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.item.TreasureChestDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.projectile.ProjectileDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.quest.QuestTemplate
|
||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.RenderParameters
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageParameters
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileModifierDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.world.BushVariant
|
||||
@ -76,6 +75,7 @@ object Registries {
|
||||
val projectiles = Registry<ProjectileDefinition>("projectile").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val tenants = Registry<TenantDefinition>("tenant").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val treasurePools = Registry<TreasurePoolDefinition>("treasure pool").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val treasureChests = Registry<TreasureChestDefinition>("treasure chest").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val monsterSkills = Registry<MonsterSkillDefinition>("monster skill").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val monsterTypes = Registry<MonsterTypeDefinition>("monster type").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val worldObjects = Registry<ObjectDefinition>("world object").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
@ -177,6 +177,7 @@ object Registries {
|
||||
tasks.addAll(loadCombined(jsonFunctions, fileTree["functions"] ?: listOf(), patchTree))
|
||||
tasks.addAll(loadCombined(json2Functions, fileTree["2functions"] ?: listOf(), patchTree))
|
||||
tasks.addAll(loadCombined(jsonConfigFunctions, fileTree["configfunctions"] ?: listOf(), patchTree))
|
||||
tasks.addAll(loadCombined(treasureChests, fileTree["treasurechests"] ?: listOf(), patchTree) { name = it })
|
||||
tasks.addAll(loadCombined(treasurePools, fileTree["treasurepools"] ?: listOf(), patchTree) { name = it })
|
||||
|
||||
return tasks
|
||||
@ -271,7 +272,7 @@ object Registries {
|
||||
renderParameters = RenderParameters.META,
|
||||
isConnectable = def.isConnectable,
|
||||
supportsMods = def.supportsMods,
|
||||
damageTable = AssetReference(TileDamageConfig(
|
||||
damageTable = AssetReference(TileDamageParameters(
|
||||
damageFactors = ImmutableMap.of(),
|
||||
damageRecovery = Double.MAX_VALUE,
|
||||
maximumEffectTime = 0.0,
|
||||
|
@ -18,13 +18,16 @@ inline fun <reified S : Any> Registry<S>.adapter(): TypeAdapterFactory {
|
||||
|
||||
class RegistryTypeAdapterFactory<S : Any>(private val registry: Registry<S>, private val clazz: KClass<S>) : TypeAdapterFactory {
|
||||
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||
val subtype = type.type as? ParameterizedType ?: return null
|
||||
if (subtype.actualTypeArguments.size != 1 || subtype.actualTypeArguments[0] != clazz.java) return null
|
||||
if (type.rawType == Registry.Entry::class.java || type.rawType == Registry.Ref::class.java) {
|
||||
val subtype = type.type as? ParameterizedType ?: throw IllegalArgumentException("Non-parametized registry reference type: $type")
|
||||
if (subtype.actualTypeArguments.size != 1) throw RuntimeException(type.toString())
|
||||
if (subtype.actualTypeArguments[0] != clazz.java) return null
|
||||
|
||||
if (type.rawType == Registry.Entry::class.java) {
|
||||
return EntryImpl(gson) as TypeAdapter<T>
|
||||
} else if (type.rawType == Registry.Ref::class.java) {
|
||||
return RefImpl(gson) as TypeAdapter<T>
|
||||
if (type.rawType == Registry.Entry::class.java) {
|
||||
return EntryImpl(gson) as TypeAdapter<T>
|
||||
} else if (type.rawType == Registry.Ref::class.java) {
|
||||
return RefImpl(gson) as TypeAdapter<T>
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
|
@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.defs
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.github.benmanes.caffeine.cache.Interner
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.TypeAdapterFactory
|
||||
import com.google.gson.reflect.TypeToken
|
||||
@ -10,6 +11,7 @@ import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kommons.gson.consumeNull
|
||||
import ru.dbotthepony.kommons.gson.set
|
||||
import ru.dbotthepony.kommons.guava.immutableMap
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonImplementation
|
||||
|
||||
@ -92,6 +94,16 @@ data class ThingDescription(
|
||||
}
|
||||
}
|
||||
|
||||
fun toJsonObject(): JsonObject {
|
||||
return JsonObject().apply {
|
||||
this["description"] = description
|
||||
|
||||
racialDescription.forEach { t, u ->
|
||||
this[t] = u
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fixDescription(newDescription: String): ThingDescription {
|
||||
return copy(
|
||||
shortdescription = if (shortdescription == "...") newDescription else shortdescription,
|
||||
|
@ -8,7 +8,6 @@ import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
data class PerlinNoiseParameters(
|
||||
val type: Type = Type.PERLIN,
|
||||
val seed: Long? = null,
|
||||
val scale: Int = DEFAULT_SCALE,
|
||||
val octaves: Int = 1,
|
||||
val gain: Double = 2.0,
|
||||
val offset: Double = 1.0,
|
||||
@ -18,11 +17,6 @@ data class PerlinNoiseParameters(
|
||||
val amplitude: Double = 1.0,
|
||||
val bias: Double = 0.0,
|
||||
) {
|
||||
init {
|
||||
require(scale >= 16) { "Too little perlin noise scale: $scale" }
|
||||
require(scale <= 2048) { "Absurd noise scale: $scale" }
|
||||
}
|
||||
|
||||
enum class Type(override val jsonName: String) : IStringSerializable {
|
||||
UNITIALIZED("uninitialized"),
|
||||
PERLIN("perlin"),
|
||||
@ -35,8 +29,4 @@ data class PerlinNoiseParameters(
|
||||
return name.lowercase() == lower
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DEFAULT_SCALE = 512
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,14 @@ package ru.dbotthepony.kstarbound.defs.dungeon
|
||||
import com.google.gson.JsonObject
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.future.await
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kstarbound.math.AABBi
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
||||
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
||||
@ -35,6 +37,7 @@ import java.util.Collections
|
||||
import java.util.LinkedHashSet
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.Supplier
|
||||
import java.util.random.RandomGenerator
|
||||
|
||||
// Facade world for generating dungeons, so generation can be performed without affecting world state,
|
||||
@ -529,6 +532,26 @@ class DungeonWorld(val parent: ServerWorld, val random: RandomGenerator, val mar
|
||||
}
|
||||
.filter { it.first != null }
|
||||
|
||||
val biomeItemsFutures = biomeItems.map {
|
||||
CompletableFuture.supplyAsync(Supplier {
|
||||
parent.template.potentialBiomeItemsAt(it.x, it.y).surfaceBiomeItems to it
|
||||
}, Starbound.EXECUTOR)
|
||||
}
|
||||
|
||||
val biomeItems = ArrayList<() -> Unit>()
|
||||
|
||||
for (biomeItem in biomeItemsFutures) {
|
||||
try {
|
||||
val (placeables, pos) = biomeItem.await()
|
||||
|
||||
for (placeable in placeables) {
|
||||
biomeItems.add(placeable.item.createPlacementFunc(parent, random, pos))
|
||||
}
|
||||
} catch (err: Throwable) {
|
||||
LOGGER.error("Exception while evaluating dungeon biome placeables", err)
|
||||
}
|
||||
}
|
||||
|
||||
// wait for all chunks to be loaded (and cell changes to be applied)
|
||||
// if any of cell change operation fails, entire generation fails... leaving world in inconsistent state,
|
||||
// but also limiting damage by exiting early.
|
||||
@ -554,6 +577,15 @@ class DungeonWorld(val parent: ServerWorld, val random: RandomGenerator, val mar
|
||||
}
|
||||
}
|
||||
|
||||
// place biome items
|
||||
for (placement in biomeItems) {
|
||||
try {
|
||||
placement()
|
||||
} catch (err: Throwable) {
|
||||
LOGGER.error("Exception while placing biome items for dungeon", err)
|
||||
}
|
||||
}
|
||||
|
||||
// objects are placed, now place wiring
|
||||
for (wiring in localWires) {
|
||||
try {
|
||||
|
@ -3,8 +3,10 @@ package ru.dbotthepony.kstarbound.defs.image
|
||||
import com.github.benmanes.caffeine.cache.AsyncLoadingCache
|
||||
import com.github.benmanes.caffeine.cache.CacheLoader
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache
|
||||
import com.github.benmanes.caffeine.cache.Scheduler
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
@ -37,6 +39,7 @@ import ru.dbotthepony.kommons.gson.contains
|
||||
import ru.dbotthepony.kommons.gson.get
|
||||
import ru.dbotthepony.kommons.gson.getObject
|
||||
import ru.dbotthepony.kstarbound.json.JsonPatch
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.FileNotFoundException
|
||||
import java.lang.ref.Reference
|
||||
@ -104,26 +107,8 @@ class Image private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
val data: CompletableFuture<ByteBuffer> get() {
|
||||
var get = dataRef?.get()
|
||||
|
||||
if (get != null)
|
||||
return CompletableFuture.completedFuture(get)
|
||||
|
||||
synchronized(lock) {
|
||||
get = dataRef?.get()
|
||||
|
||||
if (get != null)
|
||||
return CompletableFuture.completedFuture(get)
|
||||
|
||||
val f = dataCache.get(source)
|
||||
|
||||
if (f.isDone)
|
||||
dataRef = WeakReference(f.get())
|
||||
|
||||
return f.copy()
|
||||
}
|
||||
}
|
||||
val data: ByteBuffer
|
||||
get() = dataCache.get(source)
|
||||
|
||||
val texture: GLTexture2D get() {
|
||||
//val get = _texture.get()?.get()
|
||||
@ -140,12 +125,10 @@ class Image private constructor(
|
||||
client.named2DTextures1.get(this) {
|
||||
val tex = GLTexture2D(width, height, GL45.GL_RGBA8)
|
||||
|
||||
data.thenApplyAsync({
|
||||
tex.upload(GL45.GL_RGBA, GL45.GL_UNSIGNED_BYTE, it)
|
||||
tex.upload(GL45.GL_RGBA, GL45.GL_UNSIGNED_BYTE, data)
|
||||
|
||||
tex.textureMinFilter = GL45.GL_NEAREST
|
||||
tex.textureMagFilter = GL45.GL_NEAREST
|
||||
}, client)
|
||||
tex.textureMinFilter = GL45.GL_NEAREST
|
||||
tex.textureMagFilter = GL45.GL_NEAREST
|
||||
|
||||
tex
|
||||
}
|
||||
@ -187,7 +170,12 @@ class Image private constructor(
|
||||
return whole.worldSpaces(pixelOffset, spaceScan, flip)
|
||||
}
|
||||
|
||||
fun worldSpaces(pixelOffset: Vector2d, spaceScan: Double, flip: Boolean): Set<Vector2i> {
|
||||
return whole.worldSpaces(Vector2i(pixelOffset.x.toInt(), pixelOffset.y.toInt()), spaceScan, flip)
|
||||
}
|
||||
|
||||
private data class DataSprite(val name: String, val coordinates: Vector4i)
|
||||
private data class SpaceScanKey(val sprite: Sprite, val pixelOffset: Vector2i, val spaceScan: Double, val flip: Boolean)
|
||||
|
||||
inner class Sprite(val name: String, val x: Int, val y: Int, val width: Int, val height: Int) : IUVCoordinates {
|
||||
// flip coordinates to account for opengl
|
||||
@ -204,7 +192,7 @@ class Image private constructor(
|
||||
require(x in 0 until width && y in 0 until height) { "Position out of bounds: $x $y" }
|
||||
|
||||
val offset = (this.y + y) * this@Image.width * 4 + (this.x + x) * 4
|
||||
val data = data.join()
|
||||
val data = data
|
||||
|
||||
return data[offset].toInt().and(0xFF) or // red
|
||||
data[offset + 1].toInt().and(0xFF).shl(8) or // green
|
||||
@ -265,6 +253,12 @@ class Image private constructor(
|
||||
}
|
||||
|
||||
fun worldSpaces(pixelOffset: Vector2i, spaceScan: Double, flip: Boolean): Set<Vector2i> {
|
||||
return spaceScanCache.get(SpaceScanKey(this, pixelOffset, spaceScan, flip)) {
|
||||
ImmutableSet.copyOf(worldSpaces0(pixelOffset, spaceScan, flip))
|
||||
}
|
||||
}
|
||||
|
||||
private fun worldSpaces0(pixelOffset: Vector2i, spaceScan: Double, flip: Boolean): Set<Vector2i> {
|
||||
val minX = pixelOffset.x / PIXELS_IN_STARBOUND_UNITi
|
||||
val minY = pixelOffset.y / PIXELS_IN_STARBOUND_UNITi
|
||||
val maxX = (width + pixelOffset.x + PIXELS_IN_STARBOUND_UNITi - 1) / PIXELS_IN_STARBOUND_UNITi
|
||||
@ -340,19 +334,24 @@ class Image private constructor(
|
||||
return ReadDirectData(data, getWidth[0], getHeight[0], components[0])
|
||||
}
|
||||
|
||||
private val dataCache: AsyncLoadingCache<IStarboundFile, ByteBuffer> = Caffeine.newBuilder()
|
||||
private val dataCache: LoadingCache<IStarboundFile, ByteBuffer> = Caffeine.newBuilder()
|
||||
.expireAfterAccess(Duration.ofMinutes(1))
|
||||
.weigher<IStarboundFile, ByteBuffer> { key, value -> value.capacity() }
|
||||
.maximumWeight((Runtime.getRuntime().maxMemory() / 4L).coerceIn(1_024L * 1_024L * 32L /* 32 МиБ */, 1_024L * 1_024L * 256L /* 256 МиБ */))
|
||||
.scheduler(Starbound)
|
||||
.executor(Starbound.EXECUTOR) // SCREENED_EXECUTOR shouldn't be used here
|
||||
.buildAsync(CacheLoader {
|
||||
readImageDirect(it).data
|
||||
})
|
||||
.build { readImageDirect(it).data }
|
||||
|
||||
private val spaceScanCache = Caffeine.newBuilder()
|
||||
.expireAfterAccess(Duration.ofMinutes(30))
|
||||
.softValues()
|
||||
.scheduler(Starbound)
|
||||
.executor(Starbound.SCREENED_EXECUTOR)
|
||||
.build<SpaceScanKey, ImmutableSet<Vector2i>>()
|
||||
|
||||
@JvmStatic
|
||||
fun get(path: String): Image? {
|
||||
return imageCache.computeIfAbsent(path) {
|
||||
return imageCache.computeIfAbsent(path.substringBefore(':').substringBefore('?')) {
|
||||
try {
|
||||
val file = Starbound.locate(it)
|
||||
|
||||
|
@ -0,0 +1,51 @@
|
||||
package ru.dbotthepony.kstarbound.defs.item
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.annotations.JsonAdapter
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.json.getAdapter
|
||||
import java.util.stream.Stream
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
@JsonAdapter(TreasureChestDefinition.Adapter::class)
|
||||
data class TreasureChestDefinition(
|
||||
val variants: ImmutableList<Variant>,
|
||||
) {
|
||||
var name: String by Delegates.notNull()
|
||||
|
||||
@JsonFactory
|
||||
data class Variant(
|
||||
val containers: ImmutableSet<Registry.Ref<ObjectDefinition>>,
|
||||
val treasurePool: Either<ImmutableSet<Registry.Ref<TreasurePoolDefinition>>, Registry.Ref<TreasurePoolDefinition>>,
|
||||
val minimumLevel: Double = 0.0
|
||||
) {
|
||||
val validContainers: ImmutableSet<Registry.Entry<ObjectDefinition>> by lazy {
|
||||
containers.stream().filter { it.isPresent }.map { it.entry!! }.collect(ImmutableSet.toImmutableSet())
|
||||
}
|
||||
|
||||
val validTreasurePools: ImmutableSet<Registry.Entry<TreasurePoolDefinition>> by lazy {
|
||||
treasurePool.map({ it.stream() }, { Stream.of(it) }).filter { it.isPresent }.map { it.entry!! }.collect(ImmutableSet.toImmutableSet())
|
||||
}
|
||||
}
|
||||
|
||||
class Adapter(gson: Gson) : TypeAdapter<TreasureChestDefinition>() {
|
||||
private val variants = gson.getAdapter<ImmutableList<Variant>>()
|
||||
|
||||
override fun write(out: JsonWriter, value: TreasureChestDefinition) {
|
||||
variants.write(out, value.variants)
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): TreasureChestDefinition {
|
||||
return TreasureChestDefinition(variants.read(`in`))
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||
import ru.dbotthepony.kstarbound.defs.JsonReference
|
||||
import ru.dbotthepony.kstarbound.defs.actor.StatModifier
|
||||
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageParameters
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kommons.gson.consumeNull
|
||||
import ru.dbotthepony.kstarbound.json.listAdapter
|
||||
@ -81,7 +81,7 @@ data class ObjectDefinition(
|
||||
val biomePlaced: Boolean = false,
|
||||
val printable: Boolean = false,
|
||||
val smashOnBreak: Boolean = false,
|
||||
val damageConfig: TileDamageConfig,
|
||||
val damageConfig: TileDamageParameters,
|
||||
val flickerPeriod: PeriodicFunction? = null,
|
||||
val orientations: ImmutableList<ObjectOrientation>,
|
||||
) {
|
||||
@ -153,7 +153,7 @@ data class ObjectDefinition(
|
||||
|
||||
private val objectRef = gson.getAdapter(JsonReference.Object::class.java)
|
||||
private val basic = gson.getAdapter(PlainData::class.java)
|
||||
private val damageConfig = gson.getAdapter(TileDamageConfig::class.java)
|
||||
private val damageConfig = gson.getAdapter(TileDamageParameters::class.java)
|
||||
private val damageTeam = gson.getAdapter(DamageTeam::class.java)
|
||||
private val orientations = gson.getAdapter(ObjectOrientation::class.java)
|
||||
private val emitter = gson.getAdapter(ParticleEmissionEntry::class.java)
|
||||
|
@ -2,8 +2,6 @@ package ru.dbotthepony.kstarbound.defs.tile
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import it.unimi.dsi.fastutil.objects.Object2DoubleMap
|
||||
import it.unimi.dsi.fastutil.objects.Object2DoubleMaps
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
@ -131,7 +129,7 @@ object BuiltinMetaMaterials {
|
||||
isMeta = true,
|
||||
supportsMods = false,
|
||||
collisionKind = collisionType,
|
||||
damageTable = AssetReference(TileDamageConfig(
|
||||
damageTable = AssetReference(TileDamageParameters(
|
||||
damageFactors = ImmutableMap.of(),
|
||||
damageRecovery = Double.MAX_VALUE,
|
||||
maximumEffectTime = 0.0,
|
||||
|
@ -3,16 +3,32 @@ package ru.dbotthepony.kstarbound.defs.tile
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.io.readMap
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeMap
|
||||
import ru.dbotthepony.kstarbound.io.readDouble
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import ru.dbotthepony.kstarbound.io.writeDouble
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
@JsonFactory
|
||||
data class TileDamageConfig(
|
||||
data class TileDamageParameters(
|
||||
val damageFactors: ImmutableMap<String, Double> = ImmutableMap.of(),
|
||||
val damageRecovery: Double = 1.0,
|
||||
val harvestLevel: Int = 1,
|
||||
val maximumEffectTime: Double = 1.5,
|
||||
val totalHealth: Double = 1.0,
|
||||
val harvestLevel: Int = 1,
|
||||
) {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||
ImmutableMap.copyOf(stream.readMap({ TileDamageType.entries[readUnsignedByte()].jsonName }, { readDouble(isLegacy) })),
|
||||
stream.readDouble(isLegacy),
|
||||
stream.readInt(),
|
||||
stream.readDouble(isLegacy),
|
||||
stream.readDouble(isLegacy),
|
||||
)
|
||||
|
||||
val damageFactorsMapped: ImmutableMap<TileDamageType, Double> = damageFactors.entries.stream().map {
|
||||
var find = TileDamageType.entries.firstOrNull { e -> e.match(it.key) }
|
||||
|
||||
@ -28,7 +44,15 @@ data class TileDamageConfig(
|
||||
return (damageFactorsMapped[damage.type] ?: 1.0) * damage.amount
|
||||
}
|
||||
|
||||
operator fun plus(other: TileDamageConfig): TileDamageConfig {
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeMap(damageFactorsMapped, { writeByte(it.ordinal) }, { writeDouble(it, isLegacy) })
|
||||
stream.writeDouble(damageRecovery, isLegacy)
|
||||
stream.writeInt(harvestLevel)
|
||||
stream.writeDouble(maximumEffectTime, isLegacy)
|
||||
stream.writeDouble(totalHealth, isLegacy)
|
||||
}
|
||||
|
||||
operator fun plus(other: TileDamageParameters): TileDamageParameters {
|
||||
val damageRecovery = damageRecovery + other.damageRecovery
|
||||
val maximumEffectTime = maximumEffectTime.coerceAtLeast(other.maximumEffectTime)
|
||||
val totalHealth = totalHealth + other.totalHealth
|
||||
@ -51,11 +75,11 @@ data class TileDamageConfig(
|
||||
}
|
||||
}
|
||||
|
||||
return TileDamageConfig(builder.build(), damageRecovery, maximumEffectTime, totalHealth, harvestLevel)
|
||||
return TileDamageParameters(builder.build(), damageRecovery, harvestLevel, maximumEffectTime, totalHealth)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val EMPTY = TileDamageConfig()
|
||||
val EMPTY = TileDamageParameters()
|
||||
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.defs.tile
|
||||
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
|
||||
// uint8_t
|
||||
enum class TileDamageType(override val jsonName: String, val isPenetrating: Boolean) : IStringSerializable {
|
||||
// Damage done that will not actually kill the target
|
||||
PROTECTED("protected", false),
|
||||
|
@ -27,7 +27,7 @@ data class TileDefinition(
|
||||
val category: String,
|
||||
|
||||
@Deprecated("", replaceWith = ReplaceWith("this.actualDamageTable"))
|
||||
val damageTable: AssetReference<TileDamageConfig> = AssetReference(Globals::tileDamage),
|
||||
val damageTable: AssetReference<TileDamageParameters> = AssetReference(Globals::tileDamage),
|
||||
|
||||
val health: Double? = null,
|
||||
val requiredHarvestLevel: Int? = null,
|
||||
@ -61,8 +61,8 @@ data class TileDefinition(
|
||||
return !isMeta && !modifier.value.isMeta && supportsMods
|
||||
}
|
||||
|
||||
val actualDamageTable: TileDamageConfig by lazy {
|
||||
val dmg = damageTable.value ?: TileDamageConfig.EMPTY
|
||||
val actualDamageTable: TileDamageParameters by lazy {
|
||||
val dmg = damageTable.value ?: TileDamageParameters.EMPTY
|
||||
|
||||
return@lazy if (health == null && requiredHarvestLevel == null) {
|
||||
dmg
|
||||
|
@ -25,7 +25,7 @@ data class TileModifierDefinition(
|
||||
val miningSounds: ImmutableList<String> = ImmutableList.of(),
|
||||
|
||||
@Deprecated("", replaceWith = ReplaceWith("this.actualDamageTable"))
|
||||
val damageTable: AssetReference<TileDamageConfig> = AssetReference(Globals::tileDamage),
|
||||
val damageTable: AssetReference<TileDamageParameters> = AssetReference(Globals::tileDamage),
|
||||
|
||||
@JsonFlat
|
||||
val descriptionData: ThingDescription,
|
||||
@ -42,8 +42,8 @@ data class TileModifierDefinition(
|
||||
require(modId == null || modId > 0) { "Invalid tile modifier ID $modId" }
|
||||
}
|
||||
|
||||
val actualDamageTable: TileDamageConfig by lazy {
|
||||
val dmg = damageTable.value ?: TileDamageConfig.EMPTY
|
||||
val actualDamageTable: TileDamageParameters by lazy {
|
||||
val dmg = damageTable.value ?: TileDamageParameters.EMPTY
|
||||
|
||||
return@lazy if (health == null && requiredHarvestLevel == null) {
|
||||
dmg
|
||||
|
@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableSet
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapter
|
||||
@ -12,8 +13,10 @@ import com.google.gson.TypeAdapterFactory
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.collect.filterNotNull
|
||||
import ru.dbotthepony.kommons.gson.consumeNull
|
||||
import ru.dbotthepony.kommons.gson.set
|
||||
import ru.dbotthepony.kommons.gson.stream
|
||||
import ru.dbotthepony.kommons.gson.value
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
@ -23,15 +26,26 @@ import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.collect.WeightedList
|
||||
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
|
||||
import ru.dbotthepony.kstarbound.defs.dungeon.DungeonDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.item.TreasureChestDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileModifierDefinition
|
||||
import ru.dbotthepony.kstarbound.json.NativeLegacy
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFlat
|
||||
import ru.dbotthepony.kstarbound.json.getAdapter
|
||||
import ru.dbotthepony.kstarbound.json.jsonArrayOf
|
||||
import ru.dbotthepony.kstarbound.json.listAdapter
|
||||
import ru.dbotthepony.kstarbound.server.world.ServerChunk
|
||||
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.util.random.staticRandomDouble
|
||||
import ru.dbotthepony.kstarbound.util.random.staticRandomInt
|
||||
import ru.dbotthepony.kstarbound.world.Direction
|
||||
import ru.dbotthepony.kstarbound.world.entities.tile.PlantEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
|
||||
import java.util.random.RandomGenerator
|
||||
import java.util.stream.Stream
|
||||
|
||||
@JsonFactory
|
||||
@ -73,6 +87,8 @@ data class BiomePlaceables(
|
||||
|
||||
abstract fun toJson(): JsonElement
|
||||
|
||||
abstract fun createPlacementFunc(world: ServerWorld, random: RandomGenerator, position: Vector2i): () -> Unit
|
||||
|
||||
companion object : TypeAdapterFactory {
|
||||
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||
if (Item::class.java.isAssignableFrom(type.rawType)) {
|
||||
@ -80,7 +96,6 @@ data class BiomePlaceables(
|
||||
private val grassVariant = gson.getAdapter(GrassVariant::class.java)
|
||||
private val bushVariant = gson.getAdapter(BushVariant::class.java)
|
||||
private val trees = gson.listAdapter<TreeVariant>()
|
||||
private val objects = gson.getAdapter(PoolTypeToken)
|
||||
|
||||
override fun write(out: JsonWriter, value: Item?) {
|
||||
if (value == null)
|
||||
@ -114,12 +129,12 @@ data class BiomePlaceables(
|
||||
// and world storage data at Chucklefish.
|
||||
// Truly our hero here.
|
||||
val obj = when (val type = `in`.nextString()) {
|
||||
"treasureBoxSet" -> TreasureBox(`in`.nextString())
|
||||
"treasureBoxSet" -> TreasureBox(Registries.treasureChests.ref(`in`.nextString()))
|
||||
"microDungeon" -> MicroDungeon(Starbound.ELEMENTS_ADAPTER.arrays.read(`in`).stream().map { Registries.dungeons.ref(it.asString) }.collect(ImmutableSet.toImmutableSet()))
|
||||
"grass" -> Grass(grassVariant.read(`in`))
|
||||
"bush" -> Bush(bushVariant.read(`in`))
|
||||
"treePair" -> Tree(trees.read(`in`))
|
||||
"objectPool" -> Object(objects.read(`in`))
|
||||
"objectPool" -> Object(objectPoolAdapter.read(`in`))
|
||||
else -> throw JsonSyntaxException("Unknown biome placement item $type")
|
||||
}
|
||||
|
||||
@ -143,14 +158,63 @@ data class BiomePlaceables(
|
||||
microdungeons.forEach { j.add(JsonPrimitive(it.key.left())) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun createPlacementFunc(world: ServerWorld, random: RandomGenerator, position: Vector2i): () -> Unit {
|
||||
return { }
|
||||
}
|
||||
}
|
||||
|
||||
data class TreasureBox(val pool: String) : Item() {
|
||||
data class TreasureBox(val pool: Registry.Ref<TreasureChestDefinition>) : Item() {
|
||||
override val type: BiomePlacementItemType
|
||||
get() = BiomePlacementItemType.TREASURE_BOX_SET
|
||||
|
||||
override fun toJson(): JsonElement {
|
||||
return JsonPrimitive(pool)
|
||||
return JsonPrimitive(pool.key.left())
|
||||
}
|
||||
|
||||
override fun createPlacementFunc(world: ServerWorld, random: RandomGenerator, position: Vector2i): () -> Unit {
|
||||
if (pool.isEmpty) {
|
||||
LOGGER.error("Tried to place treasure chest '${pool.key.left()}' at ${position}, however, no such treasure chest exist")
|
||||
} else {
|
||||
val pools = pool.value!!.variants.filter { it.minimumLevel <= world.template.threatLevel }
|
||||
|
||||
if (pools.isEmpty())
|
||||
return {}
|
||||
|
||||
val pool = pools.random(random)
|
||||
|
||||
if (pool.validContainers.isEmpty()) {
|
||||
LOGGER.error("Tried to place treasure chest '${this.pool.key.left()}' at ${position}, however, no valid container objects exist for it (candidates: ${pool.containers})")
|
||||
return {}
|
||||
}
|
||||
|
||||
if (pool.validTreasurePools.isEmpty()) {
|
||||
LOGGER.error("Tried to place treasure chest '${this.pool.key.left()}' at ${position}, however, no valid treasure pools exist for it (candidates: ${pool.treasurePool})")
|
||||
return {}
|
||||
}
|
||||
|
||||
val create = WorldObject.create(pool.validContainers.random(random), position, JsonObject().apply {
|
||||
this["treasurePools"] = jsonArrayOf(pool.validTreasurePools.random(random).key)
|
||||
this["treasureSeed"] = random.nextLong() // this value is ignored if created object is an actual container
|
||||
// because of call to randomize()
|
||||
})
|
||||
|
||||
if (create != null) {
|
||||
val direction = Direction.entries[random.nextInt(2)]
|
||||
create.randomize(random, world.template.threatLevel)
|
||||
|
||||
return {
|
||||
val orientation = create.config.value.findValidOrientation(world, position, direction)
|
||||
|
||||
if (orientation != -1) {
|
||||
create.orientationIndex = orientation.toLong()
|
||||
create.joinWorld(world)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { }
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,6 +225,11 @@ data class BiomePlaceables(
|
||||
override fun toJson(): JsonElement {
|
||||
return Starbound.gson.toJsonTree(value)
|
||||
}
|
||||
|
||||
override fun createPlacementFunc(world: ServerWorld, random: RandomGenerator, position: Vector2i): () -> Unit {
|
||||
val plant = PlantEntity(value, random)
|
||||
return { plant.plant(world, position) }
|
||||
}
|
||||
}
|
||||
|
||||
data class Bush(val value: BushVariant) : Item() {
|
||||
@ -170,6 +239,11 @@ data class BiomePlaceables(
|
||||
override fun toJson(): JsonElement {
|
||||
return Starbound.gson.toJsonTree(value)
|
||||
}
|
||||
|
||||
override fun createPlacementFunc(world: ServerWorld, random: RandomGenerator, position: Vector2i): () -> Unit {
|
||||
val plant = PlantEntity(value, random)
|
||||
return { plant.plant(world, position) }
|
||||
}
|
||||
}
|
||||
|
||||
data class Tree(val trees: ImmutableList<TreeVariant>) : Item() {
|
||||
@ -182,19 +256,48 @@ data class BiomePlaceables(
|
||||
TreeVariant::class.java
|
||||
).type)
|
||||
}
|
||||
}
|
||||
|
||||
private object PoolTypeToken : TypeToken<WeightedList<Pair<String, JsonElement>>>()
|
||||
override fun createPlacementFunc(world: ServerWorld, random: RandomGenerator, position: Vector2i): () -> Unit {
|
||||
val plant = PlantEntity(trees.random(random), random)
|
||||
return { plant.plant(world, position) }
|
||||
}
|
||||
}
|
||||
|
||||
// This structure sucks, but at least it allows unique parameters per
|
||||
// each object (lmao, whos gonna write world json by hand anyway????
|
||||
// considering this is world generation data.)
|
||||
data class Object(val pool: WeightedList<Pair<String, JsonElement>>) : Item() {
|
||||
data class Object(val pool: WeightedList<Pair<Registry.Ref<ObjectDefinition>, JsonObject>>) : Item() {
|
||||
override val type: BiomePlacementItemType
|
||||
get() = BiomePlacementItemType.OBJECT
|
||||
|
||||
override fun toJson(): JsonElement {
|
||||
return Starbound.gson.toJsonTree(pool, PoolTypeToken.type)
|
||||
return objectPoolAdapter.toJsonTree(pool)
|
||||
}
|
||||
|
||||
override fun createPlacementFunc(world: ServerWorld, random: RandomGenerator, position: Vector2i): () -> Unit {
|
||||
pool.sample(random).ifPresent { (ref, parameters) ->
|
||||
if (ref.isEmpty) {
|
||||
LOGGER.error("Tried to place object '${ref.key}' at ${position}, however, no such object exist")
|
||||
} else {
|
||||
val create = WorldObject.create(ref.entry!!, position, parameters)
|
||||
|
||||
if (create != null) {
|
||||
val direction = Direction.entries[random.nextInt(2)]
|
||||
create.randomize(random, world.template.threatLevel)
|
||||
|
||||
return {
|
||||
val orientation = create.config.value.findValidOrientation(world, position, direction)
|
||||
|
||||
if (orientation != -1) {
|
||||
create.orientationIndex = orientation.toLong()
|
||||
create.joinWorld(world)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { }
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,4 +359,21 @@ data class BiomePlaceables(
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
// required because object : TypeToken<> will compile into wildcard type (because Pair<> is)
|
||||
private val typeToken = TypeToken.getParameterized(
|
||||
WeightedList::class.java,
|
||||
TypeToken.getParameterized(
|
||||
Pair::class.java,
|
||||
TypeToken.getParameterized(Registry.Ref::class.java, ObjectDefinition::class.java).type,
|
||||
JsonObject::class.java
|
||||
).type
|
||||
) as TypeToken<WeightedList<Pair<Registry.Ref<ObjectDefinition>, JsonObject>>>
|
||||
|
||||
private val objectPoolAdapter by lazy {
|
||||
Starbound.gson.getAdapter(typeToken)
|
||||
}
|
||||
}
|
||||
}
|
@ -5,12 +5,15 @@ import com.google.common.collect.ImmutableSet
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import ru.dbotthepony.kommons.collect.filterNotNull
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.collect.WeightedList
|
||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||
import ru.dbotthepony.kstarbound.defs.PerlinNoiseParameters
|
||||
import ru.dbotthepony.kstarbound.defs.dungeon.DungeonDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.item.TreasureChestDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileModifierDefinition
|
||||
import ru.dbotthepony.kstarbound.json.NativeLegacy
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
@ -73,12 +76,23 @@ data class BiomePlaceablesDefinition(
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class TreasureBox(val treasureBoxSets: ImmutableSet<String> = ImmutableSet.of()) : DistributionItemData() {
|
||||
data class TreasureBox(val treasureBoxSets: ImmutableSet<Registry.Ref<TreasureChestDefinition>> = ImmutableSet.of()) : DistributionItemData() {
|
||||
override val type: BiomePlacementItemType
|
||||
get() = BiomePlacementItemType.MICRO_DUNGEON
|
||||
|
||||
val validTreasureBoxSets: ImmutableSet<Registry.Entry<TreasureChestDefinition>> by lazy {
|
||||
treasureBoxSets.stream().filter { it.isPresent }.map { it.entry!! }.collect(ImmutableSet.toImmutableSet())
|
||||
}
|
||||
|
||||
override fun create(biome: BiomeDefinition.CreationParams): BiomePlaceables.Item {
|
||||
return BiomePlaceables.TreasureBox(treasureBoxSets.random(biome.random))
|
||||
// this is quite ugly solution to cases where someone fucked up and specified all treasure chests wrong
|
||||
if (treasureBoxSets.isEmpty()) {
|
||||
return BiomePlaceables.TreasureBox(Registries.treasureChests.emptyRef)
|
||||
} else if (validTreasureBoxSets.isEmpty()) {
|
||||
return BiomePlaceables.TreasureBox(treasureBoxSets.random(biome.random))
|
||||
}
|
||||
|
||||
return BiomePlaceables.TreasureBox(validTreasureBoxSets.random(biome.random).ref)
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +108,7 @@ data class BiomePlaceablesDefinition(
|
||||
throw NoSuchElementException("None of grass variants are valid (candidates: $grasses)")
|
||||
}
|
||||
|
||||
return BiomePlaceables.Grass(GrassVariant.Companion.create(valid.random(biome.random), biome.hueShift))
|
||||
return BiomePlaceables.Grass(GrassVariant.create(valid.random(biome.random), biome.hueShift))
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,7 +239,7 @@ data class BiomePlaceablesDefinition(
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class ObjectPool(val pool: ImmutableList<Pair<Double, String>> = ImmutableList.of(), val parameters: JsonElement = JsonObject())
|
||||
data class ObjectPool(val pool: ImmutableList<Pair<Double, Registry.Ref<ObjectDefinition>>> = ImmutableList.of(), val parameters: JsonObject = JsonObject())
|
||||
|
||||
@JsonFactory
|
||||
data class Object(val objectSets: ImmutableList<ObjectPool>) : DistributionItemData() {
|
||||
@ -284,7 +298,6 @@ data class BiomePlaceablesDefinition(
|
||||
val densityOffset: Double = 2.0,
|
||||
val typePeriod: Double = 10.0,
|
||||
val noiseType: PerlinNoiseParameters.Type = PerlinNoiseParameters.Type.PERLIN,
|
||||
val noiseScale: Int = PerlinNoiseParameters.DEFAULT_SCALE,
|
||||
) : DistributionData() {
|
||||
override val type: BiomePlacementDistributionType
|
||||
get() = BiomePlacementDistributionType.PERIODIC
|
||||
@ -303,7 +316,6 @@ data class BiomePlaceablesDefinition(
|
||||
densityFunction = AbstractPerlinNoise.of(
|
||||
PerlinNoiseParameters(
|
||||
type = noiseType,
|
||||
scale = noiseScale,
|
||||
octaves = octaves,
|
||||
alpha = alpha,
|
||||
beta = beta,
|
||||
@ -318,7 +330,6 @@ data class BiomePlaceablesDefinition(
|
||||
modulusDistortion = AbstractPerlinNoise.of(
|
||||
PerlinNoiseParameters(
|
||||
type = noiseType,
|
||||
scale = noiseScale,
|
||||
octaves = octaves,
|
||||
alpha = alpha,
|
||||
beta = beta,
|
||||
@ -336,7 +347,6 @@ data class BiomePlaceablesDefinition(
|
||||
it to AbstractPerlinNoise.of(
|
||||
PerlinNoiseParameters(
|
||||
type = noiseType,
|
||||
scale = noiseScale,
|
||||
octaves = octaves,
|
||||
alpha = alpha,
|
||||
beta = beta,
|
||||
|
@ -8,7 +8,7 @@ import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageParameters
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFlat
|
||||
|
||||
@ -29,7 +29,7 @@ class BushVariant(
|
||||
val ceiling: Boolean,
|
||||
|
||||
val ephemeral: Boolean,
|
||||
val tileDamageParameters: TileDamageConfig,
|
||||
val tileDamageParameters: TileDamageParameters,
|
||||
) {
|
||||
@JsonFactory(asList = true)
|
||||
data class Shape(val image: String, val mods: ImmutableList<String>)
|
||||
@ -46,7 +46,7 @@ class BushVariant(
|
||||
val mods: ImmutableSet<String> = ImmutableSet.of(),
|
||||
val ceiling: Boolean = false,
|
||||
val ephemeral: Boolean = true,
|
||||
val damageTable: AssetReference<TileDamageConfig>? = null,
|
||||
val damageTable: AssetReference<TileDamageParameters>? = null,
|
||||
val health: Double = 1.0,
|
||||
)
|
||||
|
||||
|
@ -7,7 +7,7 @@ import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageParameters
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFlat
|
||||
|
||||
@ -20,7 +20,7 @@ data class GrassVariant(
|
||||
val descriptions: ImmutableMap<String, String> = ImmutableMap.of(),
|
||||
val ceiling: Boolean,
|
||||
val ephemeral: Boolean,
|
||||
val tileDamageParameters: TileDamageConfig,
|
||||
val tileDamageParameters: TileDamageParameters,
|
||||
) {
|
||||
@JsonFactory
|
||||
data class Data(
|
||||
@ -31,7 +31,7 @@ data class GrassVariant(
|
||||
val ceiling: Boolean = false,
|
||||
val ephemeral: Boolean = true,
|
||||
val description: String = name,
|
||||
val damageTable: AssetReference<TileDamageConfig>? = null,
|
||||
val damageTable: AssetReference<TileDamageParameters>? = null,
|
||||
val health: Double = 1.0
|
||||
) {
|
||||
init {
|
||||
|
@ -2,14 +2,13 @@ package ru.dbotthepony.kstarbound.defs.world
|
||||
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import ru.dbotthepony.kstarbound.Globals
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||
import ru.dbotthepony.kstarbound.defs.ThingDescription
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageParameters
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFlat
|
||||
|
||||
@ -21,15 +20,15 @@ data class TreeVariant(
|
||||
val stemDirectory: String,
|
||||
// may i fucking ask you why do you embed ENTIRE FUCKING FILE in
|
||||
// this struct, Chucklefuck???????
|
||||
val stemSettings: JsonElement,
|
||||
val stemSettings: JsonObject = JsonObject(),
|
||||
val stemHueShift: Double,
|
||||
|
||||
val foliageDirectory: String,
|
||||
// AGAIN.
|
||||
val foliageSettings: JsonElement,
|
||||
val foliageSettings: JsonObject = JsonObject(),
|
||||
val foliageHueShift: Double,
|
||||
|
||||
val descriptions: ImmutableMap<String, String> = ImmutableMap.of(),
|
||||
val descriptions: JsonObject = JsonObject(),
|
||||
val ceiling: Boolean,
|
||||
|
||||
val ephemeral: Boolean,
|
||||
@ -37,7 +36,7 @@ data class TreeVariant(
|
||||
val stemDropConfig: JsonElement,
|
||||
val foliageDropConfig: JsonElement,
|
||||
|
||||
val tileDamageParameters: TileDamageConfig,
|
||||
val tileDamageParameters: TileDamageParameters,
|
||||
) {
|
||||
@JsonFactory
|
||||
data class StemData(
|
||||
@ -55,7 +54,7 @@ data class TreeVariant(
|
||||
@JsonFlat
|
||||
val descriptions: ThingDescription,
|
||||
|
||||
val damageTable: AssetReference<TileDamageConfig>? = null,
|
||||
val damageTable: AssetReference<TileDamageParameters>? = null,
|
||||
val health: Double = 1.0,
|
||||
)
|
||||
|
||||
@ -75,11 +74,11 @@ data class TreeVariant(
|
||||
fun create(data: Registry.Entry<StemData>, stemHueShift: Double): TreeVariant {
|
||||
return TreeVariant(
|
||||
stemDirectory = data.file?.computeDirectory() ?: "/",
|
||||
stemSettings = data.json.deepCopy(),
|
||||
stemSettings = data.json.asJsonObject.deepCopy(),
|
||||
stemHueShift = stemHueShift,
|
||||
ceiling = data.value.ceiling,
|
||||
stemDropConfig = data.value.dropConfig.deepCopy(),
|
||||
descriptions = data.value.descriptions.fixDescription(data.key).toMap(),
|
||||
descriptions = data.value.descriptions.fixDescription(data.key).toJsonObject(),
|
||||
ephemeral = data.value.ephemeral,
|
||||
tileDamageParameters = (data.value.damageTable?.value ?: Globals.treeDamage).copy(totalHealth = data.value.health),
|
||||
|
||||
@ -102,15 +101,15 @@ data class TreeVariant(
|
||||
fun create(data: Registry.Entry<StemData>, stemHueShift: Double, fdata: Registry.Entry<FoliageData>, foliageHueShift: Double): TreeVariant {
|
||||
return TreeVariant(
|
||||
stemDirectory = data.file?.computeDirectory() ?: "/",
|
||||
stemSettings = data.json.deepCopy(),
|
||||
stemSettings = data.json.asJsonObject.deepCopy(),
|
||||
stemHueShift = stemHueShift,
|
||||
ceiling = data.value.ceiling,
|
||||
stemDropConfig = data.value.dropConfig.deepCopy(),
|
||||
descriptions = data.value.descriptions.fixDescription("${data.key} with ${fdata.key}").toMap(),
|
||||
descriptions = data.value.descriptions.fixDescription("${data.key} with ${fdata.key}").toJsonObject(),
|
||||
ephemeral = data.value.ephemeral,
|
||||
tileDamageParameters = (data.value.damageTable?.value ?: Globals.treeDamage).copy(totalHealth = data.value.health),
|
||||
|
||||
foliageSettings = fdata.json,
|
||||
foliageSettings = fdata.json.asJsonObject.deepCopy(),
|
||||
foliageDropConfig = fdata.value.dropConfig.deepCopy(),
|
||||
foliageName = fdata.key,
|
||||
foliageDirectory = fdata.file?.computeDirectory() ?: "/",
|
||||
|
@ -89,8 +89,6 @@ class WorldLayout {
|
||||
WorldGeometry(worldSize, loopX, loopY)
|
||||
}
|
||||
|
||||
private object StartingRegionsToken : TypeToken<ArrayList<AABBi>>()
|
||||
|
||||
@JsonFactory
|
||||
data class SerializedLayer(
|
||||
val yStart: Int,
|
||||
|
@ -171,8 +171,6 @@ class WorldTemplate(val geometry: WorldGeometry) {
|
||||
return geometry.size.y / 2
|
||||
}
|
||||
|
||||
fun seedFor(x: Int, y: Int) = staticRandom64(geometry.x.cell(x), geometry.y.cell(y), seed, "Block")
|
||||
|
||||
class PotentialBiomeItems(
|
||||
// Potential items that would spawn at the given block assuming it is at
|
||||
val surfaceBiomeItems: List<BiomePlaceables.Placement>,
|
||||
|
@ -1,5 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.io
|
||||
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||
import ru.dbotthepony.kommons.io.DelegateSyncher
|
||||
import ru.dbotthepony.kommons.io.StreamCodec
|
||||
import ru.dbotthepony.kommons.io.readBinaryString
|
||||
@ -8,8 +10,11 @@ import ru.dbotthepony.kommons.io.readFloat
|
||||
import ru.dbotthepony.kommons.io.readInt
|
||||
import ru.dbotthepony.kommons.io.readSignedVarInt
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeByteArray
|
||||
import ru.dbotthepony.kommons.io.writeDouble
|
||||
import ru.dbotthepony.kommons.io.writeFloat
|
||||
import ru.dbotthepony.kommons.io.writeInt
|
||||
import ru.dbotthepony.kommons.io.writeSignedVarInt
|
||||
import ru.dbotthepony.kommons.io.writeStruct2d
|
||||
import ru.dbotthepony.kommons.io.writeStruct2f
|
||||
import ru.dbotthepony.kommons.io.writeStruct2i
|
||||
@ -263,3 +268,27 @@ fun DelegateSyncher.vec4d(value: Vector4d, setter: DelegateSetter<Vector4d> = De
|
||||
ListenableDelegate.maskSmart(value, getter, setter), Vector4dCodec)
|
||||
fun DelegateSyncher.vec4f(value: Vector4f, setter: DelegateSetter<Vector4f> = DelegateSetter.passthrough(), getter: DelegateGetter<Vector4f> = DelegateGetter.passthrough()) = Slot(
|
||||
ListenableDelegate.maskSmart(value, getter, setter), Vector4fCodec)
|
||||
|
||||
fun OutputStream.writeEnumStupid(index: Int, isLegacy: Boolean) {
|
||||
if (isLegacy) writeInt(index) else write(index)
|
||||
}
|
||||
|
||||
fun InputStream.readEnumStupid(isLegacy: Boolean): Int {
|
||||
return if (isLegacy) readInt() else readUnsignedByte()
|
||||
}
|
||||
|
||||
fun OutputStream.writeIntStupid(index: Int, isLegacy: Boolean) {
|
||||
if (isLegacy) writeInt(index) else writeSignedVarInt(index)
|
||||
}
|
||||
|
||||
fun InputStream.readIntStupid(isLegacy: Boolean): Int {
|
||||
return if (isLegacy) readInt() else readSignedVarInt()
|
||||
}
|
||||
|
||||
fun OutputStream.writeByteArray(array: ByteArrayList) {
|
||||
writeByteArray(array.elements(), 0, array.size)
|
||||
}
|
||||
|
||||
fun OutputStream.writeByteArray(array: FastByteArrayOutputStream) {
|
||||
writeByteArray(array.array, 0, array.length)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
||||
import java.util.random.RandomGenerator
|
||||
|
||||
interface IContainer {
|
||||
var size: Int
|
||||
@ -49,6 +50,17 @@ interface IContainer {
|
||||
return count
|
||||
}
|
||||
|
||||
fun shuffle(random: RandomGenerator) {
|
||||
for (i in 0 until size) {
|
||||
val rand = random.nextInt(size)
|
||||
val a = this[i]
|
||||
val b = this[rand]
|
||||
|
||||
this[rand] = a
|
||||
this[i] = b
|
||||
}
|
||||
}
|
||||
|
||||
// puts item into container, returns remaining not put items
|
||||
fun add(item: ItemStack, simulate: Boolean = false): ItemStack {
|
||||
val copy = item.copy()
|
||||
|
@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.item
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.gson.contains
|
||||
import ru.dbotthepony.kommons.gson.get
|
||||
@ -18,6 +19,7 @@ import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
||||
import ru.dbotthepony.kstarbound.fromJson
|
||||
import ru.dbotthepony.kstarbound.json.JsonPatch
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.Future
|
||||
|
||||
@ -44,12 +46,24 @@ object ItemRegistry {
|
||||
|
||||
val AIR = Entry("", ItemType.GENERIC, JsonObject(), true, "air", ImmutableSet.of(), ImmutableSet.of(), null)
|
||||
|
||||
private val loggedMisses = Collections.synchronizedSet(ObjectOpenHashSet<String>())
|
||||
|
||||
init {
|
||||
entries[""] = AIR
|
||||
}
|
||||
|
||||
operator fun get(name: String): Entry {
|
||||
return entries[name] ?: AIR
|
||||
val entry = entries[name]
|
||||
|
||||
if (entry == null) {
|
||||
if (loggedMisses.add(name)) {
|
||||
LOGGER.warn("No such item '$name'")
|
||||
}
|
||||
|
||||
return AIR
|
||||
}
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
|
@ -14,6 +14,7 @@ import ru.dbotthepony.kommons.io.readSignedVarLong
|
||||
import ru.dbotthepony.kommons.io.readString
|
||||
import ru.dbotthepony.kommons.io.readVarInt
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import java.io.DataInputStream
|
||||
import java.io.EOFException
|
||||
import java.io.InputStream
|
||||
@ -30,7 +31,7 @@ fun DataInputStream.readJsonElement(): JsonElement {
|
||||
BinaryJsonReader.TYPE_DOUBLE -> JsonPrimitive(readDouble())
|
||||
BinaryJsonReader.TYPE_BOOLEAN -> InternedJsonElementAdapter.of(readBoolean())
|
||||
BinaryJsonReader.TYPE_INT -> JsonPrimitive(readSignedVarLong())
|
||||
BinaryJsonReader.TYPE_STRING -> JsonPrimitive(Starbound.STRINGS.intern(readBinaryString()))
|
||||
BinaryJsonReader.TYPE_STRING -> JsonPrimitive(readInternedString())
|
||||
BinaryJsonReader.TYPE_ARRAY -> readJsonArray()
|
||||
BinaryJsonReader.TYPE_OBJECT -> readJsonObject()
|
||||
else -> throw JsonParseException("Unknown element type $id")
|
||||
|
@ -87,3 +87,7 @@ annotation class JsonImplementation(val implementingClass: KClass<*>)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class JsonSingleton
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class EnumAsInt
|
||||
|
@ -9,6 +9,8 @@ import org.classdump.luna.LuaType
|
||||
import org.classdump.luna.StateContext
|
||||
import org.classdump.luna.Table
|
||||
import org.classdump.luna.Variable
|
||||
import org.classdump.luna.compiler.CompilerChunkLoader
|
||||
import org.classdump.luna.compiler.CompilerSettings
|
||||
import org.classdump.luna.env.RuntimeEnvironments
|
||||
import org.classdump.luna.exec.DirectCallExecutor
|
||||
import org.classdump.luna.impl.DefaultTable
|
||||
@ -29,6 +31,7 @@ import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||
import ru.dbotthepony.kstarbound.lua.bindings.provideRootBindings
|
||||
import ru.dbotthepony.kstarbound.lua.bindings.provideUtilityBindings
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
class LuaEnvironment : StateContext {
|
||||
private var nilMeta: Table? = null
|
||||
@ -280,7 +283,7 @@ class LuaEnvironment : StateContext {
|
||||
return true
|
||||
}
|
||||
|
||||
fun invokeGlobal(name: String, vararg arguments: Any?): Array<out Any?> {
|
||||
fun invokeGlobal(name: String, vararg arguments: Any?): Array<Any?> {
|
||||
if (errorState)
|
||||
return arrayOf()
|
||||
|
||||
@ -299,7 +302,15 @@ class LuaEnvironment : StateContext {
|
||||
return arrayOf()
|
||||
}
|
||||
|
||||
private val loader by lazy { CompilerChunkLoader.of(CompilerSettings.defaultNoAccountingSettings(), "sb_lua${COUNTER.getAndIncrement()}_") }
|
||||
|
||||
// leaks memory until LuaEnvironment goes out of scope. Too bad!
|
||||
fun eval(chunk: String, name: String = "eval"): Array<Any?> {
|
||||
return executor.call(this, loader.compileTextChunk(chunk, name).newInstance(Variable(globals)))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
private val COUNTER = AtomicLong()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
package ru.dbotthepony.kstarbound.lua.bindings
|
||||
|
||||
import org.classdump.luna.Table
|
||||
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
|
||||
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||
|
||||
fun provideServerWorldBindings(self: ServerWorld, callbacks: Table, lua: LuaEnvironment) {
|
||||
|
||||
}
|
@ -586,7 +586,3 @@ fun provideWorldBindings(self: World<*, *>, lua: LuaEnvironment) {
|
||||
provideServerWorldBindings(self, callbacks, lua)
|
||||
}
|
||||
}
|
||||
|
||||
private fun provideServerWorldBindings(self: ServerWorld, callbacks: Table, lua: LuaEnvironment) {
|
||||
|
||||
}
|
||||
|
@ -1,22 +1,28 @@
|
||||
package ru.dbotthepony.kstarbound.network.syncher
|
||||
|
||||
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||
import ru.dbotthepony.kommons.io.StreamCodec
|
||||
import ru.dbotthepony.kommons.io.readByteArray
|
||||
import ru.dbotthepony.kommons.io.readVarInt
|
||||
import ru.dbotthepony.kommons.io.writeVarInt
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kstarbound.collect.RandomListIterator
|
||||
import ru.dbotthepony.kstarbound.collect.RandomSubList
|
||||
import ru.dbotthepony.kstarbound.io.writeByteArray
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
// original engine does not have "networked list", so it is always networked
|
||||
// the dumb way on legacy protocol
|
||||
// "extraStupid" will wrap data in extra byte array on legacy protocol
|
||||
class NetworkedList<E>(
|
||||
val codec: StreamCodec<E>,
|
||||
val legacyCodec: StreamCodec<E> = codec,
|
||||
private val maxBacklogSize: Int = 100,
|
||||
private val elementsFactory: (Int) -> MutableList<E> = ::ArrayList
|
||||
private val elementsFactory: (Int) -> MutableList<E> = ::ArrayList,
|
||||
private val extraStupid: Boolean = false,
|
||||
) : NetworkedElement(), MutableList<E> {
|
||||
private val backlog = ArrayDeque<Pair<Long, Entry<E>>>()
|
||||
private val elements = elementsFactory(10)
|
||||
@ -48,6 +54,16 @@ class NetworkedList<E>(
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
/**
|
||||
* re-networks element at [index]
|
||||
*/
|
||||
fun markDirtyAtIndex(index: Int) {
|
||||
val element = this[index]
|
||||
backlog.add(currentVersion() to Entry(index))
|
||||
backlog.add(currentVersion() to Entry(index, element))
|
||||
purgeBacklog()
|
||||
}
|
||||
|
||||
private fun purgeBacklog() {
|
||||
while (backlog.size >= maxBacklogSize) {
|
||||
backlog.removeFirst()
|
||||
@ -75,17 +91,18 @@ class NetworkedList<E>(
|
||||
queue.clear()
|
||||
elements.clear()
|
||||
|
||||
val count = data.readVarInt()
|
||||
val stream = if (isLegacy && extraStupid) DataInputStream(FastByteArrayInputStream(data.readByteArray())) else data
|
||||
val count = stream.readVarInt()
|
||||
|
||||
if (isLegacy) {
|
||||
for (i in 0 until count) {
|
||||
val read = legacyCodec.read(data)
|
||||
val read = legacyCodec.read(stream)
|
||||
elements.add(read)
|
||||
backlog.add(currentVersion() to Entry(elements.size - 1, read))
|
||||
}
|
||||
} else {
|
||||
for (i in 0 until count) {
|
||||
val read = codec.read(data)
|
||||
val read = codec.read(stream)
|
||||
elements.add(read)
|
||||
backlog.add(currentVersion() to Entry(elements.size - 1, read))
|
||||
}
|
||||
@ -97,12 +114,23 @@ class NetworkedList<E>(
|
||||
|
||||
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
||||
val latest = latestState()
|
||||
data.writeVarInt(latest.size)
|
||||
|
||||
if (isLegacy) {
|
||||
latest.forEach { legacyCodec.write(data, it) }
|
||||
if (isLegacy && extraStupid) {
|
||||
val stream = FastByteArrayOutputStream()
|
||||
val dstream = DataOutputStream(stream)
|
||||
|
||||
dstream.writeVarInt(latest.size)
|
||||
latest.forEach { legacyCodec.write(dstream, it) }
|
||||
|
||||
data.writeByteArray(stream)
|
||||
} else {
|
||||
latest.forEach { codec.write(data, it) }
|
||||
data.writeVarInt(latest.size)
|
||||
|
||||
if (isLegacy) {
|
||||
latest.forEach { legacyCodec.write(data, it) }
|
||||
} else {
|
||||
latest.forEach { codec.write(data, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,19 @@
|
||||
package ru.dbotthepony.kstarbound.server.world
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.future.await
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.arrays.Object2DArray
|
||||
import ru.dbotthepony.kommons.gson.JsonArray
|
||||
import ru.dbotthepony.kommons.gson.set
|
||||
import ru.dbotthepony.kommons.guava.immutableList
|
||||
import ru.dbotthepony.kstarbound.math.AABBi
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
@ -33,14 +36,18 @@ import ru.dbotthepony.kstarbound.defs.world.Biome
|
||||
import ru.dbotthepony.kstarbound.defs.world.BiomePlaceables
|
||||
import ru.dbotthepony.kstarbound.defs.world.FloatingDungeonWorldParameters
|
||||
import ru.dbotthepony.kstarbound.defs.world.WorldTemplate
|
||||
import ru.dbotthepony.kstarbound.json.jsonArrayOf
|
||||
import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.TileDamageUpdatePacket
|
||||
import ru.dbotthepony.kstarbound.util.ExecutionTimePacer
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.util.random.staticRandom64
|
||||
import ru.dbotthepony.kstarbound.util.random.staticRandomDouble
|
||||
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF
|
||||
import ru.dbotthepony.kstarbound.world.Chunk
|
||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||
import ru.dbotthepony.kstarbound.world.ChunkState
|
||||
import ru.dbotthepony.kstarbound.world.Direction
|
||||
import ru.dbotthepony.kstarbound.world.IChunkListener
|
||||
import ru.dbotthepony.kstarbound.world.TileHealth
|
||||
import ru.dbotthepony.kstarbound.world.api.AbstractCell
|
||||
@ -50,17 +57,19 @@ import ru.dbotthepony.kstarbound.world.api.MutableTileState
|
||||
import ru.dbotthepony.kstarbound.world.api.TileColor
|
||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.tile.PlantEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.function.Predicate
|
||||
import java.util.function.Supplier
|
||||
import java.util.random.RandomGenerator
|
||||
import kotlin.concurrent.withLock
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.math.min
|
||||
|
||||
class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, ServerChunk>(world, pos) {
|
||||
override var state: ChunkState = ChunkState.FRESH
|
||||
@ -185,10 +194,15 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
||||
CompletableFuture.runAsync(Runnable { finalizeCells() }, Starbound.EXECUTOR).await()
|
||||
|
||||
// skip if we have no layout
|
||||
if (world.template.worldLayout != null && world.template.worldParameters !is FloatingDungeonWorldParameters) {
|
||||
if (world.template.worldLayout != null) {
|
||||
placeGrass()
|
||||
}
|
||||
|
||||
// skip if we have no layout, or it is a floating dungeon world
|
||||
if (world.template.worldLayout != null && world.template.worldParameters !is FloatingDungeonWorldParameters) {
|
||||
placeBiomeItems()
|
||||
}
|
||||
|
||||
signalChunkContentsUpdated()
|
||||
}
|
||||
|
||||
@ -717,13 +731,14 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
||||
pos.tile + Vector2i(width + CHUNK_SIZE_FF, height + CHUNK_SIZE_FF)
|
||||
)
|
||||
|
||||
val pacer = ExecutionTimePacer(500_000L, 40L)
|
||||
val random = random(staticRandom64(world.template.seed, pos.x, pos.y, "microdungeon placement"))
|
||||
|
||||
for (placement in placements) {
|
||||
if (placement.item is BiomePlaceables.MicroDungeon) {
|
||||
if (placement.item.microdungeons.isEmpty())
|
||||
continue // ???
|
||||
|
||||
val seed = world.template.seedFor(placement.position.x, placement.position.y)
|
||||
val random = random(seed)
|
||||
val dungeon = placement.item.microdungeons.elementAt(random.nextInt(placement.item.microdungeons.size))
|
||||
|
||||
if (dungeon.isEmpty) {
|
||||
@ -763,7 +778,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
||||
|
||||
// some breathing room for other code, since placement checking is performance intense operation
|
||||
if (!world.isInPreparation && world.clients.isNotEmpty())
|
||||
delay(min(60L, anchor.reader.size.x * anchor.reader.size.y / 100L))
|
||||
pacer.measureAndSuspend()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1087,6 +1102,43 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun placeBiomeItems() {
|
||||
val placements = CompletableFuture.supplyAsync(Supplier {
|
||||
val placements = ArrayList<BiomePlaceables.Placement>()
|
||||
|
||||
for (x in 0 until width) {
|
||||
for (y in 0 until height) {
|
||||
if (cells.value[x, y].dungeonId == NO_DUNGEON_ID) {
|
||||
placements.addAll(world.template.validBiomeItemsAt(pos.tileX + x, pos.tileY + y))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
placements.sortByDescending { it.priority }
|
||||
|
||||
val random = random(staticRandom64(world.template.seed, pos.x, pos.y, "biome placement"))
|
||||
val funcs = ArrayList<() -> Unit>()
|
||||
|
||||
for (placement in placements) {
|
||||
try {
|
||||
funcs.add(placement.item.createPlacementFunc(world, random, placement.position))
|
||||
} catch (err: Throwable) {
|
||||
LOGGER.error("Exception while evaluating biome placeables for chunk $pos in $world", err)
|
||||
}
|
||||
}
|
||||
|
||||
funcs
|
||||
}, Starbound.EXECUTOR).await()
|
||||
|
||||
for (placement in placements) {
|
||||
try {
|
||||
placement()
|
||||
} catch (err: Throwable) {
|
||||
LOGGER.error("Exception while placing biome placeables for chunk $pos in $world", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
|
||||
|
@ -7,7 +7,6 @@ import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.future.asCompletableFuture
|
||||
import kotlinx.coroutines.future.await
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kstarbound.math.AABB
|
||||
@ -37,7 +36,7 @@ import ru.dbotthepony.kstarbound.server.StarboundServer
|
||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||
import ru.dbotthepony.kstarbound.util.BlockableEventLoop
|
||||
import ru.dbotthepony.kstarbound.util.Pacer
|
||||
import ru.dbotthepony.kstarbound.util.ActionPacer
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||
import ru.dbotthepony.kstarbound.world.ChunkState
|
||||
@ -164,7 +163,7 @@ class ServerWorld private constructor(
|
||||
/**
|
||||
* this method does not block if pacer is null (safe to use with runBlocking {})
|
||||
*/
|
||||
suspend fun damageTiles(positions: Collection<IStruct2i>, isBackground: Boolean, sourcePosition: Vector2d, damage: TileDamage, source: AbstractEntity? = null, pacer: Pacer? = null): TileDamageResult {
|
||||
suspend fun damageTiles(positions: Collection<IStruct2i>, isBackground: Boolean, sourcePosition: Vector2d, damage: TileDamage, source: AbstractEntity? = null, pacer: ActionPacer? = null): TileDamageResult {
|
||||
if (damage.amount <= 0.0)
|
||||
return TileDamageResult.NONE
|
||||
|
||||
@ -235,7 +234,7 @@ class ServerWorld private constructor(
|
||||
return runBlocking { applyTileModifications(modifications, allowEntityOverlap, ignoreTileProtection, null) }
|
||||
}
|
||||
|
||||
suspend fun applyTileModifications(modifications: Collection<Pair<Vector2i, TileModification>>, allowEntityOverlap: Boolean, ignoreTileProtection: Boolean = false, pacer: Pacer?): List<Pair<Vector2i, TileModification>> {
|
||||
suspend fun applyTileModifications(modifications: Collection<Pair<Vector2i, TileModification>>, allowEntityOverlap: Boolean, ignoreTileProtection: Boolean = false, pacer: ActionPacer?): List<Pair<Vector2i, TileModification>> {
|
||||
val unapplied = ArrayList(modifications)
|
||||
var size: Int
|
||||
|
||||
|
@ -9,7 +9,6 @@ import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
@ -37,7 +36,7 @@ import ru.dbotthepony.kstarbound.network.packets.clientbound.TileModificationFai
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.WorldStartPacket
|
||||
import ru.dbotthepony.kstarbound.network.packets.clientbound.WorldStopPacket
|
||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||
import ru.dbotthepony.kstarbound.util.Pacer
|
||||
import ru.dbotthepony.kstarbound.util.ActionPacer
|
||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||
import ru.dbotthepony.kstarbound.world.IChunkListener
|
||||
import ru.dbotthepony.kstarbound.world.TileHealth
|
||||
@ -76,8 +75,8 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
|
||||
}
|
||||
|
||||
private data class DamageTileEntry(val positions: Collection<Vector2i>, val isBackground: Boolean, val sourcePosition: Vector2d, val damage: TileDamage, val source: AbstractEntity? = null)
|
||||
private val damageTilesQueue = Channel<DamageTileEntry>(64) // 64 pending tile damages should be enough
|
||||
private val tileModificationBudget = Pacer.actionsPerSecond(actions = 512, handicap = 2048) // TODO: make this configurable
|
||||
private val damageTilesQueue = Channel<DamageTileEntry>(64) // 64 pending tile group damage requests should be more than enough
|
||||
private val tileModificationBudget = ActionPacer(actions = 512, handicap = 2048) // TODO: make this configurable
|
||||
private val modifyTilesQueue = Channel<Pair<Collection<Pair<Vector2i, TileModification>>, Boolean>>(64)
|
||||
|
||||
private suspend fun damageTilesLoop() {
|
||||
|
@ -0,0 +1,26 @@
|
||||
package ru.dbotthepony.kstarbound.util
|
||||
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
/**
|
||||
* Allows to perform up to certain amount of actions per given time window,
|
||||
* otherwise starts throttling
|
||||
*/
|
||||
class ActionPacer(actions: Int, handicap: Int = 0) {
|
||||
private val delayBetween = 1_000_000_000L / actions
|
||||
private val maxBackwardNanos = handicap * delayBetween
|
||||
private var currentTime = System.nanoTime() - maxBackwardNanos
|
||||
|
||||
suspend fun consume(actions: Int = 1) {
|
||||
require(actions >= 1) { "Invalid amount of actions to consume: $actions" }
|
||||
val time = System.nanoTime()
|
||||
|
||||
if (time - currentTime > maxBackwardNanos)
|
||||
currentTime = time - maxBackwardNanos
|
||||
|
||||
currentTime += delayBetween * (actions - 1)
|
||||
val diff = (currentTime - time) / 1_000_000L
|
||||
currentTime += delayBetween
|
||||
if (diff > 0L) delay(diff)
|
||||
}
|
||||
}
|
@ -221,7 +221,7 @@ open class BlockableEventLoop(name: String) : Thread(name), ScheduledExecutorSer
|
||||
}
|
||||
|
||||
fun ensureSameThread() {
|
||||
check(this === currentThread()) { "Performing non-threadsafe operation outside of event loop thread" }
|
||||
check(this === currentThread()) { "Performing non-threadsafe operation outside of event loop thread $this" }
|
||||
}
|
||||
|
||||
fun isSameThread() = this === currentThread()
|
||||
|
@ -0,0 +1,14 @@
|
||||
package ru.dbotthepony.kstarbound.util
|
||||
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
class ExecutionTimePacer(private val budget: Long, private val pause: Long) {
|
||||
private var origin = System.nanoTime()
|
||||
|
||||
suspend fun measureAndSuspend() {
|
||||
if (System.nanoTime() - origin >= budget) {
|
||||
delay(pause)
|
||||
origin = System.nanoTime()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.util
|
||||
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
/**
|
||||
* Allows to perform up to [maxForward] actions per given time window,
|
||||
* otherwise pauses execution
|
||||
*/
|
||||
class Pacer(val maxForward: Int, val delayBetween: Long) {
|
||||
private val maxForwardNanos = maxForward * delayBetween
|
||||
private var currentTime = System.nanoTime() - maxForwardNanos
|
||||
|
||||
suspend fun consume(actions: Int = 1) {
|
||||
require(actions >= 1) { "Invalid amount of actions to consume: $actions" }
|
||||
val time = System.nanoTime()
|
||||
|
||||
if (time - currentTime > maxForwardNanos)
|
||||
currentTime = time - maxForwardNanos
|
||||
|
||||
currentTime += delayBetween * (actions - 1)
|
||||
val diff = (currentTime - time) / 1_000_000L
|
||||
currentTime += delayBetween
|
||||
if (diff > 0L) delay(diff)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun actionsPerSecond(actions: Int, handicap: Int = 0): Pacer {
|
||||
return Pacer(handicap, 1_000_000_000L / actions)
|
||||
}
|
||||
}
|
||||
}
|
@ -36,16 +36,16 @@ abstract class AbstractPerlinNoise(val parameters: PerlinNoiseParameters) {
|
||||
|
||||
protected data class Setup(val b0: Int, val b1: Int, val r0: Double, val r1: Double)
|
||||
|
||||
protected val p by lazy(LazyThreadSafetyMode.NONE) { IntArray(parameters.scale * 2 + 2) }
|
||||
protected val g1 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(parameters.scale * 2 + 2) }
|
||||
protected val p by lazy(LazyThreadSafetyMode.NONE) { IntArray(SCALE * 2 + 2) }
|
||||
protected val g1 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(SCALE * 2 + 2) }
|
||||
|
||||
// flat arrays for performance
|
||||
protected val g2_0 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(parameters.scale * 2 + 2) }
|
||||
protected val g2_1 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(parameters.scale * 2 + 2) }
|
||||
protected val g2_0 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(SCALE * 2 + 2) }
|
||||
protected val g2_1 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(SCALE * 2 + 2) }
|
||||
|
||||
protected val g3_0 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(parameters.scale * 2 + 2) }
|
||||
protected val g3_1 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(parameters.scale * 2 + 2) }
|
||||
protected val g3_2 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(parameters.scale * 2 + 2) }
|
||||
protected val g3_0 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(SCALE * 2 + 2) }
|
||||
protected val g3_1 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(SCALE * 2 + 2) }
|
||||
protected val g3_2 by lazy(LazyThreadSafetyMode.NONE) { DoubleArray(SCALE * 2 + 2) }
|
||||
|
||||
private var init = false
|
||||
private val initLock = Any()
|
||||
@ -99,17 +99,17 @@ abstract class AbstractPerlinNoise(val parameters: PerlinNoiseParameters) {
|
||||
|
||||
val random = random(seed)
|
||||
|
||||
for (i in 0 until parameters.scale) {
|
||||
for (i in 0 until SCALE) {
|
||||
p[i] = i
|
||||
|
||||
g1[i] = random.nextInt(-parameters.scale, parameters.scale) / parameters.scale.toDouble()
|
||||
g1[i] = random.nextInt(-SCALE, SCALE) / SCALE.toDouble()
|
||||
|
||||
g2_0[i] = random.nextInt(-parameters.scale, parameters.scale) / parameters.scale.toDouble()
|
||||
g2_1[i] = random.nextInt(-parameters.scale, parameters.scale) / parameters.scale.toDouble()
|
||||
g2_0[i] = random.nextInt(-SCALE, SCALE) / SCALE.toDouble()
|
||||
g2_1[i] = random.nextInt(-SCALE, SCALE) / SCALE.toDouble()
|
||||
|
||||
g3_0[i] = random.nextInt(-parameters.scale, parameters.scale) / parameters.scale.toDouble()
|
||||
g3_1[i] = random.nextInt(-parameters.scale, parameters.scale) / parameters.scale.toDouble()
|
||||
g3_2[i] = random.nextInt(-parameters.scale, parameters.scale) / parameters.scale.toDouble()
|
||||
g3_0[i] = random.nextInt(-SCALE, SCALE) / SCALE.toDouble()
|
||||
g3_1[i] = random.nextInt(-SCALE, SCALE) / SCALE.toDouble()
|
||||
g3_2[i] = random.nextInt(-SCALE, SCALE) / SCALE.toDouble()
|
||||
|
||||
val l2 = sqrt(g2_0[i] * g2_0[i] + g2_1[i] * g2_1[i])
|
||||
val l3 = sqrt(g3_0[i] * g3_0[i] + g3_1[i] * g3_1[i] + g3_2[i] * g3_2[i])
|
||||
@ -133,23 +133,23 @@ abstract class AbstractPerlinNoise(val parameters: PerlinNoiseParameters) {
|
||||
}
|
||||
}
|
||||
|
||||
for (i in parameters.scale downTo 1) {
|
||||
for (i in SCALE downTo 1) {
|
||||
val k = p[i]
|
||||
val j = random.nextInt(0, parameters.scale - 1)
|
||||
val j = random.nextInt(0, SCALE - 1)
|
||||
p[i] = p[j]
|
||||
p[j] = k
|
||||
}
|
||||
|
||||
for (i in 0 until parameters.scale + 2) {
|
||||
p[parameters.scale + i] = p[i]
|
||||
g1[parameters.scale + i] = g1[i]
|
||||
for (i in 0 until SCALE + 2) {
|
||||
p[SCALE + i] = p[i]
|
||||
g1[SCALE + i] = g1[i]
|
||||
|
||||
g2_0[parameters.scale + i] = g2_0[i]
|
||||
g2_1[parameters.scale + i] = g2_1[i]
|
||||
g2_0[SCALE + i] = g2_0[i]
|
||||
g2_1[SCALE + i] = g2_1[i]
|
||||
|
||||
g3_0[parameters.scale + i] = g3_0[i]
|
||||
g3_1[parameters.scale + i] = g3_1[i]
|
||||
g3_2[parameters.scale + i] = g3_2[i]
|
||||
g3_0[SCALE + i] = g3_0[i]
|
||||
g3_1[SCALE + i] = g3_1[i]
|
||||
g3_2[SCALE + i] = g3_2[i]
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,8 +163,8 @@ abstract class AbstractPerlinNoise(val parameters: PerlinNoiseParameters) {
|
||||
val iv: Int = floor(value).toInt()
|
||||
val fv: Double = value - iv
|
||||
|
||||
val b0 = iv and (parameters.scale - 1)
|
||||
val b1 = (iv + 1) and (parameters.scale - 1)
|
||||
val b0 = iv and (SCALE - 1)
|
||||
val b1 = (iv + 1) and (SCALE - 1)
|
||||
val r1 = fv - 1.0
|
||||
|
||||
return Setup(b0, b1, fv, r1)
|
||||
@ -260,6 +260,8 @@ abstract class AbstractPerlinNoise(val parameters: PerlinNoiseParameters) {
|
||||
}
|
||||
|
||||
companion object : TypeAdapterFactory {
|
||||
const val SCALE = 512
|
||||
|
||||
fun of(parameters: PerlinNoiseParameters): AbstractPerlinNoise {
|
||||
return when (parameters.type) {
|
||||
PerlinNoiseParameters.Type.PERLIN -> PerlinNoise(parameters)
|
||||
|
@ -235,6 +235,10 @@ fun RandomGenerator.nextRange(range: IStruct2i): Int {
|
||||
return if (range.component1() == range.component2()) return range.component1() else nextInt(range.component1(), range.component2())
|
||||
}
|
||||
|
||||
fun RandomGenerator.nextRange(min: Int, max: Int): Int {
|
||||
return if (min == max) return min else nextInt(min, max)
|
||||
}
|
||||
|
||||
fun RandomGenerator.nextRange(range: IStruct2d): Double {
|
||||
return if (range.component1() == range.component2()) return range.component1() else nextDouble(range.component1(), range.component2())
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageParameters
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageType
|
||||
import ru.dbotthepony.kstarbound.math.Interpolator
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||
@ -81,7 +81,7 @@ sealed class TileHealth() {
|
||||
damageEffectTimeFactor = 0.0
|
||||
}
|
||||
|
||||
fun damage(config: TileDamageConfig, source: Vector2d, damage: TileDamage) {
|
||||
fun damage(config: TileDamageParameters, source: Vector2d, damage: TileDamage) {
|
||||
val actualDamage = config.damageDone(damage) / config.totalHealth
|
||||
damagePercent = (damagePercent + actualDamage).coerceAtMost(1.0)
|
||||
isHarvested = damage.harvestLevel >= config.harvestLevel
|
||||
@ -97,7 +97,7 @@ sealed class TileHealth() {
|
||||
val isTicking: Boolean
|
||||
get() = !isHealthy && !isDead
|
||||
|
||||
fun tick(config: TileDamageConfig, delta: Double): Boolean {
|
||||
fun tick(config: TileDamageParameters, delta: Double): Boolean {
|
||||
if (isDead || isHealthy)
|
||||
return false
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
package ru.dbotthepony.kstarbound.world.api
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Interner
|
||||
import ru.dbotthepony.kommons.io.writeStruct2i
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.tile.NO_DUNGEON_ID
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState
|
||||
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
||||
import java.io.DataInputStream
|
||||
@ -29,6 +31,10 @@ sealed class AbstractCell {
|
||||
|
||||
abstract val biomeTransition: Boolean
|
||||
|
||||
// If set, a plant or object is rooted to the tile and tile damage
|
||||
// should be redirected to this position
|
||||
abstract val rootSource: Vector2i?
|
||||
|
||||
abstract fun immutable(): ImmutableCell
|
||||
abstract fun mutable(): MutableCell
|
||||
|
||||
@ -53,14 +59,19 @@ sealed class AbstractCell {
|
||||
background.write(stream)
|
||||
liquid.write(stream)
|
||||
|
||||
stream.write(0) // collisionMap
|
||||
stream.write(foreground.material.value.collisionKind.ordinal)
|
||||
|
||||
stream.writeShort(dungeonId)
|
||||
stream.writeByte(blockBiome)
|
||||
stream.writeByte(envBiome)
|
||||
stream.writeBoolean(biomeTransition)
|
||||
|
||||
stream.write(0) // unknown
|
||||
if (rootSource == null) {
|
||||
stream.writeBoolean(false)
|
||||
} else {
|
||||
stream.writeBoolean(true)
|
||||
stream.writeStruct2i(rootSource!!)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.world.api
|
||||
|
||||
import ru.dbotthepony.kstarbound.defs.tile.NO_DUNGEON_ID
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState
|
||||
|
||||
data class ImmutableCell(
|
||||
@ -12,6 +13,7 @@ data class ImmutableCell(
|
||||
override val blockBiome: Int = 0,
|
||||
override val envBiome: Int = 0,
|
||||
override val biomeTransition: Boolean = false,
|
||||
override val rootSource: Vector2i? = null,
|
||||
) : AbstractCell() {
|
||||
override fun immutable(): ImmutableCell {
|
||||
return this
|
||||
|
@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.world.api
|
||||
|
||||
import ru.dbotthepony.kstarbound.io.readVector2i
|
||||
import ru.dbotthepony.kstarbound.defs.tile.NO_DUNGEON_ID
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
import java.io.DataInputStream
|
||||
|
||||
data class MutableCell(
|
||||
@ -13,6 +14,7 @@ data class MutableCell(
|
||||
override var blockBiome: Int = 0,
|
||||
override var envBiome: Int = 0,
|
||||
override var biomeTransition: Boolean = false,
|
||||
override var rootSource: Vector2i? = null,
|
||||
) : AbstractCell() {
|
||||
fun readLegacy(stream: DataInputStream, version: Int = 419): MutableCell {
|
||||
foreground.read(stream)
|
||||
@ -33,10 +35,12 @@ data class MutableCell(
|
||||
|
||||
if (version < 418) {
|
||||
stream.skipNBytes(1) // leftover
|
||||
rootSource = null
|
||||
} else {
|
||||
// TODO: root source
|
||||
if (stream.readBoolean()) {
|
||||
stream.readVector2i()
|
||||
rootSource = stream.readVector2i()
|
||||
} else {
|
||||
rootSource = null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,8 +72,8 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
|
||||
|
||||
abstract val type: EntityType
|
||||
|
||||
open val isEphemeral: Boolean
|
||||
get() = false
|
||||
var isEphemeral: Boolean = false
|
||||
protected set
|
||||
|
||||
/**
|
||||
* If set, then the entity will be discoverable by its unique id and will be
|
||||
|
@ -143,6 +143,8 @@ class ContainerObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(co
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items.shuffle(random)
|
||||
}
|
||||
|
||||
override fun randomize(random: RandomGenerator, threatLevel: Double) {
|
||||
|
@ -0,0 +1,751 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities.tile
|
||||
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.TypeAdapter
|
||||
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
import org.apache.logging.log4j.LogManager
|
||||
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.kommons.io.readByteArray
|
||||
import ru.dbotthepony.kommons.io.readSignedVarInt
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeSignedVarInt
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||
import ru.dbotthepony.kstarbound.defs.image.Image
|
||||
import ru.dbotthepony.kstarbound.defs.tile.NO_DUNGEON_ID
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageParameters
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isEmptyTile
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isMetaTile
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile
|
||||
import ru.dbotthepony.kstarbound.defs.tile.isNullTile
|
||||
import ru.dbotthepony.kstarbound.defs.world.BushVariant
|
||||
import ru.dbotthepony.kstarbound.defs.world.GrassVariant
|
||||
import ru.dbotthepony.kstarbound.defs.world.TreeVariant
|
||||
import ru.dbotthepony.kstarbound.io.readDouble
|
||||
import ru.dbotthepony.kstarbound.io.readEnumStupid
|
||||
import ru.dbotthepony.kstarbound.io.readIntStupid
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import ru.dbotthepony.kstarbound.io.readVector2d
|
||||
import ru.dbotthepony.kstarbound.io.writeDouble
|
||||
import ru.dbotthepony.kstarbound.io.writeEnumStupid
|
||||
import ru.dbotthepony.kstarbound.io.writeIntStupid
|
||||
import ru.dbotthepony.kstarbound.io.writeStruct2d
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.json.readJsonElement
|
||||
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
||||
import ru.dbotthepony.kstarbound.math.AABB
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedList
|
||||
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
|
||||
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedEventCounter
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedFloat
|
||||
import ru.dbotthepony.kstarbound.server.world.ServerWorld
|
||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||
import ru.dbotthepony.kstarbound.util.random.nextRange
|
||||
import ru.dbotthepony.kstarbound.util.random.random
|
||||
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNIT
|
||||
import ru.dbotthepony.kstarbound.world.TileHealth
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.*
|
||||
import java.util.random.RandomGenerator
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
class PlantEntity() : TileEntity() {
|
||||
@JsonFactory
|
||||
data class Piece(
|
||||
val image: String,
|
||||
val offset: Vector2d,
|
||||
var segmentIdx: Int,
|
||||
val isStructuralSegment: Boolean,
|
||||
val kind: Kind,
|
||||
val rotationType: Rotation = Rotation.DONT_ROTATE,
|
||||
val rotationOffset: Double = 0.0,
|
||||
val flip: Boolean = false,
|
||||
) {
|
||||
// no need to serialize
|
||||
var imageSize: Vector2i = Vector2i.ZERO
|
||||
var spaces: Set<Vector2i> = setOf()
|
||||
var zLevel: Double = 0.0
|
||||
|
||||
// int32_t
|
||||
enum class Kind {
|
||||
NONE, STEM, FOLIAGE
|
||||
}
|
||||
|
||||
// int32_t
|
||||
enum class Rotation(override val jsonName: String) : IStringSerializable {
|
||||
DONT_ROTATE("dontRotate"),
|
||||
ROTATE_BRANCH("rotateBranch"),
|
||||
ROTATE_LEAVES("rotateLeaves"),
|
||||
ROTATE_CROWN_BRANCH("rotateCrownBranch"),
|
||||
ROTATE_CROWN_LEAVES("rotateCrownLeaves")
|
||||
}
|
||||
|
||||
// lmao this order of writing has almost zero correlation with how those
|
||||
// fields are declared in struct {} itself
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeBinaryString(image)
|
||||
stream.writeStruct2d(offset, isLegacy)
|
||||
|
||||
stream.writeEnumStupid(rotationType.ordinal, isLegacy)
|
||||
stream.writeDouble(rotationOffset, isLegacy)
|
||||
stream.writeBoolean(isStructuralSegment)
|
||||
stream.writeEnumStupid(kind.ordinal, isLegacy)
|
||||
stream.writeIntStupid(segmentIdx, isLegacy)
|
||||
stream.writeBoolean(flip)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC = nativeCodec(::read, Piece::write)
|
||||
val LEGACY_CODEC = legacyCodec(::read, Piece::write)
|
||||
|
||||
val ADAPTER: TypeAdapter<Piece> by lazy { Starbound.gson.getAdapter(Piece::class.java) }
|
||||
|
||||
fun read(stream: DataInputStream, isLegacy: Boolean): Piece {
|
||||
val image = stream.readInternedString()
|
||||
val offset = stream.readVector2d(isLegacy)
|
||||
val rotationType = Rotation.entries[stream.readEnumStupid(isLegacy)]
|
||||
val rotationOffset = stream.readDouble(isLegacy)
|
||||
val isStructuralSegment = stream.readBoolean()
|
||||
val kind = Kind.entries[stream.readEnumStupid(isLegacy)]
|
||||
val segmentIndex = stream.readIntStupid(isLegacy)
|
||||
val flip = stream.readBoolean()
|
||||
|
||||
return Piece(
|
||||
image = image,
|
||||
offset = offset,
|
||||
rotationType = rotationType,
|
||||
rotationOffset = rotationOffset,
|
||||
isStructuralSegment = isStructuralSegment,
|
||||
kind = kind,
|
||||
segmentIdx = segmentIndex,
|
||||
flip = flip,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun deserialize(data: JsonObject) {
|
||||
super.deserialize(data)
|
||||
|
||||
isCeiling = data.get("ceiling", false)
|
||||
stemDropConfig = data["stemDropConfig"] as? JsonObject ?: JsonObject()
|
||||
foliageDropConfig = data["foliageDropConfig"] as? JsonObject ?: JsonObject()
|
||||
saplingDropConfig = data["saplingDropConfig"] as? JsonObject ?: JsonObject()
|
||||
descriptions = data["descriptions"] as? JsonObject ?: JsonObject()
|
||||
isEphemeral = data.get("ephemeral", false)
|
||||
fallsWhenDead = data.get("fallsWhenDead", false)
|
||||
tileDamageParameters = data.get("tileDamageParameters", damageParameters)
|
||||
|
||||
this.piecesInternal.clear()
|
||||
|
||||
for (v in data.get("pieces", JsonArray())) {
|
||||
this.piecesInternal.add(Piece.ADAPTER.fromJsonTree((v as JsonObject).apply {
|
||||
val kind = this["kind"]
|
||||
|
||||
// holy shiiiiieeeet
|
||||
if (kind is JsonPrimitive && kind.isNumber) {
|
||||
this["kind"] = Piece.Kind.entries[kind.asInt].name
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
scanSpacesAndRoots()
|
||||
}
|
||||
|
||||
override fun serialize(): JsonObject {
|
||||
val data = super.serialize()
|
||||
|
||||
data["ceiling"] = isCeiling
|
||||
data["stemDropConfig"] = stemDropConfig.deepCopy()
|
||||
data["foliageDropConfig"] = foliageDropConfig.deepCopy()
|
||||
data["saplingDropConfig"] = saplingDropConfig.deepCopy()
|
||||
data["descriptions"] = descriptions.deepCopy()
|
||||
data["ephemeral"] = isEphemeral
|
||||
data["fallsWhenDead"] = fallsWhenDead
|
||||
data["tileDamageParameters"] = damageParameters.toJsonTree(tileDamageParameters)
|
||||
|
||||
// holy shiiiiieeeet
|
||||
data["pieces"] = piecesInternal.stream()
|
||||
.map { Piece.ADAPTER.toJsonTree(it) as JsonObject }
|
||||
.peek { it["kind"] = Piece.Kind.valueOf(it["kind"].asString).ordinal }
|
||||
.collect(JsonArrayCollector)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
override val type: EntityType
|
||||
get() = EntityType.PLANT
|
||||
|
||||
val health = TileHealth.TileEntity().also { networkGroup.upstream.add(it.networkGroup) }
|
||||
|
||||
private val piecesInternal = NetworkedList(Piece.CODEC, Piece.LEGACY_CODEC, extraStupid = true).also { networkGroup.upstream.add(it) }
|
||||
private var piecesDirty = false
|
||||
|
||||
init {
|
||||
piecesInternal.addListener(Runnable {
|
||||
piecesDirty = true
|
||||
})
|
||||
}
|
||||
|
||||
val pieces: List<Piece> = Collections.unmodifiableList(piecesInternal)
|
||||
|
||||
var tileDamageX by networkedFloat().also { networkGroup.upstream.add(it) }
|
||||
private set
|
||||
var tileDamageY by networkedFloat().also { networkGroup.upstream.add(it) }
|
||||
private set
|
||||
val tileDamageEvent = networkedEventCounter().also { networkGroup.upstream.add(it) }
|
||||
|
||||
var isCeiling = false
|
||||
private set
|
||||
var fallsWhenDead = false
|
||||
private set
|
||||
var stemDropConfig: JsonObject = JsonObject()
|
||||
private set
|
||||
var foliageDropConfig: JsonObject = JsonObject()
|
||||
private set
|
||||
var saplingDropConfig: JsonObject = JsonObject()
|
||||
private set
|
||||
var descriptions: JsonObject = JsonObject()
|
||||
private set
|
||||
var tileDamageParameters = TileDamageParameters.EMPTY
|
||||
private set
|
||||
|
||||
constructor(config: TreeVariant, random: RandomGenerator) : this() {
|
||||
isCeiling = config.ceiling
|
||||
|
||||
stemDropConfig = (config.stemDropConfig as? JsonObject)?.deepCopy() ?: JsonObject()
|
||||
foliageDropConfig = (config.foliageDropConfig as? JsonObject)?.deepCopy() ?: JsonObject()
|
||||
|
||||
if (stemDropConfig.isJsonNull)
|
||||
stemDropConfig = JsonObject()
|
||||
|
||||
if (foliageDropConfig.isJsonNull)
|
||||
foliageDropConfig = JsonObject()
|
||||
|
||||
stemDropConfig["hueshift"] = config.stemHueShift
|
||||
foliageDropConfig["hueshift"] = config.foliageHueShift
|
||||
|
||||
val saplingDropConfig = JsonObject()
|
||||
saplingDropConfig["stemName"] = config.stemName
|
||||
saplingDropConfig["stemHueShift"] = config.stemHueShift
|
||||
|
||||
// original engine has "always true" condition because it checks against field "foliageDropConfig"
|
||||
// which is coalesced to json object if config.foliageDropConfig is null
|
||||
if (!config.foliageDropConfig.isJsonNull && config.foliageName.isNotBlank()) {
|
||||
saplingDropConfig["foliageName"] = config.foliageName
|
||||
saplingDropConfig["foliageHueShift"] = config.foliageHueShift
|
||||
}
|
||||
|
||||
this.saplingDropConfig = saplingDropConfig
|
||||
|
||||
var xOffset = 0.0
|
||||
var yOffset = 0.0
|
||||
val roffset = random.nextDouble(0.5)
|
||||
|
||||
descriptions = config.descriptions.deepCopy()
|
||||
isEphemeral = config.ephemeral
|
||||
tileDamageParameters = config.tileDamageParameters
|
||||
|
||||
var segment = 0
|
||||
|
||||
fun leaf(key: String, leaves: JsonObject, xOff: Double = xOffset, yOff: Double = yOffset, rotationOffset: Double? = null, rotationType: Piece.Rotation = Piece.Rotation.ROTATE_LEAVES) {
|
||||
if (key in leaves) {
|
||||
val settings = leaves[key].asJsonObject
|
||||
val attachment = settings.get("attachment", JsonObject())
|
||||
|
||||
val xOf = xOff + attachment.get("bx", 0.0) / PIXELS_IN_STARBOUND_UNIT
|
||||
val yOf = yOff + attachment.get("by", 0.0) / PIXELS_IN_STARBOUND_UNIT
|
||||
|
||||
if ("image" in settings && settings["image"].asString.isNotBlank()) {
|
||||
val file = AssetPathStack.relativeTo(config.foliageDirectory, settings["image"].asString)
|
||||
|
||||
piecesInternal.add(Piece(
|
||||
image = "$file?hueshift=${config.foliageHueShift.toInt()}",
|
||||
offset = Vector2d(xOf, yOf),
|
||||
segmentIdx = segment,
|
||||
isStructuralSegment = false,
|
||||
kind = Piece.Kind.FOLIAGE,
|
||||
rotationType = if (isCeiling) Piece.Rotation.DONT_ROTATE else rotationType,
|
||||
rotationOffset = rotationOffset ?: (random.nextDouble() + roffset)
|
||||
).apply { zLevel = 3.0 })
|
||||
}
|
||||
|
||||
if ("backimage" in settings && settings["backimage"].asString.isNotBlank()) {
|
||||
val file = AssetPathStack.relativeTo(config.foliageDirectory, settings["backimage"].asString)
|
||||
|
||||
piecesInternal.add(Piece(
|
||||
image = "$file?hueshift=${config.foliageHueShift.toInt()}",
|
||||
offset = Vector2d(xOf, yOf),
|
||||
segmentIdx = segment,
|
||||
isStructuralSegment = false,
|
||||
kind = Piece.Kind.FOLIAGE,
|
||||
rotationType = if (isCeiling) Piece.Rotation.DONT_ROTATE else rotationType,
|
||||
rotationOffset = rotationOffset ?: (random.nextDouble() + roffset)
|
||||
).apply { zLevel = -1.0 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// base
|
||||
run {
|
||||
val bases = config.stemSettings.asJsonObject["base"].asJsonObject
|
||||
val baseKey = bases.keySet().random(random)
|
||||
val baseSettings = bases[baseKey].asJsonObject
|
||||
val attachmentSettings = baseSettings.get("attachment", JsonObject())
|
||||
|
||||
xOffset += attachmentSettings.get("bx", 0.0) / PIXELS_IN_STARBOUND_UNIT
|
||||
yOffset += attachmentSettings.get("by", 0.0) / PIXELS_IN_STARBOUND_UNIT
|
||||
|
||||
val baseFile = AssetPathStack.relativeTo(config.stemDirectory, baseSettings["image"].asString)
|
||||
|
||||
if (isCeiling) {
|
||||
val img = Image.get(baseFile)
|
||||
|
||||
if (img == null) {
|
||||
LOGGER.error("Unable to load Tree's base stem image $baseFile, expect bad things to happen!")
|
||||
return
|
||||
}
|
||||
|
||||
yOffset = 1.0 - img.size.y / PIXELS_IN_STARBOUND_UNIT
|
||||
}
|
||||
|
||||
piecesInternal.add(Piece(
|
||||
image = "$baseFile?hueshift=${config.stemHueShift.toInt()}",
|
||||
offset = Vector2d(xOffset, yOffset),
|
||||
segmentIdx = segment,
|
||||
isStructuralSegment = true,
|
||||
kind = Piece.Kind.STEM,
|
||||
rotationType = Piece.Rotation.DONT_ROTATE,
|
||||
rotationOffset = random.nextDouble() + roffset
|
||||
))
|
||||
|
||||
// base leaves
|
||||
leaf(baseKey, config.foliageSettings.get("baseLeaves", JsonObject()))
|
||||
|
||||
xOffset += attachmentSettings.get("x", 0.0) / PIXELS_IN_STARBOUND_UNIT
|
||||
yOffset += attachmentSettings.get("y", 0.0) / PIXELS_IN_STARBOUND_UNIT // trunk height
|
||||
|
||||
segment++
|
||||
}
|
||||
|
||||
var branchYOffset = yOffset
|
||||
|
||||
// trunk
|
||||
run {
|
||||
val middles = config.stemSettings.get("middle").asJsonObject
|
||||
val middleHeight = random.nextRange(config.stemSettings.get("middleMinSize", 1), config.stemSettings.get("middleMaxSize", 6))
|
||||
|
||||
val branches = config.stemSettings["branch"]?.asJsonObject ?: JsonObject()
|
||||
|
||||
for (i in 0 until middleHeight) {
|
||||
val middleKey = middles.keySet().random(random)
|
||||
val middleSettings = middles[middleKey].asJsonObject
|
||||
val attachmentSettings = middleSettings.get("attachment", JsonObject())
|
||||
|
||||
xOffset += attachmentSettings.get("bx", 0.0) / PIXELS_IN_STARBOUND_UNIT
|
||||
yOffset += attachmentSettings.get("by", 0.0) / PIXELS_IN_STARBOUND_UNIT
|
||||
|
||||
val middleFile = AssetPathStack.relativeTo(config.stemDirectory, middleSettings["image"].asString)
|
||||
|
||||
piecesInternal.add(Piece(
|
||||
image = "$middleFile?hueshift=${config.stemHueShift.toInt()}",
|
||||
offset = Vector2d(xOffset, yOffset),
|
||||
segmentIdx = segment,
|
||||
isStructuralSegment = true,
|
||||
kind = Piece.Kind.STEM,
|
||||
rotationType = Piece.Rotation.DONT_ROTATE,
|
||||
rotationOffset = random.nextDouble() + roffset
|
||||
).apply { zLevel = 1.0 })
|
||||
|
||||
// trunk leaves
|
||||
leaf(middleKey, config.foliageSettings.get("trunkLeaves", JsonObject()))
|
||||
|
||||
xOffset += attachmentSettings.get("x", 0.0) / PIXELS_IN_STARBOUND_UNIT
|
||||
yOffset += attachmentSettings.get("y", 0.0) / PIXELS_IN_STARBOUND_UNIT
|
||||
|
||||
// branch
|
||||
while (branches.size() != 0 && yOffset >= branchYOffset && middleHeight - i > 0) {
|
||||
val branchKey = branches.keySet().random(random)
|
||||
|
||||
val branchSettings = branches[branchKey].asJsonObject
|
||||
val attachmentSettings = branchSettings.get("attachment", JsonObject())
|
||||
|
||||
val h = attachmentSettings.get("h", 0.0) / PIXELS_IN_STARBOUND_UNIT
|
||||
|
||||
if (yOffset < branchYOffset + h / 2.0)
|
||||
break
|
||||
|
||||
val xO = xOffset + attachmentSettings.get("bx", 0.0) / PIXELS_IN_STARBOUND_UNIT
|
||||
val yO = branchYOffset + attachmentSettings.get("by", 0.0) / PIXELS_IN_STARBOUND_UNIT
|
||||
|
||||
if (config.stemSettings.get("alwaysBranch", false) || random.nextInt(2 + i) != 0) {
|
||||
val boffset = random.nextDouble() + roffset
|
||||
val branchFile = AssetPathStack.relativeTo(config.stemDirectory, branchSettings["image"].asString)
|
||||
|
||||
piecesInternal.add(Piece(
|
||||
image = "$branchFile?hueshift=${config.stemHueShift.toInt()}",
|
||||
offset = Vector2d(xO, yO),
|
||||
segmentIdx = segment,
|
||||
isStructuralSegment = false,
|
||||
kind = Piece.Kind.STEM,
|
||||
rotationType = if (isCeiling) Piece.Rotation.DONT_ROTATE else Piece.Rotation.ROTATE_BRANCH,
|
||||
rotationOffset = boffset
|
||||
))
|
||||
|
||||
branchYOffset += h
|
||||
|
||||
// branch leaves
|
||||
leaf(branchKey, config.foliageSettings.get("branchLeaves", JsonObject()), xO, yO, boffset)
|
||||
} else {
|
||||
branchYOffset += h / random.nextDouble(1.0, 4.0)
|
||||
}
|
||||
}
|
||||
|
||||
segment++
|
||||
}
|
||||
}
|
||||
|
||||
// crown
|
||||
run {
|
||||
val crowns = config.stemSettings.get("crown", JsonObject())
|
||||
|
||||
if (crowns.size() != 0) {
|
||||
val crownKey = crowns.keySet().random(random)
|
||||
val crownSettings = crowns[crownKey].asJsonObject
|
||||
val attachmentSettings = crownSettings.get("attachment", JsonObject())
|
||||
|
||||
xOffset += attachmentSettings.get("bx", 0.0) / PIXELS_IN_STARBOUND_UNIT
|
||||
yOffset += attachmentSettings.get("by", 0.0) / PIXELS_IN_STARBOUND_UNIT
|
||||
|
||||
val coffset = random.nextDouble() + roffset
|
||||
val crownFile = AssetPathStack.relativeTo(config.stemDirectory, crownSettings["image"].asString)
|
||||
|
||||
piecesInternal.add(Piece(
|
||||
image = "$crownFile?hueshift=${config.stemHueShift.toInt()}",
|
||||
offset = Vector2d(xOffset, yOffset),
|
||||
segmentIdx = segment,
|
||||
isStructuralSegment = false,
|
||||
kind = Piece.Kind.STEM,
|
||||
rotationType = if (isCeiling) Piece.Rotation.DONT_ROTATE else Piece.Rotation.ROTATE_CROWN_BRANCH,
|
||||
rotationOffset = coffset
|
||||
))
|
||||
|
||||
// crown leaves
|
||||
leaf(crownKey, config.foliageSettings.get("crownLeaves", JsonObject()), rotationOffset = coffset, rotationType = Piece.Rotation.ROTATE_CROWN_LEAVES)
|
||||
}
|
||||
}
|
||||
|
||||
piecesInternal.sortBy { it.zLevel }
|
||||
scanSpacesAndRoots()
|
||||
}
|
||||
|
||||
constructor(config: BushVariant, random: RandomGenerator) : this() {
|
||||
val shape = config.shapes.random(random)
|
||||
val shapeImageName = AssetPathStack.relativeTo(config.directory, shape.image)
|
||||
var offset = Vector2d.ZERO
|
||||
isCeiling = config.ceiling
|
||||
|
||||
if (isCeiling) {
|
||||
val img = Image.get(shapeImageName)
|
||||
|
||||
if (img == null) {
|
||||
LOGGER.error("Unable to load Bush variant's image $shapeImageName, expect bad things to happen!")
|
||||
return
|
||||
}
|
||||
|
||||
offset = Vector2d(y = 1.0 - img.size.y / PIXELS_IN_STARBOUND_UNIT)
|
||||
}
|
||||
|
||||
piecesInternal.add(Piece(
|
||||
image = "$shapeImageName?hueshift=${config.baseHueShift.toInt()}",
|
||||
offset = offset,
|
||||
segmentIdx = 0,
|
||||
isStructuralSegment = true,
|
||||
kind = Piece.Kind.NONE
|
||||
))
|
||||
|
||||
if (shape.mods.isNotEmpty()) {
|
||||
val mod = shape.mods.random(random)
|
||||
|
||||
piecesInternal.add(Piece(
|
||||
image = "${AssetPathStack.relativeTo(config.directory, mod)}?hueshift=${config.modHueShift.toInt()}",
|
||||
offset = offset,
|
||||
segmentIdx = 0,
|
||||
isStructuralSegment = false,
|
||||
kind = Piece.Kind.NONE
|
||||
))
|
||||
}
|
||||
|
||||
scanSpacesAndRoots()
|
||||
}
|
||||
|
||||
constructor(config: GrassVariant, random: RandomGenerator) : this() {
|
||||
var offset = Vector2d.ZERO
|
||||
val image = AssetPathStack.relativeTo(config.directory, config.images.random(random))
|
||||
isCeiling = config.ceiling
|
||||
|
||||
if (isCeiling) {
|
||||
// If this is a ceiling plant, offset the image so that the [0, 0] space is at the top
|
||||
val img = Image.get(image)
|
||||
|
||||
if (img == null) {
|
||||
LOGGER.error("Unable to load Grass variant's image $image, expect bad things to happen!")
|
||||
return
|
||||
}
|
||||
|
||||
offset = Vector2d(y = 1.0 - img.size.y / PIXELS_IN_STARBOUND_UNIT)
|
||||
}
|
||||
|
||||
val piece = Piece(
|
||||
image = "$image?hueshift=${config.hueShift.toInt()}",
|
||||
offset = offset,
|
||||
segmentIdx = 0,
|
||||
isStructuralSegment = true,
|
||||
kind = Piece.Kind.NONE,
|
||||
)
|
||||
|
||||
piecesInternal.add(piece)
|
||||
scanSpacesAndRoots()
|
||||
}
|
||||
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this() {
|
||||
xTilePosition = stream.readSignedVarInt()
|
||||
yTilePosition = stream.readSignedVarInt()
|
||||
|
||||
isCeiling = stream.readBoolean()
|
||||
stemDropConfig = stream.readJsonElement() as JsonObject
|
||||
foliageDropConfig = stream.readJsonElement() as JsonObject
|
||||
saplingDropConfig = stream.readJsonElement() as JsonObject
|
||||
descriptions = stream.readJsonElement() as JsonObject
|
||||
|
||||
isEphemeral = stream.readBoolean()
|
||||
tileDamageParameters = TileDamageParameters(stream, isLegacy)
|
||||
fallsWhenDead = stream.readBoolean()
|
||||
health.read(stream, isLegacy)
|
||||
|
||||
val readPieces = if (isLegacy) {
|
||||
DataInputStream(FastByteArrayInputStream(stream.readByteArray()))
|
||||
} else {
|
||||
stream
|
||||
}
|
||||
|
||||
piecesInternal.readInitial(stream, isLegacy)
|
||||
scanSpacesAndRoots()
|
||||
}
|
||||
|
||||
constructor(data: JsonObject) : this() {
|
||||
deserialize(data)
|
||||
}
|
||||
|
||||
override fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeSignedVarInt(xTilePosition)
|
||||
stream.writeSignedVarInt(yTilePosition)
|
||||
|
||||
stream.writeBoolean(isCeiling)
|
||||
stream.writeJsonElement(stemDropConfig)
|
||||
stream.writeJsonElement(foliageDropConfig)
|
||||
stream.writeJsonElement(saplingDropConfig)
|
||||
stream.writeJsonElement(descriptions)
|
||||
stream.writeBoolean(isEphemeral)
|
||||
tileDamageParameters.write(stream, isLegacy)
|
||||
stream.writeBoolean(fallsWhenDead)
|
||||
health.write(stream, isLegacy)
|
||||
piecesInternal.writeInitial(stream, isLegacy)
|
||||
}
|
||||
|
||||
private var calculatedBoundingBox = AABB.ZERO
|
||||
private var calculatedOccupySpaces: Set<Vector2i> = setOf()
|
||||
private var calculatedRoots: Set<Vector2i> = setOf()
|
||||
|
||||
override var metaBoundingBox: AABB = calculatedBoundingBox
|
||||
private set
|
||||
|
||||
override var occupySpaces: ImmutableSet<Vector2i> = ImmutableSet.of()
|
||||
private set
|
||||
|
||||
override val materialSpaces: Collection<Pair<Vector2i, Registry.Ref<TileDefinition>>>
|
||||
get() = setOf()
|
||||
|
||||
override var roots: ImmutableSet<Vector2i> = ImmutableSet.of()
|
||||
private set
|
||||
|
||||
val primaryRoot: Vector2i
|
||||
get() = if (isCeiling) Vector2i(xTilePosition, yTilePosition + 1) else Vector2i(xTilePosition, yTilePosition - 1)
|
||||
|
||||
private fun scanSpacesAndRoots() {
|
||||
if (!piecesDirty) return
|
||||
piecesDirty = false
|
||||
val spaces = ObjectArraySet<Vector2i>()
|
||||
spaces.add(Vector2i.ZERO)
|
||||
|
||||
for (piece in piecesInternal) {
|
||||
val image = Image.get(piece.image)
|
||||
|
||||
if (image == null) {
|
||||
LOGGER.error("Unable to load image ${piece.image} for $this, expect bad things to happen!")
|
||||
continue
|
||||
}
|
||||
|
||||
piece.imageSize = image.size
|
||||
piece.spaces = image.worldSpaces(piece.offset * PIXELS_IN_STARBOUND_UNIT, 0.1, piece.flip)
|
||||
spaces.addAll(piece.spaces)
|
||||
}
|
||||
|
||||
this.calculatedOccupySpaces = spaces
|
||||
|
||||
val minX = spaces.minOf { it.x }
|
||||
val maxX = spaces.maxOf { it.x }
|
||||
val minY = spaces.minOf { it.y }
|
||||
val maxY = spaces.maxOf { it.y }
|
||||
|
||||
this.calculatedBoundingBox = AABB(
|
||||
Vector2d(minX - 1.0, minY - 1.0),
|
||||
Vector2d(maxX + 2.0, maxY + 2.0),
|
||||
)
|
||||
|
||||
val roots = ObjectArraySet<Vector2i>()
|
||||
|
||||
for (space in spaces) {
|
||||
if (space.y == 0) {
|
||||
if (isCeiling) {
|
||||
roots.add(Vector2i(space.x, 1))
|
||||
} else {
|
||||
roots.add(Vector2i(space.x, -1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.calculatedRoots = roots
|
||||
|
||||
moveSpaces()
|
||||
}
|
||||
|
||||
override fun tick(delta: Double) {
|
||||
super.tick(delta)
|
||||
|
||||
if (world.isServer && piecesInternal.isEmpty()) {
|
||||
remove(RemovalReason.REMOVED)
|
||||
}
|
||||
}
|
||||
|
||||
private fun moveSpaces() {
|
||||
scanSpacesAndRoots()
|
||||
this.metaBoundingBox = this.calculatedBoundingBox + this.position
|
||||
this.occupySpaces = this.calculatedOccupySpaces.stream().map { it + tilePosition }.collect(ImmutableSet.toImmutableSet())
|
||||
this.roots = this.calculatedRoots.stream().map { it + tilePosition }.collect(ImmutableSet.toImmutableSet())
|
||||
markSpacesDirty()
|
||||
}
|
||||
|
||||
override fun onPositionUpdated() {
|
||||
super.onPositionUpdated()
|
||||
moveSpaces()
|
||||
}
|
||||
|
||||
override fun damage(damageSpaces: List<Vector2i>, source: Vector2d, damage: TileDamage): Boolean {
|
||||
// TODO
|
||||
return false
|
||||
}
|
||||
|
||||
fun plant(world: ServerWorld, position: Vector2i, ignoreDungeonID: Int = NO_DUNGEON_ID): Boolean {
|
||||
if (isInWorld)
|
||||
throw IllegalStateException("Already in world")
|
||||
|
||||
world.eventLoop.ensureSameThread()
|
||||
tilePosition = position
|
||||
|
||||
val primaryCell = world.getCell(position)
|
||||
val adjustBackground = primaryCell.background.material.isEmptyTile
|
||||
|
||||
// Bail out if we don't have at least one free space, and root in the primary
|
||||
// root position, or if we're in a dungeon region.
|
||||
val rootCell = world.getCell(primaryRoot).mutable()
|
||||
|
||||
if (
|
||||
primaryCell.dungeonId != ignoreDungeonID ||
|
||||
rootCell.dungeonId != ignoreDungeonID ||
|
||||
primaryCell.foreground.material.value.isConnectable ||
|
||||
!rootCell.foreground.material.value.isConnectable
|
||||
) return false
|
||||
|
||||
// First bail out if we can't fit anything we're not adjusting
|
||||
for (space in occupySpaces) {
|
||||
// TODO: conditions seems to be inverted
|
||||
if (withinAdjustments(space, position) && world.entityIndex.tileEntitiesAt(space).any { it is PlantEntity }) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Bail out if we hit a different plant's root tile, or if we're not in the
|
||||
// adjustment space and we hit a non-empty tile.
|
||||
val cell = world.getCell(space)
|
||||
|
||||
if (cell.rootSource != null || (!withinAdjustments(space, position) && cell.foreground.material.isNotEmptyTile)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check all the roots outside of the adjustment limit
|
||||
for (root in roots) {
|
||||
if (!withinAdjustments(root, position) && !world.getCell(root).foreground.material.value.isConnectable) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all the necessary blocks within the adjustment limit
|
||||
for (space in occupySpaces) {
|
||||
if (!withinAdjustments(space, position))
|
||||
continue
|
||||
|
||||
var cell = world.getCell(space).mutable()
|
||||
|
||||
if (cell.foreground.material.value.isConnectable) {
|
||||
cell = primaryCell.mutable()
|
||||
}
|
||||
|
||||
if (adjustBackground) {
|
||||
cell.background.empty()
|
||||
}
|
||||
|
||||
world.setCell(space, cell)
|
||||
}
|
||||
|
||||
// Make all the root blocks a real material based on the primary root.
|
||||
for (root in roots) {
|
||||
val cell = world.getCell(root)
|
||||
|
||||
if (cell.foreground.material.isMetaTile) {
|
||||
// what the hell original engine does here?
|
||||
world.setCell(root, rootCell.copy(rootSource = tilePosition))
|
||||
}
|
||||
}
|
||||
|
||||
joinWorld(world)
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun withinAdjustments(root: Vector2i, position: Vector2i): Boolean {
|
||||
return (root.x - position.x).absoluteValue <= PLANT_ADJUSTMENT_LIMIT && (root.y - position.y).absoluteValue <= PLANT_ADJUSTMENT_LIMIT
|
||||
}
|
||||
|
||||
const val PLANT_ADJUSTMENT_LIMIT = 2
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
private val damageParameters by lazy { Starbound.gson.getAdapter(TileDamageParameters::class.java) }
|
||||
}
|
||||
}
|
@ -1,12 +1,16 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities.tile
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import kotlinx.coroutines.future.await
|
||||
import kotlinx.coroutines.launch
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.gson.get
|
||||
import ru.dbotthepony.kommons.gson.set
|
||||
import ru.dbotthepony.kstarbound.math.AABB
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.math.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||
@ -23,6 +27,16 @@ import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||
* (Hopefully) Static world entities (Plants, Objects, etc), which reside on cell grid
|
||||
*/
|
||||
abstract class TileEntity : AbstractEntity() {
|
||||
open fun deserialize(data: JsonObject) {
|
||||
tilePosition = data.get("tilePosition", vectors)
|
||||
}
|
||||
|
||||
open fun serialize(): JsonObject {
|
||||
val into = JsonObject()
|
||||
into["tilePosition"] = vectors.toJsonTree(tilePosition)
|
||||
return into
|
||||
}
|
||||
|
||||
protected val xTilePositionNet = networkedSignedInt()
|
||||
protected val yTilePositionNet = networkedSignedInt()
|
||||
|
||||
@ -228,5 +242,6 @@ abstract class TileEntity : AbstractEntity() {
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
private val vectors by lazy { Starbound.gson.getAdapter(Vector2i::class.java) }
|
||||
}
|
||||
}
|
||||
|
@ -81,23 +81,25 @@ import ru.dbotthepony.kstarbound.world.Direction
|
||||
import ru.dbotthepony.kstarbound.world.TileHealth
|
||||
import ru.dbotthepony.kstarbound.world.World
|
||||
import ru.dbotthepony.kstarbound.world.entities.Animator
|
||||
import ru.dbotthepony.kstarbound.world.entities.api.ScriptedEntity
|
||||
import java.io.DataOutputStream
|
||||
import java.util.Collections
|
||||
import java.util.HashMap
|
||||
import java.util.random.RandomGenerator
|
||||
|
||||
open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntity() {
|
||||
open fun deserialize(data: JsonObject) {
|
||||
open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntity(), ScriptedEntity {
|
||||
override fun deserialize(data: JsonObject) {
|
||||
super.deserialize(data)
|
||||
direction = data.get("direction", directions) { Direction.LEFT }
|
||||
orientationIndex = data.get("orientationIndex", -1).toLong()
|
||||
isInteractive = data.get("interactive", false)
|
||||
tilePosition = data.get("tilePosition", vectors)
|
||||
|
||||
lua.globals["storage"] = lua.from(data.get("scriptStorage") { JsonObject() })
|
||||
|
||||
uniqueID.accept(KOptional.ofNullable(data["uniqueId"]?.asStringOrNull))
|
||||
|
||||
loadParameters(data.get("parameters") { JsonObject() })
|
||||
|
||||
if ("uniqueId" in data)
|
||||
uniqueID.accept(KOptional.ofNullable(data["uniqueId"]?.asStringOrNull))
|
||||
}
|
||||
|
||||
open fun loadParameters(parameters: JsonObject) {
|
||||
@ -110,10 +112,9 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
||||
}
|
||||
}
|
||||
|
||||
open fun serialize(): JsonObject {
|
||||
val into = JsonObject()
|
||||
override fun serialize(): JsonObject {
|
||||
val into = super.serialize()
|
||||
into["name"] = config.key
|
||||
into["tilePosition"] = vectors.toJsonTree(tilePosition)
|
||||
into["direction"] = directions.toJsonTree(direction)
|
||||
into["orientationIndex"] = orientationIndex
|
||||
into["interactive"] = isInteractive
|
||||
@ -619,6 +620,14 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
||||
return tileHealth.isDead
|
||||
}
|
||||
|
||||
override fun callScript(fnName: String, vararg arguments: Any?): Array<Any?> {
|
||||
return lua.invokeGlobal(fnName, *arguments)
|
||||
}
|
||||
|
||||
override fun evalScript(code: String): Array<Any?> {
|
||||
return lua.eval(code)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val lightColorPath = JsonPath("lightColor")
|
||||
private val lightColorsPath = JsonPath("lightColors")
|
||||
|
@ -13,7 +13,6 @@ class DisplacementTerrainSelector(data: Data, parameters: TerrainSelectorParamet
|
||||
@JsonFactory
|
||||
data class Data(
|
||||
val xType: PerlinNoiseParameters.Type,
|
||||
val xScale: Int = PerlinNoiseParameters.DEFAULT_SCALE,
|
||||
val xOctaves: Int,
|
||||
val xFreq: Double,
|
||||
val xAmp: Double,
|
||||
@ -22,7 +21,6 @@ class DisplacementTerrainSelector(data: Data, parameters: TerrainSelectorParamet
|
||||
val xBeta: Double = 2.0,
|
||||
|
||||
val yType: PerlinNoiseParameters.Type,
|
||||
val yScale: Int = PerlinNoiseParameters.DEFAULT_SCALE,
|
||||
val yOctaves: Int,
|
||||
val yFreq: Double,
|
||||
val yAmp: Double,
|
||||
@ -56,7 +54,6 @@ class DisplacementTerrainSelector(data: Data, parameters: TerrainSelectorParamet
|
||||
|
||||
xFn = AbstractPerlinNoise.of(PerlinNoiseParameters(
|
||||
type = data.xType,
|
||||
scale = data.xScale,
|
||||
octaves = data.xOctaves,
|
||||
frequency = data.xFreq,
|
||||
amplitude = data.xAmp,
|
||||
@ -67,7 +64,6 @@ class DisplacementTerrainSelector(data: Data, parameters: TerrainSelectorParamet
|
||||
|
||||
yFn = AbstractPerlinNoise.of(PerlinNoiseParameters(
|
||||
type = data.yType,
|
||||
scale = data.yScale,
|
||||
octaves = data.yOctaves,
|
||||
frequency = data.yFreq,
|
||||
amplitude = data.yAmp,
|
||||
|
Loading…
Reference in New Issue
Block a user