Functional serverside player network receive
This commit is contained in:
parent
551eac4072
commit
9c1772a766
@ -18,3 +18,7 @@
|
||||
* Rolled per each "stem-foliage" combination
|
||||
* Also two more properties were added: `sameStemHueShift` (defaults to `true`) and `sameFoliageHueShift` (defaults to `false`), which fixate hue shifts within same "stem-foliage" combination
|
||||
* Original engine always generates two tree types when processing placeable items, new engine however, allows to generate any number of trees.
|
||||
|
||||
### player.config
|
||||
* Inventory bags are no longer limited to 255 slots
|
||||
* However, when joining original servers with mod which increase bag size past 255 slots will result in undefined behavior (joining servers with inventory size bag mods will already result in nearly instant desync though, so you may not ever live to see the side effects; and if original server installs said mod, original clients and original server will experience severe desyncs/undefined behavior too)
|
||||
|
@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m
|
||||
|
||||
kotlinVersion=1.9.10
|
||||
kotlinCoroutinesVersion=1.8.0
|
||||
kommonsVersion=2.9.25
|
||||
kommonsVersion=2.10.2
|
||||
|
||||
ffiVersion=2.2.13
|
||||
lwjglVersion=3.3.0
|
||||
|
@ -5,8 +5,10 @@ import com.google.gson.TypeAdapter
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||
import ru.dbotthepony.kstarbound.defs.ClientConfigParameters
|
||||
import ru.dbotthepony.kstarbound.defs.CurrencyDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
||||
import ru.dbotthepony.kstarbound.defs.UniverseServerConfig
|
||||
import ru.dbotthepony.kstarbound.defs.actor.player.PlayerConfig
|
||||
import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
|
||||
import ru.dbotthepony.kstarbound.defs.world.TerrestrialWorldsConfig
|
||||
import ru.dbotthepony.kstarbound.defs.world.AsteroidWorldsConfig
|
||||
@ -25,6 +27,9 @@ import kotlin.reflect.KMutableProperty0
|
||||
object GlobalDefaults {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
|
||||
var player by Delegates.notNull<PlayerConfig>()
|
||||
private set
|
||||
|
||||
var actorMovementParameters = ActorMovementParameters()
|
||||
private set
|
||||
|
||||
@ -61,6 +66,9 @@ object GlobalDefaults {
|
||||
var universeServer by Delegates.notNull<UniverseServerConfig>()
|
||||
private set
|
||||
|
||||
var currencies by Delegates.notNull<ImmutableMap<String, CurrencyDefinition>>()
|
||||
private set
|
||||
|
||||
private object EmptyTask : ForkJoinTask<Unit>() {
|
||||
private fun readResolve(): Any = EmptyTask
|
||||
override fun getRawResult() {
|
||||
@ -109,12 +117,14 @@ object GlobalDefaults {
|
||||
tasks.add(load("/world_template.config", ::worldTemplate))
|
||||
tasks.add(load("/sky.config", ::sky))
|
||||
tasks.add(load("/universe_server.config", ::universeServer))
|
||||
tasks.add(load("/player.config", ::player))
|
||||
|
||||
tasks.add(load("/plants/grassDamage.config", ::grassDamage))
|
||||
tasks.add(load("/plants/treeDamage.config", ::treeDamage))
|
||||
tasks.add(load("/plants/bushDamage.config", ::bushDamage))
|
||||
|
||||
tasks.add(load("/dungeon_worlds.config", ::dungeonWorlds, Starbound.gson.mapAdapter()))
|
||||
tasks.add(load("/currencies.config", ::currencies, Starbound.gson.mapAdapter()))
|
||||
|
||||
return tasks
|
||||
}
|
||||
|
@ -43,14 +43,14 @@ object RecipeRegistry {
|
||||
}).add(recipe)
|
||||
}
|
||||
|
||||
output2recipesInternal.computeIfAbsent(value.output.item.key.left(), Object2ObjectFunction { p ->
|
||||
output2recipesInternal.computeIfAbsent(value.output.name, Object2ObjectFunction { p ->
|
||||
ArrayList<Entry>(1).also {
|
||||
output2recipesBacking[p as String] = Collections.unmodifiableList(it)
|
||||
}
|
||||
}).add(recipe)
|
||||
|
||||
for (input in value.input) {
|
||||
input2recipesInternal.computeIfAbsent(input.item.key.left(), Object2ObjectFunction { p ->
|
||||
input2recipesInternal.computeIfAbsent(input.name, Object2ObjectFunction { p ->
|
||||
ArrayList<Entry>(1).also {
|
||||
input2recipesBacking[p as String] = Collections.unmodifiableList(it)
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import com.google.gson.TypeAdapter
|
||||
import com.google.gson.TypeAdapterFactory
|
||||
import com.google.gson.stream.JsonReader
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kstarbound.defs.Json2Function
|
||||
import ru.dbotthepony.kstarbound.defs.JsonConfigFunction
|
||||
import ru.dbotthepony.kstarbound.defs.JsonFunction
|
||||
@ -29,8 +30,8 @@ import ru.dbotthepony.kstarbound.defs.monster.MonsterTypeDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.npc.NpcTypeDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.npc.TenantDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.particle.ParticleDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.actor.player.TechDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.animation.ParticleConfig
|
||||
import ru.dbotthepony.kstarbound.defs.projectile.ProjectileDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.quest.QuestTemplate
|
||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||
@ -66,7 +67,7 @@ object Registries {
|
||||
val liquid = Registry<LiquidDefinition>("liquid").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val species = Registry<Species>("species").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val statusEffects = Registry<StatusEffectDefinition>("status effect").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val particles = Registry<ParticleDefinition>("particle").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val particles = Registry<ParticleConfig>("particle").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val items = Registry<IItemDefinition>("item").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val questTemplates = Registry<QuestTemplate>("quest template").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
val techs = Registry<TechDefinition>("tech").also(registriesInternal::add).also { adapters.add(it.adapter()) }
|
||||
@ -153,7 +154,7 @@ object Registries {
|
||||
tasks.addAll(loadRegistry(worldObjects, fileTree["object"] ?: listOf(), key(ObjectDefinition::objectName)))
|
||||
tasks.addAll(loadRegistry(statusEffects, fileTree["statuseffect"] ?: listOf(), key(StatusEffectDefinition::name)))
|
||||
tasks.addAll(loadRegistry(species, fileTree["species"] ?: listOf(), key(Species::kind)))
|
||||
tasks.addAll(loadRegistry(particles, fileTree["particle"] ?: listOf(), key(ParticleDefinition::kind)))
|
||||
tasks.addAll(loadRegistry(particles, fileTree["particle"] ?: listOf(), { (it.kind ?: throw NullPointerException("Missing 'kind' value")) to null }))
|
||||
tasks.addAll(loadRegistry(questTemplates, fileTree["questtemplate"] ?: listOf(), key(QuestTemplate::id)))
|
||||
tasks.addAll(loadRegistry(techs, fileTree["tech"] ?: listOf(), key(TechDefinition::name)))
|
||||
tasks.addAll(loadRegistry(npcTypes, fileTree["npctype"] ?: listOf(), key(NpcTypeDefinition::type)))
|
||||
|
@ -14,6 +14,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kstarbound.util.traverseJsonPath
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.function.Supplier
|
||||
@ -21,9 +22,9 @@ import kotlin.collections.set
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
class Registry<T : Any>(val name: String) {
|
||||
private val keysInternal = Object2ObjectOpenHashMap<String, Impl>()
|
||||
private val keysInternal = HashMap<String, Impl>()
|
||||
private val idsInternal = Int2ObjectOpenHashMap<Impl>()
|
||||
private val keyRefs = Object2ObjectOpenHashMap<String, RefImpl>()
|
||||
private val keyRefs = HashMap<String, RefImpl>()
|
||||
private val idRefs = Int2ObjectOpenHashMap<RefImpl>()
|
||||
private val backlog = ConcurrentLinkedQueue<Runnable>()
|
||||
|
||||
@ -45,7 +46,7 @@ class Registry<T : Any>(val name: String) {
|
||||
|
||||
private val lock = ReentrantLock()
|
||||
|
||||
val keys: Object2ObjectMap<String, out Entry<T>> = Object2ObjectMaps.unmodifiable(keysInternal)
|
||||
val keys: Map<String, Entry<T>> = Collections.unmodifiableMap(keysInternal)
|
||||
val ids: Int2ObjectMap<out Entry<T>> = Int2ObjectMaps.unmodifiable(idsInternal)
|
||||
|
||||
sealed class Ref<T : Any> : Supplier<Entry<T>?> {
|
||||
@ -56,6 +57,9 @@ class Registry<T : Any>(val name: String) {
|
||||
val isPresent: Boolean
|
||||
get() = value != null
|
||||
|
||||
val isEmpty: Boolean
|
||||
get() = value == null
|
||||
|
||||
val value: T?
|
||||
get() = entry?.value
|
||||
|
||||
@ -76,6 +80,7 @@ class Registry<T : Any>(val name: String) {
|
||||
abstract val file: IStarboundFile?
|
||||
abstract val registry: Registry<T>
|
||||
abstract val isBuiltin: Boolean
|
||||
abstract val ref: Ref<T>
|
||||
|
||||
fun traverseJsonPath(path: String): JsonElement? {
|
||||
return traverseJsonPath(path, json)
|
||||
@ -94,6 +99,8 @@ class Registry<T : Any>(val name: String) {
|
||||
override var file: IStarboundFile? = null
|
||||
override var isBuiltin: Boolean = false
|
||||
|
||||
override val ref: Ref<T> by lazy { ref(key) }
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return this === other
|
||||
}
|
||||
@ -168,7 +175,7 @@ class Registry<T : Any>(val name: String) {
|
||||
var valid = true
|
||||
|
||||
keyRefs.values.forEach {
|
||||
if (!it.isPresent) {
|
||||
if (!it.isPresent && it.key.left() != "") {
|
||||
LOGGER.warn("Registry '$name' reference at '${it.key.left()}' is not bound to value, expect problems (referenced ${it.references} times)")
|
||||
valid = false
|
||||
}
|
||||
@ -185,6 +192,8 @@ class Registry<T : Any>(val name: String) {
|
||||
}
|
||||
|
||||
fun add(key: String, value: T, json: JsonElement, file: IStarboundFile): Entry<T> {
|
||||
require(key != "") { "Adding $name with empty name (empty name is reserved)" }
|
||||
|
||||
lock.withLock {
|
||||
if (key in keysInternal) {
|
||||
LOGGER.warn("Overwriting $name at '$key' (new def originate from $file; old def originate from ${keysInternal[key]?.file ?: "<code>"})")
|
||||
@ -211,6 +220,8 @@ class Registry<T : Any>(val name: String) {
|
||||
}
|
||||
|
||||
fun add(key: String, id: Int, value: T, json: JsonElement, file: IStarboundFile): Entry<T> {
|
||||
require(key != "") { "Adding $name with empty name (empty name is reserved)" }
|
||||
|
||||
lock.withLock {
|
||||
if (key in keysInternal) {
|
||||
LOGGER.warn("Overwriting $name at '$key' (new def originate from $file; old def originate from ${keysInternal[key]?.file ?: "<code>"})")
|
||||
@ -243,6 +254,8 @@ class Registry<T : Any>(val name: String) {
|
||||
}
|
||||
|
||||
fun add(key: String, value: T, isBuiltin: Boolean = false): Entry<T> {
|
||||
require(key != "") { "Adding $name with empty name (empty name is reserved)" }
|
||||
|
||||
lock.withLock {
|
||||
if (key in keysInternal) {
|
||||
LOGGER.warn("Overwriting $name at '$key' (new def originate from <code>; old def originate from ${keysInternal[key]?.file ?: "<code>"})")
|
||||
@ -270,6 +283,8 @@ class Registry<T : Any>(val name: String) {
|
||||
}
|
||||
|
||||
fun add(key: String, id: Int, value: T, isBuiltin: Boolean = false): Entry<T> {
|
||||
require(key != "") { "Adding $name with empty name (empty name is reserved)" }
|
||||
|
||||
lock.withLock {
|
||||
if (key in keysInternal) {
|
||||
LOGGER.warn("Overwriting $name at '$key' (new def originate from <code>; old def originate from ${keysInternal[key]?.file ?: "<code>"})")
|
||||
@ -302,6 +317,8 @@ class Registry<T : Any>(val name: String) {
|
||||
}
|
||||
}
|
||||
|
||||
val emptyRef = ref("")
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import ru.dbotthepony.kommons.gson.Vector4iTypeAdapter
|
||||
import ru.dbotthepony.kommons.util.MailboxExecutorService
|
||||
import ru.dbotthepony.kstarbound.collect.WeightedList
|
||||
import ru.dbotthepony.kstarbound.defs.*
|
||||
import ru.dbotthepony.kstarbound.defs.actor.StatModifier
|
||||
import ru.dbotthepony.kstarbound.defs.image.Image
|
||||
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
|
||||
import ru.dbotthepony.kstarbound.defs.item.api.IArmorItemDefinition
|
||||
@ -32,6 +33,8 @@ import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation
|
||||
import ru.dbotthepony.kstarbound.defs.actor.player.BlueprintLearnList
|
||||
import ru.dbotthepony.kstarbound.defs.animation.Particle
|
||||
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
||||
import ru.dbotthepony.kstarbound.defs.world.CelestialParameters
|
||||
import ru.dbotthepony.kstarbound.defs.world.VisitableWorldParametersType
|
||||
import ru.dbotthepony.kstarbound.defs.world.terrain.BiomePlaceables
|
||||
@ -54,7 +57,7 @@ import ru.dbotthepony.kstarbound.json.factory.RGBAColorTypeAdapter
|
||||
import ru.dbotthepony.kstarbound.json.factory.SingletonTypeAdapterFactory
|
||||
import ru.dbotthepony.kstarbound.math.*
|
||||
import ru.dbotthepony.kstarbound.server.world.UniverseChunk
|
||||
import ru.dbotthepony.kstarbound.util.ItemStack
|
||||
import ru.dbotthepony.kstarbound.item.ItemStack
|
||||
import ru.dbotthepony.kstarbound.util.SBPattern
|
||||
import ru.dbotthepony.kstarbound.util.HashTableInterner
|
||||
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
||||
@ -86,8 +89,8 @@ object Starbound : ISBFileLocator {
|
||||
const val ENGINE_VERSION = "0.0.1"
|
||||
const val NATIVE_PROTOCOL_VERSION = 748
|
||||
const val LEGACY_PROTOCOL_VERSION = 747
|
||||
const val TICK_TIME_ADVANCE = 1.0 / 60.0
|
||||
const val TICK_TIME_ADVANCE_NANOS = (TICK_TIME_ADVANCE * 1_000_000_000L).toLong()
|
||||
const val TIMESTEP = 1.0 / 60.0
|
||||
const val TICK_TIME_ADVANCE_NANOS = (TIMESTEP * 1_000_000_000L).toLong()
|
||||
|
||||
// compile flags. uuuugh
|
||||
const val DEDUP_CELL_STATES = true
|
||||
@ -245,7 +248,7 @@ object Starbound : ISBFileLocator {
|
||||
|
||||
registerTypeAdapter(ItemStack.Adapter(this@Starbound))
|
||||
|
||||
registerTypeAdapterFactory(ItemReference.Factory(STRINGS))
|
||||
registerTypeAdapter(ItemDescriptor::Adapter)
|
||||
registerTypeAdapterFactory(TreasurePoolDefinition.Companion)
|
||||
|
||||
registerTypeAdapterFactory(UniverseChunk.Companion)
|
||||
@ -255,6 +258,7 @@ object Starbound : ISBFileLocator {
|
||||
registerTypeAdapterFactory(Poly.Companion)
|
||||
|
||||
registerTypeAdapter(CelestialParameters::Adapter)
|
||||
registerTypeAdapter(Particle::Adapter)
|
||||
|
||||
registerTypeAdapterFactory(BiomePlacementDistributionType.DATA_ADAPTER)
|
||||
registerTypeAdapterFactory(BiomePlacementDistributionType.DEFINITION_ADAPTER)
|
||||
@ -289,21 +293,15 @@ object Starbound : ISBFileLocator {
|
||||
}
|
||||
|
||||
fun item(name: String): ItemStack {
|
||||
return ItemStack(Registries.items[name] ?: return ItemStack.EMPTY)
|
||||
TODO()
|
||||
}
|
||||
|
||||
fun item(name: String, count: Long): ItemStack {
|
||||
if (count <= 0L)
|
||||
return ItemStack.EMPTY
|
||||
|
||||
return ItemStack(Registries.items[name] ?: return ItemStack.EMPTY, count = count)
|
||||
TODO()
|
||||
}
|
||||
|
||||
fun item(name: String, count: Long, parameters: JsonObject): ItemStack {
|
||||
if (count <= 0L)
|
||||
return ItemStack.EMPTY
|
||||
|
||||
return ItemStack(Registries.items[name] ?: return ItemStack.EMPTY, count = count, parameters = parameters)
|
||||
TODO()
|
||||
}
|
||||
|
||||
fun item(descriptor: JsonObject): ItemStack {
|
||||
|
@ -925,7 +925,7 @@ class StarboundClient private constructor(val clientID: Int) : Closeable {
|
||||
}
|
||||
|
||||
input.think()
|
||||
camera.think(Starbound.TICK_TIME_ADVANCE)
|
||||
camera.think(Starbound.TIMESTEP)
|
||||
executeQueuedTasks()
|
||||
|
||||
layers.clear()
|
||||
@ -990,8 +990,8 @@ class StarboundClient private constructor(val clientID: Int) : Closeable {
|
||||
ply.movement.controlRun = !input.KEY_LEFT_SHIFT_DOWN
|
||||
} else {
|
||||
camera.pos += Vector2d(
|
||||
(if (input.KEY_A_DOWN) -Starbound.TICK_TIME_ADVANCE * 32f / settings.zoom else 0.0) + (if (input.KEY_D_DOWN) Starbound.TICK_TIME_ADVANCE * 32f / settings.zoom else 0.0),
|
||||
(if (input.KEY_W_DOWN) Starbound.TICK_TIME_ADVANCE * 32f / settings.zoom else 0.0) + (if (input.KEY_S_DOWN) -Starbound.TICK_TIME_ADVANCE * 32f / settings.zoom else 0.0)
|
||||
(if (input.KEY_A_DOWN) -Starbound.TIMESTEP * 32f / settings.zoom else 0.0) + (if (input.KEY_D_DOWN) Starbound.TIMESTEP * 32f / settings.zoom else 0.0),
|
||||
(if (input.KEY_W_DOWN) Starbound.TIMESTEP * 32f / settings.zoom else 0.0) + (if (input.KEY_S_DOWN) -Starbound.TIMESTEP * 32f / settings.zoom else 0.0)
|
||||
)
|
||||
|
||||
camera.pos = world?.geometry?.wrap(camera.pos) ?: camera.pos
|
||||
|
@ -17,9 +17,5 @@ class ForgetEntityPacket(val uuid: UUID) : IClientPacket {
|
||||
|
||||
override fun play(connection: ClientConnection) {
|
||||
val world = connection.client.world ?: return
|
||||
|
||||
world.mailbox.execute {
|
||||
world.entities.firstOrNull { it.entityID == 0 }?.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -302,7 +302,7 @@ class ClientWorld(
|
||||
}
|
||||
}
|
||||
|
||||
for (ent in entities) {
|
||||
for (ent in entities.values) {
|
||||
ent.render(client, layers)
|
||||
ent.addLights(client.viewportLighting, client.viewportCellX, client.viewportCellY)
|
||||
}
|
||||
|
52
src/main/kotlin/ru/dbotthepony/kstarbound/collect/IdMap.kt
Normal file
52
src/main/kotlin/ru/dbotthepony/kstarbound/collect/IdMap.kt
Normal file
@ -0,0 +1,52 @@
|
||||
package ru.dbotthepony.kstarbound.collect
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
|
||||
|
||||
class IdMap<T : Any>(val min: Int = 0, val max: Int = Int.MAX_VALUE, private val map: MutableMap<Int, T> = Int2ObjectAVLTreeMap()) : MutableMap<Int, T> by map {
|
||||
private var nextIndex = min - 1
|
||||
private val range = max - min
|
||||
|
||||
init {
|
||||
require(range > 1) { "Invalid range for ID Map: $min .. $max" }
|
||||
}
|
||||
|
||||
private fun next(): Int {
|
||||
if (++nextIndex > max) {
|
||||
nextIndex = min
|
||||
}
|
||||
|
||||
return nextIndex
|
||||
}
|
||||
|
||||
fun add(value: T): Int {
|
||||
var i = 0
|
||||
var index = next()
|
||||
|
||||
if (map is Int2ObjectMap) {
|
||||
while ((map as Int2ObjectMap).containsKey(index)) {
|
||||
index = next()
|
||||
|
||||
if (i++ > range) {
|
||||
throw RuntimeException("No more free slots in ID Map")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while (map.containsKey(index)) {
|
||||
index = next()
|
||||
|
||||
if (i++ > range) {
|
||||
throw RuntimeException("No more free slots in ID Map")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
map[index] = value
|
||||
return index
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
nextIndex = min - 1
|
||||
map.clear()
|
||||
}
|
||||
}
|
@ -10,6 +10,8 @@ import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||
|
||||
data class AssetPath(val path: String, val fullPath: String) {
|
||||
constructor(path: String) : this(path, path)
|
||||
|
||||
companion object : TypeAdapterFactory {
|
||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||
if (type.rawType == AssetPath::class.java) {
|
||||
|
@ -18,6 +18,7 @@ import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
data class AssetReference<V>(val path: String?, val fullPath: String?, val value: V?, val json: JsonElement?) {
|
||||
companion object : TypeAdapterFactory {
|
||||
@ -30,7 +31,7 @@ data class AssetReference<V>(val path: String?, val fullPath: String?, val value
|
||||
val param = type.type as? ParameterizedType ?: return null
|
||||
|
||||
return object : TypeAdapter<AssetReference<T>>() {
|
||||
private val cache = Collections.synchronizedMap(Object2ObjectOpenHashMap<String, Pair<T, JsonElement>>())
|
||||
private val cache = Collections.synchronizedMap(HashMap<String, Pair<T, JsonElement>>())
|
||||
private val adapter = gson.getAdapter(TypeToken.get(param.actualTypeArguments[0])) as TypeAdapter<T>
|
||||
private val strings = gson.getAdapter(String::class.java)
|
||||
private val jsons = gson.getAdapter(JsonElement::class.java)
|
||||
|
@ -0,0 +1,9 @@
|
||||
package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
|
||||
|
||||
data class CurrencyDefinition(
|
||||
val representativeItem: Registry.Ref<IItemDefinition>,
|
||||
val playerMax: Long = 999999,
|
||||
)
|
@ -1,68 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
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
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonToken
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
|
||||
import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kommons.gson.consumeNull
|
||||
import ru.dbotthepony.kstarbound.util.ItemStack
|
||||
|
||||
/**
|
||||
* Прототип [ItemStack] в JSON файлах
|
||||
*/
|
||||
data class ItemReference(
|
||||
val item: Registry.Ref<IItemDefinition>,
|
||||
val count: Long = 1,
|
||||
val parameters: JsonObject = JsonObject()
|
||||
) {
|
||||
init {
|
||||
require(item.key.isLeft) { "Can't reference item by ID" }
|
||||
}
|
||||
|
||||
fun makeStack(): ItemStack {
|
||||
return ItemStack(item.entry ?: return ItemStack.EMPTY, count, parameters)
|
||||
}
|
||||
|
||||
class Factory(val stringInterner: Interner<String> = Interner { it }) : TypeAdapterFactory {
|
||||
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||
if (type.rawType == ItemReference::class.java) {
|
||||
return object : TypeAdapter<ItemReference>() {
|
||||
private val regularObject = FactoryAdapter.createFor(ItemReference::class, JsonFactory(asList = false), gson, stringInterner)
|
||||
private val regularList = FactoryAdapter.createFor(ItemReference::class, JsonFactory(asList = true), gson, stringInterner)
|
||||
private val references = gson.getAdapter(TypeToken.getParameterized(Registry.Ref::class.java, IItemDefinition::class.java)) as TypeAdapter<Registry.Ref<IItemDefinition>>
|
||||
|
||||
override fun write(out: JsonWriter, value: ItemReference?) {
|
||||
if (value == null)
|
||||
out.nullValue()
|
||||
else
|
||||
regularObject.write(out, value)
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): ItemReference? {
|
||||
if (`in`.consumeNull())
|
||||
return null
|
||||
|
||||
if (`in`.peek() == JsonToken.STRING) {
|
||||
return ItemReference(references.read(`in`))
|
||||
} else if (`in`.peek() == JsonToken.BEGIN_ARRAY) {
|
||||
return regularList.read(`in`)
|
||||
} else {
|
||||
return regularObject.read(`in`)
|
||||
}
|
||||
}
|
||||
} as TypeAdapter<T>
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -22,10 +22,10 @@ import kotlin.reflect.javaType
|
||||
*/
|
||||
abstract class JsonDriven(val path: String) {
|
||||
private val delegates = ArrayList<Property<*>>()
|
||||
private val delegatesMap = Object2ObjectOpenHashMap<String, ArrayList<Property<*>>>()
|
||||
private val delegatesMap = HashMap<String, ArrayList<Property<*>>>()
|
||||
|
||||
private val lazies = ArrayList<LazyData<*>>()
|
||||
private val namedLazies = Object2ObjectOpenHashMap<String, ArrayList<LazyData<*>>>()
|
||||
private val namedLazies = HashMap<String, ArrayList<LazyData<*>>>()
|
||||
|
||||
protected val properties = JsonObject()
|
||||
|
||||
@ -91,7 +91,7 @@ abstract class JsonDriven(val path: String) {
|
||||
|
||||
var name: String? = name
|
||||
private set(value) {
|
||||
if (field != null)
|
||||
if (field != null || value == null)
|
||||
throw IllegalStateException()
|
||||
|
||||
field = value
|
||||
|
@ -1,58 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.defs
|
||||
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kommons.gson.consumeNull
|
||||
import ru.dbotthepony.kommons.gson.contains
|
||||
import ru.dbotthepony.kommons.gson.get
|
||||
|
||||
enum class StatModifierType(vararg names: String) {
|
||||
BASE_ADDITION("value", "baseAddition"),
|
||||
BASE_MULTIPLICATION("baseMultiplier"),
|
||||
|
||||
OVERALL_ADDITION("effectiveValue", "effectiveAddition"),
|
||||
OVERALL_MULTIPLICATION("effectiveMultiplier");
|
||||
|
||||
val names: ImmutableSet<String> = ImmutableSet.copyOf(names)
|
||||
}
|
||||
|
||||
data class StatModifier(val stat: String, val value: Double, val type: StatModifierType) {
|
||||
class Adapter(gson: Gson) : TypeAdapter<StatModifier>() {
|
||||
private val objects = gson.getAdapter(JsonObject::class.java)
|
||||
|
||||
override fun write(out: JsonWriter, value: StatModifier?) {
|
||||
if (value == null) {
|
||||
out.nullValue()
|
||||
} else {
|
||||
out.beginObject()
|
||||
out.name("stat")
|
||||
out.value(value.stat)
|
||||
out.name(value.type.names.first())
|
||||
out.value(value.value)
|
||||
out.endObject()
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): StatModifier? {
|
||||
if (`in`.consumeNull()) {
|
||||
return null
|
||||
} else {
|
||||
val read = objects.read(`in`)
|
||||
|
||||
val stat = read["stat"]?.asString ?: throw JsonSyntaxException("No stat to modify specified")
|
||||
|
||||
for (type in StatModifierType.entries)
|
||||
for (name in type.names)
|
||||
if (name in read)
|
||||
return StatModifier(stat, read.get(name, 0.0), type)
|
||||
|
||||
throw JsonSyntaxException("Not a stat modifier: $read")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
package ru.dbotthepony.kstarbound.defs.actor
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kommons.gson.consumeNull
|
||||
import ru.dbotthepony.kommons.gson.contains
|
||||
import ru.dbotthepony.kommons.gson.get
|
||||
import ru.dbotthepony.kommons.io.readBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
|
||||
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
data class StatModifier(val stat: String, val value: Double, val type: StatModifierType) {
|
||||
class Adapter(gson: Gson) : TypeAdapter<StatModifier>() {
|
||||
private val objects = gson.getAdapter(JsonObject::class.java)
|
||||
|
||||
override fun write(out: JsonWriter, value: StatModifier?) {
|
||||
if (value == null) {
|
||||
out.nullValue()
|
||||
} else {
|
||||
out.beginObject()
|
||||
out.name("stat")
|
||||
out.value(value.stat)
|
||||
out.name(value.type.names.first())
|
||||
out.value(value.value)
|
||||
out.endObject()
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): StatModifier? {
|
||||
if (`in`.consumeNull()) {
|
||||
return null
|
||||
} else {
|
||||
val read = objects.read(`in`)
|
||||
|
||||
val stat = read["stat"]?.asString ?: throw JsonSyntaxException("No stat to modify specified")
|
||||
|
||||
for (type in StatModifierType.entries)
|
||||
for (name in type.names)
|
||||
if (name in read)
|
||||
return StatModifier(stat, read.get(name, 0.0), type)
|
||||
|
||||
throw JsonSyntaxException("Not a stat modifier: $read")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
if (isLegacy) {
|
||||
when (type) {
|
||||
// legacy network protocol doesn't support overall addition
|
||||
StatModifierType.OVERALL_ADDITION, StatModifierType.BASE_ADDITION -> stream.writeByte(1)
|
||||
StatModifierType.BASE_MULTIPLICATION -> stream.writeByte(2)
|
||||
StatModifierType.OVERALL_MULTIPLICATION -> stream.writeByte(3)
|
||||
}
|
||||
} else {
|
||||
stream.writeByte(type.ordinal)
|
||||
}
|
||||
|
||||
stream.writeBinaryString(stat)
|
||||
|
||||
if (isLegacy)
|
||||
stream.writeFloat(value.toFloat())
|
||||
else
|
||||
stream.writeDouble(value)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC = nativeCodec(::read, StatModifier::write)
|
||||
val LEGACY_CODEC = legacyCodec(::read, StatModifier::write)
|
||||
|
||||
fun read(stream: DataInputStream, isLegacy: Boolean): StatModifier {
|
||||
if (isLegacy) {
|
||||
return when (val type = stream.readUnsignedByte()) {
|
||||
0 -> throw UnsupportedOperationException("Empty StatModifier MVariant received on network, this is not supported by this game engine")
|
||||
1 -> value(stream.readInternedString(), stream.readFloat().toDouble())
|
||||
2 -> multBase(stream.readInternedString(), stream.readFloat().toDouble())
|
||||
3 -> multEffective(stream.readInternedString(), stream.readFloat().toDouble())
|
||||
else -> throw IllegalArgumentException("Unknown StatModifier type: $type")
|
||||
}
|
||||
} else {
|
||||
val type = StatModifierType.entries[stream.readUnsignedByte()]
|
||||
return StatModifier(stream.readInternedString(), stream.readDouble(), type)
|
||||
}
|
||||
}
|
||||
|
||||
fun value(name: String, value: Double): StatModifier {
|
||||
return StatModifier(name, value, StatModifierType.BASE_ADDITION)
|
||||
}
|
||||
|
||||
fun multBase(name: String, value: Double): StatModifier {
|
||||
return StatModifier(name, value, StatModifierType.BASE_MULTIPLICATION)
|
||||
}
|
||||
|
||||
fun valueEffective(name: String, value: Double): StatModifier {
|
||||
return StatModifier(name, value, StatModifierType.OVERALL_ADDITION)
|
||||
}
|
||||
|
||||
fun multEffective(name: String, value: Double): StatModifier {
|
||||
return StatModifier(name, value, StatModifierType.OVERALL_MULTIPLICATION)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package ru.dbotthepony.kstarbound.defs.actor
|
||||
|
||||
import com.google.common.collect.ImmutableSet
|
||||
|
||||
enum class StatModifierType(vararg names: String) {
|
||||
BASE_ADDITION("amount", "baseAddition"),
|
||||
|
||||
// Multipliers act exactly the way you'd expect: 0.0 is a 100% reduction of the
|
||||
// base stat, while 2.0 is a 100% increase. Since these are *base* multipliers
|
||||
// they do not interact with each other, thus stacking a 0.0 and a 2.0 leaves
|
||||
// the stat unmodified
|
||||
|
||||
// in other words, they stack additively.
|
||||
BASE_MULTIPLICATION("baseMultiplier"),
|
||||
|
||||
// KStarbound extension, added on top of base addition and base multiplication,
|
||||
// but BEFORE effectiveMultiplier
|
||||
OVERALL_ADDITION("effectiveAmount", "effectiveAddition"),
|
||||
|
||||
// Unlike base multipliers, these all stack multiplicatively with the final
|
||||
// stat value (including all base and value modifiers) such that an effective
|
||||
// multiplier of 0.0 will ALWAYS reduce the stat to 0 regardless of other
|
||||
// effects
|
||||
|
||||
// in other words, they stack multiplicatively
|
||||
OVERALL_MULTIPLICATION("effectiveMultiplier");
|
||||
|
||||
val names: ImmutableSet<String> = ImmutableSet.copyOf(names)
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package ru.dbotthepony.kstarbound.defs.actor
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.JsonObject
|
||||
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@JsonFactory
|
||||
data class StatusControllerConfig(
|
||||
val statusProperties: JsonObject = JsonObject(),
|
||||
val minimumLiquidStatusEffectPercentage: Double,
|
||||
val appliesEnvironmentStatusEffects: Boolean,
|
||||
val appliesWeatherStatusEffects: Boolean,
|
||||
val environmentStatusEffectUpdateTimer: Double = 0.15,
|
||||
val primaryAnimationConfig: AssetPath? = null,
|
||||
val primaryScriptSources: ImmutableList<AssetPath> = ImmutableList.of(),
|
||||
val primaryScriptDelta: Int = 1,
|
||||
val keepDamageNotificationSteps: Int = 120,
|
||||
val stats: ImmutableMap<String, Stat> = ImmutableMap.of(),
|
||||
val resources: ImmutableMap<String, Resource> = ImmutableMap.of(),
|
||||
) {
|
||||
init {
|
||||
require(primaryScriptDelta >= 1) { "Non-positive primaryScriptDelta: $primaryScriptDelta" }
|
||||
require(keepDamageNotificationSteps >= 1) { "Non-positive keepDamageNotificationSteps: $keepDamageNotificationSteps" }
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class Stat(
|
||||
val baseValue: Double = 0.0,
|
||||
)
|
||||
|
||||
@JsonFactory
|
||||
data class Resource(
|
||||
val maxStat: String? = null,
|
||||
val deltaStat: String? = null,
|
||||
val maxValue: Double? = null,
|
||||
val deltaValue: Double? = null,
|
||||
val initialValue: Double? = null,
|
||||
val initialPercentage: Double? = null,
|
||||
)
|
||||
}
|
@ -22,3 +22,21 @@ enum class HumanoidEmote(override val jsonName: String) : IStringSerializable {
|
||||
EAT("Eat"),
|
||||
SLEEP("Sleep");
|
||||
}
|
||||
|
||||
enum class EquipmentSlot(override val jsonName: String) : IStringSerializable {
|
||||
HEAD("Head"),
|
||||
CHEST("Chest"),
|
||||
LEGS("Legs"),
|
||||
BACK("Back"),
|
||||
HEAD_COSMETIC("HeadCosmetic"),
|
||||
CHEST_COSMETIC("ChestCosmetic"),
|
||||
LEGS_COSMETIC("LegsCosmetic"),
|
||||
BACK_COSMETIC("BackCosmetic");
|
||||
}
|
||||
|
||||
enum class EssentialSlot(override val jsonName: String) : IStringSerializable {
|
||||
BEAM_AXE("BeamAxe"),
|
||||
WIRE_TOOL("WireTool"),
|
||||
PAINT_TOOL("PaintTool"),
|
||||
INSPECTION_TOOL("InspectionTool");
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package ru.dbotthepony.kstarbound.defs.actor.player
|
||||
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.util.ItemStack
|
||||
import ru.dbotthepony.kstarbound.item.ItemStack
|
||||
import java.util.function.Predicate
|
||||
|
||||
@JsonFactory
|
||||
@ -17,9 +17,9 @@ data class BagFilterConfig(
|
||||
return false
|
||||
|
||||
if (typeBlacklist != null) {
|
||||
return !typeBlacklist.contains(t.item!!.value.category)
|
||||
return !typeBlacklist.contains(t.config.value!!.category)
|
||||
} else if (typeWhitelist != null) {
|
||||
return typeWhitelist.contains(t.item!!.value.category)
|
||||
return typeWhitelist.contains(t.config.value!!.category)
|
||||
}
|
||||
|
||||
return true
|
||||
|
@ -6,8 +6,8 @@ import ru.dbotthepony.kstarbound.util.WriteOnce
|
||||
|
||||
@JsonFactory
|
||||
data class InventoryConfig(
|
||||
val customBarGroups: Int,
|
||||
val customBarIndexes: Int,
|
||||
val customBarGroups: Int, // hotbar configurations / groups
|
||||
val customBarIndexes: Int, // hotbar slots
|
||||
|
||||
val itemBags: ImmutableMap<String, Bag>,
|
||||
) {
|
||||
@ -15,13 +15,5 @@ data class InventoryConfig(
|
||||
class Bag(
|
||||
val priority: Int,
|
||||
val size: Int
|
||||
) {
|
||||
var name: String by WriteOnce()
|
||||
}
|
||||
|
||||
init {
|
||||
for ((k, v) in itemBags) {
|
||||
v.name = k
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
package ru.dbotthepony.kstarbound.defs.actor.player
|
||||
|
||||
import ru.dbotthepony.kommons.io.IntValueCodec
|
||||
import ru.dbotthepony.kommons.io.StreamCodec
|
||||
import ru.dbotthepony.kommons.io.map
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
|
||||
enum class PlayerBusyState(override val jsonName: String) : IStringSerializable {
|
||||
NONE("none"),
|
||||
CHATTING("chatting"),
|
||||
MENU("menu");
|
||||
|
||||
companion object {
|
||||
val CODEC = StreamCodec.Enum(PlayerBusyState::class.java)
|
||||
val LEGACY_CODEC = IntValueCodec.map({ entries[this] }, { ordinal })
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||
import ru.dbotthepony.kstarbound.defs.Species
|
||||
import ru.dbotthepony.kstarbound.defs.actor.StatusControllerConfig
|
||||
import ru.dbotthepony.kstarbound.util.SBPattern
|
||||
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
|
||||
@ -35,7 +36,7 @@ data class PlayerConfig(
|
||||
val movementParameters: ActorMovementParameters,
|
||||
val zeroGMovementParameters: ActorMovementParameters,
|
||||
|
||||
val statusControllerSettings: StatusControllerSettings,
|
||||
val statusControllerSettings: StatusControllerConfig,
|
||||
|
||||
val foodLowThreshold: Double,
|
||||
val foodLowStatusEffects: ImmutableList<String> = ImmutableList.of(),
|
||||
@ -75,4 +76,4 @@ data class PlayerConfig(
|
||||
val genericScriptContexts: ImmutableMap<String, String>,
|
||||
val inventory: InventoryConfig,
|
||||
val inventoryFilters: ImmutableMap<String, BagFilterConfig>,
|
||||
)
|
||||
)
|
@ -0,0 +1,17 @@
|
||||
package ru.dbotthepony.kstarbound.defs.actor.player
|
||||
|
||||
import ru.dbotthepony.kommons.io.IntValueCodec
|
||||
import ru.dbotthepony.kommons.io.StreamCodec
|
||||
import ru.dbotthepony.kommons.io.map
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
|
||||
enum class PlayerGamemode(override val jsonName: String) : IStringSerializable {
|
||||
CASUAL("casual"),
|
||||
SURVIVAL("survival"),
|
||||
HARDCORE("hardcore");
|
||||
|
||||
companion object {
|
||||
val CODEC = StreamCodec.Enum(PlayerGamemode::class.java)
|
||||
val LEGACY_CODEC = IntValueCodec.map({ entries[this] }, { ordinal })
|
||||
}
|
||||
}
|
@ -3,13 +3,13 @@ package ru.dbotthepony.kstarbound.defs.actor.player
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import ru.dbotthepony.kstarbound.defs.ItemReference
|
||||
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@JsonFactory
|
||||
data class RecipeDefinition(
|
||||
val input: ImmutableList<ItemReference>,
|
||||
val output: ItemReference,
|
||||
val input: ImmutableList<ItemDescriptor>,
|
||||
val output: ItemDescriptor,
|
||||
val groups: ImmutableSet<String> = ImmutableSet.of(),
|
||||
val matchInputParameters: Boolean = false,
|
||||
val duration: Double = 0.5,
|
||||
|
@ -1,48 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.defs.actor.player
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@JsonFactory
|
||||
data class StatusControllerSettings(
|
||||
val statusProperties: Properties,
|
||||
val appliesEnvironmentStatusEffects: Boolean = true,
|
||||
val appliesWeatherStatusEffects: Boolean = true,
|
||||
val minimumLiquidStatusEffectPercentage: Double = 0.1,
|
||||
|
||||
val primaryScriptSources: ImmutableList<String> = ImmutableList.of(),
|
||||
|
||||
val primaryScriptDelta: Int,
|
||||
|
||||
val stats: ImmutableMap<String, Stat> = ImmutableMap.of(),
|
||||
val resources: ImmutableMap<String, Resource> = ImmutableMap.of(),
|
||||
) {
|
||||
@JsonFactory
|
||||
data class Properties(
|
||||
val targetMaterialKind: String,
|
||||
val mouthPosition: Vector2d,
|
||||
val breathHealthPenaltyPercentageRate: Double,
|
||||
val hitInvulnerabilityThreshold: Double,
|
||||
val hitInvulnerabilityTime: Double,
|
||||
val hitInvulnerabilityFlash: Double,
|
||||
val shieldHitInvulnerabilityTime: Double,
|
||||
val damageFlashOnDirectives: String = "",
|
||||
val damageFlashOffDirectives: String = ""
|
||||
)
|
||||
|
||||
@JsonFactory
|
||||
data class Stat(
|
||||
val baseValue: Double
|
||||
)
|
||||
|
||||
@JsonFactory
|
||||
data class Resource(
|
||||
val maxStat: String? = null,
|
||||
val deltaStat: String? = null,
|
||||
val deltaValue: Double? = null,
|
||||
val initialPercentage: Double? = null,
|
||||
val initialValue: Double? = null,
|
||||
)
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package ru.dbotthepony.kstarbound.defs.animation
|
||||
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.JsonObject
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@JsonFactory
|
||||
data class AnimatedPartsDefinition(
|
||||
val stateTypes: ImmutableMap<String, StateType> = ImmutableMap.of(),
|
||||
val parts: ImmutableMap<String, Part> = ImmutableMap.of(),
|
||||
) {
|
||||
enum class AnimationMode(override val jsonName: String) : IStringSerializable {
|
||||
END("end"),
|
||||
TRANSITION("transition"),
|
||||
LOOP("loop");
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class StateType(
|
||||
val default: String = "",
|
||||
val priority: Double = 0.0,
|
||||
val enabled: Boolean = true,
|
||||
val states: ImmutableMap<String, State> = ImmutableMap.of(),
|
||||
val properties: JsonObject = JsonObject(),
|
||||
) {
|
||||
@JsonFactory
|
||||
data class State(
|
||||
val frames: Int = 1,
|
||||
val cycle: Double = 1.0,
|
||||
val mode: AnimationMode = AnimationMode.END,
|
||||
val transition: String = "",
|
||||
val properties: JsonObject = JsonObject(),
|
||||
val frameProperties: JsonObject = JsonObject(),
|
||||
)
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class Part(
|
||||
val properties: JsonObject = JsonObject(),
|
||||
val partStates: ImmutableMap<String, ImmutableMap<String, State>> = ImmutableMap.of(),
|
||||
) {
|
||||
@JsonFactory
|
||||
data class State(
|
||||
val properties: JsonObject = JsonObject(),
|
||||
val frameProperties: JsonObject = JsonObject(),
|
||||
)
|
||||
}
|
||||
}
|
@ -2,10 +2,14 @@ package ru.dbotthepony.kstarbound.defs.animation
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.util.AABB
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
|
||||
import ru.dbotthepony.kstarbound.defs.particle.ParticleEmitter
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@JsonFactory
|
||||
@ -16,79 +20,98 @@ data class AnimationDefinition(
|
||||
val animationCycle: Double? = null,
|
||||
val offset: Vector2d? = null,
|
||||
|
||||
val animatedParts: AnimatedParts? = null,
|
||||
val animatedParts: AnimatedPartsDefinition? = null,
|
||||
|
||||
val sounds: ImmutableMap<String, Either<ImmutableList<String>, CustomSound>> = ImmutableMap.of(),
|
||||
val transformationGroups: ImmutableMap<String, TransformConfig> = ImmutableMap.of(),
|
||||
val particleEmitters: ImmutableMap<String, ParticleEmitter> = ImmutableMap.of(),
|
||||
val transformationGroups: ImmutableMap<String, Transform> = ImmutableMap.of(),
|
||||
val rotationGroups: ImmutableMap<String, Rotation> = ImmutableMap.of(),
|
||||
val particleEmitters: ImmutableMap<String, AnimationParticles> = ImmutableMap.of(),
|
||||
val lights: ImmutableMap<String, Light> = ImmutableMap.of(),
|
||||
val effects: ImmutableMap<String, Effect> = ImmutableMap.of(),
|
||||
|
||||
val globalTagDefaults: ImmutableMap<String, String> = ImmutableMap.of(),
|
||||
val partTagDefaults: ImmutableMap<String, ImmutableMap<String, String>> = ImmutableMap.of(),
|
||||
) {
|
||||
@JsonFactory
|
||||
data class CustomSound(
|
||||
val pool: ImmutableList<String>,
|
||||
data class Effect(
|
||||
val type: String,
|
||||
val time: Double = 0.0,
|
||||
val directives: String,
|
||||
)
|
||||
|
||||
@JsonFactory
|
||||
data class TransformConfig(
|
||||
val interpolated: Boolean? = null
|
||||
)
|
||||
|
||||
@JsonFactory
|
||||
data class AnimatedParts(
|
||||
val stateTypes: ImmutableMap<String, StateType> = ImmutableMap.of(),
|
||||
val parts: ImmutableMap<String, Part> = ImmutableMap.of(),
|
||||
data class AnimationParticles(
|
||||
val emissionRate: Double = 1.0,
|
||||
val emissionRateVariance: Double = 0.0,
|
||||
val offsetRegion: AABB? = null,
|
||||
val anchorPart: String? = null,
|
||||
val rotationGroup: String? = null,
|
||||
val rotationCenter: Vector2d? = null,
|
||||
val transformationGroups: ImmutableList<String> = ImmutableList.of(),
|
||||
val particles: ImmutableList<AnimationParticle>,
|
||||
val burstCount: Int = 1,
|
||||
val randomSelectCount: Int = particles.size,
|
||||
val active: Boolean = false,
|
||||
) {
|
||||
@JsonFactory
|
||||
data class StateType(
|
||||
val default: String,
|
||||
val priority: Int = 0,
|
||||
val states: ImmutableMap<String, State> = ImmutableMap.of(),
|
||||
) {
|
||||
@JsonFactory
|
||||
data class State(
|
||||
val frames: Int = 0,
|
||||
val cycle: Double = 0.0,
|
||||
val mode: Mode? = null,
|
||||
val transition: TransitionType? = null,
|
||||
val properties: Properties = Properties()
|
||||
) {
|
||||
enum class TransitionType {
|
||||
NONE
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
TRANSITION,
|
||||
LOOP,
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class Properties(
|
||||
val immediateSound: String? = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class Part(
|
||||
val properties: Properties = Properties.EMPTY,
|
||||
val partStates: ImmutableMap<String, ImmutableMap<String, State>> = ImmutableMap.of(),
|
||||
) {
|
||||
@JsonFactory
|
||||
data class Properties(
|
||||
val fullbright: Boolean? = null,
|
||||
val centered: Boolean? = null,
|
||||
val transformationGroups: ImmutableList<String>? = null,
|
||||
val offset: Vector2d? = null,
|
||||
val image: SpriteReference? = null
|
||||
) {
|
||||
companion object {
|
||||
val EMPTY = Properties()
|
||||
}
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class State(
|
||||
val properties: Properties = Properties.EMPTY
|
||||
)
|
||||
init {
|
||||
require(burstCount >= 0) { "Negative burstCount: $burstCount" }
|
||||
require(randomSelectCount >= 0) { "Negative randomSelectCount: $randomSelectCount" }
|
||||
}
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class AnimationParticle(
|
||||
val particle: Either<Registry.Ref<ParticleConfig>, ParticleConfig.JsonData>,
|
||||
val count: Int = 1,
|
||||
val offset: Vector2d = Vector2d.ZERO,
|
||||
val flip: Boolean = false,
|
||||
) {
|
||||
init {
|
||||
require(count >= 0) { "Negative count: $count" }
|
||||
}
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class Light(
|
||||
val active: Boolean = true,
|
||||
val position: Vector2d = Vector2d.ZERO,
|
||||
val color: RGBAColor = RGBAColor.WHITE,
|
||||
val anchorPart: String? = null,
|
||||
val transformationGroups: ImmutableList<String> = ImmutableList.of(),
|
||||
val rotationGroup: String? = null,
|
||||
val rotationCenter: Vector2d? = null,
|
||||
val pointLight: Boolean = false,
|
||||
val pointBeam: Float = 0f,
|
||||
val beamAmbience: Float = 0f,
|
||||
val pointAngle: Double = 0.0,
|
||||
|
||||
// sigh
|
||||
val flickerPeriod: Double? = null,
|
||||
val flickerMinIntensity: Double = 0.0,
|
||||
val flickerMaxIntensity: Double = 0.0,
|
||||
val flickerPeriodVariance: Double = 0.0,
|
||||
val flickerIntensityVariance: Double = 0.0,
|
||||
)
|
||||
|
||||
@JsonFactory
|
||||
data class CustomSound(
|
||||
val pool: ImmutableList<AssetPath> = ImmutableList.of(),
|
||||
val position: Vector2d = Vector2d.ZERO,
|
||||
val volume: Double = 1.0,
|
||||
val volumeRampTime: Double = 0.0,
|
||||
val pitchMultiplier: Double = 1.0,
|
||||
val pitchMultiplierRampTime: Double = 0.0,
|
||||
val rangeMultiplier: Double = 1.0,
|
||||
)
|
||||
|
||||
@JsonFactory
|
||||
data class Rotation(
|
||||
val angularVelocity: Double = 0.0,
|
||||
val rotationCenter: Vector2d = Vector2d.ZERO,
|
||||
)
|
||||
|
||||
@JsonFactory
|
||||
data class Transform(
|
||||
val interpolated: Boolean = false
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.defs.animation
|
||||
|
||||
enum class DestructionAction {
|
||||
FADE
|
||||
}
|
@ -0,0 +1,186 @@
|
||||
package ru.dbotthepony.kstarbound.defs.animation
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import ru.dbotthepony.kommons.gson.consumeNull
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||
import ru.dbotthepony.kstarbound.fromJson
|
||||
import java.util.random.RandomGenerator
|
||||
import kotlin.math.PI
|
||||
|
||||
// this struct is a complete mess
|
||||
// mostly, because it represents all possible particle types
|
||||
// and, of course, used directly when working with particles
|
||||
// (instancing happens just by copying this structure)
|
||||
data class Particle(
|
||||
var type: ParticleConfig.Type = ParticleConfig.Type.VARIANCE,
|
||||
// Defaults to 1.0, 1.0 will produce a reasonable size particle for whatever
|
||||
// the type is.
|
||||
var size: Double = 0.0,
|
||||
var baseSize: Double = 0.0, // track the original size for shrink destruction action
|
||||
var color: RGBAColor = RGBAColor.WHITE,
|
||||
var light: RGBAColor = RGBAColor.TRANSPARENT_BLACK,
|
||||
// Used differently depending on the type of the particle.
|
||||
var string: String = "",
|
||||
var fade: Double = 0.0,
|
||||
var fullbright: Boolean = false,
|
||||
var position: Vector2d = Vector2d.ZERO,
|
||||
var velocity: Vector2d = Vector2d.ZERO,
|
||||
var finalVelocity: Vector2d = Vector2d.ZERO,
|
||||
var approach: Vector2d = Vector2d.ZERO,
|
||||
var rotation: Double = 0.0,
|
||||
var angularVelocity: Double = 0.0,
|
||||
var timeToLive: Double = 0.0,
|
||||
var layer: ParticleConfig.Layer = ParticleConfig.Layer.MIDDLE,
|
||||
var collidesForeground: Boolean = true,
|
||||
var collidesLiquid: Boolean = true,
|
||||
var underwaterOnly: Boolean = false,
|
||||
var ignoreWind: Boolean = true,
|
||||
var length: Double = 0.0,
|
||||
var destructionAction: ParticleConfig.DestructionAction = ParticleConfig.DestructionAction.NONE,
|
||||
var destructionTime: Double = 0.0,
|
||||
var trail: Boolean = false,
|
||||
var flippable: Boolean = true,
|
||||
var flip: Boolean = false,
|
||||
var directives: String = "",
|
||||
var destructionImage: String = "",
|
||||
) {
|
||||
constructor(data: ParticleConfig.JsonData) : this() {
|
||||
type = data.type
|
||||
|
||||
if (type == ParticleConfig.Type.VARIANCE) {
|
||||
size = 0.0
|
||||
color = RGBAColor.TRANSPARENT_BLACK
|
||||
} else {
|
||||
size = 1.0
|
||||
color = RGBAColor.WHITE
|
||||
}
|
||||
|
||||
size = data.size ?: size
|
||||
baseSize = size
|
||||
|
||||
string = data.image?.fullPath ?: data.text ?: data.animation?.fullPath ?: data.string ?: ""
|
||||
|
||||
if (data.color != null)
|
||||
color = data.color
|
||||
|
||||
light = data.light
|
||||
fade = data.fade
|
||||
fullbright = data.fullbright
|
||||
position = data.position
|
||||
velocity = data.velocity ?: data.initialVelocity ?: Vector2d.ZERO
|
||||
|
||||
if (type == ParticleConfig.Type.VARIANCE)
|
||||
finalVelocity = Vector2d.ZERO
|
||||
else
|
||||
finalVelocity = velocity
|
||||
|
||||
finalVelocity = data.finalVelocity ?: finalVelocity
|
||||
approach = data.approach
|
||||
|
||||
flip = data.flip
|
||||
flippable = data.flippable
|
||||
rotation = data.rotation * PI / 180.0
|
||||
angularVelocity = data.angularVelocity * PI / 180.0
|
||||
length = data.length
|
||||
destructionAction = data.destructionAction
|
||||
|
||||
if (destructionAction == ParticleConfig.DestructionAction.IMAGE)
|
||||
destructionImage = data.destructionImage?.fullPath ?: ""
|
||||
else
|
||||
destructionImage = data.destructionImage?.path ?: ""
|
||||
|
||||
destructionTime = data.destructionTime
|
||||
timeToLive = data.timeToLive
|
||||
layer = data.layer
|
||||
underwaterOnly = data.underwaterOnly
|
||||
collidesForeground = data.collidesForeground ?: (layer != ParticleConfig.Layer.FRONT || underwaterOnly)
|
||||
collidesLiquid = data.collidesLiquid ?: (type == ParticleConfig.Type.EMBER)
|
||||
ignoreWind = data.ignoreWind
|
||||
trail = data.trail
|
||||
}
|
||||
|
||||
constructor(data: JsonObject) : this(Starbound.gson.fromJson(data, ParticleConfig.JsonData::class.java))
|
||||
|
||||
fun toJson(): JsonObject {
|
||||
val json = Starbound.gson.toJsonTree(toJsonData()) as JsonObject
|
||||
|
||||
for (k in json.keySet().filter { json[it] == JsonNull.INSTANCE }) {
|
||||
json.remove(k)
|
||||
}
|
||||
|
||||
return json
|
||||
}
|
||||
|
||||
fun toJsonData(): ParticleConfig.JsonData {
|
||||
return ParticleConfig.JsonData(
|
||||
type = type,
|
||||
size = size,
|
||||
string = string,
|
||||
color = color,
|
||||
light = light,
|
||||
fade = fade,
|
||||
fullbright = fullbright,
|
||||
position = position,
|
||||
velocity = velocity,
|
||||
finalVelocity = finalVelocity,
|
||||
approach = approach,
|
||||
flip = flip,
|
||||
flippable = flippable,
|
||||
rotation = rotation,
|
||||
angularVelocity = angularVelocity,
|
||||
length = length,
|
||||
destructionAction = destructionAction,
|
||||
destructionImage = AssetPath(destructionImage),
|
||||
destructionTime = destructionTime,
|
||||
timeToLive = timeToLive,
|
||||
layer = layer,
|
||||
underwaterOnly = underwaterOnly,
|
||||
collidesForeground = collidesForeground,
|
||||
collidesLiquid = collidesLiquid,
|
||||
ignoreWind = ignoreWind,
|
||||
trail = trail,
|
||||
)
|
||||
}
|
||||
|
||||
fun applyVariance(variance: Particle, random: RandomGenerator): Particle {
|
||||
size += variance.size * random.nextDouble(-1.0, 1.0)
|
||||
position += Vector2d(variance.position.x * random.nextDouble(-1.0, 1.0), variance.position.y * random.nextDouble(-1.0, 1.0))
|
||||
velocity += Vector2d(variance.velocity.x * random.nextDouble(-1.0, 1.0), variance.velocity.y * random.nextDouble(-1.0, 1.0))
|
||||
finalVelocity += Vector2d(variance.finalVelocity.x * random.nextDouble(-1.0, 1.0), variance.finalVelocity.y * random.nextDouble(-1.0, 1.0))
|
||||
rotation += variance.rotation * random.nextDouble(-1.0, 1.0)
|
||||
angularVelocity += variance.angularVelocity * random.nextDouble(-1.0, 1.0)
|
||||
length += variance.length * random.nextDouble(-1.0, 1.0)
|
||||
timeToLive += variance.timeToLive * random.nextDouble(-1.0, 1.0)
|
||||
return this
|
||||
}
|
||||
|
||||
fun initializeAnimation() {
|
||||
|
||||
}
|
||||
|
||||
class Adapter(gson: Gson) : TypeAdapter<Particle>() {
|
||||
private val data = gson.getAdapter(ParticleConfig.JsonData::class.java)
|
||||
|
||||
override fun write(out: JsonWriter, value: Particle?) {
|
||||
if (value == null)
|
||||
out.nullValue()
|
||||
else
|
||||
data.write(out, value.toJsonData())
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): Particle? {
|
||||
if (`in`.consumeNull())
|
||||
return null
|
||||
|
||||
return Particle(data.read(`in`))
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package ru.dbotthepony.kstarbound.defs.animation
|
||||
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import java.util.random.RandomGenerator
|
||||
|
||||
interface ParticleFactory {
|
||||
fun create(): Particle
|
||||
fun create(random: RandomGenerator): Particle
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
class ParticleConfig(
|
||||
val kind: String? = null,
|
||||
val definition: JsonData,
|
||||
) : ParticleFactory {
|
||||
override fun create(): Particle {
|
||||
return definition.create()
|
||||
}
|
||||
|
||||
override fun create(random: RandomGenerator): Particle {
|
||||
return definition.create(random)
|
||||
}
|
||||
|
||||
enum class Type(override val jsonName: String) : IStringSerializable {
|
||||
VARIANCE("variance"),
|
||||
EMBER("ember"),
|
||||
TEXTURED("textured"),
|
||||
ANIMATED("animated"),
|
||||
STREAK("streak"),
|
||||
TEXT("text");
|
||||
}
|
||||
|
||||
enum class Layer(override val jsonName: String) : IStringSerializable {
|
||||
BACK("back"),
|
||||
MIDDLE("middle"),
|
||||
FRONT("front");
|
||||
}
|
||||
|
||||
enum class DestructionAction(override val jsonName: String) : IStringSerializable {
|
||||
NONE("none"),
|
||||
IMAGE("image"),
|
||||
FADE("fade"),
|
||||
SHRINK("shrink");
|
||||
}
|
||||
|
||||
@JsonFactory
|
||||
data class JsonData(
|
||||
val variance: JsonData? = null,
|
||||
val type: Type = Type.VARIANCE,
|
||||
val size: Double? = null,
|
||||
val image: AssetPath? = null,
|
||||
val animation: AssetPath? = null,
|
||||
val text: String? = null,
|
||||
val string: String? = null,
|
||||
val color: RGBAColor? = null,
|
||||
val light: RGBAColor = RGBAColor.TRANSPARENT_BLACK,
|
||||
val fade: Double = 0.0,
|
||||
val fullbright: Boolean = false,
|
||||
val position: Vector2d = Vector2d.ZERO,
|
||||
val velocity: Vector2d? = null,
|
||||
val initialVelocity: Vector2d? = null,
|
||||
val finalVelocity: Vector2d? = null,
|
||||
val approach: Vector2d = Vector2d.ZERO,
|
||||
val flip: Boolean = false,
|
||||
val flippable: Boolean = true,
|
||||
val rotation: Double = 0.0,
|
||||
val angularVelocity: Double = 0.0,
|
||||
val length: Double = 10.0,
|
||||
val destructionAction: DestructionAction = DestructionAction.NONE,
|
||||
val destructionImage: AssetPath? = null,
|
||||
val destructionTime: Double = 0.0,
|
||||
val timeToLive: Double = 0.0,
|
||||
val layer: Layer = Layer.MIDDLE,
|
||||
val underwaterOnly: Boolean = false,
|
||||
val collidesForeground: Boolean? = null,
|
||||
val collidesLiquid: Boolean? = null,
|
||||
val ignoreWind: Boolean = true,
|
||||
val trail: Boolean = false,
|
||||
) : ParticleFactory {
|
||||
private val variance2 = if (variance != null) Particle(variance) else null
|
||||
private val prototype = Particle(this)
|
||||
|
||||
override fun create(): Particle {
|
||||
return prototype.copy()
|
||||
}
|
||||
|
||||
override fun create(random: RandomGenerator): Particle {
|
||||
if (variance2 == null)
|
||||
return prototype.copy()
|
||||
|
||||
return prototype.copy().applyVariance(variance2, random)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,22 +1,43 @@
|
||||
package ru.dbotthepony.kstarbound.defs.item
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import org.classdump.luna.ByteString
|
||||
import org.classdump.luna.LuaRuntimeException
|
||||
import org.classdump.luna.Table
|
||||
import org.classdump.luna.TableFactory
|
||||
import ru.dbotthepony.kommons.gson.consumeNull
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kstarbound.lua.StateMachine
|
||||
import ru.dbotthepony.kstarbound.lua.from
|
||||
import ru.dbotthepony.kstarbound.lua.toJsonObject
|
||||
import ru.dbotthepony.kommons.gson.get
|
||||
import ru.dbotthepony.kommons.gson.value
|
||||
import ru.dbotthepony.kommons.io.StreamCodec
|
||||
import ru.dbotthepony.kommons.io.readVarLong
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeVarLong
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import ru.dbotthepony.kstarbound.item.ItemStack
|
||||
import ru.dbotthepony.kstarbound.json.readJsonElement
|
||||
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.function.Supplier
|
||||
|
||||
private val EMPTY_JSON = JsonObject()
|
||||
|
||||
fun ItemDescriptor(data: JsonElement): ItemDescriptor {
|
||||
if (data is JsonPrimitive) {
|
||||
return ItemDescriptor(data.asString, 1L)
|
||||
@ -57,12 +78,40 @@ fun ItemDescriptor(data: Table, stateMachine: StateMachine): Supplier<ItemDescri
|
||||
return Supplier { result.value }
|
||||
}
|
||||
|
||||
fun ItemDescriptor(stream: DataInputStream): ItemDescriptor {
|
||||
val name = stream.readInternedString()
|
||||
val count = stream.readVarLong()
|
||||
val params = stream.readJsonElement()
|
||||
|
||||
if (name == "")
|
||||
return ItemDescriptor.EMPTY
|
||||
|
||||
return ItemDescriptor(name, count, if (params === JsonNull.INSTANCE || params == EMPTY_JSON) EMPTY_JSON else params as JsonObject)
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes a prototype of item
|
||||
*
|
||||
* [parameters] is considered to be immutable and should not be modified
|
||||
* directly (must be copied for mutable context)
|
||||
*/
|
||||
data class ItemDescriptor(
|
||||
val name: String,
|
||||
val count: Long,
|
||||
val parameters: JsonObject = JsonObject()
|
||||
val parameters: JsonObject = EMPTY_JSON
|
||||
) {
|
||||
val isEmpty get() = count <= 0L
|
||||
constructor(ref: Registry.Ref<IItemDefinition>, count: Long, parameters: JsonObject) : this(ref.key.left(), count, parameters)
|
||||
|
||||
val isEmpty get() = count <= 0L || name == "" || ref.isEmpty
|
||||
val ref by lazy { Registries.items.ref(name) }
|
||||
|
||||
override fun toString(): String {
|
||||
return "ItemDescriptor[$name, $count, $parameters]"
|
||||
}
|
||||
|
||||
fun makeStack(): ItemStack {
|
||||
return ItemStack.create(this)
|
||||
}
|
||||
|
||||
fun toJson(): JsonObject? {
|
||||
if (isEmpty) {
|
||||
@ -87,4 +136,35 @@ data class ItemDescriptor(
|
||||
it.rawset("parameters", allocator.from(parameters))
|
||||
}
|
||||
}
|
||||
|
||||
fun write(stream: DataOutputStream) {
|
||||
stream.writeBinaryString(name)
|
||||
stream.writeVarLong(count.coerceAtLeast(0L))
|
||||
stream.writeJsonElement(parameters)
|
||||
}
|
||||
|
||||
class Adapter(gson: Gson) : TypeAdapter<ItemDescriptor>() {
|
||||
private val elements = gson.getAdapter(JsonElement::class.java)
|
||||
|
||||
override fun write(out: JsonWriter, value: ItemDescriptor?) {
|
||||
if (value == null)
|
||||
out.nullValue()
|
||||
else if (value.isEmpty)
|
||||
out.nullValue()
|
||||
else
|
||||
out.value(value.toJson())
|
||||
}
|
||||
|
||||
override fun read(`in`: JsonReader): ItemDescriptor {
|
||||
if (`in`.consumeNull())
|
||||
return EMPTY
|
||||
|
||||
return ItemDescriptor(elements.read(`in`))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val EMPTY = ItemDescriptor("", 0)
|
||||
val CODEC = StreamCodec.Impl(::ItemDescriptor, { a, b -> b.write(a) })
|
||||
}
|
||||
}
|
||||
|
@ -14,10 +14,9 @@ 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.ItemReference
|
||||
import ru.dbotthepony.kommons.gson.consumeNull
|
||||
import ru.dbotthepony.kstarbound.json.stream
|
||||
import ru.dbotthepony.kstarbound.util.ItemStack
|
||||
import ru.dbotthepony.kstarbound.item.ItemStack
|
||||
import ru.dbotthepony.kstarbound.util.WriteOnce
|
||||
import java.util.Random
|
||||
import java.util.random.RandomGenerator
|
||||
@ -54,7 +53,7 @@ class TreasurePoolDefinition(pieces: List<Piece>) {
|
||||
data class Piece(
|
||||
val level: Double,
|
||||
val pool: ImmutableList<PoolEntry> = ImmutableList.of(),
|
||||
val fill: ImmutableList<Either<ItemReference, Registry.Ref<TreasurePoolDefinition>>> = ImmutableList.of(),
|
||||
val fill: ImmutableList<Either<ItemDescriptor, Registry.Ref<TreasurePoolDefinition>>> = ImmutableList.of(),
|
||||
val poolRounds: IPoolRounds = OneRound,
|
||||
// TODO: что оно делает?
|
||||
// оно точно не запрещает ему появляться несколько раз за одну генерацию treasure pool
|
||||
@ -150,7 +149,7 @@ class TreasurePoolDefinition(pieces: List<Piece>) {
|
||||
|
||||
data class PoolEntry(
|
||||
val weight: Double,
|
||||
val treasure: Either<ItemReference, Registry.Ref<TreasurePoolDefinition>>
|
||||
val treasure: Either<ItemDescriptor, Registry.Ref<TreasurePoolDefinition>>
|
||||
) {
|
||||
init {
|
||||
require(weight > 0.0) { "Invalid pool entry weight: $weight" }
|
||||
@ -161,7 +160,7 @@ class TreasurePoolDefinition(pieces: List<Piece>) {
|
||||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||
if (type.rawType === TreasurePoolDefinition::class.java) {
|
||||
return object : TypeAdapter<TreasurePoolDefinition>() {
|
||||
private val itemAdapter = gson.getAdapter(ItemReference::class.java)
|
||||
private val itemAdapter = gson.getAdapter(ItemDescriptor::class.java)
|
||||
private val poolAdapter = gson.getAdapter(TypeToken.getParameterized(Registry.Ref::class.java, TreasurePoolDefinition::class.java)) as TypeAdapter<Registry.Ref<TreasurePoolDefinition>>
|
||||
private val objReader = gson.getAdapter(JsonObject::class.java)
|
||||
|
||||
@ -189,7 +188,7 @@ class TreasurePoolDefinition(pieces: List<Piece>) {
|
||||
val things = objReader.read(`in`)
|
||||
|
||||
val pool = ImmutableList.Builder<PoolEntry>()
|
||||
val fill = ImmutableList.Builder<Either<ItemReference, Registry.Ref<TreasurePoolDefinition>>>()
|
||||
val fill = ImmutableList.Builder<Either<ItemDescriptor, Registry.Ref<TreasurePoolDefinition>>>()
|
||||
var poolRounds: IPoolRounds = OneRound
|
||||
val allowDuplication = things["allowDuplication"]?.asBoolean ?: false
|
||||
|
||||
|
@ -15,9 +15,8 @@ import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.defs.AssetPath
|
||||
import ru.dbotthepony.kstarbound.defs.ItemReference
|
||||
import ru.dbotthepony.kstarbound.defs.JsonReference
|
||||
import ru.dbotthepony.kstarbound.defs.StatModifier
|
||||
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.json.builder.JsonFactory
|
||||
@ -29,6 +28,7 @@ import ru.dbotthepony.kommons.gson.contains
|
||||
import ru.dbotthepony.kommons.gson.get
|
||||
import ru.dbotthepony.kommons.gson.getArray
|
||||
import ru.dbotthepony.kommons.gson.set
|
||||
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
||||
|
||||
data class ObjectDefinition(
|
||||
val objectName: String,
|
||||
@ -43,9 +43,9 @@ data class ObjectDefinition(
|
||||
val retainObjectParametersInItem: Boolean = false,
|
||||
val breakDropPool: Registry.Ref<TreasurePoolDefinition>? = null,
|
||||
// null - not specified, empty list - always drop nothing
|
||||
val breakDropOptions: ImmutableList<ImmutableList<ItemReference>>? = null,
|
||||
val breakDropOptions: ImmutableList<ImmutableList<ItemDescriptor>>? = null,
|
||||
val smashDropPool: Registry.Ref<TreasurePoolDefinition>? = null,
|
||||
val smashDropOptions: ImmutableList<ImmutableList<ItemReference>> = ImmutableList.of(),
|
||||
val smashDropOptions: ImmutableList<ImmutableList<ItemDescriptor>> = ImmutableList.of(),
|
||||
//val animation: AssetReference<AnimationDefinition>? = null,
|
||||
val animation: AssetPath? = null,
|
||||
val smashSounds: ImmutableSet<AssetPath> = ImmutableSet.of(),
|
||||
@ -96,9 +96,9 @@ data class ObjectDefinition(
|
||||
val retainObjectParametersInItem: Boolean = false,
|
||||
val breakDropPool: Registry.Ref<TreasurePoolDefinition>? = null,
|
||||
// null - not specified, empty list - always drop nothing
|
||||
val breakDropOptions: ImmutableList<ImmutableList<ItemReference>>? = null,
|
||||
val breakDropOptions: ImmutableList<ImmutableList<ItemDescriptor>>? = null,
|
||||
val smashDropPool: Registry.Ref<TreasurePoolDefinition>? = null,
|
||||
val smashDropOptions: ImmutableList<ImmutableList<ItemReference>> = ImmutableList.of(),
|
||||
val smashDropOptions: ImmutableList<ImmutableList<ItemDescriptor>> = ImmutableList.of(),
|
||||
//val animation: AssetReference<AnimationDefinition>? = null,
|
||||
val animation: AssetPath? = null,
|
||||
val smashSounds: ImmutableSet<AssetPath> = ImmutableSet.of(),
|
||||
|
@ -1,39 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.defs.particle
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.defs.animation.DestructionAction
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonImplementation
|
||||
import ru.dbotthepony.kstarbound.util.VirtualProperty
|
||||
import ru.dbotthepony.kstarbound.util.SBPattern
|
||||
|
||||
@JsonImplementation(ParticleConfig::class)
|
||||
interface IParticleConfig : IParticleVariance {
|
||||
val finalVelocity: Vector2d?
|
||||
val destructionAction: DestructionAction?
|
||||
val destructionTime: Double?
|
||||
val fade: Double?
|
||||
val layer: ParticleLayer?
|
||||
val timeToLive: Double?
|
||||
val variance: IParticleVariance?
|
||||
val text: SBPattern?
|
||||
|
||||
companion object {
|
||||
fun chain(vararg particles: IParticleConfig): IParticleConfig {
|
||||
val chain = IParticleVariance.chain(*particles)
|
||||
@Suppress("name_shadowing")
|
||||
val particles = ImmutableList.copyOf(particles)
|
||||
|
||||
return object : IParticleConfig, IParticleVariance by chain {
|
||||
override val finalVelocity by VirtualProperty(IParticleConfig::finalVelocity, particles)
|
||||
override val destructionAction by VirtualProperty(IParticleConfig::destructionAction, particles)
|
||||
override val destructionTime by VirtualProperty(IParticleConfig::destructionTime, particles)
|
||||
override val fade by VirtualProperty(IParticleConfig::fade, particles)
|
||||
override val layer by VirtualProperty(IParticleConfig::layer, particles)
|
||||
override val timeToLive by VirtualProperty(IParticleConfig::timeToLive, particles)
|
||||
override val variance by VirtualProperty(IParticleConfig::variance, particles)
|
||||
override val text by VirtualProperty(IParticleConfig::text, particles)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.defs.particle
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.Vector4d
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonImplementation
|
||||
import ru.dbotthepony.kstarbound.util.VirtualProperty
|
||||
|
||||
@JsonImplementation(ParticleVariance::class)
|
||||
interface IParticleVariance {
|
||||
val offset: Vector2d?
|
||||
val position: Vector2d?
|
||||
val offsetRegion: Vector4d?
|
||||
val initialVelocity: Vector2d?
|
||||
val approach: Vector2d?
|
||||
val angularVelocity: Double?
|
||||
val size: Double?
|
||||
val color: RGBAColor?
|
||||
|
||||
companion object {
|
||||
fun chain(vararg particles: IParticleVariance): IParticleVariance {
|
||||
@Suppress("name_shadowing")
|
||||
val particles = ImmutableList.copyOf(particles)
|
||||
|
||||
return object : IParticleVariance {
|
||||
override val offset by VirtualProperty(IParticleVariance::offset, particles)
|
||||
override val position by VirtualProperty(IParticleVariance::position, particles)
|
||||
override val offsetRegion by VirtualProperty(IParticleVariance::offsetRegion, particles)
|
||||
override val initialVelocity by VirtualProperty(IParticleVariance::initialVelocity, particles)
|
||||
override val approach by VirtualProperty(IParticleVariance::approach, particles)
|
||||
override val angularVelocity by VirtualProperty(IParticleVariance::angularVelocity, particles)
|
||||
override val size by VirtualProperty(IParticleVariance::size, particles)
|
||||
override val color by VirtualProperty(IParticleVariance::color, particles)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.defs.particle
|
||||
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.Vector4d
|
||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.animation.DestructionAction
|
||||
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.util.SBPattern
|
||||
|
||||
@JsonFactory
|
||||
data class ParticleConfig(
|
||||
val type: ParticleType,
|
||||
val animation: AssetReference<AnimationDefinition>? = null,
|
||||
val image: SpriteReference? = null,
|
||||
override val offset: Vector2d? = null,
|
||||
override val position: Vector2d? = null,
|
||||
override val offsetRegion: Vector4d? = null,
|
||||
override val initialVelocity: Vector2d? = null,
|
||||
override val finalVelocity: Vector2d? = null,
|
||||
override val approach: Vector2d? = null,
|
||||
override val angularVelocity: Double? = null,
|
||||
override val destructionAction: DestructionAction? = null,
|
||||
override val destructionTime: Double? = null,
|
||||
override val fade: Double? = null,
|
||||
override val size: Double? = null,
|
||||
override val layer: ParticleLayer? = null,
|
||||
override val timeToLive: Double? = null,
|
||||
override val variance: IParticleVariance? = null,
|
||||
override val color: RGBAColor? = null,
|
||||
override val text: SBPattern? = null,
|
||||
) : IParticleConfig
|
@ -1,27 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.defs.particle
|
||||
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@JsonFactory
|
||||
data class ParticleCreator(
|
||||
val count: Int = 1,
|
||||
val particle: Either<Registry.Ref<ParticleDefinition>, IParticleConfig>,
|
||||
|
||||
//override val offset: Vector2d? = null,
|
||||
//override val position: Vector2d? = null,
|
||||
//override val offsetRegion: Vector4d? = null,
|
||||
//override val initialVelocity: Vector2d? = null,
|
||||
//override val finalVelocity: Vector2d? = null,
|
||||
//override val approach: Vector2d? = null,
|
||||
//override val angularVelocity: Double? = null,
|
||||
//override val destructionAction: DestructionAction? = null,
|
||||
//override val destructionTime: Double? = null,
|
||||
//override val fade: Double? = null,
|
||||
//override val size: Double? = null,
|
||||
//override val layer: ParticleLayer? = null,
|
||||
//override val timeToLive: Double? = null,
|
||||
//override val variance: IParticleVariance? = null,
|
||||
//override val color: Color? = null,
|
||||
) //: IParticleConfig
|
@ -1,9 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.defs.particle
|
||||
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@JsonFactory
|
||||
data class ParticleDefinition(
|
||||
val kind: String,
|
||||
val definition: IParticleConfig
|
||||
)
|
@ -1,35 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.defs.particle
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.Vector4d
|
||||
import ru.dbotthepony.kstarbound.defs.animation.DestructionAction
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.util.SBPattern
|
||||
|
||||
@JsonFactory
|
||||
data class ParticleEmitter(
|
||||
val enabled: Boolean = true,
|
||||
val emissionRate: Double = 1.0,
|
||||
val count: Int = 1,
|
||||
val transformationGroups: ImmutableList<String> = ImmutableList.of(),
|
||||
val particles: ImmutableList<ParticleCreator>,
|
||||
|
||||
override val offset: Vector2d? = null,
|
||||
override val position: Vector2d? = null,
|
||||
override val offsetRegion: Vector4d? = null,
|
||||
override val initialVelocity: Vector2d? = null,
|
||||
override val finalVelocity: Vector2d? = null,
|
||||
override val approach: Vector2d? = null,
|
||||
override val angularVelocity: Double? = null,
|
||||
override val destructionAction: DestructionAction? = null,
|
||||
override val destructionTime: Double? = null,
|
||||
override val fade: Double? = null,
|
||||
override val size: Double? = null,
|
||||
override val layer: ParticleLayer? = null,
|
||||
override val timeToLive: Double? = null,
|
||||
override val variance: IParticleVariance? = null,
|
||||
override val color: RGBAColor? = null,
|
||||
override val text: SBPattern? = null,
|
||||
) : IParticleConfig
|
@ -1,5 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.defs.particle
|
||||
|
||||
enum class ParticleLayer {
|
||||
FRONT
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.defs.particle
|
||||
|
||||
enum class ParticleType {
|
||||
ANIMATED,
|
||||
TEXTURED,
|
||||
EMBER,
|
||||
TEXT
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.defs.particle
|
||||
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.Vector4d
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
||||
@JsonFactory
|
||||
data class ParticleVariance(
|
||||
override val offset: Vector2d? = null,
|
||||
override val position: Vector2d? = null,
|
||||
override val offsetRegion: Vector4d? = null,
|
||||
override val initialVelocity: Vector2d? = null,
|
||||
override val approach: Vector2d? = null,
|
||||
override val angularVelocity: Double? = null,
|
||||
override val size: Double? = null,
|
||||
override val color: RGBAColor? = null,
|
||||
) : IParticleVariance
|
@ -34,7 +34,7 @@ private fun flattenJsonArray(input: JsonArray, interner: Interner<String> = Inte
|
||||
}
|
||||
|
||||
private fun flattenJsonObject(input: JsonObject, interner: Interner<String> = Interner { it }): MutableMap<String, Any> {
|
||||
val flattened = Object2ObjectOpenHashMap<String, Any>()
|
||||
val flattened = HashMap<String, Any>()
|
||||
|
||||
for ((k, v) in input.entrySet()) {
|
||||
when (v) {
|
||||
|
@ -7,7 +7,7 @@ import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.defs.image.Image
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
import ru.dbotthepony.kstarbound.util.RenderDirectives
|
||||
import ru.dbotthepony.kstarbound.util.Directives
|
||||
import ru.dbotthepony.kstarbound.util.random.nextRange
|
||||
import java.util.random.RandomGenerator
|
||||
|
||||
@ -128,7 +128,7 @@ class Parallax(
|
||||
lightMapped = lightMapped,
|
||||
fadePercent = fadePercent,
|
||||
alpha = 1.0,
|
||||
directives = RenderDirectives(directives),
|
||||
directives = Directives(directives),
|
||||
textures = ImmutableList.copyOf(textures),
|
||||
)
|
||||
}
|
||||
@ -136,7 +136,7 @@ class Parallax(
|
||||
|
||||
@JsonFactory
|
||||
data class Layer(
|
||||
var directives: RenderDirectives,
|
||||
var directives: Directives,
|
||||
val textures: ImmutableList<String>,
|
||||
val alpha: Double,
|
||||
val parallaxValue: Vector2d,
|
||||
|
@ -8,11 +8,17 @@ import ru.dbotthepony.kommons.io.readDouble
|
||||
import ru.dbotthepony.kommons.io.readFloat
|
||||
import ru.dbotthepony.kommons.io.readLong
|
||||
import ru.dbotthepony.kommons.io.readSignedVarInt
|
||||
import ru.dbotthepony.kommons.io.readVector2d
|
||||
import ru.dbotthepony.kommons.io.readVector2f
|
||||
import ru.dbotthepony.kommons.io.writeDouble
|
||||
import ru.dbotthepony.kommons.io.writeFloat
|
||||
import ru.dbotthepony.kommons.io.writeLong
|
||||
import ru.dbotthepony.kommons.io.writeSignedVarInt
|
||||
import ru.dbotthepony.kommons.io.writeStruct2d
|
||||
import ru.dbotthepony.kommons.io.writeStruct2f
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.util.AABB
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
@ -58,3 +64,50 @@ fun InputStream.readColor(): RGBAColor {
|
||||
}
|
||||
|
||||
fun InputStream.readInternedString(): String = Starbound.STRINGS.intern(readBinaryString())
|
||||
|
||||
fun InputStream.readAABBLegacy(): AABB {
|
||||
val mins = readVector2f()
|
||||
val maxs = readVector2f()
|
||||
return AABB(mins.toDoubleVector(), maxs.toDoubleVector())
|
||||
}
|
||||
|
||||
fun InputStream.readAABBLegacyOptional(): KOptional<AABB> {
|
||||
val mins = readVector2f()
|
||||
val maxs = readVector2f()
|
||||
|
||||
// what the fuck are you doing?!
|
||||
if (
|
||||
mins.x == Float.MAX_VALUE && mins.y == Float.MAX_VALUE &&
|
||||
maxs.x == -Float.MAX_VALUE && maxs.y == -Float.MAX_VALUE
|
||||
) {
|
||||
return KOptional()
|
||||
}
|
||||
|
||||
return KOptional(AABB(mins.toDoubleVector(), maxs.toDoubleVector()))
|
||||
}
|
||||
|
||||
fun InputStream.readAABB(): AABB {
|
||||
return AABB(readVector2d(), readVector2d())
|
||||
}
|
||||
|
||||
fun OutputStream.writeAABBLegacy(value: AABB) {
|
||||
writeStruct2f(value.mins.toFloatVector())
|
||||
writeStruct2f(value.maxs.toFloatVector())
|
||||
}
|
||||
|
||||
fun OutputStream.writeAABBLegacyOptional(value: KOptional<AABB>) {
|
||||
value.ifPresent {
|
||||
writeStruct2f(it.mins.toFloatVector())
|
||||
writeStruct2f(it.maxs.toFloatVector())
|
||||
}.ifNotPresent {
|
||||
writeFloat(Float.MAX_VALUE)
|
||||
writeFloat(Float.MAX_VALUE)
|
||||
writeFloat(Float.MIN_VALUE)
|
||||
writeFloat(Float.MIN_VALUE)
|
||||
}
|
||||
}
|
||||
|
||||
fun OutputStream.writeAABB(value: AABB) {
|
||||
writeStruct2d(value.mins)
|
||||
writeStruct2d(value.maxs)
|
||||
}
|
||||
|
13
src/main/kotlin/ru/dbotthepony/kstarbound/item/Container.kt
Normal file
13
src/main/kotlin/ru/dbotthepony/kstarbound/item/Container.kt
Normal file
@ -0,0 +1,13 @@
|
||||
package ru.dbotthepony.kstarbound.item
|
||||
|
||||
open class Container(final override val size: Int) : IContainer {
|
||||
private val slots = Array(size) { ItemStack.EMPTY }
|
||||
|
||||
override fun get(index: Int): ItemStack {
|
||||
return slots[index]
|
||||
}
|
||||
|
||||
override fun set(index: Int, value: ItemStack) {
|
||||
slots[index] = value
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.item
|
||||
|
||||
interface IContainer {
|
||||
val size: Int
|
||||
operator fun get(index: Int): ItemStack
|
||||
operator fun set(index: Int, value: ItemStack)
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.util
|
||||
package ru.dbotthepony.kstarbound.item
|
||||
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.TypeAdapter
|
||||
@ -13,34 +14,59 @@ import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
||||
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
|
||||
import ru.dbotthepony.kommons.gson.consumeNull
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeVarLong
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
||||
import ru.dbotthepony.kstarbound.lua.from
|
||||
import java.io.DataOutputStream
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
class ItemStack private constructor(item: Registry.Entry<IItemDefinition>?, count: Long, val parameters: JsonObject, marker: Unit) {
|
||||
constructor(item: Registry.Entry<IItemDefinition>, count: Long = 1L, parameters: JsonObject = JsonObject()) : this(item, count, parameters, Unit)
|
||||
/**
|
||||
* Base class for instanced items in game
|
||||
*/
|
||||
open class ItemStack {
|
||||
constructor() {
|
||||
this.config = Registries.items.emptyRef
|
||||
this.parameters = JsonObject()
|
||||
}
|
||||
|
||||
var item: Registry.Entry<IItemDefinition>? = item
|
||||
constructor(descriptor: ItemDescriptor) {
|
||||
this.config = Registries.items.ref(descriptor.name)
|
||||
this.count = descriptor.count
|
||||
this.parameters = descriptor.parameters.deepCopy()
|
||||
}
|
||||
|
||||
/**
|
||||
* unique number utilized to determine whenever stack has changed
|
||||
*/
|
||||
var changeset: Long = CHANGESET.incrementAndGet()
|
||||
private set
|
||||
|
||||
var count = count
|
||||
/**
|
||||
* it uses global atomic long to guarantee stacks having different
|
||||
* changesets throughout entire lifetime of game
|
||||
*/
|
||||
protected fun bumpVersion() {
|
||||
changeset = CHANGESET.incrementAndGet()
|
||||
}
|
||||
|
||||
var count: Long = 0L
|
||||
set(value) {
|
||||
if (field == 0L || item == null)
|
||||
return
|
||||
|
||||
field = value.coerceAtLeast(0L)
|
||||
|
||||
if (field == 0L) {
|
||||
item = null
|
||||
}
|
||||
}
|
||||
|
||||
val config: Registry.Ref<IItemDefinition>
|
||||
val parameters: JsonObject
|
||||
|
||||
val isEmpty: Boolean
|
||||
get() = count <= 0 || item == null
|
||||
get() = count <= 0 || config.isEmpty
|
||||
|
||||
val isNotEmpty: Boolean
|
||||
get() = count > 0 && item != null
|
||||
get() = count > 0 && config.isPresent
|
||||
|
||||
val maxStackSize: Long
|
||||
get() = item?.value?.maxStack ?: 0L
|
||||
get() = config.value?.maxStack ?: 0L
|
||||
|
||||
fun grow(amount: Long) {
|
||||
count += amount
|
||||
@ -52,9 +78,22 @@ class ItemStack private constructor(item: Registry.Entry<IItemDefinition>?, coun
|
||||
|
||||
fun createDescriptor(): ItemDescriptor {
|
||||
if (isEmpty)
|
||||
return ItemDescriptor("", 0L, JsonObject())
|
||||
return ItemDescriptor.EMPTY
|
||||
|
||||
return ItemDescriptor(item!!.key, count, parameters.deepCopy())
|
||||
return ItemDescriptor(config.key.left(), count, parameters.deepCopy())
|
||||
}
|
||||
|
||||
// faster than creating an item descriptor and writing it (because it avoids copying and allocation)
|
||||
fun write(stream: DataOutputStream) {
|
||||
if (isEmpty) {
|
||||
stream.writeBinaryString("")
|
||||
stream.writeVarLong(0L)
|
||||
stream.writeJsonElement(JsonNull.INSTANCE)
|
||||
} else {
|
||||
stream.writeBinaryString(config.key.left())
|
||||
stream.writeVarLong(count)
|
||||
stream.writeJsonElement(parameters)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,7 +109,7 @@ class ItemStack private constructor(item: Registry.Entry<IItemDefinition>?, coun
|
||||
|
||||
fun mergeFrom(other: ItemStack, simulate: Boolean) {
|
||||
if (isStackable(other)) {
|
||||
val newCount = (count + other.count).coerceAtMost(item!!.value.maxStack)
|
||||
val newCount = (count + other.count).coerceAtMost(maxStackSize)
|
||||
val diff = newCount - count
|
||||
other.count -= diff
|
||||
|
||||
@ -86,11 +125,14 @@ class ItemStack private constructor(item: Registry.Entry<IItemDefinition>?, coun
|
||||
if (isEmpty)
|
||||
return other.isEmpty
|
||||
|
||||
return other.count == count && other.item == item
|
||||
return other.count == count && other.config == config
|
||||
}
|
||||
|
||||
fun isStackable(other: ItemStack): Boolean {
|
||||
return count != 0L && other.count != 0L && item!!.value.maxStack < count && other.item == item && other.parameters == parameters
|
||||
if (isEmpty || other.isEmpty)
|
||||
return false
|
||||
|
||||
return count != 0L && other.count != 0L && maxStackSize < count && other.config == config && other.parameters == parameters
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@ -100,35 +142,33 @@ class ItemStack private constructor(item: Registry.Entry<IItemDefinition>?, coun
|
||||
if (isEmpty)
|
||||
return other.isEmpty
|
||||
|
||||
return other.count == count && other.item == item && other.parameters == parameters
|
||||
return other.count == count && other.config == config && other.parameters == parameters
|
||||
}
|
||||
|
||||
// мы не можем делать хеш из count и parameters так как они изменяемы
|
||||
override fun hashCode(): Int {
|
||||
return item.hashCode()
|
||||
return config.hashCode()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
if (isEmpty)
|
||||
return "ItemDescriptor.EMPTY"
|
||||
return "ItemStack.EMPTY"
|
||||
|
||||
return "ItemDescriptor[${item!!.value.itemName}, count = $count, params = $parameters]"
|
||||
return "ItemDescriptor[${config.value?.itemName}, count = $count, params = $parameters]"
|
||||
}
|
||||
|
||||
fun copy(): ItemStack {
|
||||
if (isEmpty)
|
||||
return this
|
||||
|
||||
return ItemStack(item, count, parameters.deepCopy(), Unit)
|
||||
return ItemStack(ItemDescriptor(config, count, parameters.deepCopy()))
|
||||
}
|
||||
|
||||
fun toJson(): JsonObject? {
|
||||
if (isEmpty) {
|
||||
if (isEmpty)
|
||||
return null
|
||||
}
|
||||
|
||||
return JsonObject().also {
|
||||
it.add("name", JsonPrimitive(item!!.key))
|
||||
it.add("name", JsonPrimitive(config.key.left()))
|
||||
it.add("count", JsonPrimitive(count))
|
||||
it.add("parameters", parameters.deepCopy())
|
||||
}
|
||||
@ -140,7 +180,7 @@ class ItemStack private constructor(item: Registry.Entry<IItemDefinition>?, coun
|
||||
}
|
||||
|
||||
return allocator.newTable(0, 3).also {
|
||||
it.rawset("name", item!!.key)
|
||||
it.rawset("name", config.key.left())
|
||||
it.rawset("count", count)
|
||||
it.rawset("parameters", allocator.from(parameters))
|
||||
}
|
||||
@ -165,7 +205,13 @@ class ItemStack private constructor(item: Registry.Entry<IItemDefinition>?, coun
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val CHANGESET = AtomicLong()
|
||||
|
||||
@JvmField
|
||||
val EMPTY = ItemStack(null, 0L, JsonObject(), Unit)
|
||||
val EMPTY = ItemStack()
|
||||
|
||||
fun create(descriptor: ItemDescriptor): ItemStack {
|
||||
return EMPTY
|
||||
}
|
||||
}
|
||||
}
|
@ -24,8 +24,9 @@ import java.util.LinkedList
|
||||
* Позволяет читать двоичный JSON прямиком в [JsonElement]
|
||||
*/
|
||||
fun DataInputStream.readJsonElement(): JsonElement {
|
||||
return when (val id = read()) {
|
||||
BinaryJsonReader.TYPE_INVALID, BinaryJsonReader.TYPE_NULL -> JsonNull.INSTANCE
|
||||
return when (val id = readUnsignedByte()) {
|
||||
BinaryJsonReader.TYPE_INVALID -> throw JsonParseException("Tried to read TYPE_INVALID from stream")
|
||||
BinaryJsonReader.TYPE_NULL -> JsonNull.INSTANCE
|
||||
BinaryJsonReader.TYPE_DOUBLE -> JsonPrimitive(readDouble())
|
||||
BinaryJsonReader.TYPE_BOOLEAN -> InternedJsonElementAdapter.of(readBoolean())
|
||||
BinaryJsonReader.TYPE_INT -> JsonPrimitive(readSignedVarLong())
|
||||
|
@ -96,7 +96,7 @@ operator fun Table.iterator(): Iterator<Map.Entry<Any, Any>> {
|
||||
|
||||
fun Table.toJson(): JsonElement {
|
||||
val arrayValues = Long2ObjectAVLTreeMap<Any>()
|
||||
val hashValues = Object2ObjectOpenHashMap<Any, Any>()
|
||||
val hashValues = HashMap<Any, Any>()
|
||||
|
||||
for ((k, v) in this) {
|
||||
if (k is Number) {
|
||||
@ -150,14 +150,8 @@ fun Table.toJson(): JsonElement {
|
||||
}
|
||||
|
||||
fun Table.toJsonObject(): JsonObject {
|
||||
val hashValues = Object2ObjectOpenHashMap<Any, Any>()
|
||||
|
||||
for ((k, v) in this) {
|
||||
hashValues[k] = v
|
||||
}
|
||||
|
||||
return JsonObject().also {
|
||||
for ((k, v) in hashValues) {
|
||||
for ((k, v) in this) {
|
||||
if (v is ByteString) {
|
||||
it.add(k.toString(), JsonPrimitive(v.decode()))
|
||||
} else if (v is Number) {
|
||||
|
@ -11,14 +11,13 @@ import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.io.StreamCodec
|
||||
import ru.dbotthepony.kommons.io.VarIntValueCodec
|
||||
import ru.dbotthepony.kstarbound.network.syncher.BasicNetworkedElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.GroupElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||
import ru.dbotthepony.kstarbound.network.syncher.MasterElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedSignedInt
|
||||
import ru.dbotthepony.kstarbound.player.Avatar
|
||||
import ru.dbotthepony.kstarbound.server.ServerChannels
|
||||
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
|
||||
import java.io.Closeable
|
||||
import java.util.*
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
abstract class Connection(val side: ConnectionSide, val type: ConnectionType) : ChannelInboundHandlerAdapter(), Closeable {
|
||||
@ -154,10 +153,9 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType) :
|
||||
// holy shit
|
||||
val clientPresenceEntities = BasicNetworkedElement(IntAVLTreeSet(), StreamCodec.Collection(VarIntValueCodec) { IntAVLTreeSet() })
|
||||
|
||||
val clientStateGroup = MasterElement(GroupElement(windowXMin, windowYMin, windowWidth, windowHeight, playerID, clientPresenceEntities))
|
||||
val clientStateGroup = MasterElement(NetworkedGroup(windowXMin, windowYMin, windowWidth, windowHeight, playerID, clientPresenceEntities))
|
||||
|
||||
companion object {
|
||||
private val EMPTY_UUID = UUID(0L, 0L)
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
|
||||
val NIO_POOL by lazy {
|
||||
|
@ -79,7 +79,7 @@ class JsonRPC {
|
||||
private val pendingWrite = ArrayList<Entry>()
|
||||
private val responses = Int2ObjectOpenHashMap<CompletableFuture<JsonElement>>()
|
||||
private val lock = ReentrantLock()
|
||||
private val handlers = Object2ObjectOpenHashMap<String, Callback>()
|
||||
private val handlers = HashMap<String, Callback>()
|
||||
|
||||
fun add(name: String, callback: Callback): (JsonElement) -> CompletableFuture<JsonElement> {
|
||||
val old = handlers.put(name, callback)
|
||||
|
@ -184,15 +184,13 @@ class PacketRegistry(val isLegacy: Boolean) {
|
||||
// separate headers for each
|
||||
// Due to nature of netty pipeline, we can't do the same on native protocol;
|
||||
// so don't do that when on native protocol
|
||||
while (stream.available() > 0 || !isLegacy) {
|
||||
do {
|
||||
try {
|
||||
ctx.fireChannelRead(readingType!!.factory.read(DataInputStream(stream), isLegacy, side))
|
||||
} catch (err: Throwable) {
|
||||
LOGGER.error("Error while reading incoming packet from network (type ${readingType!!.id}; ${readingType!!.type})", err)
|
||||
}
|
||||
|
||||
if (!isLegacy) break
|
||||
}
|
||||
} while (stream.available() > 0 && isLegacy)
|
||||
|
||||
stream.close()
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets
|
||||
|
||||
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.io.readByteArray
|
||||
import ru.dbotthepony.kommons.io.readSignedVarInt
|
||||
@ -10,8 +11,10 @@ import ru.dbotthepony.kstarbound.defs.EntityType
|
||||
import ru.dbotthepony.kstarbound.network.IClientPacket
|
||||
import ru.dbotthepony.kstarbound.network.IServerPacket
|
||||
import ru.dbotthepony.kstarbound.server.ServerConnection
|
||||
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.io.File
|
||||
|
||||
class EntityCreatePacket(val entityType: EntityType, val storeData: ByteArray, val firstNetState: ByteArray, val entityID: Int) : IServerPacket, IClientPacket {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
|
||||
@ -32,13 +35,22 @@ class EntityCreatePacket(val entityType: EntityType, val storeData: ByteArray, v
|
||||
if (entityID !in connection.entityIDRange) {
|
||||
LOGGER.error("Player $connection tried to create entity $entityType with ID $entityID, but that's outside of allowed range ${connection.entityIDRange}!")
|
||||
} else {
|
||||
connection.enqueue {
|
||||
val entity = when (entityType) {
|
||||
EntityType.PLAYER -> {
|
||||
val player = PlayerEntity(DataInputStream(FastByteArrayInputStream(storeData)), connection.isLegacy)
|
||||
player.networkGroup.read(firstNetState, isLegacy = connection.isLegacy)
|
||||
player
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
entity?.entityID = entityID
|
||||
|
||||
connection.enqueue {
|
||||
entity?.joinWorld(this)
|
||||
}
|
||||
}
|
||||
|
||||
println(entityType)
|
||||
println(entityID)
|
||||
}
|
||||
|
||||
override fun play(connection: ClientConnection) {
|
||||
|
@ -1,7 +1,9 @@
|
||||
package ru.dbotthepony.kstarbound.network.packets
|
||||
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.io.readByteArray
|
||||
import ru.dbotthepony.kommons.io.readSignedVarInt
|
||||
import ru.dbotthepony.kommons.io.readVarInt
|
||||
@ -27,7 +29,15 @@ class EntityUpdateSetPacket(val forConnection: Int, val deltas: Int2ObjectMap<By
|
||||
}
|
||||
|
||||
override fun play(connection: ServerConnection) {
|
||||
|
||||
connection.enqueue {
|
||||
for ((id, delta) in deltas) {
|
||||
if (id !in connection.entityIDRange) {
|
||||
LOGGER.error("Player $connection tried to update entity with ID $id, but that's outside of allowed range ${connection.entityIDRange}!")
|
||||
} else {
|
||||
entities[id]?.readDelta(ByteArrayList.wrap(delta), 0.0, connection.isLegacy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun play(connection: ClientConnection) {
|
||||
@ -49,5 +59,7 @@ class EntityUpdateSetPacket(val forConnection: Int, val deltas: Int2ObjectMap<By
|
||||
|
||||
return EntityUpdateSetPacket(forConnection, deltas)
|
||||
}
|
||||
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
}
|
@ -8,12 +8,16 @@ import java.io.DataOutputStream
|
||||
import java.util.*
|
||||
import java.util.function.Consumer
|
||||
|
||||
open class BasicNetworkedElement<TYPE, LEGACY>(private var value: TYPE, val codec: StreamCodec<TYPE>, val legacyCodec: StreamCodec<LEGACY>, val toLegacy: (TYPE) -> LEGACY, val fromLegacy: (LEGACY) -> TYPE) : NetworkedElement(), ListenableDelegate<TYPE> {
|
||||
open class BasicNetworkedElement<TYPE, LEGACY>(private var value: TYPE, protected val codec: StreamCodec<TYPE>, protected val legacyCodec: StreamCodec<LEGACY>, protected val toLegacy: (TYPE) -> LEGACY, protected val fromLegacy: (LEGACY) -> TYPE) : NetworkedElement(), ListenableDelegate<TYPE> {
|
||||
protected val valueListeners = Listenable.Impl<TYPE>()
|
||||
protected val queue = LinkedList<Pair<Double, TYPE>>()
|
||||
protected var isInterpolating = false
|
||||
protected var currentTime = 0.0
|
||||
|
||||
override fun toString(): String {
|
||||
return "BasicNetworkedElement[$value, isInterpolating=$isInterpolating, currentTime=$currentTime]"
|
||||
}
|
||||
|
||||
override fun accept(t: TYPE) {
|
||||
if (t != value) {
|
||||
value = t
|
||||
|
@ -1,8 +1,12 @@
|
||||
package ru.dbotthepony.kstarbound.network.syncher
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.TypeAdapter
|
||||
import ru.dbotthepony.kommons.io.BinaryStringCodec
|
||||
import ru.dbotthepony.kommons.io.BooleanValueCodec
|
||||
import ru.dbotthepony.kommons.io.RGBACodec
|
||||
import ru.dbotthepony.kommons.io.StreamCodec
|
||||
import ru.dbotthepony.kommons.io.UnsignedVarIntCodec
|
||||
import ru.dbotthepony.kommons.io.UnsignedVarLongCodec
|
||||
@ -10,16 +14,34 @@ import ru.dbotthepony.kommons.io.VarIntValueCodec
|
||||
import ru.dbotthepony.kommons.io.VarLongValueCodec
|
||||
import ru.dbotthepony.kommons.io.Vector2dCodec
|
||||
import ru.dbotthepony.kommons.io.Vector2fCodec
|
||||
import ru.dbotthepony.kommons.io.koptional
|
||||
import ru.dbotthepony.kommons.io.readByteArray
|
||||
import ru.dbotthepony.kommons.io.readVarInt
|
||||
import ru.dbotthepony.kommons.io.readVarLong
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeByteArray
|
||||
import ru.dbotthepony.kommons.io.writeVarInt
|
||||
import ru.dbotthepony.kommons.io.writeVarLong
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.util.AABB
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
||||
import ru.dbotthepony.kstarbound.defs.world.SkyType
|
||||
import ru.dbotthepony.kstarbound.io.readAABB
|
||||
import ru.dbotthepony.kstarbound.io.readAABBLegacy
|
||||
import ru.dbotthepony.kstarbound.io.readAABBLegacyOptional
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import ru.dbotthepony.kstarbound.io.writeAABB
|
||||
import ru.dbotthepony.kstarbound.io.writeAABBLegacy
|
||||
import ru.dbotthepony.kstarbound.io.writeAABBLegacyOptional
|
||||
import ru.dbotthepony.kstarbound.item.ItemStack
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
import ru.dbotthepony.kstarbound.json.readJsonElement
|
||||
import ru.dbotthepony.kstarbound.json.readJsonObject
|
||||
import ru.dbotthepony.kstarbound.json.writeJsonElement
|
||||
import ru.dbotthepony.kstarbound.json.writeJsonObject
|
||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
@ -31,6 +53,19 @@ fun <TYPE> BasicNetworkedElement(value: TYPE, codec: StreamCodec<TYPE>): BasicNe
|
||||
}
|
||||
|
||||
val ByteArrayCodec = StreamCodec.Impl(DataInputStream::readByteArray, DataOutputStream::writeByteArray)
|
||||
val InternedStringCodec = StreamCodec.Impl(DataInputStream::readInternedString, DataOutputStream::writeBinaryString)
|
||||
|
||||
val AABBCodecLegacy = StreamCodec.Impl(DataInputStream::readAABBLegacy, DataOutputStream::writeAABBLegacy)
|
||||
val AABBCodecLegacyOptional = StreamCodec.Impl(DataInputStream::readAABBLegacyOptional, DataOutputStream::writeAABBLegacyOptional)
|
||||
val AABBCodecNative = StreamCodec.Impl(DataInputStream::readAABB, DataOutputStream::writeAABB)
|
||||
|
||||
val ValidatingBooleanCodec = StreamCodec.Impl({
|
||||
when (val read = it.readUnsignedByte()) {
|
||||
0 -> false
|
||||
1 -> true
|
||||
else -> throw IllegalStateException("Expected to read proper boolean from stream, but incoming byte was: $read")
|
||||
}
|
||||
}, DataOutputStream::writeBoolean)
|
||||
|
||||
// networking size_t...
|
||||
// god help us all
|
||||
@ -69,17 +104,26 @@ fun networkedSignedInt(value: Int = 0) = BasicNetworkedElement(value, VarIntValu
|
||||
fun networkedUnsignedInt(value: Int = 0) = BasicNetworkedElement(value, UnsignedVarIntCodec)
|
||||
fun networkedSignedLong(value: Long = 0L) = BasicNetworkedElement(value, VarLongValueCodec)
|
||||
fun networkedUnsignedLong(value: Long = 0L) = BasicNetworkedElement(value, UnsignedVarLongCodec)
|
||||
fun networkedBoolean(value: Boolean = false) = BasicNetworkedElement(value, BooleanValueCodec)
|
||||
fun networkedBoolean(value: Boolean = false) = BasicNetworkedElement(value, ValidatingBooleanCodec)
|
||||
fun networkedPointer(value: Long = 0L) = BasicNetworkedElement(value, SizeTCodec)
|
||||
fun networkedVec2f(value: Vector2d = Vector2d.ZERO) = BasicNetworkedElement(value, Vector2dCodec, Vector2fCodec, { it.toFloatVector() }, { it.toDoubleVector() })
|
||||
fun networkedBytes(value: ByteArray = ByteArray(0)) = BasicNetworkedElement(value, ByteArrayCodec)
|
||||
fun <E : Enum<E>> networkedEnum(value: E) = BasicNetworkedElement(value, StreamCodec.Enum(value::class.java))
|
||||
|
||||
fun <T> networkedData(value: T, codec: StreamCodec<T>) = BasicNetworkedElement(value, codec)
|
||||
fun <T> networkedData(value: T, codec: StreamCodec<T>, legacyCodec: StreamCodec<T>) = BasicNetworkedElement(value, codec, legacyCodec, { it }, { it })
|
||||
|
||||
fun networkedAABB(value: AABB = AABB.ZERO) = networkedData(value, AABBCodecNative, AABBCodecLegacy)
|
||||
fun networkedAABBNullable(value: KOptional<AABB> = KOptional()) = networkedData(value, AABBCodecNative.koptional(), AABBCodecLegacyOptional)
|
||||
|
||||
// this is ugly because of invariant generics, but we must bear with it.
|
||||
fun <E> networkedList(codec: StreamCodec<E>): BasicNetworkedElement<List<E>, List<E>> = networkedData(ArrayList(), StreamCodec.Collection(codec, ::ArrayList)) as BasicNetworkedElement<List<E>, List<E>>
|
||||
fun networkedItem(value: ItemStack = ItemStack.EMPTY) = NetworkedItemStack(value)
|
||||
fun networkedStatefulItem(value: ItemStack = ItemStack.EMPTY) = NetworkedStatefulItemStack(value)
|
||||
|
||||
fun networkedEventCounter() = EventCounterElement()
|
||||
fun networkedString(value: String = "") = BasicNetworkedElement(value, BinaryStringCodec)
|
||||
fun <S> networkedSignals(codec: StreamCodec<S>) = NetworkedSignal(codec)
|
||||
fun <S> networkedSignals(codec: StreamCodec<S>, maxSize: Int) = NetworkedSignal(codec, maxSize)
|
||||
fun networkedString(value: String = "") = BasicNetworkedElement(value, InternedStringCodec)
|
||||
|
||||
fun networkedPoly(value: Poly) = networkedData(value, Poly.CODEC, Poly.LEGACY_CODEC)
|
||||
|
||||
@ -91,6 +135,11 @@ inline fun <reified T> networkedJson(value: T, adapter: TypeAdapter<T> = Starbou
|
||||
}
|
||||
}
|
||||
|
||||
val JsonElementCodec = StreamCodec.Impl(DataInputStream::readJsonElement, DataOutputStream::writeJsonElement)
|
||||
val JsonObjectCodec = StreamCodec.Impl(DataInputStream::readJsonObject, DataOutputStream::writeJsonObject)
|
||||
fun networkedJsonElement(value: JsonElement = JsonNull.INSTANCE) = networkedData(value, JsonElementCodec)
|
||||
fun networkedJsonObject(value: JsonObject) = networkedData(value, JsonObjectCodec)
|
||||
|
||||
fun <T> nativeCodec(reader: DataInputStream.(Boolean) -> T, writer: T.(DataOutputStream, Boolean) -> Unit): StreamCodec<T> {
|
||||
return StreamCodec.Impl({ reader(it, false) }, { a, b -> writer(b, a, false) })
|
||||
}
|
||||
@ -99,7 +148,12 @@ fun <T> legacyCodec(reader: DataInputStream.(Boolean) -> T, writer: T.(DataOutpu
|
||||
return StreamCodec.Impl({ reader(it, true) }, { a, b -> writer(b, a, true) })
|
||||
}
|
||||
|
||||
// networks a signed variable length integer on legacy protocol
|
||||
fun networkedColor(value: RGBAColor = RGBAColor.BLACK) = networkedData(value, RGBACodec)
|
||||
|
||||
// networks enum as unsigned variable length integer
|
||||
fun <E : Enum<E>> networkedEnum(value: E) = BasicNetworkedElement(value, StreamCodec.Enum(value::class.java))
|
||||
|
||||
// networks enum as a signed variable length integer on legacy protocol
|
||||
fun <E : Enum<E>> networkedEnumStupid(value: E): BasicNetworkedElement<E, Int> {
|
||||
val codec = StreamCodec.Enum(value::class.java)
|
||||
return BasicNetworkedElement(value, codec, VarIntValueCodec, { it.ordinal.shl(1) }, { codec.values[it.ushr(1)] })
|
||||
@ -108,5 +162,5 @@ fun <E : Enum<E>> networkedEnumStupid(value: E): BasicNetworkedElement<E, Int> {
|
||||
// networks enum as string on legacy protocol
|
||||
fun <E : Enum<E>> networkedEnumExtraStupid(value: E): BasicNetworkedElement<E, String> {
|
||||
val codec = StreamCodec.Enum(value::class.java)
|
||||
return BasicNetworkedElement(value, codec, BinaryStringCodec, { if (it is IStringSerializable) it.jsonName else it.name }, { s -> codec.values.firstOrNull { if (it is IStringSerializable) it.match(s) else it.name == s } ?: throw NoSuchElementException(s) })
|
||||
return BasicNetworkedElement(value, codec, InternedStringCodec, { if (it is IStringSerializable) it.jsonName else it.name }, { s -> codec.values.firstOrNull { if (it is IStringSerializable) it.match(s) else it.name == s } ?: throw NoSuchElementException(s) })
|
||||
}
|
||||
|
@ -72,6 +72,10 @@ class FloatingNetworkedElement(private var value: Double = 0.0, val ops: Ops, va
|
||||
var extrapolation = 0.0
|
||||
private set
|
||||
|
||||
override fun toString(): String {
|
||||
return "FloatingNetworkedElement[$value, isInterpolating=$isInterpolating, currentTime=$currentTime]"
|
||||
}
|
||||
|
||||
private val valueListeners = Listenable.Impl<Double>()
|
||||
|
||||
override fun accept(t: Double) {
|
||||
|
@ -66,6 +66,10 @@ class MasterElement<E : NetworkedElement>(val upstream: E) : LongSupplier {
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "MasterElement[$upstream]"
|
||||
}
|
||||
|
||||
fun read(data: ByteArray, interpolationTime: Double = 0.0, isLegacy: Boolean = false) {
|
||||
return read(FastByteArrayInputStream(data), interpolationTime, isLegacy)
|
||||
}
|
||||
|
@ -0,0 +1,288 @@
|
||||
package ru.dbotthepony.kstarbound.network.syncher
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
|
||||
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||
import ru.dbotthepony.kommons.io.readByteArray
|
||||
import ru.dbotthepony.kommons.io.readVarInt
|
||||
import ru.dbotthepony.kommons.io.writeByteArray
|
||||
import ru.dbotthepony.kommons.io.writeVarInt
|
||||
import ru.dbotthepony.kstarbound.collect.IdMap
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.Collections
|
||||
import java.util.function.LongSupplier
|
||||
|
||||
/**
|
||||
* A dynamic group of NetElements that manages creation and destruction of
|
||||
* individual elements, that is itself a NetElement. Element changes are not
|
||||
* delayed by the interpolation delay, they will always happen immediately, but
|
||||
* this does not inhibit the Elements themselves from handling their own delta
|
||||
* update delays normally.
|
||||
*/
|
||||
class NetworkedDynamicGroup<T : Any>(private val factory: () -> T, private val accessor: (T) -> NetworkedElement, private val maxBacklogSize: Int = 100) : NetworkedElement() {
|
||||
private val elementsInternal = IdMap<T>(min = 1)
|
||||
val elements: Map<Int, T> = Collections.unmodifiableMap(elementsInternal)
|
||||
private val backlog = ArrayDeque<Pair<Long, Entry>>()
|
||||
private var isInterpolating = false
|
||||
private var extrapolation = 0.0
|
||||
|
||||
private enum class Action {
|
||||
CLEAR, REMOVE, ADD;
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "NetworkedDynamicGroup[keys = ${elementsInternal.size}]"
|
||||
}
|
||||
|
||||
// Storing data seems to be redundant at first, but this actually makes sense
|
||||
// since networked elements might have interpolation enabled
|
||||
//
|
||||
// If we would store element itself in Entry, and write it's initial state
|
||||
// upon networking to remote, we would skip all interpolation queues, since
|
||||
// IF element has interpolation enabled, writeInitial will ALWAYS write latest-possible
|
||||
// data (data from end of interpolation queue), and this is not something desired
|
||||
// (because writes of this data happens on both writeInitial and writeDelta inside this class)
|
||||
// Major downside of this is keeping byte arrays around
|
||||
private class Entry(
|
||||
val action: Action,
|
||||
val id: Int,
|
||||
val dataNative: ByteArray?,
|
||||
val dataLegacy: ByteArray?,
|
||||
) {
|
||||
constructor(id: Int, element: NetworkedElement) : this(
|
||||
Action.ADD,
|
||||
id,
|
||||
FastByteArrayOutputStream().let {
|
||||
element.writeInitial(DataOutputStream(it), false)
|
||||
it.array.copyOf(it.length)
|
||||
},
|
||||
FastByteArrayOutputStream().let {
|
||||
element.writeInitial(DataOutputStream(it), true)
|
||||
it.array.copyOf(it.length)
|
||||
})
|
||||
|
||||
constructor(id: Int) : this(Action.REMOVE, id, null, null)
|
||||
}
|
||||
|
||||
private fun setupElement(element: NetworkedElement) {
|
||||
val versionCounter = versionCounter
|
||||
|
||||
if (versionCounter != null)
|
||||
element.specifyVersioner(versionCounter)
|
||||
|
||||
if (isInterpolating)
|
||||
element.enableInterpolation(extrapolation)
|
||||
else
|
||||
element.disableInterpolation()
|
||||
}
|
||||
|
||||
fun add(element: T): Int {
|
||||
check(element !in elementsInternal.values) { "Already containing $element" }
|
||||
setupElement(accessor(element))
|
||||
val id = elementsInternal.add(element)
|
||||
backlog.add(currentVersion() to Entry(id, accessor(element)))
|
||||
purgeBacklog()
|
||||
return id
|
||||
}
|
||||
|
||||
fun remove(index: Int): Boolean {
|
||||
return elementsInternal.remove(index) != null
|
||||
}
|
||||
|
||||
private fun purgeBacklog() {
|
||||
while (backlog.size >= maxBacklogSize) {
|
||||
backlog.removeFirst()
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasChangedSince(version: Long): Boolean {
|
||||
return version == 0L || backlog.any { it.first >= version } || elementsInternal.values.any { accessor(it).hasChangedSince(version) }
|
||||
}
|
||||
|
||||
override fun specifyVersioner(versionCounter: LongSupplier) {
|
||||
super.specifyVersioner(versionCounter)
|
||||
backlog.clear()
|
||||
backlog.add(currentVersion() to clearAction)
|
||||
|
||||
for ((id, element) in elementsInternal) {
|
||||
accessor(element).specifyVersioner(versionCounter)
|
||||
backlog.add(currentVersion() to Entry(id, accessor(element)))
|
||||
}
|
||||
|
||||
purgeBacklog()
|
||||
}
|
||||
|
||||
override fun readInitial(data: DataInputStream, isLegacy: Boolean) {
|
||||
val size = data.readVarInt()
|
||||
|
||||
backlog.clear()
|
||||
elementsInternal.clear()
|
||||
|
||||
backlog.add(currentVersion() to clearAction)
|
||||
|
||||
for (i in 0 until size) {
|
||||
val index = data.readVarInt()
|
||||
|
||||
val stream = if (isLegacy) {
|
||||
// sigh
|
||||
DataInputStream(FastByteArrayInputStream(data.readByteArray()))
|
||||
} else {
|
||||
data
|
||||
}
|
||||
|
||||
val element = factory()
|
||||
setupElement(accessor(element))
|
||||
elementsInternal[index] = element
|
||||
accessor(element).readInitial(stream, isLegacy)
|
||||
backlog.add(currentVersion() to Entry(index, accessor(element)))
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
||||
data.writeVarInt(elementsInternal.size)
|
||||
|
||||
for ((id, element) in elementsInternal) {
|
||||
data.writeVarInt(id)
|
||||
|
||||
if (isLegacy) {
|
||||
// sigh
|
||||
val wrap = FastByteArrayOutputStream()
|
||||
accessor(element).writeInitial(DataOutputStream(wrap), true)
|
||||
data.writeByteArray(wrap.array, 0, wrap.length)
|
||||
} else {
|
||||
accessor(element).writeInitial(data, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun readDelta(data: DataInputStream, interpolationDelay: Double, isLegacy: Boolean) {
|
||||
if (data.readBoolean()) {
|
||||
readInitial(data, isLegacy)
|
||||
} else {
|
||||
val received = IntAVLTreeSet()
|
||||
|
||||
while (true) {
|
||||
when (val id = data.readVarInt()) {
|
||||
0 -> break
|
||||
|
||||
1 -> {
|
||||
when (Action.entries[data.readUnsignedByte()]) {
|
||||
Action.CLEAR -> {
|
||||
elementsInternal.clear()
|
||||
backlog.add(currentVersion() to clearAction)
|
||||
}
|
||||
|
||||
Action.REMOVE -> {
|
||||
// inconsistent usage of VarInt and int32_t when networking is driving me insane
|
||||
val id = if (isLegacy) data.readInt() else data.readVarInt()
|
||||
backlog.add(currentVersion() to Entry(id))
|
||||
elementsInternal.remove(id)
|
||||
}
|
||||
|
||||
Action.ADD -> {
|
||||
// inconsistent usage of VarInt and int32_t when networking is driving me insane
|
||||
val id = if (isLegacy) data.readInt() else data.readVarInt()
|
||||
val element = factory()
|
||||
setupElement(accessor(element))
|
||||
check(id !in elementsInternal) { "Already has networked element $id" }
|
||||
elementsInternal[id] = element
|
||||
|
||||
if (isLegacy) {
|
||||
accessor(element).readInitial(DataInputStream(FastByteArrayInputStream(data.readByteArray())), true)
|
||||
} else {
|
||||
accessor(element).readInitial(data, false)
|
||||
}
|
||||
|
||||
backlog.add(currentVersion() to Entry(id, accessor(element)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
val element = elementsInternal[id - 1] ?: throw NoSuchElementException("Unknown network element with ID ${id - 1}, net state is corrupt!")
|
||||
accessor(element).readDelta(data, interpolationDelay, isLegacy)
|
||||
|
||||
if (isInterpolating) {
|
||||
received.add(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isInterpolating) {
|
||||
for ((id, element) in elementsInternal) {
|
||||
if (!received.contains(id)) {
|
||||
accessor(element).readBlankDelta(interpolationDelay)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeDelta(data: DataOutputStream, remoteVersion: Long, isLegacy: Boolean) {
|
||||
// backlog is guaranteed to be not empty once first networked element is added
|
||||
if (backlog.isNotEmpty() && remoteVersion < backlog.first().first) {
|
||||
// we fell way behind, write full state
|
||||
data.writeBoolean(true)
|
||||
writeInitial(data, isLegacy)
|
||||
return
|
||||
}
|
||||
|
||||
data.writeBoolean(false)
|
||||
|
||||
for ((version, entry) in backlog) {
|
||||
if (version >= remoteVersion) {
|
||||
data.writeByte(1)
|
||||
data.writeByte(entry.action.ordinal)
|
||||
|
||||
when (entry.action) {
|
||||
Action.CLEAR -> {}
|
||||
Action.REMOVE -> if (isLegacy) data.writeInt(entry.id) else data.writeVarInt(entry.id)
|
||||
|
||||
Action.ADD -> {
|
||||
if (isLegacy) {
|
||||
data.writeInt(entry.id)
|
||||
data.writeByteArray(entry.dataLegacy!!)
|
||||
} else {
|
||||
data.writeVarInt(entry.id)
|
||||
data.write(entry.dataNative!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ((id, element) in elementsInternal) {
|
||||
if (accessor(element).hasChangedSince(remoteVersion)) {
|
||||
data.writeVarInt(id + 1)
|
||||
accessor(element).writeDelta(data, remoteVersion, isLegacy)
|
||||
}
|
||||
}
|
||||
|
||||
data.writeByte(0)
|
||||
}
|
||||
|
||||
override fun readBlankDelta(interpolationDelay: Double) {
|
||||
elementsInternal.values.forEach { accessor(it).readBlankDelta(interpolationDelay) }
|
||||
}
|
||||
|
||||
override fun enableInterpolation(extrapolation: Double) {
|
||||
isInterpolating = true
|
||||
this.extrapolation = extrapolation
|
||||
elementsInternal.values.forEach { accessor(it).enableInterpolation(extrapolation) }
|
||||
}
|
||||
|
||||
override fun disableInterpolation() {
|
||||
isInterpolating = false
|
||||
elementsInternal.values.forEach { accessor(it).disableInterpolation() }
|
||||
}
|
||||
|
||||
override fun tickInterpolation(delta: Double) {
|
||||
elementsInternal.values.forEach { accessor(it).tickInterpolation(delta) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val clearAction = Entry(Action.CLEAR, 0, null, null)
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package ru.dbotthepony.kstarbound.network.syncher
|
||||
|
||||
import ru.dbotthepony.kommons.util.Listenable
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.function.LongSupplier
|
||||
@ -66,21 +65,22 @@ abstract class NetworkedElement {
|
||||
// NetElementVersion. When elements are updated, they will mark the version
|
||||
// number at the time they are updated so that a delta can be constructed
|
||||
// that contains only changes since any past version.
|
||||
var version: Long = 0L
|
||||
protected set(value) {
|
||||
protected var version: Long = 0L
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
require(value > field) { "Downgrading element version from $field to $value" }
|
||||
field = value
|
||||
listeners.accept(value)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun currentVersion(): Long {
|
||||
return versionCounter?.asLong ?: version
|
||||
}
|
||||
|
||||
open fun hasChangedSince(version: Long): Boolean {
|
||||
return this.version >= version
|
||||
}
|
||||
|
||||
val listeners = Listenable.Impl<Long>()
|
||||
|
||||
var versionCounter: LongSupplier? = null
|
||||
protected set
|
||||
|
||||
@ -94,4 +94,54 @@ abstract class NetworkedElement {
|
||||
open fun bumpVersion() {
|
||||
version = versionCounter?.asLong ?: version
|
||||
}
|
||||
|
||||
abstract class Passthrough : NetworkedElement() {
|
||||
protected abstract val parentElement: NetworkedElement
|
||||
|
||||
override fun readInitial(data: DataInputStream, isLegacy: Boolean) {
|
||||
parentElement.readInitial(data, isLegacy)
|
||||
}
|
||||
|
||||
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
||||
parentElement.writeInitial(data, isLegacy)
|
||||
}
|
||||
|
||||
override fun readDelta(data: DataInputStream, interpolationDelay: Double, isLegacy: Boolean) {
|
||||
parentElement.readDelta(data, interpolationDelay, isLegacy)
|
||||
}
|
||||
|
||||
override fun writeDelta(data: DataOutputStream, remoteVersion: Long, isLegacy: Boolean) {
|
||||
parentElement.writeDelta(data, remoteVersion, isLegacy)
|
||||
}
|
||||
|
||||
override fun readBlankDelta(interpolationDelay: Double) {
|
||||
parentElement.readBlankDelta(interpolationDelay)
|
||||
}
|
||||
|
||||
override fun enableInterpolation(extrapolation: Double) {
|
||||
parentElement.enableInterpolation(extrapolation)
|
||||
}
|
||||
|
||||
override fun disableInterpolation() {
|
||||
parentElement.disableInterpolation()
|
||||
}
|
||||
|
||||
override fun tickInterpolation(delta: Double) {
|
||||
parentElement.tickInterpolation(delta)
|
||||
}
|
||||
|
||||
override fun hasChangedSince(version: Long): Boolean {
|
||||
return super.hasChangedSince(version) || parentElement.hasChangedSince(version)
|
||||
}
|
||||
|
||||
override fun specifyVersioner(versionCounter: LongSupplier) {
|
||||
super.specifyVersioner(versionCounter)
|
||||
parentElement.specifyVersioner(versionCounter)
|
||||
}
|
||||
|
||||
override fun bumpVersion() {
|
||||
super.bumpVersion()
|
||||
parentElement.bumpVersion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import java.io.DataOutputStream
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.LongSupplier
|
||||
|
||||
class GroupElement() : NetworkedElement() {
|
||||
class NetworkedGroup() : NetworkedElement() {
|
||||
constructor(element: NetworkedElement, vararg rest: NetworkedElement) : this() {
|
||||
add(element)
|
||||
rest.forEach { add(it) }
|
||||
@ -21,6 +21,17 @@ class GroupElement() : NetworkedElement() {
|
||||
var extrapolation = 0.0
|
||||
private set
|
||||
|
||||
fun clear() {
|
||||
elements.clear()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
if (elements.isEmpty())
|
||||
return "NetworkedGroup[]"
|
||||
|
||||
return "NetworkedGroup[\n${elements.joinToString("\n") { "\t" + it.first.toString() }}]"
|
||||
}
|
||||
|
||||
override fun specifyVersioner(versionCounter: LongSupplier) {
|
||||
super.specifyVersioner(versionCounter)
|
||||
elements.forEach { it.first.specifyVersioner(versionCounter) }
|
||||
@ -42,10 +53,13 @@ class GroupElement() : NetworkedElement() {
|
||||
elements.forEach { it.first.tickInterpolation(delta) }
|
||||
}
|
||||
|
||||
fun <E : NetworkedElement> add(element: E, propagateInterpolation: Boolean = true): E {
|
||||
require(elements.none { it.first == element }) { "Already has element $element in $this" }
|
||||
elements.add(element to propagateInterpolation)
|
||||
private var trap: Consumer<DataInputStream>? = null
|
||||
|
||||
fun putTrap(trap: Consumer<DataInputStream>) {
|
||||
this.trap = trap
|
||||
}
|
||||
|
||||
private fun setupElement(element: NetworkedElement, propagateInterpolation: Boolean) {
|
||||
if (propagateInterpolation) {
|
||||
if (isInterpolating)
|
||||
element.enableInterpolation(extrapolation)
|
||||
@ -56,16 +70,25 @@ class GroupElement() : NetworkedElement() {
|
||||
if (versionCounter != null) {
|
||||
element.specifyVersioner(versionCounter!!)
|
||||
}
|
||||
}
|
||||
|
||||
element.listeners.addListener(Consumer {
|
||||
this.version = this.version.coerceAtLeast(it)
|
||||
})
|
||||
|
||||
this.version = this.version.coerceAtLeast(element.version)
|
||||
fun <E : NetworkedElement> add(element: E, propagateInterpolation: Boolean = true): E {
|
||||
require(elements.none { it.first === element }) { "Already has element $element in $this" }
|
||||
elements.add(element to propagateInterpolation)
|
||||
setupElement(element, propagateInterpolation)
|
||||
return element
|
||||
}
|
||||
|
||||
fun replace(find: NetworkedElement, replace: NetworkedElement) {
|
||||
if (find === replace) return
|
||||
val index = elements.indexOfFirst { it.first === find }
|
||||
check(index != -1) { "Unable to find $find in $this" }
|
||||
setupElement(replace, elements[index].second)
|
||||
elements[index] = replace to elements[index].second
|
||||
}
|
||||
|
||||
override fun readInitial(data: DataInputStream, isLegacy: Boolean) {
|
||||
trap?.accept(data)
|
||||
elements.forEach { it.first.readInitial(data, isLegacy) }
|
||||
}
|
||||
|
||||
@ -142,10 +165,7 @@ class GroupElement() : NetworkedElement() {
|
||||
}
|
||||
|
||||
override fun hasChangedSince(version: Long): Boolean {
|
||||
if (elements.isEmpty())
|
||||
return false
|
||||
|
||||
return super.hasChangedSince(version)
|
||||
return elements.any { it.first.hasChangedSince(version) }
|
||||
}
|
||||
|
||||
override fun bumpVersion() {
|
@ -0,0 +1,64 @@
|
||||
package ru.dbotthepony.kstarbound.network.syncher
|
||||
|
||||
import ru.dbotthepony.kommons.util.Delegate
|
||||
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
||||
import ru.dbotthepony.kstarbound.item.ItemStack
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
// Due to this class observing values... event-based
|
||||
// "hasChangedSince" in GroupElement had to be removed :( tough shit
|
||||
// Not that it matters anyway, since even more classes followed to mangle
|
||||
// event driven updates due to their complexity.
|
||||
// Creating more efficient system for this case will be major complication of entire system,
|
||||
// and major complications with little improvement are bad.
|
||||
open class NetworkedItemStack(private var itemStack: ItemStack = ItemStack.EMPTY) : NetworkedElement(), Delegate<ItemStack> {
|
||||
override fun readInitial(data: DataInputStream, isLegacy: Boolean) {
|
||||
val read = ItemDescriptor(data)
|
||||
itemStack = ItemStack.create(read)
|
||||
observedVersion = itemStack.changeset
|
||||
bumpVersion()
|
||||
}
|
||||
|
||||
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
||||
itemStack.write(data)
|
||||
}
|
||||
|
||||
override fun readDelta(data: DataInputStream, interpolationDelay: Double, isLegacy: Boolean) {
|
||||
readInitial(data, isLegacy)
|
||||
}
|
||||
|
||||
override fun writeDelta(data: DataOutputStream, remoteVersion: Long, isLegacy: Boolean) {
|
||||
writeInitial(data, isLegacy)
|
||||
}
|
||||
|
||||
override fun readBlankDelta(interpolationDelay: Double) {}
|
||||
override fun enableInterpolation(extrapolation: Double) {}
|
||||
override fun disableInterpolation() {}
|
||||
override fun tickInterpolation(delta: Double) {}
|
||||
|
||||
private var observedVersion = -1L
|
||||
|
||||
override fun toString(): String {
|
||||
return "NetworkedItemStack[$itemStack]"
|
||||
}
|
||||
|
||||
override fun accept(t: ItemStack) {
|
||||
itemStack = t
|
||||
observedVersion = t.changeset
|
||||
bumpVersion()
|
||||
}
|
||||
|
||||
override fun get(): ItemStack {
|
||||
return itemStack
|
||||
}
|
||||
|
||||
override fun hasChangedSince(version: Long): Boolean {
|
||||
if (observedVersion != itemStack.changeset) {
|
||||
observedVersion = itemStack.changeset
|
||||
bumpVersion()
|
||||
}
|
||||
|
||||
return super.hasChangedSince(version)
|
||||
}
|
||||
}
|
@ -0,0 +1,340 @@
|
||||
package ru.dbotthepony.kstarbound.network.syncher
|
||||
|
||||
import ru.dbotthepony.kommons.collect.ListenableMap
|
||||
import ru.dbotthepony.kommons.io.StreamCodec
|
||||
import ru.dbotthepony.kommons.io.readVarInt
|
||||
import ru.dbotthepony.kommons.io.writeVarInt
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
/**
|
||||
* [isDumb] is responsible for specifying whenever legacy protocol networks entire map each time
|
||||
* instead of deltas
|
||||
*
|
||||
* [keyCodec] and [valueCodec] specify codecs as `native -> legacy` pair
|
||||
*/
|
||||
class NetworkedMap<K, V>(
|
||||
private val keyCodec: Pair<StreamCodec<K>, StreamCodec<K>>,
|
||||
private val valueCodec: Pair<StreamCodec<V>, StreamCodec<V>>,
|
||||
private val isDumb: Boolean = false,
|
||||
map: ListenableMap<K, V> = ListenableMap(),
|
||||
private val maxBacklogSize: Int = 100,
|
||||
) : NetworkedElement(), MutableMap<K, V> by map {
|
||||
constructor(
|
||||
keyCodec: StreamCodec<K>,
|
||||
valueCodec: StreamCodec<V>,
|
||||
isDumb: Boolean = false,
|
||||
map: ListenableMap<K, V> = ListenableMap(),
|
||||
maxBacklogSize: Int = 100,
|
||||
) : this(keyCodec to keyCodec, valueCodec to valueCodec, isDumb, map, maxBacklogSize)
|
||||
|
||||
constructor(
|
||||
keyCodec: StreamCodec<K>,
|
||||
valueCodec: Pair<StreamCodec<V>, StreamCodec<V>>,
|
||||
isDumb: Boolean = false,
|
||||
map: ListenableMap<K, V> = ListenableMap(),
|
||||
maxBacklogSize: Int = 100,
|
||||
) : this(keyCodec to keyCodec, valueCodec, isDumb, map, maxBacklogSize)
|
||||
|
||||
constructor(
|
||||
keyCodec: Pair<StreamCodec<K>, StreamCodec<K>>,
|
||||
valueCodec: StreamCodec<V>,
|
||||
isDumb: Boolean = false,
|
||||
map: ListenableMap<K, V> = ListenableMap(),
|
||||
maxBacklogSize: Int = 100,
|
||||
) : this(keyCodec, valueCodec to valueCodec, isDumb, map, maxBacklogSize)
|
||||
|
||||
init {
|
||||
map.addListener(object : ListenableMap.MapListener<K, V> {
|
||||
override fun onClear() {
|
||||
if (isReading) return
|
||||
check(!isRemote) { "This map is not owned by this side" }
|
||||
|
||||
// this is fragile (due to interpolation fuckery, we remove everything before applying delayed changes),
|
||||
// but let's hope it doesn't break
|
||||
delayed.clear()
|
||||
backlog.add(currentVersion() to clearEntry)
|
||||
|
||||
purgeBacklog()
|
||||
}
|
||||
|
||||
override fun onValueAdded(key: K, value: V) {
|
||||
if (isReading) return
|
||||
check(!isRemote) { "This map is not owned by this side" }
|
||||
backlog.add(currentVersion() to Entry(Action.ADD, KOptional(nativeKey.copy(key)), KOptional(nativeValue.copy(value))))
|
||||
purgeBacklog()
|
||||
}
|
||||
|
||||
override fun onValueRemoved(key: K, value: V) {
|
||||
if (isReading) return
|
||||
check(!isRemote) { "This map is not owned by this side" }
|
||||
backlog.add(currentVersion() to Entry(Action.REMOVE, KOptional(nativeKey.copy(key)), KOptional()))
|
||||
purgeBacklog()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private val dumbCodec by lazy {
|
||||
StreamCodec.Map(keyCodec.second, valueCodec.second, ::HashMap)
|
||||
}
|
||||
|
||||
private enum class Action {
|
||||
ADD, REMOVE, CLEAR;
|
||||
}
|
||||
|
||||
private data class Entry<K, V>(val action: Action, val key: KOptional<K>, val value: KOptional<V>) {
|
||||
fun apply(map: MutableMap<K, V>) {
|
||||
when (action) {
|
||||
Action.ADD -> map[key.value] = value.value
|
||||
Action.REMOVE -> map.remove(key.value)
|
||||
Action.CLEAR -> map.clear()
|
||||
}
|
||||
}
|
||||
|
||||
fun write(data: DataOutputStream, isLegacy: Boolean, self: NetworkedMap<K, V>) {
|
||||
if (isLegacy) {
|
||||
when (action) {
|
||||
Action.ADD -> {
|
||||
self.legacyKey.write(data, key.value)
|
||||
self.legacyValue.write(data, value.value)
|
||||
}
|
||||
|
||||
Action.REMOVE -> {
|
||||
self.legacyKey.write(data, key.value)
|
||||
}
|
||||
|
||||
Action.CLEAR -> {}
|
||||
}
|
||||
} else {
|
||||
when (action) {
|
||||
Action.ADD -> {
|
||||
self.nativeKey.write(data, key.value)
|
||||
self.nativeValue.write(data, value.value)
|
||||
}
|
||||
|
||||
Action.REMOVE -> {
|
||||
self.nativeKey.write(data, key.value)
|
||||
}
|
||||
|
||||
Action.CLEAR -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun readLegacyEntry(data: DataInputStream): Entry<K, V> {
|
||||
return when (Action.entries[data.readUnsignedByte()]) {
|
||||
Action.ADD -> Entry(Action.ADD, KOptional(legacyKey.read(data)), KOptional(legacyValue.read(data)))
|
||||
Action.REMOVE -> Entry(Action.REMOVE, KOptional(legacyKey.read(data)), KOptional())
|
||||
Action.CLEAR -> clearEntry
|
||||
}
|
||||
}
|
||||
|
||||
private fun readNativeEntry(data: DataInputStream): Entry<K, V> {
|
||||
return when (Action.entries[data.readUnsignedByte()]) {
|
||||
Action.ADD -> Entry(Action.ADD, KOptional(nativeKey.read(data)), KOptional(nativeValue.read(data)))
|
||||
Action.REMOVE -> Entry(Action.REMOVE, KOptional(nativeKey.read(data)), KOptional())
|
||||
Action.CLEAR -> clearEntry
|
||||
}
|
||||
}
|
||||
|
||||
private val clearEntry = Entry<K, V>(Action.CLEAR, KOptional(), KOptional())
|
||||
private val backlog = ArrayDeque<Pair<Long, Entry<K, V>>>()
|
||||
private val delayed = ArrayDeque<Pair<Double, Entry<K, V>>>()
|
||||
private var currentTime = 0.0
|
||||
private var isReading = false
|
||||
private var isRemote = false
|
||||
private var isInterpolating = false
|
||||
|
||||
val legacyKey get() = keyCodec.second
|
||||
val nativeKey get() = keyCodec.first
|
||||
val legacyValue get() = valueCodec.second
|
||||
val nativeValue get() = valueCodec.first
|
||||
|
||||
private fun purgeBacklog() {
|
||||
while (backlog.size >= maxBacklogSize) {
|
||||
backlog.removeFirst()
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "NetworkedMap[keys = ${keys.size}]"
|
||||
}
|
||||
|
||||
override fun readInitial(data: DataInputStream, isLegacy: Boolean) {
|
||||
try {
|
||||
isRemote = true
|
||||
isReading = true
|
||||
|
||||
backlog.clear()
|
||||
delayed.clear()
|
||||
clear()
|
||||
|
||||
backlog.add(currentVersion() to clearEntry)
|
||||
|
||||
if (isDumb && isLegacy) {
|
||||
val readMap = dumbCodec.read(data)
|
||||
|
||||
for ((k, v) in readMap) {
|
||||
val action = Entry(Action.ADD, KOptional(k), KOptional(v))
|
||||
backlog.add(currentVersion() to action)
|
||||
action.apply(this)
|
||||
}
|
||||
} else {
|
||||
val values = data.readVarInt()
|
||||
|
||||
if (isLegacy) {
|
||||
for (i in 0 until values) {
|
||||
val action = readLegacyEntry(data)
|
||||
backlog.add(currentVersion() to action)
|
||||
action.apply(this)
|
||||
}
|
||||
} else {
|
||||
for (i in 0 until values) {
|
||||
val action = readNativeEntry(data)
|
||||
backlog.add(currentVersion() to action)
|
||||
action.apply(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
purgeBacklog()
|
||||
} finally {
|
||||
isReading = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
||||
if (isDumb && isLegacy) {
|
||||
val construct = HashMap<K, V>(size + delayed.size)
|
||||
|
||||
for ((k, v) in entries) {
|
||||
construct[k] = v
|
||||
}
|
||||
|
||||
for ((_, v) in delayed) {
|
||||
v.apply(construct)
|
||||
}
|
||||
|
||||
dumbCodec.write(data, construct)
|
||||
} else {
|
||||
data.writeVarInt(size + delayed.size)
|
||||
|
||||
for ((k, v) in entries) {
|
||||
Entry(Action.ADD, KOptional(k), KOptional(v)).write(data, isLegacy, this)
|
||||
}
|
||||
|
||||
for ((_, v) in delayed) {
|
||||
v.write(data, isLegacy, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun readDelta(data: DataInputStream, interpolationDelay: Double, isLegacy: Boolean) {
|
||||
if (isDumb && isLegacy) {
|
||||
readInitial(data, true)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
isReading = true
|
||||
|
||||
while (true) {
|
||||
when (val action = data.readUnsignedByte()) {
|
||||
0 -> break
|
||||
1 -> {
|
||||
readInitial(data, isLegacy)
|
||||
isReading = true
|
||||
}
|
||||
2 -> {
|
||||
val change = if (isLegacy) readLegacyEntry(data) else readNativeEntry(data)
|
||||
backlog.add(currentVersion() to change)
|
||||
|
||||
if (isInterpolating && interpolationDelay > 0.0) {
|
||||
val actualDelay = interpolationDelay + currentTime
|
||||
|
||||
if (delayed.isNotEmpty() && delayed.last().first > actualDelay) {
|
||||
delayed.forEach { it.second.apply(this) }
|
||||
delayed.clear()
|
||||
}
|
||||
|
||||
delayed.add(actualDelay to change)
|
||||
} else {
|
||||
change.apply(this)
|
||||
}
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException("Invalid delta change type $action")
|
||||
}
|
||||
}
|
||||
|
||||
purgeBacklog()
|
||||
} finally {
|
||||
isReading = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeDelta(data: DataOutputStream, remoteVersion: Long, isLegacy: Boolean) {
|
||||
if (isDumb && isLegacy) {
|
||||
writeInitial(data, true)
|
||||
return
|
||||
}
|
||||
|
||||
if (remoteVersion < backlog.first().first) {
|
||||
// we fell way behind, serialize entire state
|
||||
data.writeByte(1)
|
||||
writeInitial(data, isLegacy)
|
||||
data.writeByte(0)
|
||||
} else {
|
||||
for ((version, entry) in backlog) {
|
||||
if (version >= remoteVersion) {
|
||||
data.writeByte(2)
|
||||
entry.write(data, isLegacy, this)
|
||||
}
|
||||
}
|
||||
|
||||
data.writeByte(0)
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasChangedSince(version: Long): Boolean {
|
||||
return backlog.any { it.first >= version }
|
||||
}
|
||||
|
||||
override fun readBlankDelta(interpolationDelay: Double) {}
|
||||
|
||||
override fun enableInterpolation(extrapolation: Double) {
|
||||
isInterpolating = true
|
||||
}
|
||||
|
||||
override fun disableInterpolation() {
|
||||
if (isInterpolating) {
|
||||
isInterpolating = false
|
||||
|
||||
try {
|
||||
isReading = true
|
||||
delayed.forEach { it.second.apply(this) }
|
||||
delayed.clear()
|
||||
} finally {
|
||||
isReading = false
|
||||
}
|
||||
|
||||
purgeBacklog()
|
||||
}
|
||||
}
|
||||
|
||||
override fun tickInterpolation(delta: Double) {
|
||||
currentTime += delta
|
||||
|
||||
try {
|
||||
isReading = true
|
||||
|
||||
while (delayed.isNotEmpty() && delayed.first().first <= currentTime) {
|
||||
delayed.removeFirst().second.apply(this)
|
||||
}
|
||||
} finally {
|
||||
isReading = false
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
package ru.dbotthepony.kstarbound.network.syncher
|
||||
|
||||
import ru.dbotthepony.kommons.io.StreamCodec
|
||||
import ru.dbotthepony.kommons.io.readVarInt
|
||||
import ru.dbotthepony.kommons.io.writeVarInt
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
/**
|
||||
* NetElement that sends signals during delta writes that can be received by
|
||||
* slaves. It has no 'state', and nothing is sent during a store / load, and
|
||||
* it only keeps past signals for a maximum number of versions. Thus, it is
|
||||
* not appropriate to use to send updates to long term states, only for event
|
||||
* like things that are not harmful if missed.
|
||||
*/
|
||||
class NetworkedSignal<S>(private val codec: StreamCodec<S>, private val maxSize: Int = 100) : NetworkedElement(), Iterable<S>, Iterator<S> {
|
||||
private data class Signal<S>(val version: Long, val signal: S)
|
||||
|
||||
private var currentTime = 0.0
|
||||
private val visibleSignals = ArrayDeque<Signal<S>>()
|
||||
private val internalSignals = ArrayDeque<Signal<S>>()
|
||||
private val delayedSignals = ArrayDeque<Pair<Double, S>>()
|
||||
private var isInterpolating = false
|
||||
|
||||
override fun readInitial(data: DataInputStream, isLegacy: Boolean) {}
|
||||
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {}
|
||||
override fun readBlankDelta(interpolationDelay: Double) {}
|
||||
|
||||
override fun readDelta(data: DataInputStream, interpolationDelay: Double, isLegacy: Boolean) {
|
||||
val toRead = data.readVarInt()
|
||||
|
||||
for (i in 0 until toRead) {
|
||||
val signal = codec.read(data)
|
||||
|
||||
if (isInterpolating && interpolationDelay > 0.0) {
|
||||
val actualDelay = interpolationDelay + currentTime
|
||||
|
||||
if (delayedSignals.isNotEmpty() && delayedSignals.last().first > actualDelay) {
|
||||
delayedSignals.forEach { push(it.second) }
|
||||
delayedSignals.clear()
|
||||
}
|
||||
|
||||
delayedSignals.add(actualDelay to signal)
|
||||
} else {
|
||||
push(signal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeDelta(data: DataOutputStream, remoteVersion: Long, isLegacy: Boolean) {
|
||||
data.writeVarInt(internalSignals.count { it.version >= remoteVersion })
|
||||
|
||||
for ((version, signal) in internalSignals) {
|
||||
if (version >= remoteVersion) {
|
||||
codec.write(data, signal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasChangedSince(version: Long): Boolean {
|
||||
return internalSignals.any { it.version >= version }
|
||||
}
|
||||
|
||||
override fun enableInterpolation(extrapolation: Double) {
|
||||
isInterpolating = true
|
||||
}
|
||||
|
||||
override fun disableInterpolation() {
|
||||
isInterpolating = false
|
||||
|
||||
for ((_, v) in delayedSignals) {
|
||||
push(v)
|
||||
}
|
||||
|
||||
delayedSignals.clear()
|
||||
}
|
||||
|
||||
override fun tickInterpolation(delta: Double) {
|
||||
currentTime += delta
|
||||
|
||||
while (delayedSignals.isNotEmpty() && delayedSignals.first().first <= currentTime) {
|
||||
push(delayedSignals.removeFirst().second)
|
||||
}
|
||||
}
|
||||
|
||||
val isEmpty: Boolean
|
||||
get() = visibleSignals.isEmpty()
|
||||
|
||||
val isNotEmpty: Boolean
|
||||
get() = visibleSignals.isNotEmpty()
|
||||
|
||||
fun push(signal: S) {
|
||||
val value = Signal(currentVersion(), signal)
|
||||
visibleSignals.add(value)
|
||||
internalSignals.add(value)
|
||||
|
||||
while (visibleSignals.size >= maxSize) {
|
||||
visibleSignals.removeFirst()
|
||||
}
|
||||
|
||||
while (internalSignals.size >= maxSize) {
|
||||
internalSignals.removeFirst()
|
||||
}
|
||||
}
|
||||
|
||||
fun poll(): KOptional<S> {
|
||||
if (visibleSignals.isEmpty())
|
||||
return KOptional()
|
||||
|
||||
return KOptional(visibleSignals.removeFirst().signal)
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<S> {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
return visibleSignals.isNotEmpty()
|
||||
}
|
||||
|
||||
override fun next(): S {
|
||||
return visibleSignals.removeFirst().signal
|
||||
}
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
package ru.dbotthepony.kstarbound.network.syncher
|
||||
|
||||
import ru.dbotthepony.kstarbound.item.ItemStack
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.function.LongSupplier
|
||||
|
||||
class NetworkedStatefulItemStack(value: ItemStack = ItemStack.EMPTY) : NetworkedItemStack(value) {
|
||||
interface Stateful {
|
||||
val networkElement: NetworkedElement
|
||||
}
|
||||
|
||||
private var isInterpolating = false
|
||||
private var extrapolation = 0.0
|
||||
|
||||
override fun enableInterpolation(extrapolation: Double) {
|
||||
isInterpolating = true
|
||||
this.extrapolation = extrapolation
|
||||
(get() as? Stateful)?.networkElement?.enableInterpolation(extrapolation)
|
||||
}
|
||||
|
||||
override fun disableInterpolation() {
|
||||
isInterpolating = false
|
||||
(get() as? Stateful)?.networkElement?.disableInterpolation()
|
||||
}
|
||||
|
||||
override fun specifyVersioner(versionCounter: LongSupplier) {
|
||||
super.specifyVersioner(versionCounter)
|
||||
(get() as? Stateful)?.networkElement?.disableInterpolation()
|
||||
}
|
||||
|
||||
override fun hasChangedSince(version: Long): Boolean {
|
||||
return super.hasChangedSince(version) || (get() as? Stateful)?.networkElement?.hasChangedSince(version) == true
|
||||
}
|
||||
|
||||
override fun accept(t: ItemStack) {
|
||||
super.accept(t)
|
||||
|
||||
if (t is Stateful) {
|
||||
if (versionCounter != null) {
|
||||
t.networkElement.specifyVersioner(versionCounter!!)
|
||||
}
|
||||
|
||||
if (isInterpolating) {
|
||||
t.networkElement.enableInterpolation(extrapolation)
|
||||
} else {
|
||||
t.networkElement.disableInterpolation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun readBlankDelta(interpolationDelay: Double) {
|
||||
super.readBlankDelta(interpolationDelay)
|
||||
|
||||
if (isInterpolating) {
|
||||
(get() as? Stateful)?.networkElement?.readBlankDelta(interpolationDelay)
|
||||
}
|
||||
}
|
||||
|
||||
override fun readInitial(data: DataInputStream, isLegacy: Boolean) {
|
||||
super.readInitial(data, isLegacy)
|
||||
|
||||
val stack = get()
|
||||
|
||||
if (stack is Stateful) {
|
||||
stack.networkElement.readInitial(data, isLegacy)
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
||||
super.writeInitial(data, isLegacy)
|
||||
|
||||
val stack = get()
|
||||
|
||||
if (stack is Stateful) {
|
||||
stack.networkElement.writeInitial(data, isLegacy)
|
||||
}
|
||||
}
|
||||
|
||||
override fun readDelta(data: DataInputStream, interpolationDelay: Double, isLegacy: Boolean) {
|
||||
while (true) {
|
||||
when (val type = data.readUnsignedByte()) {
|
||||
0 -> break
|
||||
|
||||
1 -> {
|
||||
super.readDelta(data, interpolationDelay, isLegacy)
|
||||
|
||||
val stack = get()
|
||||
|
||||
if (stack is Stateful) {
|
||||
if (versionCounter != null) {
|
||||
stack.networkElement.specifyVersioner(versionCounter!!)
|
||||
}
|
||||
|
||||
stack.networkElement.readInitial(data, isLegacy)
|
||||
|
||||
if (isInterpolating) {
|
||||
stack.networkElement.enableInterpolation(extrapolation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2 -> {
|
||||
val stack = get()
|
||||
|
||||
if (stack is Stateful) {
|
||||
stack.networkElement.readInitial(data, isLegacy)
|
||||
} else {
|
||||
throw IllegalStateException("Remote and Local disagree whenever ItemStack has networked state (local item: $stack)")
|
||||
}
|
||||
}
|
||||
|
||||
3 -> {
|
||||
val stack = get()
|
||||
|
||||
if (stack is Stateful) {
|
||||
stack.networkElement.readDelta(data, interpolationDelay, isLegacy)
|
||||
} else {
|
||||
throw IllegalStateException("Remote and Local disagree whenever ItemStack has networked state (local item: $stack)")
|
||||
}
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException("Unknown change type $type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeDelta(data: DataOutputStream, remoteVersion: Long, isLegacy: Boolean) {
|
||||
if (super.hasChangedSince(remoteVersion)) {
|
||||
data.writeByte(1)
|
||||
super.writeDelta(data, remoteVersion, isLegacy)
|
||||
|
||||
val stack = get()
|
||||
|
||||
if (stack is Stateful) {
|
||||
data.writeByte(2)
|
||||
stack.networkElement.writeInitial(data, isLegacy)
|
||||
}
|
||||
}
|
||||
|
||||
val stack = get()
|
||||
|
||||
if (stack is Stateful && stack.networkElement.hasChangedSince(remoteVersion)) {
|
||||
data.writeByte(3)
|
||||
stack.networkElement.writeDelta(data, remoteVersion, isLegacy)
|
||||
}
|
||||
|
||||
data.writeByte(0)
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ import ru.dbotthepony.kstarbound.lua.luaFunction0String
|
||||
import ru.dbotthepony.kstarbound.lua.luaFunctionN
|
||||
import ru.dbotthepony.kstarbound.lua.luaStub
|
||||
import ru.dbotthepony.kstarbound.lua.set
|
||||
import ru.dbotthepony.kstarbound.util.ItemStack
|
||||
import ru.dbotthepony.kstarbound.item.ItemStack
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
@ -48,13 +48,13 @@ class Avatar(val uniqueId: UUID) {
|
||||
private val essentialSlots = EnumMap<EssentialSlot, ItemStack>(EssentialSlot::class.java)
|
||||
private val equipmentSlots = EnumMap<EquipmentSlot, ItemStack>(EquipmentSlot::class.java)
|
||||
private val bags = ArrayList<AvatarBag>()
|
||||
private val quests = Object2ObjectOpenHashMap<String, QuestInstance>()
|
||||
private val quests = HashMap<String, QuestInstance>()
|
||||
|
||||
var cursorItem = ItemStack.EMPTY
|
||||
|
||||
private val availableTechs = ObjectOpenHashSet<Registry.Entry<TechDefinition>>()
|
||||
private val enabledTechs = ObjectOpenHashSet<Registry.Entry<TechDefinition>>()
|
||||
private val equippedTechs = Object2ObjectOpenHashMap<String, Registry.Entry<TechDefinition>>()
|
||||
private val equippedTechs = HashMap<String, Registry.Entry<TechDefinition>>()
|
||||
|
||||
private val knownBlueprints = ObjectOpenHashSet<ItemStack>()
|
||||
// С подписью NEW
|
||||
|
@ -2,7 +2,7 @@ package ru.dbotthepony.kstarbound.player
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import ru.dbotthepony.kstarbound.defs.actor.player.InventoryConfig
|
||||
import ru.dbotthepony.kstarbound.util.ItemStack
|
||||
import ru.dbotthepony.kstarbound.item.ItemStack
|
||||
import java.util.function.Predicate
|
||||
|
||||
class AvatarBag(val avatar: Avatar, val config: InventoryConfig.Bag, val filter: Predicate<ItemStack>) {
|
||||
@ -20,10 +20,10 @@ class AvatarBag(val avatar: Avatar, val config: InventoryConfig.Bag, val filter:
|
||||
fun mergeFrom(value: ItemStack, simulate: Boolean) {
|
||||
if (item == null) {
|
||||
if (!simulate) {
|
||||
item = value.copy().also { it.count = value.count.coerceAtMost(value.item!!.value.maxStack) }
|
||||
item = value.copy().also { it.count = value.count.coerceAtMost(value.maxStackSize) }
|
||||
}
|
||||
|
||||
value.count -= value.item!!.value.maxStack
|
||||
value.count -= value.maxStackSize
|
||||
} else {
|
||||
item!!.mergeFrom(value, simulate)
|
||||
}
|
||||
|
@ -9,8 +9,9 @@ import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.quest.QuestTemplate
|
||||
import ru.dbotthepony.kstarbound.lua.NewLuaState
|
||||
import ru.dbotthepony.kstarbound.util.ItemStack
|
||||
import ru.dbotthepony.kstarbound.item.ItemStack
|
||||
import ru.dbotthepony.kommons.gson.set
|
||||
import java.util.HashMap
|
||||
import java.util.UUID
|
||||
|
||||
class QuestInstance(
|
||||
@ -47,7 +48,7 @@ class QuestInstance(
|
||||
private val portraits = JsonObject()
|
||||
private val params = descriptor.parameters.deepCopy()
|
||||
|
||||
private val portraitTitles = Object2ObjectOpenHashMap<String, String>()
|
||||
private val portraitTitles = HashMap<String, String>()
|
||||
|
||||
private var isInitialized = false
|
||||
private var successfulInit = false
|
||||
|
@ -32,6 +32,7 @@ import ru.dbotthepony.kstarbound.world.IChunkListener
|
||||
import ru.dbotthepony.kstarbound.world.api.ImmutableCell
|
||||
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.WorldObject
|
||||
import java.util.HashMap
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
@ -94,7 +95,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
||||
}
|
||||
}
|
||||
|
||||
private val shipChunks = Object2ObjectOpenHashMap<ByteKey, KOptional<ByteArray>>()
|
||||
private val shipChunks = HashMap<ByteKey, KOptional<ByteArray>>()
|
||||
private val modifiedShipChunks = ObjectOpenHashSet<ByteKey>()
|
||||
var shipChunkSource by Delegates.notNull<WorldStorage>()
|
||||
private set
|
||||
@ -114,7 +115,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
||||
shipChunks.putAll(chunks)
|
||||
}
|
||||
|
||||
private val tickets = Object2ObjectOpenHashMap<ChunkPos, ServerWorld.ITicket>()
|
||||
private val tickets = HashMap<ChunkPos, ServerWorld.ITicket>()
|
||||
private val pendingSend = ObjectLinkedOpenHashSet<ChunkPos>()
|
||||
|
||||
private var needsToRecomputeTrackedChunks = true
|
||||
@ -290,8 +291,8 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
|
||||
try {
|
||||
msg.play(this)
|
||||
} catch (err: Throwable) {
|
||||
LOGGER.error("Failed to read serverbound packet $msg", err)
|
||||
disconnect(err.toString())
|
||||
LOGGER.error("Failed to handle serverbound packet $msg", err)
|
||||
disconnect("Incoming packet caused an exception: $err")
|
||||
}
|
||||
} else {
|
||||
LOGGER.error("Unknown serverbound packet type $msg")
|
||||
|
@ -175,7 +175,13 @@ class ServerWorld private constructor(
|
||||
internalPlayers.forEach {
|
||||
if (!isClosed.get() && it.worldStartAcknowledged && it.channel.isOpen) {
|
||||
it.send(packet)
|
||||
it.tickWorld()
|
||||
|
||||
try {
|
||||
it.tickWorld()
|
||||
} catch (err: Throwable) {
|
||||
LOGGER.error("Exception while ticking player $it", err)
|
||||
//it.disconnect("Exception while ticking player: $err")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,11 +23,12 @@ import ru.dbotthepony.kstarbound.defs.world.CelestialPlanet
|
||||
import ru.dbotthepony.kstarbound.json.pairAdapter
|
||||
import ru.dbotthepony.kstarbound.json.pairListAdapter
|
||||
import ru.dbotthepony.kstarbound.world.UniversePos
|
||||
import java.util.HashMap
|
||||
|
||||
class UniverseChunk(var chunkPos: Vector2i = Vector2i.ZERO) {
|
||||
data class System(val parameters: CelestialParameters, val planets: Int2ObjectMap<CelestialPlanet>)
|
||||
|
||||
val systems = Object2ObjectOpenHashMap<Vector3i, System>()
|
||||
val systems = HashMap<Vector3i, System>()
|
||||
val constellations = ObjectOpenHashSet<Pair<Vector2i, Vector2i>>()
|
||||
|
||||
fun parameters(coordinate: UniversePos): CelestialParameters? {
|
||||
|
@ -4,7 +4,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps
|
||||
|
||||
class RenderDirectives private constructor(private val directivesInternal: Object2ObjectAVLTreeMap<String, String>) {
|
||||
class Directives private constructor(private val directivesInternal: Object2ObjectAVLTreeMap<String, String>) {
|
||||
constructor() : this(Object2ObjectAVLTreeMap())
|
||||
constructor(directives: String) : this() {
|
||||
if (directives.isNotBlank()) {
|
||||
@ -36,29 +36,29 @@ class RenderDirectives private constructor(private val directivesInternal: Objec
|
||||
|
||||
override fun toString(): String {
|
||||
if (directivesInternal.isEmpty())
|
||||
return "RenderDirectives[empty]"
|
||||
return "Directives[empty]"
|
||||
else
|
||||
return "RenderDirectives[?${directivesInternal.entries.joinToString("?") { "${it.key}=${it.value}" }}]"
|
||||
return "Directives[?${directivesInternal.entries.joinToString("?") { "${it.key}=${it.value}" }}]"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return this === other || other is RenderDirectives && directivesInternal == other.directivesInternal
|
||||
return this === other || other is Directives && directivesInternal == other.directivesInternal
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return directivesInternal.hashCode()
|
||||
}
|
||||
|
||||
fun add(directive: String, value: String): RenderDirectives {
|
||||
fun add(directive: String, value: String): Directives {
|
||||
if (directivesInternal[directive] == value)
|
||||
return this
|
||||
|
||||
val copy = directivesInternal.clone()
|
||||
copy[directive] = value
|
||||
return RenderDirectives(copy)
|
||||
return Directives(copy)
|
||||
}
|
||||
|
||||
fun add(directives: String): RenderDirectives {
|
||||
fun add(directives: String): Directives {
|
||||
if ('?' !in directives) {
|
||||
if ('=' !in directives) {
|
||||
throw IllegalArgumentException("Missing render directive delimiter in $directives")
|
||||
@ -87,7 +87,7 @@ class RenderDirectives private constructor(private val directivesInternal: Objec
|
||||
copy[key] = value
|
||||
}
|
||||
|
||||
return RenderDirectives(copy)
|
||||
return Directives(copy)
|
||||
}
|
||||
}
|
||||
}
|
@ -26,12 +26,12 @@ class GameTimer(val time: Double = 0.0) {
|
||||
timer = time - timer
|
||||
}
|
||||
|
||||
fun tick(delta: Double = Starbound.TICK_TIME_ADVANCE): Boolean {
|
||||
fun tick(delta: Double = Starbound.TIMESTEP): Boolean {
|
||||
timer = (timer - delta).coerceAtLeast(0.0)
|
||||
return timer == 0.0
|
||||
}
|
||||
|
||||
fun wrapTick(delta: Double = Starbound.TICK_TIME_ADVANCE): Boolean {
|
||||
fun wrapTick(delta: Double = Starbound.TIMESTEP): Boolean {
|
||||
val result = tick(delta)
|
||||
if (result) reset()
|
||||
return result
|
||||
|
@ -1,24 +1,18 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||
import ru.dbotthepony.kommons.io.VarIntValueCodec
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.kommons.util.value
|
||||
import ru.dbotthepony.kstarbound.defs.world.SkyParameters
|
||||
import ru.dbotthepony.kstarbound.defs.world.SkyType
|
||||
import ru.dbotthepony.kstarbound.defs.world.WarpPhase
|
||||
import ru.dbotthepony.kstarbound.network.syncher.BasicNetworkedElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.GroupElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||
import ru.dbotthepony.kstarbound.network.syncher.MasterElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedDouble
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedEnumStupid
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedFloat
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedJson
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedSignedInt
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedUnsignedInt
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedVec2f
|
||||
|
||||
@ -54,7 +48,7 @@ class Sky() {
|
||||
var flyingTimer by flyingTimerNetState
|
||||
private set
|
||||
|
||||
val networkedGroup = MasterElement(GroupElement(
|
||||
val networkedGroup = MasterElement(NetworkedGroup(
|
||||
skyParametersNetState,
|
||||
skyTypeNetState,
|
||||
timeNetState,
|
||||
|
@ -1,6 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.ints.IntArraySet
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
@ -227,7 +228,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
val lock = ReentrantLock()
|
||||
|
||||
val orphanedEntities = ReferenceOpenHashSet<AbstractEntity>()
|
||||
val entities = ReferenceOpenHashSet<AbstractEntity>()
|
||||
val entities = Int2ObjectOpenHashMap<AbstractEntity>()
|
||||
val dynamicEntities = ReferenceOpenHashSet<DynamicEntity>()
|
||||
val tileEntities = ReferenceOpenHashSet<TileEntity>()
|
||||
|
||||
@ -271,7 +272,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
ForkJoinPool.commonPool().submit(ParallelPerform(dynamicEntities.spliterator(), { it.movement.move() })).join()
|
||||
mailbox.executeQueuedTasks()
|
||||
|
||||
entities.forEach { it.think() }
|
||||
entities.values.forEach { it.think() }
|
||||
mailbox.executeQueuedTasks()
|
||||
|
||||
for (chunk in chunkMap) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities
|
||||
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||
import ru.dbotthepony.kommons.util.MailboxExecutorService
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
@ -9,6 +10,7 @@ import ru.dbotthepony.kstarbound.world.Chunk
|
||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||
import ru.dbotthepony.kstarbound.world.LightCalculator
|
||||
import ru.dbotthepony.kstarbound.world.World
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.UUID
|
||||
import kotlin.concurrent.withLock
|
||||
@ -88,9 +90,8 @@ abstract class AbstractEntity(path: String) : JsonDriven(path) {
|
||||
|
||||
abstract fun writeToNetwork(stream: DataOutputStream, isLegacy: Boolean)
|
||||
|
||||
/**
|
||||
* MUST be called by [World] itself
|
||||
*/
|
||||
abstract fun readDelta(stream: ByteArrayList, interpolationTime: Double = 0.0, isLegacy: Boolean)
|
||||
|
||||
fun joinWorld(world: World<*, *>) {
|
||||
if (innerWorld != null)
|
||||
throw IllegalStateException("Already spawned (in world $innerWorld)")
|
||||
@ -100,11 +101,13 @@ abstract class AbstractEntity(path: String) : JsonDriven(path) {
|
||||
|
||||
world.ensureSameThread()
|
||||
|
||||
check(!world.entities.containsKey(entityID)) { "Duplicate entity ID: $entityID" }
|
||||
|
||||
if (mailbox.isShutdown)
|
||||
mailbox = MailboxExecutorService()
|
||||
|
||||
innerWorld = world
|
||||
world.entities.add(this)
|
||||
world.entities[entityID] = this
|
||||
world.orphanedEntities.add(this)
|
||||
onJoinWorld(world)
|
||||
}
|
||||
@ -115,7 +118,7 @@ abstract class AbstractEntity(path: String) : JsonDriven(path) {
|
||||
|
||||
mailbox.shutdownNow()
|
||||
chunk = null
|
||||
world.entities.remove(this)
|
||||
check(world.entities.remove(entityID) == this) { "Tried to remove $this from $world, but removed something else!" }
|
||||
world.orphanedEntities.remove(this)
|
||||
onRemove(world)
|
||||
innerWorld = null
|
||||
|
@ -5,4 +5,5 @@ package ru.dbotthepony.kstarbound.world.entities
|
||||
*/
|
||||
abstract class ActorEntity(path: String) : DynamicEntity(path) {
|
||||
final override val movement: ActorMovementController = ActorMovementController()
|
||||
abstract val statusController: StatusController
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities
|
||||
|
||||
import ru.dbotthepony.kommons.io.koptional
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
@ -9,6 +11,7 @@ import ru.dbotthepony.kstarbound.defs.JumpProfile
|
||||
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
||||
import ru.dbotthepony.kstarbound.defs.actor.player.ActorMovementModifiers
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedData
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedEnum
|
||||
import ru.dbotthepony.kstarbound.util.GameTimer
|
||||
import ru.dbotthepony.kstarbound.world.Direction
|
||||
@ -26,32 +29,35 @@ class ActorMovementController : MovementController() {
|
||||
var controlFly: Vector2d? = null
|
||||
var controlFace: Direction1D? = null
|
||||
|
||||
var isWalking: Boolean by networkGroup.upstream.add(networkedBoolean())
|
||||
var isWalking: Boolean by networkGroup.add(networkedBoolean())
|
||||
private set
|
||||
var isRunning: Boolean by networkGroup.upstream.add(networkedBoolean())
|
||||
var isRunning: Boolean by networkGroup.add(networkedBoolean())
|
||||
private set
|
||||
|
||||
var movingDirection: Direction1D by networkGroup.upstream.add(networkedEnum(Direction1D.RIGHT))
|
||||
var movingDirection: Direction1D by networkGroup.add(networkedEnum(Direction1D.RIGHT))
|
||||
private set
|
||||
var facingDirection: Direction1D by networkGroup.upstream.add(networkedEnum(Direction1D.RIGHT))
|
||||
var facingDirection: Direction1D by networkGroup.add(networkedEnum(Direction1D.RIGHT))
|
||||
private set
|
||||
|
||||
var isCrouching: Boolean by networkGroup.upstream.add(networkedBoolean())
|
||||
var isCrouching: Boolean by networkGroup.add(networkedBoolean())
|
||||
private set
|
||||
|
||||
var isFlying: Boolean by networkGroup.upstream.add(networkedBoolean())
|
||||
var isFlying: Boolean by networkGroup.add(networkedBoolean())
|
||||
private set
|
||||
var isFalling: Boolean by networkGroup.upstream.add(networkedBoolean())
|
||||
var isFalling: Boolean by networkGroup.add(networkedBoolean())
|
||||
private set
|
||||
|
||||
var canJump: Boolean by networkGroup.upstream.add(networkedBoolean())
|
||||
var canJump: Boolean by networkGroup.add(networkedBoolean())
|
||||
private set
|
||||
var isJumping: Boolean by networkGroup.upstream.add(networkedBoolean())
|
||||
var isJumping: Boolean by networkGroup.add(networkedBoolean())
|
||||
private set
|
||||
|
||||
var isGroundMovement: Boolean by networkGroup.upstream.add(networkedBoolean())
|
||||
var isGroundMovement: Boolean by networkGroup.add(networkedBoolean())
|
||||
private set
|
||||
var isLiquidMovement: Boolean by networkGroup.upstream.add(networkedBoolean())
|
||||
var isLiquidMovement: Boolean by networkGroup.add(networkedBoolean())
|
||||
private set
|
||||
|
||||
var anchorState by networkGroup.add(networkedData(KOptional(), AnchorState.CODEC.koptional(), AnchorState.LEGACY_CODEC.koptional()))
|
||||
private set
|
||||
|
||||
var controlJump: Boolean = false
|
||||
@ -217,7 +223,7 @@ class ActorMovementController : MovementController() {
|
||||
isGroundMovement = false
|
||||
isLiquidMovement = false
|
||||
|
||||
velocity = (anchorEntity.position - position) / Starbound.TICK_TIME_ADVANCE
|
||||
velocity = (anchorEntity.position - position) / Starbound.TIMESTEP
|
||||
super.move()
|
||||
position = anchorEntity.position
|
||||
} else {
|
||||
@ -262,8 +268,8 @@ class ActorMovementController : MovementController() {
|
||||
|
||||
targetHorizontalAmbulatingVelocity = 0.0
|
||||
|
||||
rotation = (rotation + controlRotationRate * Starbound.TICK_TIME_ADVANCE) % (PI * 2.0)
|
||||
velocity += controlAcceleration * Starbound.TICK_TIME_ADVANCE + controlForce / mass * Starbound.TICK_TIME_ADVANCE
|
||||
rotation = (rotation + controlRotationRate * Starbound.TIMESTEP) % (PI * 2.0)
|
||||
velocity += controlAcceleration * Starbound.TIMESTEP + controlForce / mass * Starbound.TIMESTEP
|
||||
|
||||
approachVelocities.forEach {
|
||||
approachVelocity(it.target, it.maxControlForce)
|
||||
|
@ -0,0 +1,22 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities
|
||||
|
||||
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
|
||||
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
|
||||
import ru.dbotthepony.kstarbound.network.syncher.readPointer
|
||||
import ru.dbotthepony.kstarbound.network.syncher.writePointer
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
data class AnchorState(val entityID: Int, val positionIndex: Int) {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readInt(), stream.readPointer().toInt())
|
||||
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeInt(entityID)
|
||||
stream.writePointer(positionIndex)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC = nativeCodec(::AnchorState, AnchorState::write)
|
||||
val LEGACY_CODEC = legacyCodec(::AnchorState, AnchorState::write)
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap
|
||||
import ru.dbotthepony.kstarbound.defs.animation.AnimatedPartsDefinition
|
||||
|
||||
class AnimatedParts {
|
||||
private class StateType(config: AnimatedPartsDefinition.StateType) {
|
||||
var enabled = config.enabled
|
||||
var activeStateDirty = true
|
||||
val priority = config.priority
|
||||
val stateTypeProperties = config.properties
|
||||
val default: String
|
||||
|
||||
// sorted by key
|
||||
val states = Object2ObjectAVLTreeMap<String, AnimatedPartsDefinition.StateType.State>()
|
||||
|
||||
init {
|
||||
config.states.forEach { (t, u) -> states[t] = u }
|
||||
|
||||
if (states.isNotEmpty() && config.default.isBlank())
|
||||
default = states.firstKey()
|
||||
else
|
||||
default = config.default
|
||||
}
|
||||
}
|
||||
|
||||
private class Part(config: AnimatedPartsDefinition.Part) {
|
||||
val partProperties = config.properties
|
||||
var activePartDirty = true
|
||||
val partStates = config.partStates
|
||||
}
|
||||
|
||||
// sorted by priority
|
||||
private val stateTypes = LinkedHashMap<String, StateType>()
|
||||
// sorted by key
|
||||
private val parts = Object2ObjectAVLTreeMap<String, Part>()
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
constructor(config: AnimatedPartsDefinition) {
|
||||
for ((k, v) in config.stateTypes.entries.sortedWith { o1, o2 -> o2.value.priority.compareTo(o1.value.priority) }) {
|
||||
stateTypes[k] = StateType(v)
|
||||
}
|
||||
|
||||
for ((k, v) in config.parts) {
|
||||
parts[k] = Part(v)
|
||||
}
|
||||
}
|
||||
|
||||
fun parts(): Collection<String> {
|
||||
return parts.keys
|
||||
}
|
||||
|
||||
fun stateTypes(): Collection<String> {
|
||||
return stateTypes.keys
|
||||
}
|
||||
}
|
@ -0,0 +1,402 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.io.BinaryStringCodec
|
||||
import ru.dbotthepony.kommons.io.IntValueCodec
|
||||
import ru.dbotthepony.kommons.io.map
|
||||
import ru.dbotthepony.kommons.io.readKOptional
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeKOptional
|
||||
import ru.dbotthepony.kommons.matrix.Matrix3f
|
||||
import ru.dbotthepony.kommons.util.AABB
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.animation.ParticleConfig
|
||||
import ru.dbotthepony.kstarbound.defs.animation.ParticleFactory
|
||||
import ru.dbotthepony.kstarbound.fromJson
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import ru.dbotthepony.kstarbound.math.Interpolator
|
||||
import ru.dbotthepony.kstarbound.math.PeriodicFunction
|
||||
import ru.dbotthepony.kstarbound.network.syncher.AABBCodecLegacy
|
||||
import ru.dbotthepony.kstarbound.network.syncher.AABBCodecNative
|
||||
import ru.dbotthepony.kstarbound.network.syncher.InternedStringCodec
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedMap
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedSignal
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedAABBNullable
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedColor
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedData
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedEventCounter
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedFloat
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedList
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedPointer
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedSignedInt
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedString
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedUnsignedInt
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.Collections
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
|
||||
|
||||
class Animator() {
|
||||
class Light {
|
||||
private val elements = ArrayList<NetworkedElement>()
|
||||
|
||||
fun addTo(group: NetworkedGroup) {
|
||||
elements.forEach { group.add(it) }
|
||||
}
|
||||
|
||||
var active by networkedBoolean().also { elements.add(it) }
|
||||
var xPosition by networkedFixedPoint(0.0125).also { elements.add(it); it.interpolator = Interpolator.Linear }
|
||||
var yPosition by networkedFixedPoint(0.0125).also { elements.add(it); it.interpolator = Interpolator.Linear }
|
||||
var color by networkedColor().also { elements.add(it) }
|
||||
var pointAngle by networkedFixedPoint(0.01).also { elements.add(it); it.interpolator = Interpolator.Linear }
|
||||
|
||||
var anchorPart: String? = null
|
||||
var transformationGroups: List<String> = listOf()
|
||||
var rotationGroup: String? = null
|
||||
var rotationCenter: Vector2d? = null
|
||||
|
||||
var flicker: PeriodicFunction? = null
|
||||
var pointLight: Boolean = false
|
||||
var pointBeam: Float = 0f
|
||||
var beamAmbience: Float = 0f
|
||||
}
|
||||
|
||||
enum class SoundSignal {
|
||||
PLAY, STOP_ALL;
|
||||
|
||||
companion object {
|
||||
val CODEC = IntValueCodec.map({ entries[this] }, { ordinal })
|
||||
}
|
||||
}
|
||||
|
||||
class Sound {
|
||||
private val elements = ArrayList<NetworkedElement>()
|
||||
|
||||
fun addTo(group: NetworkedGroup) {
|
||||
elements.forEach { group.add(it) }
|
||||
}
|
||||
|
||||
var rangeMultiplier = 0.0
|
||||
|
||||
var soundPool by networkedList(InternedStringCodec).also { elements.add(it) }
|
||||
var xPosition by networkedFixedPoint(0.0125).also { elements.add(it); it.interpolator = Interpolator.Linear }
|
||||
var yPosition by networkedFixedPoint(0.0125).also { elements.add(it); it.interpolator = Interpolator.Linear }
|
||||
var volumeTarget by networkedFloat(1.0).also { elements.add(it) }
|
||||
var volumeRampTime by networkedFloat(0.0).also { elements.add(it) }
|
||||
var pitchMultiplierTarget by networkedFloat(1.0).also { elements.add(it) }
|
||||
var pitchMultiplierRampTime by networkedFloat(0.0).also { elements.add(it) }
|
||||
var loops by networkedSignedInt().also { elements.add(it) }
|
||||
val signals = NetworkedSignal(SoundSignal.CODEC).also { elements.add(it) }
|
||||
}
|
||||
|
||||
class Effect(val type: String, val time: Double, val directives: String) {
|
||||
val enabled = networkedBoolean()
|
||||
var timer: Double = 0.0
|
||||
}
|
||||
|
||||
class StateInfo {
|
||||
val stateIndex = networkedPointer()
|
||||
val startedEvent = networkedEventCounter()
|
||||
}
|
||||
|
||||
class RotationGroup {
|
||||
var angularVelocity = 0.0
|
||||
var rotationCenter = Vector2d.ZERO
|
||||
val targetAngle = networkedFloat()
|
||||
var currentAngle = 0.0
|
||||
val immediateEvent = networkedEventCounter()
|
||||
}
|
||||
|
||||
class TransformationGroup {
|
||||
private val elements = ArrayList<NetworkedElement>()
|
||||
|
||||
fun addTo(group: NetworkedGroup) {
|
||||
elements.forEach { group.add(it) }
|
||||
}
|
||||
|
||||
var interpolated = false
|
||||
|
||||
var xTranslation by networkedFloat(0.0).also { elements.add(it); it.interpolator = Interpolator.Linear }
|
||||
var yTranslation by networkedFloat(0.0).also { elements.add(it); it.interpolator = Interpolator.Linear }
|
||||
var xScale by networkedFloat(1.0).also { elements.add(it); it.interpolator = Interpolator.Linear }
|
||||
var yScale by networkedFloat(1.0).also { elements.add(it); it.interpolator = Interpolator.Linear }
|
||||
var xShear by networkedFloat(0.0).also { elements.add(it); it.interpolator = Interpolator.Linear }
|
||||
var yShear by networkedFloat(0.0).also { elements.add(it); it.interpolator = Interpolator.Linear }
|
||||
|
||||
fun affineTransform(): Matrix3f {
|
||||
return Matrix3f.rowMajor(
|
||||
(xScale * cos(xShear)).toFloat(), (xScale * sin(xShear)).toFloat(), xTranslation.toFloat(),
|
||||
(yScale * sin(yShear)).toFloat(), (yScale * cos(yShear)).toFloat(), yTranslation.toFloat(),
|
||||
0f, 0f, 1f
|
||||
)
|
||||
}
|
||||
|
||||
fun setAffineTransform(value: Matrix3f) {
|
||||
xTranslation = value[2, 0].toDouble()
|
||||
yTranslation = value[2, 1].toDouble()
|
||||
|
||||
xScale = sqrt(value[0, 0].toDouble() * value[0, 0].toDouble() + value[1, 0].toDouble() * value[1, 0].toDouble())
|
||||
yScale = sqrt(value[0, 1].toDouble() * value[0, 1].toDouble() + value[1, 1].toDouble() * value[1, 1].toDouble())
|
||||
|
||||
xShear = atan2(value[1, 0].toDouble(), value[0, 0].toDouble())
|
||||
yShear = atan2(value[0, 1].toDouble(), value[1, 1].toDouble())
|
||||
}
|
||||
}
|
||||
|
||||
class ParticleEmitter {
|
||||
data class Config(val count: Int, val offset: Vector2d, val flip: Boolean, val factory: ParticleFactory)
|
||||
|
||||
private val elements = ArrayList<NetworkedElement>()
|
||||
|
||||
fun addTo(group: NetworkedGroup) {
|
||||
elements.forEach { group.add(it) }
|
||||
}
|
||||
|
||||
var emissionRate by networkedFloat().also { elements.add(it) }
|
||||
var burstCount by networkedUnsignedInt().also { elements.add(it) }
|
||||
var randomSelectCount by networkedUnsignedInt().also { elements.add(it) }
|
||||
var emissionRateVariance = 0.0
|
||||
var offsetRegion by networkedAABBNullable().also { elements.add(it) }
|
||||
var anchorPart: String? = null
|
||||
var transformationGroups: List<String> = listOf()
|
||||
var rotationGroup: String? = null
|
||||
var rotationCenter: Vector2d? = null
|
||||
|
||||
val particleList = ArrayList<Config>()
|
||||
|
||||
var active by networkedBoolean().also { elements.add(it) }
|
||||
val burstEvent = networkedEventCounter().also { elements.add(it); it.ignoreOccurrencesOnLoad = true }
|
||||
|
||||
var timer = 0.0
|
||||
}
|
||||
|
||||
val networkGroup = NetworkedGroup()
|
||||
|
||||
private val elements = ArrayList<NetworkedElement>()
|
||||
|
||||
var animatedParts = AnimatedParts()
|
||||
private set
|
||||
|
||||
var processingDirectives by networkedString().also { elements.add(it) }
|
||||
var zoom by networkedFloat().also { elements.add(it) }
|
||||
var isFlipped by networkedBoolean().also { elements.add(it) }
|
||||
var flippedRelativeCenterLine by networkedFloat().also { elements.add(it) }
|
||||
var animationRate by networkedFloat(1.0).also { elements.add(it); it.interpolator = Interpolator.Linear }
|
||||
|
||||
private val globalTags = NetworkedMap(InternedStringCodec, InternedStringCodec)
|
||||
private val partTags = HashMap<String, NetworkedMap<String, String>>()
|
||||
|
||||
private val stateInfo = Object2ObjectAVLTreeMap<String, StateInfo>()
|
||||
private val rotationGroups = Object2ObjectAVLTreeMap<String, RotationGroup>()
|
||||
private val transformationGroups = Object2ObjectAVLTreeMap<String, TransformationGroup>()
|
||||
private val particleEmitters = Object2ObjectAVLTreeMap<String, ParticleEmitter>()
|
||||
private val lights = Object2ObjectAVLTreeMap<String, Light>()
|
||||
private val sounds = Object2ObjectAVLTreeMap<String, Sound>()
|
||||
private val effects = Object2ObjectAVLTreeMap<String, Effect>()
|
||||
|
||||
init {
|
||||
setupNetworkElements()
|
||||
}
|
||||
|
||||
constructor(config: AnimationDefinition) : this() {
|
||||
if (config.animatedParts != null)
|
||||
animatedParts = AnimatedParts(config.animatedParts)
|
||||
|
||||
for ((k, v) in config.globalTagDefaults) {
|
||||
globalTags[k] = v
|
||||
}
|
||||
|
||||
for ((part, tags) in config.partTagDefaults) {
|
||||
for ((k, v) in tags) {
|
||||
setPartTag(part, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
for ((k, v) in config.transformationGroups) {
|
||||
val group = TransformationGroup()
|
||||
transformationGroups[k] = group
|
||||
group.interpolated = v.interpolated
|
||||
}
|
||||
|
||||
for ((k, v) in config.rotationGroups) {
|
||||
val group = RotationGroup()
|
||||
group.angularVelocity = v.angularVelocity
|
||||
group.rotationCenter = v.rotationCenter
|
||||
rotationGroups[k] = group
|
||||
}
|
||||
|
||||
for ((k, v) in config.particleEmitters) {
|
||||
val emitter = ParticleEmitter()
|
||||
|
||||
emitter.emissionRate = v.emissionRate
|
||||
emitter.emissionRateVariance = v.emissionRateVariance
|
||||
emitter.offsetRegion = KOptional.ofNullable(v.offsetRegion)
|
||||
emitter.anchorPart = v.anchorPart
|
||||
emitter.transformationGroups = v.transformationGroups
|
||||
emitter.rotationGroup = v.rotationGroup
|
||||
emitter.rotationCenter = v.rotationCenter
|
||||
emitter.burstCount = v.burstCount
|
||||
emitter.randomSelectCount = v.randomSelectCount
|
||||
emitter.active = v.active
|
||||
|
||||
for (particle in v.particles) {
|
||||
val factory = particle.particle.map({ it.value }, { it }) ?: continue // not a valid particle, too bad.
|
||||
emitter.particleList.add(ParticleEmitter.Config(particle.count, particle.offset, particle.flip, factory))
|
||||
}
|
||||
|
||||
particleEmitters[k] = emitter
|
||||
}
|
||||
|
||||
for ((k, v) in config.lights) {
|
||||
val light = Light()
|
||||
|
||||
light.active = v.active
|
||||
light.xPosition = v.position.x
|
||||
light.yPosition = v.position.y
|
||||
light.color = v.color
|
||||
light.anchorPart = v.anchorPart
|
||||
light.transformationGroups = v.transformationGroups
|
||||
light.rotationGroup = v.rotationGroup
|
||||
light.rotationCenter = v.rotationCenter
|
||||
light.pointAngle = Math.toRadians(v.pointAngle)
|
||||
light.pointLight = v.pointLight
|
||||
light.pointBeam = v.pointBeam
|
||||
light.beamAmbience = v.beamAmbience
|
||||
|
||||
if (v.flickerPeriod != null)
|
||||
light.flicker = PeriodicFunction(v.flickerPeriod, v.flickerMinIntensity, v.flickerMaxIntensity, v.flickerPeriodVariance, v.flickerIntensityVariance)
|
||||
|
||||
lights[k] = light
|
||||
}
|
||||
|
||||
for ((k, v) in config.sounds) {
|
||||
val sound = Sound()
|
||||
|
||||
if (v.isLeft) {
|
||||
sound.soundPool = v.left()
|
||||
} else {
|
||||
val conf = v.right()
|
||||
sound.soundPool = conf.pool.map { it.fullPath }
|
||||
sound.xPosition = conf.position.x
|
||||
sound.yPosition = conf.position.y
|
||||
sound.volumeTarget = conf.volume
|
||||
sound.volumeRampTime = conf.volumeRampTime
|
||||
sound.pitchMultiplierTarget = conf.pitchMultiplier
|
||||
sound.pitchMultiplierRampTime = conf.pitchMultiplierRampTime
|
||||
sound.rangeMultiplier = conf.rangeMultiplier
|
||||
}
|
||||
|
||||
sounds[k] = sound
|
||||
}
|
||||
|
||||
for ((k, v) in config.effects) {
|
||||
effects[k] = Effect(v.type, v.time, v.directives)
|
||||
}
|
||||
|
||||
for (k in animatedParts.stateTypes()) {
|
||||
stateInfo[k] = StateInfo()
|
||||
}
|
||||
|
||||
for (k in animatedParts.parts()) {
|
||||
partTags.computeIfAbsent(k) { NetworkedMap(InternedStringCodec, InternedStringCodec) }
|
||||
}
|
||||
|
||||
setupNetworkElements()
|
||||
}
|
||||
|
||||
// Every part image can have one or more <tag> directives in it, which if set
|
||||
// here will be replaced by the tag value when constructing Drawables. All
|
||||
// Drawables can also have a <frame> tag which will be set to whatever the
|
||||
// current state frame is (1 indexed, so the first frame is 1).
|
||||
fun setGlobalTag(key: String, value: String) {
|
||||
globalTags[key] = value
|
||||
}
|
||||
|
||||
fun setPartTag(partName: String, tagKey: String, tagValue: String) {
|
||||
var tags = partTags[partName]
|
||||
|
||||
if (tags == null) {
|
||||
tags = NetworkedMap(InternedStringCodec, InternedStringCodec)
|
||||
partTags[partName] = tags
|
||||
}
|
||||
|
||||
tags[tagKey] = tagValue
|
||||
}
|
||||
|
||||
private fun setupNetworkElements() {
|
||||
networkGroup.clear()
|
||||
elements.forEach { networkGroup.add(it) }
|
||||
|
||||
networkGroup.add(globalTags)
|
||||
|
||||
// animated part set
|
||||
for (v in animatedParts.parts()) {
|
||||
networkGroup.add(partTags[v] ?: throw RuntimeException("Missing animated part $v!"))
|
||||
}
|
||||
|
||||
for (v in stateInfo.values) {
|
||||
networkGroup.add(v.stateIndex)
|
||||
networkGroup.add(v.startedEvent)
|
||||
}
|
||||
|
||||
for (v in transformationGroups.values) {
|
||||
v.addTo(networkGroup)
|
||||
}
|
||||
|
||||
for (v in rotationGroups.values) {
|
||||
networkGroup.add(v.targetAngle)
|
||||
networkGroup.add(v.immediateEvent)
|
||||
}
|
||||
|
||||
for (v in particleEmitters.values) {
|
||||
v.addTo(networkGroup)
|
||||
}
|
||||
|
||||
for (v in lights.values) {
|
||||
v.addTo(networkGroup)
|
||||
}
|
||||
|
||||
for (v in sounds.values) {
|
||||
v.addTo(networkGroup)
|
||||
}
|
||||
|
||||
for (v in effects.values) {
|
||||
networkGroup.add(v.enabled)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
// lame
|
||||
fun load(path: String): Animator {
|
||||
val json = Starbound.loadJsonAsset(path)
|
||||
|
||||
if (json == null) {
|
||||
if (missing.add(path)) {
|
||||
LOGGER.error("Unable to instance Animator from $path! This very likely gonna make networking code go haywire.")
|
||||
}
|
||||
|
||||
return Animator()
|
||||
}
|
||||
|
||||
return Animator(Starbound.gson.fromJson(json, AnimationDefinition::class.java))
|
||||
}
|
||||
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
private val missing = Collections.synchronizedSet(ObjectOpenHashSet<String>())
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||
import ru.dbotthepony.kommons.io.BinaryStringCodec
|
||||
import ru.dbotthepony.kommons.io.StreamCodec
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.kstarbound.network.syncher.InternedStringCodec
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedData
|
||||
import java.util.function.Consumer
|
||||
|
||||
class EffectEmitter(val entity: AbstractEntity) {
|
||||
val networkGroup = NetworkedGroup()
|
||||
|
||||
// stoopid
|
||||
var currentEffects by networkGroup.add(networkedData(setOf(), pairCodec))
|
||||
private set
|
||||
|
||||
companion object {
|
||||
private val pairCodec = StreamCodec.Collection(StreamCodec.Pair(InternedStringCodec, InternedStringCodec), ::ObjectOpenHashSet) as StreamCodec<Set<Pair<String, String>>>
|
||||
}
|
||||
}
|
@ -1,10 +1,53 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities
|
||||
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.math.Interpolator
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedItem
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedStatefulItem
|
||||
import java.io.DataInputStream
|
||||
|
||||
/**
|
||||
* Players and NPCs
|
||||
*/
|
||||
abstract class HumanoidActorEntity(path: String) : ActorEntity(path) {
|
||||
abstract val aimPosition: Vector2d
|
||||
|
||||
val effects = EffectEmitter(this)
|
||||
|
||||
// it makes no sense to split ToolUser' logic into separate class
|
||||
protected val toolsNetworkGroup = NetworkedGroup()
|
||||
|
||||
var primaryHandItem by toolsNetworkGroup.add(networkedStatefulItem())
|
||||
var secondaryHandItem by toolsNetworkGroup.add(networkedStatefulItem())
|
||||
var primaryFireTimerNetState by toolsNetworkGroup.add(networkedFixedPoint(Starbound.TIMESTEP).also { it.interpolator = ToolFiringInterpolator })
|
||||
var secondaryFireTimerNetState by toolsNetworkGroup.add(networkedFixedPoint(Starbound.TIMESTEP).also { it.interpolator = ToolFiringInterpolator })
|
||||
var primaryTimeFiringNetState by toolsNetworkGroup.add(networkedFixedPoint(Starbound.TIMESTEP).also { it.interpolator = ToolFiringInterpolator })
|
||||
var secondaryTimeFiringNetState by toolsNetworkGroup.add(networkedFixedPoint(Starbound.TIMESTEP).also { it.interpolator = ToolFiringInterpolator })
|
||||
var primaryItemActive by toolsNetworkGroup.add(networkedBoolean())
|
||||
var secondaryItemActive by toolsNetworkGroup.add(networkedBoolean())
|
||||
|
||||
private object ToolFiringInterpolator : Interpolator {
|
||||
override fun interpolate(t: Double, a: Double, b: Double): Double {
|
||||
return if (a >= b) b else Interpolator.Linear.interpolate(t, a, b)
|
||||
}
|
||||
}
|
||||
|
||||
// same as above
|
||||
protected val armorNetworkGroup = NetworkedGroup()
|
||||
|
||||
var headItem by armorNetworkGroup.add(networkedItem())
|
||||
var chestItem by armorNetworkGroup.add(networkedItem())
|
||||
var legsItem by armorNetworkGroup.add(networkedItem())
|
||||
var backItem by armorNetworkGroup.add(networkedItem())
|
||||
var headCosmeticItem by armorNetworkGroup.add(networkedItem())
|
||||
var chestCosmeticItem by armorNetworkGroup.add(networkedItem())
|
||||
var legsCosmeticItem by armorNetworkGroup.add(networkedItem())
|
||||
var backCosmeticItem by armorNetworkGroup.add(networkedItem())
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities
|
||||
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||
import ru.dbotthepony.kommons.io.DoubleValueCodec
|
||||
import ru.dbotthepony.kommons.io.FloatValueCodec
|
||||
import ru.dbotthepony.kommons.io.StreamCodec
|
||||
@ -16,8 +15,8 @@ import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kommons.vector.times
|
||||
import ru.dbotthepony.kstarbound.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
||||
import ru.dbotthepony.kstarbound.network.syncher.GroupElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.MasterElement
|
||||
import ru.dbotthepony.kstarbound.math.Interpolator
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedData
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint
|
||||
@ -51,34 +50,34 @@ open class MovementController() {
|
||||
|
||||
private val legacyPoly = networkedPoly(Poly.EMPTY)
|
||||
|
||||
protected val networkGroup = MasterElement(GroupElement(legacyPoly))
|
||||
val networkGroup = NetworkedGroup(legacyPoly)
|
||||
|
||||
var mass by networkGroup.upstream.add(networkedFloat())
|
||||
var mass by networkGroup.add(networkedFloat())
|
||||
|
||||
private var xPosition by networkGroup.upstream.add(networkedFixedPoint(0.0125))
|
||||
private var yPosition by networkGroup.upstream.add(networkedFixedPoint(0.0125))
|
||||
private var xVelocity by networkGroup.upstream.add(networkedFixedPoint(0.00625))
|
||||
private var yVelocity by networkGroup.upstream.add(networkedFixedPoint(0.00625))
|
||||
private var xPosition by networkGroup.add(networkedFixedPoint(0.0125))
|
||||
private var yPosition by networkGroup.add(networkedFixedPoint(0.0125))
|
||||
private var xVelocity by networkGroup.add(networkedFixedPoint(0.00625).also { it.interpolator = Interpolator.Linear })
|
||||
private var yVelocity by networkGroup.add(networkedFixedPoint(0.00625).also { it.interpolator = Interpolator.Linear })
|
||||
|
||||
var rotation by networkGroup.upstream.add(networkedFixedPoint(0.01))
|
||||
var rotation by networkGroup.add(networkedFixedPoint(0.01).also { it.interpolator = Interpolator.Linear })
|
||||
|
||||
var isColliding: Boolean by networkGroup.upstream.add(networkedBoolean())
|
||||
var isColliding: Boolean by networkGroup.add(networkedBoolean())
|
||||
protected set
|
||||
var isCollisionStuck: Boolean by networkGroup.upstream.add(networkedBoolean())
|
||||
var isCollisionStuck: Boolean by networkGroup.add(networkedBoolean())
|
||||
protected set
|
||||
var isCollidingWithNull: Boolean by networkGroup.upstream.add(networkedBoolean())
|
||||
var isCollidingWithNull: Boolean by networkGroup.add(networkedBoolean())
|
||||
protected set
|
||||
|
||||
private val stickingDirectionField = networkGroup.upstream.add(networkedData(KOptional(), DoubleValueCodec.koptional(), FloatValueCodec.map({ toDouble() }, { toFloat() }).koptional()))
|
||||
private val stickingDirectionField = networkGroup.add(networkedData(KOptional(), DoubleValueCodec.koptional(), FloatValueCodec.map({ toDouble() }, { toFloat() }).koptional()))
|
||||
|
||||
var isOnGround: Boolean by networkGroup.upstream.add(networkedBoolean())
|
||||
var isOnGround: Boolean by networkGroup.add(networkedBoolean())
|
||||
protected set
|
||||
var isZeroGravity: Boolean by networkGroup.upstream.add(networkedBoolean())
|
||||
var isZeroGravity: Boolean by networkGroup.add(networkedBoolean())
|
||||
protected set
|
||||
|
||||
private val surfaceMovingCollisionField = networkGroup.upstream.add(networkedData(KOptional(), StreamCodec.Impl(::MovingCollisionID, { a, b -> b.write(a) }).koptional()))
|
||||
private val relativeXSurfaceVelocity = networkGroup.upstream.add(networkedFloat())
|
||||
private val relativeYSurfaceVelocity = networkGroup.upstream.add(networkedFloat())
|
||||
private val surfaceMovingCollisionField = networkGroup.add(networkedData(KOptional(), StreamCodec.Impl(::MovingCollisionID, { a, b -> b.write(a) }).koptional()))
|
||||
private val relativeXSurfaceVelocity = networkGroup.add(networkedFixedPoint(0.0125).also { it.interpolator = Interpolator.Linear })
|
||||
private val relativeYSurfaceVelocity = networkGroup.add(networkedFixedPoint(0.0125).also { it.interpolator = Interpolator.Linear })
|
||||
|
||||
var position: Vector2d
|
||||
get() = Vector2d(xPosition, yPosition)
|
||||
@ -144,7 +143,7 @@ open class MovementController() {
|
||||
|
||||
if (mag == 0.0) return
|
||||
|
||||
val maximumAcceleration = maxControlForce / mass * Starbound.TICK_TIME_ADVANCE
|
||||
val maximumAcceleration = maxControlForce / mass * Starbound.TIMESTEP
|
||||
val clampedMag = mag.coerceIn(0.0, maximumAcceleration)
|
||||
|
||||
velocity += diff * (clampedMag / mag)
|
||||
@ -164,7 +163,7 @@ open class MovementController() {
|
||||
|
||||
if (diff == 0.0 || positiveOnly && diff < 0.0) return
|
||||
|
||||
val maximumAcceleration = maxControlForce / mass * Starbound.TICK_TIME_ADVANCE
|
||||
val maximumAcceleration = maxControlForce / mass * Starbound.TIMESTEP
|
||||
val diffMag = diff.absoluteValue
|
||||
val clampedMag = diffMag.coerceIn(0.0, maximumAcceleration)
|
||||
|
||||
@ -196,7 +195,7 @@ open class MovementController() {
|
||||
// TODO: Here: moving platforms sticky code
|
||||
|
||||
if (movementParameters.collisionPoly == null || !movementParameters.collisionPoly.map({ true }, { it.isNotEmpty() }) || movementParameters.collisionEnabled != true) {
|
||||
position += velocity * Starbound.TICK_TIME_ADVANCE
|
||||
position += velocity * Starbound.TIMESTEP
|
||||
surfaceSlope = Vector2d.POSITIVE_Y
|
||||
surfaceVelocity = Vector2d.ZERO
|
||||
isOnGround = false
|
||||
@ -210,14 +209,14 @@ open class MovementController() {
|
||||
var steps = 1
|
||||
|
||||
movementParameters.maxMovementPerStep?.let {
|
||||
steps = (velocity.length * Starbound.TICK_TIME_ADVANCE / it).toInt() + 1
|
||||
steps = (velocity.length * Starbound.TIMESTEP / it).toInt() + 1
|
||||
}
|
||||
|
||||
var relativeVelocity = velocity
|
||||
surfaceSlope = Vector2d.POSITIVE_Y
|
||||
// TODO: Here: moving platforms sticky code
|
||||
|
||||
val dt = Starbound.TICK_TIME_ADVANCE / steps
|
||||
val dt = Starbound.TIMESTEP / steps
|
||||
|
||||
for (step in 0 until steps) {
|
||||
val velocityMagnitude = relativeVelocity.length
|
||||
@ -289,14 +288,14 @@ open class MovementController() {
|
||||
// independently).
|
||||
|
||||
if (relativeVelocity.x < 0.0 && correction.x > 0.0)
|
||||
relativeVelocity = relativeVelocity.copy(x = (relativeVelocity.x + correction.x / Starbound.TICK_TIME_ADVANCE).coerceAtMost(0.0))
|
||||
relativeVelocity = relativeVelocity.copy(x = (relativeVelocity.x + correction.x / Starbound.TIMESTEP).coerceAtMost(0.0))
|
||||
else if (relativeVelocity.x > 0.0 && correction.x < 0.0)
|
||||
relativeVelocity = relativeVelocity.copy(x = (relativeVelocity.x + correction.x / Starbound.TICK_TIME_ADVANCE).coerceAtLeast(0.0))
|
||||
relativeVelocity = relativeVelocity.copy(x = (relativeVelocity.x + correction.x / Starbound.TIMESTEP).coerceAtLeast(0.0))
|
||||
|
||||
if (relativeVelocity.y < 0.0 && correction.y > 0.0)
|
||||
relativeVelocity = relativeVelocity.copy(y = (relativeVelocity.y + correction.y / Starbound.TICK_TIME_ADVANCE).coerceAtMost(0.0))
|
||||
relativeVelocity = relativeVelocity.copy(y = (relativeVelocity.y + correction.y / Starbound.TIMESTEP).coerceAtMost(0.0))
|
||||
else if (relativeVelocity.y > 0.0 && correction.y < 0.0)
|
||||
relativeVelocity = relativeVelocity.copy(y = (relativeVelocity.y + correction.y / Starbound.TICK_TIME_ADVANCE).coerceAtLeast(0.0))
|
||||
relativeVelocity = relativeVelocity.copy(y = (relativeVelocity.y + correction.y / Starbound.TIMESTEP).coerceAtLeast(0.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -315,7 +314,7 @@ open class MovementController() {
|
||||
if (!isZeroGravity && stickingDirection == null) {
|
||||
val buoyancy = (movementParameters.liquidBuoyancy ?: 0.0).coerceIn(0.0, 1.0) + liquidPercentage + (movementParameters.airBuoyancy ?: 0.0).coerceIn(0.0, 1.0) * (1.0 - liquidPercentage)
|
||||
val gravity = determineGravity() * (movementParameters.gravityMultiplier ?: 1.0) * (1.0 - buoyancy)
|
||||
var environmentVelocity = gravity * Starbound.TICK_TIME_ADVANCE
|
||||
var environmentVelocity = gravity * Starbound.TIMESTEP
|
||||
|
||||
if (isOnGround && (movementParameters.slopeSlidingFactor ?: 0.0) != 0.0 && surfaceSlope != Vector2d.ZERO)
|
||||
environmentVelocity += -surfaceSlope * (surfaceSlope.x * surfaceSlope.y) * (movementParameters.slopeSlidingFactor ?: 0.0)
|
||||
@ -337,7 +336,7 @@ open class MovementController() {
|
||||
// but it is applied here as a multiplicative factor from [0, 1] so it does
|
||||
// not induce oscillation at very high friction and so it cannot be
|
||||
// negative.
|
||||
val frictionFactor = (friction / mass * Starbound.TICK_TIME_ADVANCE).coerceIn(0.0, 1.0)
|
||||
val frictionFactor = (friction / mass * Starbound.TIMESTEP).coerceIn(0.0, 1.0)
|
||||
newVelocity = linearInterpolation(frictionFactor, newVelocity, refVel)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,379 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||
import ru.dbotthepony.kommons.collect.ListenableMap
|
||||
import ru.dbotthepony.kommons.io.IntValueCodec
|
||||
import ru.dbotthepony.kommons.io.KOptionalIntValueCodec
|
||||
import ru.dbotthepony.kommons.io.KOptionalVarIntValueCodec
|
||||
import ru.dbotthepony.kommons.io.StreamCodec
|
||||
import ru.dbotthepony.kommons.io.UnsignedVarIntCodec
|
||||
import ru.dbotthepony.kommons.io.readKOptional
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeKOptional
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.kommons.util.value
|
||||
import ru.dbotthepony.kstarbound.collect.IdMap
|
||||
import ru.dbotthepony.kstarbound.defs.actor.StatModifier
|
||||
import ru.dbotthepony.kstarbound.defs.actor.StatModifierType
|
||||
import ru.dbotthepony.kstarbound.defs.actor.StatusControllerConfig
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import ru.dbotthepony.kstarbound.math.Interpolator
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedDynamicGroup
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedMap
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedData
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedDouble
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedFloat
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedJsonObject
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedString
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.Collections
|
||||
import java.util.function.LongSupplier
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
// this is unnatural to have this class separated, but since it contains
|
||||
// lots of internal state, it would be nice to have it encapsulated;
|
||||
// despite this class being used exactly once - as part of ActorEntity.
|
||||
|
||||
// On other hand, original code separates StatCollection into subclass,
|
||||
// and we won't do this.
|
||||
|
||||
// On side note, it appears game initially separated StatSet from StatCollection,
|
||||
// which means early in development Starbound had true singleplayer, which eventually
|
||||
// got removed in favor of listening server.
|
||||
// Unfortunately for me, this means I have to untangle this piece of code
|
||||
// and unify StatSet and StatCollection logic into one thing.
|
||||
|
||||
// StatSet original docs:
|
||||
// Manages a collection of Stats and Resources.
|
||||
//
|
||||
// Stats are named floating point values of any base value, with an arbitrary
|
||||
// number of "stat modifiers" attached to them. Stat modifiers can be added
|
||||
// and removed in groups, and they can either raise or lower stats by a
|
||||
// constant value or a percentage of the stat value without any other
|
||||
// percentage modifications applied. The effective stat value is always the
|
||||
// value with all mods applied. If a modifier is created for a stat that does
|
||||
// not exist, there will be an effective stat value for the modified stat, but
|
||||
// NO base stat. If the modifier is a base percentage modifier, it will have
|
||||
// no effect because it is assumed that base stats that do not exist are zero.
|
||||
//
|
||||
// Resources are also named floating point values, but are in a different
|
||||
// namespaced and are intended to be used as values that change regularly.
|
||||
// They are always >= 0.0f, and optionally have a maximum value based on a
|
||||
// given value or stat. In addition to a max value, they can also have a
|
||||
// "delta" value or stat, which automatically adds or removes that delta to the
|
||||
// resource every second.
|
||||
//
|
||||
// If a resource has a maximum value, then rather than trying to keep the
|
||||
// *value* of the resource constant, this class will instead attempt to keep
|
||||
// the *percentage* of the resource constant across stat changes. For example,
|
||||
// if "health" is a stat with a max of 100, and the current health value is 50,
|
||||
// and the max health stat is changed to 200 through any means, the health
|
||||
// value will automatically update to 100.
|
||||
|
||||
// StatCollection original docs:
|
||||
// Extension of StatSet that can easily be set up from config, and is network
|
||||
// capable.
|
||||
class StatusController(val entity: ActorEntity, val config: StatusControllerConfig) : NetworkedElement.Passthrough() {
|
||||
// status effects
|
||||
private val networkGroup = NetworkedGroup()
|
||||
private val statNetworkGroup = NetworkedGroup()
|
||||
|
||||
override val parentElement: NetworkedElement
|
||||
get() = networkGroup
|
||||
|
||||
init {
|
||||
networkGroup.add(statNetworkGroup)
|
||||
}
|
||||
|
||||
private var statusProperties by networkedJsonObject(config.statusProperties).also { networkGroup.add(it) }
|
||||
|
||||
var parentDirectives by networkedString().also { networkGroup.add(it) }
|
||||
private set
|
||||
|
||||
private val uniqueEffectMetadata = NetworkedDynamicGroup(::UniqueEffectMetadata, UniqueEffectMetadata::networkGroup).also { networkGroup.add(it) }
|
||||
private val effectAnimators = NetworkedDynamicGroup(::EffectAnimator, { it }).also { networkGroup.add(it) }
|
||||
|
||||
fun update(delta: Double) {
|
||||
updateStats(delta)
|
||||
}
|
||||
|
||||
private class EffectAnimator(var config: KOptional<String> = KOptional()) : Passthrough() {
|
||||
var animator = Animator()
|
||||
private set
|
||||
|
||||
override val parentElement: NetworkedElement
|
||||
get() = animator.networkGroup
|
||||
|
||||
override fun readInitial(data: DataInputStream, isLegacy: Boolean) {
|
||||
val old = config
|
||||
config = data.readKOptional { readInternedString() }
|
||||
|
||||
if (old != config)
|
||||
animator = config.map { Animator.load(it) }.orElse { Animator() }
|
||||
|
||||
super.readInitial(data, isLegacy)
|
||||
}
|
||||
|
||||
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
||||
data.writeKOptional(config) { writeBinaryString(it) }
|
||||
super.writeInitial(data, isLegacy)
|
||||
}
|
||||
}
|
||||
|
||||
private class UniqueEffectMetadata {
|
||||
val networkGroup = NetworkedGroup()
|
||||
|
||||
var duration by networkedFixedPoint(0.01).also { networkGroup.add(it); it.interpolator = Interpolator.Linear }
|
||||
var maxDuration by networkedFixedPoint(0.01).also { networkGroup.add(it) }
|
||||
var sourceEntity by networkedData(KOptional(), KOptionalIntValueCodec)
|
||||
}
|
||||
|
||||
// stats
|
||||
sealed class LiveStat {
|
||||
abstract val baseValue: Double
|
||||
|
||||
// Value with just the base percent modifiers applied and the value
|
||||
// modifiers
|
||||
abstract val baseModifiedValue: Double
|
||||
|
||||
// Final modified value that includes the effective modifiers.
|
||||
abstract val effectiveModifiedValue: Double
|
||||
}
|
||||
|
||||
private class LiveStatImpl : LiveStat() {
|
||||
override var baseValue: Double = 0.0
|
||||
|
||||
// Value with just the base percent modifiers applied and the value
|
||||
// modifiers
|
||||
override var baseModifiedValue: Double = 0.0
|
||||
|
||||
// Final modified value that includes the effective modifiers.
|
||||
override var effectiveModifiedValue: Double = 0.0
|
||||
}
|
||||
|
||||
sealed class Resource {
|
||||
// null means no limit
|
||||
abstract val max: Either<String, Double>?
|
||||
abstract val delta: Either<String, Double>?
|
||||
|
||||
abstract val name: String
|
||||
|
||||
abstract var isLocked: Boolean
|
||||
abstract var value: Double
|
||||
|
||||
abstract val maxValue: Double?
|
||||
|
||||
fun setAsPercentage(percent: Double) {
|
||||
val maxValue = maxValue ?: throw IllegalArgumentException("$name does not have max value")
|
||||
this.value = maxValue * percent
|
||||
}
|
||||
}
|
||||
|
||||
private class ResourceImpl(
|
||||
override val name: String,
|
||||
override val max: Either<String, Double>?,
|
||||
override val delta: Either<String, Double>?
|
||||
) : Resource() {
|
||||
val actualValue = networkedFloat()
|
||||
|
||||
override var value: Double
|
||||
get() = actualValue.value
|
||||
set(value) {
|
||||
val maxValue = maxValue
|
||||
|
||||
if (maxValue == null) {
|
||||
actualValue.value = value
|
||||
} else {
|
||||
actualValue.value = value.coerceAtMost(maxValue)
|
||||
}
|
||||
}
|
||||
|
||||
val actualIsLocked = networkedBoolean()
|
||||
|
||||
override var isLocked: Boolean
|
||||
get() = actualIsLocked.value
|
||||
set(value) { actualIsLocked.value = value }
|
||||
|
||||
override var maxValue: Double? = null
|
||||
|
||||
private var defaultValue: Double = 0.0
|
||||
|
||||
fun mark() {
|
||||
defaultValue = value
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
value = defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
// in original code it is named effectiveStats
|
||||
private val liveStatsInternal = HashMap<String, LiveStatImpl>()
|
||||
|
||||
private val statModifiersMap = NetworkedMap(
|
||||
IntValueCodec,
|
||||
NATIVE_MODIFIERS_CODEC to LEGACY_MODIFIERS_CODEC,
|
||||
map = ListenableMap(Int2ObjectAVLTreeMap()))
|
||||
|
||||
private val statModifiers = IdMap(map = statModifiersMap)
|
||||
private val resourcesInternal = HashMap<String, ResourceImpl>()
|
||||
|
||||
val liveStats: Map<String, LiveStat> = Collections.unmodifiableMap(liveStatsInternal)
|
||||
val resources: Map<String, Resource> = Collections.unmodifiableMap(resourcesInternal)
|
||||
|
||||
init {
|
||||
for ((statName, stat) in config.stats) {
|
||||
val live = LiveStatImpl()
|
||||
liveStatsInternal[statName] = live
|
||||
live.baseValue = stat.baseValue
|
||||
live.baseModifiedValue = stat.baseValue
|
||||
}
|
||||
|
||||
for ((resourceName, res) in config.resources) {
|
||||
resourcesInternal[resourceName] = ResourceImpl(
|
||||
resourceName,
|
||||
max = res.maxValue?.let { Either.right(it) } ?: res.maxStat?.let { Either.left(it) },
|
||||
delta = res.deltaValue?.let { Either.right(it) } ?: res.deltaStat?.let { Either.left(it) },
|
||||
)
|
||||
}
|
||||
|
||||
updateStats(0.0)
|
||||
|
||||
for ((resourceName, res) in config.resources) {
|
||||
val resource = resourcesInternal[resourceName]!!
|
||||
|
||||
if (res.initialValue != null) {
|
||||
resource.value = res.initialValue
|
||||
} else if (res.initialPercentage != null) {
|
||||
resource.setAsPercentage(res.initialPercentage)
|
||||
} else if (resource.maxValue != null) {
|
||||
resource.setAsPercentage(1.0)
|
||||
}
|
||||
|
||||
resource.mark()
|
||||
}
|
||||
|
||||
statNetworkGroup.add(statModifiersMap)
|
||||
|
||||
for (k in resourcesInternal.keys.sorted()) {
|
||||
val resource = resourcesInternal[k]!!
|
||||
|
||||
statNetworkGroup.add(resource.actualValue)
|
||||
statNetworkGroup.add(resource.actualIsLocked)
|
||||
}
|
||||
}
|
||||
|
||||
fun resetResources() {
|
||||
for (resource in resourcesInternal.values) {
|
||||
resource.reset()
|
||||
}
|
||||
}
|
||||
|
||||
fun addStatModifiers(modifiers: Collection<StatModifier>): Int {
|
||||
return statModifiers.add(ArrayList(modifiers))
|
||||
}
|
||||
|
||||
fun removeStatModifiers(index: Int): Boolean {
|
||||
return statModifiers.remove(index) != null
|
||||
}
|
||||
|
||||
private fun updateStats(delta: Double) {
|
||||
// We use two intermediate values for calculating the effective stat value.
|
||||
// The baseModifiedValue represents the application of the base percentage
|
||||
// modifiers and the value modifiers, which only depend on the baseValue.
|
||||
// The effectiveModifiedValue is the application of all effective percentage
|
||||
// modifiers successively on the baseModifiedValue, causing them to stack with
|
||||
// each other in addition to base multipliers and value modifiers
|
||||
|
||||
val neverVisited = ObjectArraySet(liveStats.keys)
|
||||
|
||||
for ((statName, stat) in config.stats) {
|
||||
val live = liveStatsInternal[statName]!!
|
||||
live.baseValue = stat.baseValue
|
||||
live.baseModifiedValue = stat.baseValue
|
||||
neverVisited.remove(statName)
|
||||
}
|
||||
|
||||
for (group in statModifiers.values) {
|
||||
for (modifier in group) {
|
||||
var live = liveStatsInternal[modifier.stat]
|
||||
|
||||
if (live == null) {
|
||||
live = LiveStatImpl()
|
||||
liveStatsInternal[modifier.stat] = live
|
||||
}
|
||||
|
||||
neverVisited.remove(modifier.stat)
|
||||
|
||||
if (modifier.type == StatModifierType.BASE_MULTIPLICATION) {
|
||||
live.baseModifiedValue += (modifier.value - 1.0) * live.baseValue
|
||||
} else if (modifier.type == StatModifierType.BASE_ADDITION) {
|
||||
live.baseModifiedValue += modifier.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then we do all the StatEffectiveMultipliers and compute the
|
||||
// final effectiveModifiedValue
|
||||
|
||||
for (value in liveStatsInternal.values)
|
||||
value.effectiveModifiedValue = value.baseModifiedValue
|
||||
|
||||
for (group in statModifiers.values) {
|
||||
for (modifier in group) {
|
||||
val live = liveStatsInternal[modifier.stat]!!
|
||||
|
||||
if (modifier.type == StatModifierType.OVERALL_MULTIPLICATION) {
|
||||
live.effectiveModifiedValue *= modifier.value
|
||||
} else if (modifier.type == StatModifierType.OVERALL_ADDITION) {
|
||||
live.effectiveModifiedValue += modifier.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (value in neverVisited) {
|
||||
val live = liveStatsInternal[value]!!
|
||||
|
||||
live.baseValue = 0.0
|
||||
live.effectiveModifiedValue = 0.0
|
||||
live.baseModifiedValue = 0.0
|
||||
}
|
||||
|
||||
// Then update all the resources due to charging and percentage tracking,
|
||||
// after updating the stats.
|
||||
|
||||
for (resource in resourcesInternal.values) {
|
||||
val oldMax = resource.maxValue
|
||||
resource.maxValue = resource.max?.map({ liveStatsInternal[it]?.effectiveModifiedValue }, { it })
|
||||
|
||||
// If the resource has a maximum value, rather than keeping the absolute
|
||||
// value of the resource the same between updates, the resource value
|
||||
// should instead track the percentage.
|
||||
if (oldMax != null && resource.maxValue != null && resource.maxValue!! > 0.0) {
|
||||
resource.value *= resource.maxValue!! / oldMax
|
||||
}
|
||||
|
||||
if (resource.maxValue != null) {
|
||||
resource.value = resource.value
|
||||
}
|
||||
|
||||
if (delta > 0.0) {
|
||||
resource.value += delta * (resource.delta?.map({ liveStatsInternal[it]?.effectiveModifiedValue }, { it }) ?: 0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LEGACY_MODIFIERS_CODEC = StreamCodec.Collection(StatModifier.LEGACY_CODEC, ::ArrayList)
|
||||
private val NATIVE_MODIFIERS_CODEC = StreamCodec.Collection(StatModifier.CODEC, ::ArrayList)
|
||||
}
|
||||
}
|
@ -4,9 +4,9 @@ import com.google.common.collect.ImmutableMap
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.TypeAdapter
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.kommons.util.MailboxExecutorService
|
||||
import ru.dbotthepony.kommons.vector.Vector2i
|
||||
import ru.dbotthepony.kstarbound.Registries
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
@ -15,7 +15,6 @@ import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||
import ru.dbotthepony.kstarbound.client.world.ClientWorld
|
||||
import ru.dbotthepony.kstarbound.defs.Drawable
|
||||
import ru.dbotthepony.kstarbound.defs.JsonDriven
|
||||
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
|
||||
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
|
||||
import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation
|
||||
@ -25,10 +24,9 @@ import ru.dbotthepony.kommons.gson.set
|
||||
import ru.dbotthepony.kstarbound.world.Side
|
||||
import ru.dbotthepony.kstarbound.world.LightCalculator
|
||||
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNITf
|
||||
import ru.dbotthepony.kstarbound.world.World
|
||||
import ru.dbotthepony.kstarbound.world.api.TileColor
|
||||
import java.io.DataOutputStream
|
||||
import kotlin.properties.Delegates
|
||||
import java.util.HashMap
|
||||
|
||||
open class WorldObject(
|
||||
val prototype: Registry.Entry<ObjectDefinition>,
|
||||
@ -48,6 +46,10 @@ open class WorldObject(
|
||||
}
|
||||
}
|
||||
|
||||
override fun readDelta(stream: ByteArrayList, interpolationTime: Double, isLegacy: Boolean) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun writeToNetwork(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
@ -74,7 +76,7 @@ open class WorldObject(
|
||||
inline val clientWorld get() = world as ClientWorld
|
||||
inline val serverWorld get() = world as ServerWorld
|
||||
inline val orientations get() = prototype.value.orientations
|
||||
protected val renderParamLocations = Object2ObjectOpenHashMap<String, () -> String?>()
|
||||
protected val renderParamLocations = HashMap<String, () -> String?>()
|
||||
private var frame = 0
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
@ -139,14 +141,14 @@ open class WorldObject(
|
||||
|
||||
override fun thinkShared() {
|
||||
super.thinkShared()
|
||||
flickerPeriod?.update(Starbound.TICK_TIME_ADVANCE, world.random)
|
||||
flickerPeriod?.update(Starbound.TIMESTEP, world.random)
|
||||
}
|
||||
|
||||
override fun thinkRemote() {
|
||||
val orientation = orientation
|
||||
|
||||
if (orientation != null) {
|
||||
frameTimer = (frameTimer + Starbound.TICK_TIME_ADVANCE) % orientation.animationCycle
|
||||
frameTimer = (frameTimer + Starbound.TIMESTEP) % orientation.animationCycle
|
||||
frame = (frameTimer / orientation.animationCycle * orientation.frames).toInt()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,77 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities.player
|
||||
|
||||
import ru.dbotthepony.kommons.io.StreamCodec
|
||||
import ru.dbotthepony.kstarbound.defs.actor.EssentialSlot
|
||||
import ru.dbotthepony.kstarbound.item.ItemStack
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
// same story as InventoryIndex
|
||||
sealed class HotbarIndex {
|
||||
abstract fun get(inventory: PlayerInventory): ItemStack
|
||||
abstract fun write(stream: DataOutputStream)
|
||||
|
||||
private object NoSelection : HotbarIndex() {
|
||||
override fun get(inventory: PlayerInventory): ItemStack {
|
||||
return ItemStack.EMPTY
|
||||
}
|
||||
|
||||
override fun write(stream: DataOutputStream) {
|
||||
stream.writeByte(0)
|
||||
}
|
||||
}
|
||||
|
||||
private class Essential(val index: EssentialSlot) : HotbarIndex() {
|
||||
override fun get(inventory: PlayerInventory): ItemStack {
|
||||
return inventory[index]
|
||||
}
|
||||
|
||||
override fun write(stream: DataOutputStream) {
|
||||
stream.writeByte(2)
|
||||
stream.writeByte(index.ordinal)
|
||||
}
|
||||
}
|
||||
|
||||
private class Index(val index: Int) : HotbarIndex() {
|
||||
init {
|
||||
require(index in 0 .. 255) { "Hotbar index out of range: $index"}
|
||||
}
|
||||
|
||||
override fun get(inventory: PlayerInventory): ItemStack {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun write(stream: DataOutputStream) {
|
||||
stream.writeByte(1)
|
||||
stream.writeByte(index)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC = StreamCodec.Impl(::read, { a, b -> b.write(a) })
|
||||
|
||||
private val indices = Array(256) { Index(it) }
|
||||
private val essentials = Array(EssentialSlot.entries.size) { Essential(EssentialSlot.entries[it]) }
|
||||
|
||||
fun nothing(): HotbarIndex {
|
||||
return NoSelection
|
||||
}
|
||||
|
||||
fun essential(slot: EssentialSlot): HotbarIndex {
|
||||
return essentials[slot.ordinal]
|
||||
}
|
||||
|
||||
fun slot(slot: Int): HotbarIndex {
|
||||
return indices[slot]
|
||||
}
|
||||
|
||||
fun read(stream: DataInputStream): HotbarIndex {
|
||||
return when (val type = stream.readUnsignedByte()) {
|
||||
0 -> NoSelection
|
||||
1 -> indices[stream.readUnsignedByte()]
|
||||
2 -> essentials[stream.readUnsignedByte()]
|
||||
else -> throw IndexOutOfBoundsException("Unknown HotbarIndex type: $type")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities.player
|
||||
|
||||
import ru.dbotthepony.kommons.guava.immutableList
|
||||
import ru.dbotthepony.kommons.guava.immutableMap
|
||||
import ru.dbotthepony.kommons.io.readVarInt
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeVarInt
|
||||
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||
import ru.dbotthepony.kstarbound.defs.actor.EquipmentSlot
|
||||
import ru.dbotthepony.kstarbound.item.ItemStack
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
// sigh
|
||||
// must do this to remain compatible with original network protocol
|
||||
sealed class InventoryIndex {
|
||||
abstract fun get(inventory: PlayerInventory): ItemStack
|
||||
abstract fun set(inventory: PlayerInventory, value: ItemStack)
|
||||
abstract fun write(stream: DataOutputStream, isLegacy: Boolean)
|
||||
|
||||
open fun isValid(inventory: PlayerInventory): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
private data class BagIndex(val bag: String, val slot: Int) : InventoryIndex() {
|
||||
init {
|
||||
require(slot >= 0) { "Negative slot index: $slot" }
|
||||
}
|
||||
|
||||
override fun get(inventory: PlayerInventory): ItemStack {
|
||||
return (inventory.bags[bag] ?: throw NoSuchElementException("No such bag $bag"))[slot]
|
||||
}
|
||||
|
||||
override fun set(inventory: PlayerInventory, value: ItemStack) {
|
||||
(inventory.bags[bag] ?: throw NoSuchElementException("No such bag $bag"))[slot] = value
|
||||
}
|
||||
|
||||
override fun isValid(inventory: PlayerInventory): Boolean {
|
||||
val bag = inventory.bags[bag] ?: return false
|
||||
return slot in 0 until bag.size
|
||||
}
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeByte(1)
|
||||
|
||||
stream.writeBinaryString(bag)
|
||||
|
||||
// god help us if new client joins original server
|
||||
// with mods which make inventory size absurd
|
||||
if (isLegacy)
|
||||
stream.writeByte(slot)
|
||||
else
|
||||
stream.writeVarInt(slot)
|
||||
}
|
||||
}
|
||||
|
||||
private data class EquipmentIndex(val slot: EquipmentSlot) : InventoryIndex() {
|
||||
override fun get(inventory: PlayerInventory): ItemStack {
|
||||
return inventory[slot]
|
||||
}
|
||||
|
||||
override fun set(inventory: PlayerInventory, value: ItemStack) {
|
||||
inventory[slot] = value
|
||||
}
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeByte(0)
|
||||
stream.writeByte(slot.ordinal)
|
||||
}
|
||||
}
|
||||
|
||||
private object Hand : InventoryIndex() {
|
||||
override fun get(inventory: PlayerInventory): ItemStack {
|
||||
return inventory.handSlot
|
||||
}
|
||||
|
||||
override fun set(inventory: PlayerInventory, value: ItemStack) {
|
||||
inventory.handSlot = value
|
||||
}
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeByte(2)
|
||||
}
|
||||
}
|
||||
|
||||
private object Trash : InventoryIndex() {
|
||||
override fun get(inventory: PlayerInventory): ItemStack {
|
||||
return inventory.trashSlot
|
||||
}
|
||||
|
||||
override fun set(inventory: PlayerInventory, value: ItemStack) {
|
||||
inventory.trashSlot = value
|
||||
}
|
||||
|
||||
override fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeByte(3)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val bagIndexCache by lazy {
|
||||
immutableMap {
|
||||
GlobalDefaults.player.inventory.itemBags.keys.forEach { n ->
|
||||
put(n, immutableList(256) {
|
||||
BagIndex(n, it)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val equipment = immutableList(EquipmentSlot.entries.size) {
|
||||
EquipmentIndex(EquipmentSlot.entries[it])
|
||||
}
|
||||
|
||||
fun hand(): InventoryIndex {
|
||||
return Hand
|
||||
}
|
||||
|
||||
fun trash(): InventoryIndex {
|
||||
return Trash
|
||||
}
|
||||
|
||||
fun equipment(slot: EquipmentSlot): InventoryIndex {
|
||||
return equipment[slot.ordinal]!!
|
||||
}
|
||||
|
||||
fun bag(name: String, index: Int): InventoryIndex {
|
||||
return bagIndexCache[name]?.getOrNull(index) ?: BagIndex(name, index)
|
||||
}
|
||||
|
||||
fun read(stream: DataInputStream, isLegacy: Boolean): InventoryIndex {
|
||||
return when (val type = stream.readUnsignedByte()) {
|
||||
0 -> equipment[stream.readUnsignedByte()]!!
|
||||
1 -> bag(stream.readInternedString(), if (isLegacy) stream.readUnsignedByte() else stream.readVarInt())
|
||||
2 -> hand()
|
||||
3 -> trash()
|
||||
else -> throw IllegalArgumentException("Unknown InventoryIndex type $type!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +1,31 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities
|
||||
package ru.dbotthepony.kstarbound.world.entities.player
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.kommons.vector.Vector2d
|
||||
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||
import ru.dbotthepony.kstarbound.defs.EntityDamageTeam
|
||||
import ru.dbotthepony.kstarbound.defs.actor.HumanoidData
|
||||
import ru.dbotthepony.kstarbound.defs.actor.HumanoidEmote
|
||||
import ru.dbotthepony.kstarbound.defs.actor.player.PlayerGamemode
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
import ru.dbotthepony.kstarbound.math.Interpolator
|
||||
import ru.dbotthepony.kstarbound.network.syncher.GroupElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||
import ru.dbotthepony.kstarbound.network.syncher.MasterElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedData
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedEnum
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedEnumExtraStupid
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedEnumStupid
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedEventCounter
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedString
|
||||
import ru.dbotthepony.kstarbound.world.entities.Animator
|
||||
import ru.dbotthepony.kstarbound.world.entities.HumanoidActorEntity
|
||||
import ru.dbotthepony.kstarbound.world.entities.StatusController
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.UUID
|
||||
import kotlin.properties.Delegates
|
||||
@ -38,11 +45,31 @@ class PlayerEntity() : HumanoidActorEntity("/") {
|
||||
LOUNGE("Lounge");
|
||||
}
|
||||
|
||||
constructor(data: DataInputStream, isLegacy: Boolean) : this() {
|
||||
uniqueID = data.readInternedString()
|
||||
println(data.readInternedString())
|
||||
gamemode = PlayerGamemode.entries[if (isLegacy) data.readInt() else data.readUnsignedByte()]
|
||||
humanoidData = HumanoidData.read(data, isLegacy)
|
||||
println(humanoidData)
|
||||
}
|
||||
|
||||
var gamemode = PlayerGamemode.CASUAL
|
||||
|
||||
override fun writeToNetwork(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
val networkGroup = MasterElement(GroupElement())
|
||||
val inventory = PlayerInventory()
|
||||
val songbook = Songbook(this)
|
||||
val effectAnimator = if (GlobalDefaults.player.effectsAnimator.value == null) Animator() else Animator(GlobalDefaults.player.effectsAnimator.value!!)
|
||||
override val statusController = StatusController(this, GlobalDefaults.player.statusControllerSettings)
|
||||
val techController = TechController(this)
|
||||
|
||||
val networkGroup = MasterElement(NetworkedGroup())
|
||||
|
||||
override fun readDelta(stream: ByteArrayList, interpolationTime: Double, isLegacy: Boolean) {
|
||||
networkGroup.read(stream, interpolationTime, isLegacy)
|
||||
}
|
||||
|
||||
var state by networkGroup.upstream.add(networkedEnum(State.IDLE))
|
||||
var shifting by networkGroup.upstream.add(networkedBoolean())
|
||||
@ -55,6 +82,18 @@ class PlayerEntity() : HumanoidActorEntity("/") {
|
||||
val newChatMessage = networkGroup.upstream.add(networkedEventCounter())
|
||||
var emote by networkGroup.upstream.add(networkedEnumExtraStupid(HumanoidEmote.IDLE))
|
||||
|
||||
init {
|
||||
networkGroup.upstream.add(inventory.networkGroup)
|
||||
networkGroup.upstream.add(toolsNetworkGroup)
|
||||
networkGroup.upstream.add(armorNetworkGroup)
|
||||
networkGroup.upstream.add(songbook.networkGroup)
|
||||
networkGroup.upstream.add(movement.networkGroup)
|
||||
networkGroup.upstream.add(effects.networkGroup)
|
||||
networkGroup.upstream.add(effectAnimator.networkGroup)
|
||||
networkGroup.upstream.add(statusController)
|
||||
networkGroup.upstream.add(techController.networkGroup)
|
||||
}
|
||||
|
||||
override val aimPosition: Vector2d
|
||||
get() = Vector2d(xAimPosition, yAimPosition)
|
||||
|
@ -0,0 +1,150 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities.player
|
||||
|
||||
import com.google.common.collect.ImmutableMap
|
||||
import it.unimi.dsi.fastutil.objects.Object2LongAVLTreeMap
|
||||
import ru.dbotthepony.kommons.arrays.Object2DArray
|
||||
import ru.dbotthepony.kommons.guava.immutableList
|
||||
import ru.dbotthepony.kommons.io.BinaryStringCodec
|
||||
import ru.dbotthepony.kommons.io.LongValueCodec
|
||||
import ru.dbotthepony.kommons.io.StreamCodec
|
||||
import ru.dbotthepony.kommons.io.UnsignedVarLongCodec
|
||||
import ru.dbotthepony.kommons.io.VarLongValueCodec
|
||||
import ru.dbotthepony.kommons.io.readKOptional
|
||||
import ru.dbotthepony.kommons.io.writeKOptional
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||
import ru.dbotthepony.kstarbound.defs.actor.EquipmentSlot
|
||||
import ru.dbotthepony.kstarbound.defs.actor.EssentialSlot
|
||||
import ru.dbotthepony.kstarbound.item.ItemStack
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
|
||||
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedData
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedItem
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedUnsignedInt
|
||||
import ru.dbotthepony.kstarbound.item.IContainer
|
||||
import ru.dbotthepony.kstarbound.network.syncher.InternedStringCodec
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedItemStack
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedMap
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
class PlayerInventory {
|
||||
inner class Bag(override val size: Int) : IContainer {
|
||||
val slots = immutableList(size) { networkedItem() }
|
||||
|
||||
override fun get(index: Int): ItemStack {
|
||||
return slots[index].get()
|
||||
}
|
||||
|
||||
override fun set(index: Int, value: ItemStack) {
|
||||
slots[index].accept(value)
|
||||
}
|
||||
}
|
||||
|
||||
data class HotbarSlot(val left: KOptional<InventoryIndex> = KOptional(), val right: KOptional<InventoryIndex> = KOptional()) {
|
||||
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readKOptional { InventoryIndex.read(stream, isLegacy) }, stream.readKOptional { InventoryIndex.read(stream, isLegacy) })
|
||||
|
||||
fun write(stream: DataOutputStream, isLegacy: Boolean) {
|
||||
stream.writeKOptional(left) { it.write(this, isLegacy) }
|
||||
stream.writeKOptional(right) { it.write(this, isLegacy) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC = nativeCodec(::HotbarSlot, HotbarSlot::write)
|
||||
val LEGACY_CODEC = legacyCodec(::HotbarSlot, HotbarSlot::write)
|
||||
}
|
||||
}
|
||||
|
||||
// here it gets interesting, original code is using List#sorted, which itself uses Star::sort,
|
||||
// which is just an alias for std::sort, and std::sort is ***not*** stable sort, meaning
|
||||
// if bags have same priority, PlayerInventory behavior becomes undefined
|
||||
// We, on the other hand, use stable sort provided by Java
|
||||
// (and if bags have the same priority they will appear in order they are defined in json)
|
||||
val bags: ImmutableMap<String, Bag> = GlobalDefaults.player.inventory.itemBags.entries
|
||||
.stream()
|
||||
.sorted { o1, o2 -> o1.value.priority.compareTo(o2.value.priority) }
|
||||
.map { it.key to Bag(it.value.size) }
|
||||
.collect(ImmutableMap.toImmutableMap({ it.first }, { it.second }))
|
||||
|
||||
val networkGroup = NetworkedGroup()
|
||||
|
||||
val equipment: ImmutableMap<EquipmentSlot, NetworkedItemStack> = EquipmentSlot.entries
|
||||
.stream()
|
||||
.map { it to networkedItem() }
|
||||
.collect(ImmutableMap.toImmutableMap({ it.first }, { it.second }))
|
||||
|
||||
val essentialSlots: ImmutableMap<EssentialSlot, NetworkedItemStack> = EssentialSlot.entries
|
||||
.stream()
|
||||
.map { it to networkedItem() }
|
||||
.collect(ImmutableMap.toImmutableMap({ it.first }, { it.second }))
|
||||
|
||||
private val currencies = NetworkedMap(keyCodec = InternedStringCodec, valueCodec = UnsignedVarLongCodec to LongValueCodec, isDumb = true)
|
||||
|
||||
init {
|
||||
// this is required for original engine
|
||||
// because otherwise
|
||||
// it will throw "element not found"
|
||||
// when trying to update currencies
|
||||
for (key in GlobalDefaults.currencies.keys) {
|
||||
currencies[key] = 0L
|
||||
}
|
||||
}
|
||||
|
||||
operator fun get(index: EquipmentSlot): ItemStack {
|
||||
return equipment[index]!!.get()
|
||||
}
|
||||
|
||||
operator fun set(index: EquipmentSlot, value: ItemStack) {
|
||||
equipment[index]!!.accept(value)
|
||||
}
|
||||
|
||||
operator fun get(index: EssentialSlot): ItemStack {
|
||||
return essentialSlots[index]!!.get()
|
||||
}
|
||||
|
||||
operator fun set(index: EssentialSlot, value: ItemStack) {
|
||||
essentialSlots[index]!!.accept(value)
|
||||
}
|
||||
|
||||
init {
|
||||
equipment.values.forEach {
|
||||
networkGroup.add(it)
|
||||
}
|
||||
|
||||
bags.values.forEach {
|
||||
it.slots.forEach {
|
||||
networkGroup.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// "swap slot" in original sources
|
||||
var handSlot by networkGroup.add(networkedItem())
|
||||
var trashSlot by networkGroup.add(networkedItem())
|
||||
|
||||
init {
|
||||
networkGroup.add(currencies)
|
||||
}
|
||||
|
||||
var hotbarGroup by networkGroup.add(networkedUnsignedInt())
|
||||
val hotbarSlots = Object2DArray(GlobalDefaults.player.inventory.customBarIndexes, GlobalDefaults.player.inventory.customBarGroups) { _, _ ->
|
||||
networkedData(HotbarSlot(), HotbarSlot.CODEC, HotbarSlot.LEGACY_CODEC)
|
||||
}
|
||||
|
||||
init {
|
||||
for (x in 0 until hotbarSlots.rows) {
|
||||
for (y in 0 until hotbarSlots.columns) {
|
||||
networkGroup.add(hotbarSlots[y, x])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hotbarIndex by networkGroup.add(networkedData(HotbarIndex.nothing(), HotbarIndex.CODEC))
|
||||
|
||||
init {
|
||||
essentialSlots.values.forEach { networkGroup.add(it) }
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities.player
|
||||
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedJsonElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedSignedInt
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedString
|
||||
import java.io.File
|
||||
|
||||
// as separate class because it contains plentiful of internal logic
|
||||
// (avoids pollution in main class)
|
||||
class Songbook(val player: PlayerEntity) {
|
||||
val networkGroup = NetworkedGroup()
|
||||
var song by networkGroup.add(networkedJsonElement())
|
||||
var songEpoch by networkGroup.add(networkedSignedInt())
|
||||
var timeSource by networkGroup.add(networkedString())
|
||||
var isActive by networkGroup.add(networkedBoolean())
|
||||
var instrument by networkGroup.add(networkedString())
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package ru.dbotthepony.kstarbound.world.entities.player
|
||||
|
||||
import ru.dbotthepony.kommons.io.IntValueCodec
|
||||
import ru.dbotthepony.kommons.io.StreamCodec
|
||||
import ru.dbotthepony.kommons.io.koptional
|
||||
import ru.dbotthepony.kommons.io.map
|
||||
import ru.dbotthepony.kommons.io.readKOptional
|
||||
import ru.dbotthepony.kommons.io.writeBinaryString
|
||||
import ru.dbotthepony.kommons.io.writeKOptional
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.kstarbound.io.readInternedString
|
||||
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedDynamicGroup
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedData
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedString
|
||||
import ru.dbotthepony.kstarbound.world.entities.Animator
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
|
||||
// i am very disappointed.
|
||||
class TechController(val player: PlayerEntity) {
|
||||
val networkGroup = NetworkedGroup()
|
||||
|
||||
private val animators = NetworkedDynamicGroup(::TechAnimator, { it }).also { networkGroup.add(it) }
|
||||
|
||||
var state by networkedData(KOptional(), STATE_CODEC, STATE_CODEC_LEGACY).also { networkGroup.add(it) }
|
||||
private set
|
||||
var directives by networkedString().also { networkGroup.add(it) }
|
||||
private set
|
||||
var xOffset by networkedFixedPoint(0.003125).also { networkGroup.add(it) }
|
||||
private set
|
||||
var yOffset by networkedFixedPoint(0.003125).also { networkGroup.add(it) }
|
||||
private set
|
||||
var isHidden by networkedBoolean().also { networkGroup.add(it) }
|
||||
private set
|
||||
var isToolUsageSuppressed by networkedBoolean().also { networkGroup.add(it) }
|
||||
private set
|
||||
|
||||
enum class State(override val jsonName: String) : IStringSerializable {
|
||||
STAND("Stand"),
|
||||
FLY("Fly"),
|
||||
FALL("Fall"),
|
||||
SIT("Sit"),
|
||||
LAY("Lay"),
|
||||
DUCK("Duck"),
|
||||
WALK("Walk"),
|
||||
RUN("Run"),
|
||||
SWIM("Swim");
|
||||
}
|
||||
|
||||
private class TechAnimator(var config: KOptional<String> = KOptional()) : NetworkedElement.Passthrough() {
|
||||
val networkGroup = NetworkedGroup()
|
||||
|
||||
var animator: Animator = Animator().also { networkGroup.add(it.networkGroup) }
|
||||
private set(value) {
|
||||
networkGroup.replace(field.networkGroup, value.networkGroup)
|
||||
field = value
|
||||
}
|
||||
|
||||
var isVisible by networkedBoolean().also { networkGroup.add(it) }
|
||||
|
||||
override val parentElement: NetworkedElement
|
||||
get() = networkGroup
|
||||
|
||||
override fun readInitial(data: DataInputStream, isLegacy: Boolean) {
|
||||
val old = config
|
||||
config = data.readKOptional { readInternedString() }
|
||||
|
||||
if (old != config)
|
||||
animator = config.map { Animator.load(it) }.orElse { Animator() }
|
||||
|
||||
super.readInitial(data, isLegacy)
|
||||
}
|
||||
|
||||
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
|
||||
data.writeKOptional(config) { writeBinaryString(it) }
|
||||
super.writeInitial(data, isLegacy)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val STATE_CODEC = StreamCodec.Enum(State::class.java).koptional()
|
||||
private val STATE_CODEC_LEGACY = IntValueCodec.map({ State.entries[this] }, { ordinal }).koptional()
|
||||
}
|
||||
}
|
@ -26,6 +26,8 @@ import ru.dbotthepony.kommons.io.writeCollection
|
||||
import ru.dbotthepony.kommons.io.writeStruct2d
|
||||
import ru.dbotthepony.kommons.io.writeStruct2f
|
||||
import ru.dbotthepony.kstarbound.json.listAdapter
|
||||
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
|
||||
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import kotlin.math.absoluteValue
|
||||
@ -356,8 +358,8 @@ class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: Imm
|
||||
}
|
||||
|
||||
companion object : TypeAdapterFactory {
|
||||
val CODEC = StreamCodec.Impl({ read(it, false) }, { a, b -> b.write(a, false) })
|
||||
val LEGACY_CODEC = StreamCodec.Impl({ read(it, true) }, { a, b -> b.write(a, true) })
|
||||
val CODEC = nativeCodec(::read, Poly::write)
|
||||
val LEGACY_CODEC = legacyCodec(::read, Poly::write)
|
||||
|
||||
val EMPTY = Poly(ImmutableList.of(), ImmutableList.of())
|
||||
|
||||
|
@ -9,8 +9,7 @@ import ru.dbotthepony.kommons.io.Vector2fCodec
|
||||
import ru.dbotthepony.kommons.vector.Vector2f
|
||||
import ru.dbotthepony.kstarbound.network.syncher.BasicNetworkedElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.EventCounterElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.FloatingNetworkedElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.GroupElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
||||
import ru.dbotthepony.kstarbound.network.syncher.MasterElement
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
|
||||
import ru.dbotthepony.kstarbound.network.syncher.networkedDouble
|
||||
@ -45,7 +44,7 @@ object NetworkedElementTests {
|
||||
val masterField10 = EventCounterElement()
|
||||
val masterField11 = BasicNetworkedElement(Vector2f.ZERO, Vector2fCodec)
|
||||
|
||||
val master = MasterElement(GroupElement())
|
||||
val master = MasterElement(NetworkedGroup())
|
||||
master.upstream.add(masterField1)
|
||||
master.upstream.add(masterField2)
|
||||
master.upstream.add(masterField3)
|
||||
@ -94,7 +93,7 @@ object NetworkedElementTests {
|
||||
val slaveField10 = EventCounterElement()
|
||||
val slaveField11 = BasicNetworkedElement(Vector2f.ZERO, Vector2fCodec)
|
||||
|
||||
val slave = MasterElement(GroupElement())
|
||||
val slave = MasterElement(NetworkedGroup())
|
||||
slave.upstream.add(slaveField1)
|
||||
slave.upstream.add(slaveField2)
|
||||
slave.upstream.add(slaveField3)
|
||||
|
Loading…
Reference in New Issue
Block a user