diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JsonDriven.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JsonDriven.kt deleted file mode 100644 index 3f7df47d..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JsonDriven.kt +++ /dev/null @@ -1,157 +0,0 @@ -package ru.dbotthepony.kstarbound.defs - -import com.google.gson.JsonElement -import com.google.gson.JsonNull -import com.google.gson.JsonObject -import com.google.gson.TypeAdapter -import com.google.gson.reflect.TypeToken -import ru.dbotthepony.kommons.util.Either -import ru.dbotthepony.kstarbound.Starbound -import ru.dbotthepony.kstarbound.util.AssetPathStack -import ru.dbotthepony.kommons.util.Delegate -import ru.dbotthepony.kstarbound.json.JsonPath -import java.util.function.Function -import java.util.function.Supplier -import kotlin.properties.Delegates -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty -import kotlin.reflect.javaType - -/** - * Base class for instances which can be mutated by altering underlying JSON - */ -abstract class JsonDriven(val path: String) { - private val delegates = ArrayList>() - private val lazies = ArrayList>() - - /** - * [JsonObject]s which define behavior of properties - */ - abstract fun lookupProperty(path: JsonPath, orElse: () -> JsonElement): JsonElement - - fun lookupProperty(key: JsonPath): JsonElement { - return lookupProperty(key) { JsonNull.INSTANCE } - } - - fun setProperty(key: JsonPath, value: JsonElement) { - setProperty0(key, value) - invalidate() - } - - protected abstract fun setProperty0(key: JsonPath, value: JsonElement) - - protected open fun invalidate() { - delegates.forEach { it.invalidate() } - lazies.forEach { it.invalidate() } - } - - inner class LazyData(private val initializer: () -> T) : Lazy { - init { - lazies.add(this) - } - - private var _value: Any? = mark - - override val value: T get() { - var value = _value - - if (value !== mark) { - return value as T - } - - value = initializer.invoke() - _value = value - return value - } - - override fun isInitialized(): Boolean { - return _value !== mark - } - - fun invalidate() { - _value = mark - } - } - - inner class Property( - val name: JsonPath, - val default: Either, JsonElement>? = null, - private var adapter: TypeAdapter? = null, - ) : Delegate, ReadWriteProperty { - constructor(name: JsonPath, default: T, adapter: TypeAdapter? = null) : this(name, Either.left(Supplier { default }), adapter) - constructor(name: JsonPath, default: Supplier, adapter: TypeAdapter? = null) : this(name, Either.left(default), adapter) - constructor(name: JsonPath, default: JsonElement, adapter: TypeAdapter? = null) : this(name, Either.right(default), adapter) - - init { - delegates.add(this) - } - - private var value: Supplier by Delegates.notNull() - - private fun compute(): T { - val value = lookupProperty(name) - - if (value.isJsonNull) { - if (default == null) { - throw NoSuchElementException("No json value present at '$name', and no default value was provided") - } else if (default.isLeft) { - return default.left().get() - } else { - AssetPathStack(this@JsonDriven.path) { - return adapter!!.fromJsonTree(default.right()) - } - } - } else { - AssetPathStack(this@JsonDriven.path) { - return adapter!!.fromJsonTree(value) - } - } - } - - fun invalidate() { - value = Supplier { - val new = compute() - this.value = Supplier { new } - new - } - } - - init { - invalidate() - } - - override fun get(): T { - return value.get() - } - - override fun accept(t: T) { - AssetPathStack(this@JsonDriven.path) { - setProperty0(name, adapter!!.toJsonTree(t)) - } - - value = Supplier { t } - } - - @OptIn(ExperimentalStdlibApi::class) - override fun getValue(thisRef: Any?, property: KProperty<*>): T { - if (adapter == null) { - adapter = Starbound.gson.getAdapter(TypeToken.get(property.returnType.javaType)) as TypeAdapter - } - - return value.get() - } - - @OptIn(ExperimentalStdlibApi::class) - override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { - if (adapter == null) { - adapter = Starbound.gson.getAdapter(TypeToken.get(property.returnType.javaType)) as TypeAdapter - } - - return accept(value) - } - } - - companion object { - private val mark = Any() - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonWorld.kt index e426260f..60eb9066 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/dungeon/DungeonWorld.kt @@ -493,7 +493,7 @@ class DungeonWorld(val parent: ServerWorld, val random: RandomGenerator, val mar val orientation = obj!!.config.value.findValidOrientation(parent, obj.tilePosition, direction) if (orientation != -1) { - obj.setOrientation(orientation) + obj.orientationIndex = orientation.toLong() obj.joinWorld(parent) } else { LOGGER.error("Tried to place object ${obj.config.key} at ${obj.tilePosition}, but it can't be placed there!") diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/LoungeControl.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/LoungeControl.kt new file mode 100644 index 00000000..5ed95862 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/LoungeControl.kt @@ -0,0 +1,17 @@ +package ru.dbotthepony.kstarbound.defs.`object` + +import ru.dbotthepony.kstarbound.json.builder.IStringSerializable + +// int32_t +enum class LoungeControl(override val jsonName: String) : IStringSerializable { + LEFT("Left"), + RIGHT("Right"), + DOWN("Down"), + UP("Up"), + JUMP("Jump"), + PRIMARY_FIRE("PrimaryFire"), + ALT_FIRE("AltFire"), + SPECIAL1("Special1"), + SPECIAL2("Special2"), + SPECIAL3("Special3"); +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/LoungeOrientation.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/LoungeOrientation.kt new file mode 100644 index 00000000..e518b6bd --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/object/LoungeOrientation.kt @@ -0,0 +1,11 @@ +package ru.dbotthepony.kstarbound.defs.`object` + +import ru.dbotthepony.kstarbound.json.builder.IStringSerializable + +// int32_t +enum class LoungeOrientation(override val jsonName: String) : IStringSerializable { + NONE("none"), + SIT("sit"), + LAY("lay"), + STAND("stand"); +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldObjectBindings.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldObjectBindings.kt index 50340291..5c06807e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldObjectBindings.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/bindings/WorldObjectBindings.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.kstarbound.lua.bindings +import com.google.gson.JsonNull import com.google.gson.JsonPrimitive import org.classdump.luna.ByteString import org.classdump.luna.Table @@ -30,7 +31,7 @@ fun provideWorldObjectBindings(self: WorldObject, lua: LuaEnvironment) { config["getParameter"] = luaFunction { name: ByteString, default: Any? -> val path = JsonPath.query(name.decode()) - val find = self.lookupProperty(path) + val find = self.lookupProperty(path) { JsonNull.INSTANCE } if (find.isJsonNull) { returnBuffer.setTo(default) @@ -206,7 +207,7 @@ fun provideWorldObjectBindings(self: WorldObject, lua: LuaEnvironment) { } table["setMaterialSpaces"] = luaFunction { spaces: Table -> - self.networkedMaterialSpaces.clear() + self.declaredMaterialSpaces.clear() for ((i, pair) in spaces) { pair as Table @@ -214,10 +215,8 @@ fun provideWorldObjectBindings(self: WorldObject, lua: LuaEnvironment) { val position = toVector2i(indexNoYield(pair, 1L) ?: throw NullPointerException("invalid space at $i")) val material = indexNoYield(pair, 2L) as? ByteString ?: throw NullPointerException("invalid space at $i") - self.networkedMaterialSpaces.add(position to Registries.tiles.ref(material.decode())) + self.declaredMaterialSpaces.add(position to Registries.tiles.ref(material.decode())) } - - self.markSpacesDirty() } table["setDamageSources"] = luaFunction { sources: Table? -> diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/ManualLazy.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/ManualLazy.kt new file mode 100644 index 00000000..70e83897 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/ManualLazy.kt @@ -0,0 +1,16 @@ +package ru.dbotthepony.kstarbound.util + +class ManualLazy(private val initializer: () -> T) : Lazy { + private var parent: Lazy = lazy(initializer) + + override val value: T + get() = parent.value + + override fun isInitialized(): Boolean { + return parent.isInitialized() + } + + fun invalidate() { + parent = lazy(initializer) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Utils.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Utils.kt index 780de28d..ec0f7e11 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Utils.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Utils.kt @@ -2,8 +2,11 @@ package ru.dbotthepony.kstarbound.util import com.google.gson.JsonElement import ru.dbotthepony.kstarbound.Starbound +import ru.dbotthepony.kstarbound.json.builder.IStringSerializable import java.util.* import java.util.stream.Stream +import kotlin.NoSuchElementException +import kotlin.collections.Collection fun String.sbIntern(): String { return Starbound.STRINGS.intern(this) @@ -65,3 +68,7 @@ fun , T : Any> Stream>.binnedChoice(value: C): Opti .filter { it.first <= value } .findFirst().map { it.second } } + +fun Collection.valueOf(value: String): E { + return firstOrNull { it.match(value) } ?: throw NoSuchElementException("$value is not a valid element") +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Direction.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Direction.kt index a0ec124a..29eafa1d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Direction.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Direction.kt @@ -4,12 +4,12 @@ import ru.dbotthepony.kommons.io.StreamCodec import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kstarbound.json.builder.IStringSerializable -enum class Direction(val normal: Vector2d, override val jsonName: String, val luaValue: Long) : IStringSerializable { - LEFT(Vector2d.NEGATIVE_X, "left", -1L) { +enum class Direction(val normal: Vector2d, override val jsonName: String, val luaValue: Long, val isRight: Boolean, val isLeft: Boolean) : IStringSerializable { + LEFT(Vector2d.NEGATIVE_X, "left", -1L, false, true) { override val opposite: Direction get() = RIGHT }, - RIGHT(Vector2d.POSITIVE_X, "right", 1L) { + RIGHT(Vector2d.POSITIVE_X, "right", 1L, true, false) { override val opposite: Direction get() = LEFT }; diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/AbstractEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/AbstractEntity.kt index f8f39a3d..89c72f86 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/AbstractEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/AbstractEntity.kt @@ -14,8 +14,6 @@ import ru.dbotthepony.kstarbound.defs.EntityDamageTeam import ru.dbotthepony.kstarbound.defs.EntityType import ru.dbotthepony.kstarbound.defs.InteractAction import ru.dbotthepony.kstarbound.defs.InteractRequest -import ru.dbotthepony.kstarbound.defs.JsonDriven -import ru.dbotthepony.kstarbound.defs.`object`.DamageTeam import ru.dbotthepony.kstarbound.network.syncher.InternedStringCodec import ru.dbotthepony.kstarbound.network.syncher.MasterElement import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup @@ -28,7 +26,7 @@ import ru.dbotthepony.kstarbound.world.World import java.io.DataOutputStream import java.util.function.Consumer -abstract class AbstractEntity(path: String) : JsonDriven(path), Comparable { +abstract class AbstractEntity : Comparable { abstract val position: Vector2d var entityID: Int = 0 diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ActorEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ActorEntity.kt index c7375b1c..973469b7 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ActorEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ActorEntity.kt @@ -3,7 +3,7 @@ package ru.dbotthepony.kstarbound.world.entities /** * Monsters, NPCs, Players */ -abstract class ActorEntity(path: String) : DynamicEntity(path) { +abstract class ActorEntity() : DynamicEntity() { final override val movement: ActorMovementController = ActorMovementController() abstract val statusController: StatusController } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/Animator.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/Animator.kt index 581cce93..48a90d26 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/Animator.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/Animator.kt @@ -192,7 +192,6 @@ class Animator() { private var activeStateChanged = false private var frameChanged = false - private var firstTime = false var activeState: AnimatedPartsDefinition.StateType.State? = null set(value) { @@ -226,14 +225,10 @@ class Animator() { fun set(state: String, alwaysStart: Boolean): Boolean { val getState = states[state] ?: return false - // allow to reset active state if calling for first time - // without this client and server will disagree what state is active - // if there default state present - if (activeState != getState || alwaysStart || firstTime) { + if (activeState?.name != getState.name || alwaysStart) { activeState = getState timer = 0.0 startedEvent.trigger() - firstTime = false return true } @@ -304,8 +299,6 @@ class Animator() { } fun finishAnimations() { - firstTime = true - while (true) { val active = activeState ?: break diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt index f306b0a2..0f5cc16b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/DynamicEntity.kt @@ -11,7 +11,7 @@ import ru.dbotthepony.kstarbound.world.World /** * Entities with dynamics (Player, Drops, Projectiles, NPCs, etc) */ -abstract class DynamicEntity(path: String) : AbstractEntity(path) { +abstract class DynamicEntity() : AbstractEntity() { private var forceChunkRepos = false override var position diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/HumanoidActorEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/HumanoidActorEntity.kt index 12f119a0..3edab08a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/HumanoidActorEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/HumanoidActorEntity.kt @@ -16,7 +16,7 @@ import java.io.DataInputStream /** * Players and NPCs */ -abstract class HumanoidActorEntity(path: String) : ActorEntity(path) { +abstract class HumanoidActorEntity() : ActorEntity() { abstract val aimPosition: Vector2d val effects = EffectEmitter(this) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemDropEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemDropEntity.kt index 5e3ff3a9..dc36c59f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemDropEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/ItemDropEntity.kt @@ -26,7 +26,7 @@ import java.io.DataOutputStream import java.util.function.Predicate import kotlin.math.min -class ItemDropEntity() : DynamicEntity("/") { +class ItemDropEntity() : DynamicEntity() { // int32_t, but networked as proper enum // костыль (именно DEAD состояние), но требуют оригинальные клиенты // мда. @@ -93,14 +93,6 @@ class ItemDropEntity() : DynamicEntity("/") { state = State.INTANGIBLE } - override fun lookupProperty(path: JsonPath, orElse: () -> JsonElement): JsonElement { - TODO("Not yet implemented") - } - - override fun setProperty0(key: JsonPath, value: JsonElement) { - TODO("Not yet implemented") - } - override val type: EntityType get() = EntityType.ITEM_DROP diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/player/PlayerEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/player/PlayerEntity.kt index 59eb6ba6..edfdcb5a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/player/PlayerEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/player/PlayerEntity.kt @@ -32,7 +32,7 @@ import java.io.DataOutputStream import java.util.UUID import kotlin.properties.Delegates -class PlayerEntity() : HumanoidActorEntity("/") { +class PlayerEntity() : HumanoidActorEntity() { enum class State(override val jsonName: String) : IStringSerializable { IDLE("Idle"), WALK("Walk"), @@ -115,12 +115,4 @@ class PlayerEntity() : HumanoidActorEntity("/") { var uuid: UUID by Delegates.notNull() private set - - override fun lookupProperty(path: JsonPath, orElse: () -> JsonElement): JsonElement { - TODO("Not yet implemented") - } - - override fun setProperty0(key: JsonPath, value: JsonElement) { - TODO("Not yet implemented") - } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/LoungeableObject.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/LoungeableObject.kt index d483c4f1..8d83a2d4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/LoungeableObject.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/LoungeableObject.kt @@ -1,10 +1,107 @@ package ru.dbotthepony.kstarbound.world.entities.tile +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableSet +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +import com.google.gson.reflect.TypeToken +import it.unimi.dsi.fastutil.objects.ObjectArraySet +import ru.dbotthepony.kommons.util.Either +import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kstarbound.Registry +import ru.dbotthepony.kstarbound.Starbound +import ru.dbotthepony.kstarbound.defs.InteractAction +import ru.dbotthepony.kstarbound.defs.InteractRequest +import ru.dbotthepony.kstarbound.defs.actor.StatModifier +import ru.dbotthepony.kstarbound.defs.`object`.LoungeOrientation import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition +import ru.dbotthepony.kstarbound.util.coalesceNull +import ru.dbotthepony.kstarbound.util.valueOf +import ru.dbotthepony.kstarbound.world.Direction +import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNIT +import java.lang.Math.toRadians class LoungeableObject(config: Registry.Entry) : WorldObject(config) { init { isInteractive = true } + + private val sitPositions = ArrayList() + private var sitFlipDirection = false + private var sitOrientation = LoungeOrientation.NONE + private var sitAngle = 0.0 + private var sitCoverImage = "" + private var sitFlipImages = false + private val sitStatusEffects = ObjectArraySet>() + private val sitEffectEmitters = ObjectArraySet() + private var sitEmote: String? = null + private var sitDance: String? = null + private var sitArmorCosmeticOverrides: JsonObject = JsonObject() + private var sitCursorOverride: String? = null + + private fun updateSitParams() { + orientation ?: return + + sitPositions.clear() + val sitPosition = lookupProperty("sitPosition") + val sitPositions = lookupProperty("sitPositions") + + if (!sitPosition.isJsonNull) { + this.sitPositions.add(vectors.fromJsonTree(sitPosition) / PIXELS_IN_STARBOUND_UNIT) + } else if (!sitPositions.isJsonNull) { + vectorsList.fromJsonTree(sitPositions).forEach { this.sitPositions.add(it / PIXELS_IN_STARBOUND_UNIT) } + } + + sitFlipDirection = lookupProperty("sitFlipDirection") { JsonPrimitive(false) }.asBoolean + sitOrientation = LoungeOrientation.entries.valueOf(lookupProperty("sitOrientation") { JsonPrimitive("sit") }.asString) + sitAngle = toRadians(lookupProperty("sitAngle") { JsonPrimitive(0.0) }.asDouble) + sitCoverImage = lookupProperty("sitCoverImage") { JsonPrimitive("") }.asString + sitFlipImages = lookupProperty("flipImages") { JsonPrimitive(false) }.asBoolean + + sitStatusEffects.clear() + sitStatusEffects.addAll(statusEffects.fromJsonTree(lookupProperty("sitStatusEffects") { JsonArray() })) + + sitEffectEmitters.clear() + lookupProperty("sitEffectEmitters") { JsonArray() }.asJsonArray.forEach { sitEffectEmitters.add(it.asString) } + sitEmote = lookupProperty("sitEmote").coalesceNull?.asString + sitDance = lookupProperty("sitDance").coalesceNull?.asString + sitArmorCosmeticOverrides = lookupProperty("sitArmorCosmeticOverrides") { JsonObject() } as JsonObject + sitCursorOverride = lookupProperty("sitCursorOverride").coalesceNull?.asString + } + + override fun parametersUpdated() { + super.parametersUpdated() + updateSitParams() + } + + override fun orientationUpdated() { + super.orientationUpdated() + updateSitParams() + } + + override fun interact(request: InteractRequest): InteractAction { + val upstream = super.interact(request) + + if (upstream.type == InteractAction.Type.NONE && sitPositions.isNotEmpty()) { + val interactOffset = if (direction.isRight) position - request.targetPos else request.targetPos - position + var index = 0 + + for (i in 1 until sitPositions.size) { + if ((sitPositions[i] + interactOffset).length < (sitPositions[index] + interactOffset).length) { + index = i + } + } + + return InteractAction(InteractAction.Type.SIT_DOWN, entityID, JsonPrimitive(index)) + } + + return upstream + } + + companion object { + private val vectors by lazy { Starbound.gson.getAdapter(Vector2d::class.java) } + private val vectorsList by lazy { Starbound.gson.getAdapter(object : TypeToken>() {}) } + private val statusEffects by lazy { Starbound.gson.getAdapter(object : TypeToken>>() {}) } + } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/TileEntity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/TileEntity.kt index 4f54926f..4a453707 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/TileEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/TileEntity.kt @@ -22,7 +22,7 @@ import ru.dbotthepony.kstarbound.world.entities.AbstractEntity /** * (Hopefully) Static world entities (Plants, Objects, etc), which reside on cell grid */ -abstract class TileEntity(path: String) : AbstractEntity(path) { +abstract class TileEntity : AbstractEntity() { protected val xTilePositionNet = networkedSignedInt() protected val yTilePositionNet = networkedSignedInt() @@ -93,7 +93,7 @@ abstract class TileEntity(path: String) : AbstractEntity(path) { private fun updateSpatialPosition() { val spatialEntry = spatialEntry ?: return - spatialEntry.fixture.move(metaBoundingBox + position) + spatialEntry.fixture.move(metaBoundingBox) } protected open fun onPositionUpdated() { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/WorldObject.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/WorldObject.kt index e3266490..3b5b6ded 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/WorldObject.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/tile/WorldObject.kt @@ -21,7 +21,6 @@ import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.render.LayeredRenderer import ru.dbotthepony.kstarbound.defs.Drawable -import ru.dbotthepony.kstarbound.defs.image.SpriteReference import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation import ru.dbotthepony.kommons.gson.get @@ -75,6 +74,7 @@ import ru.dbotthepony.kstarbound.lua.set import ru.dbotthepony.kstarbound.lua.tableOf import ru.dbotthepony.kstarbound.lua.toJson import ru.dbotthepony.kstarbound.lua.toJsonFromLua +import ru.dbotthepony.kstarbound.util.ManualLazy import ru.dbotthepony.kstarbound.util.asStringOrNull import ru.dbotthepony.kstarbound.world.Direction import ru.dbotthepony.kstarbound.world.LightCalculator @@ -87,7 +87,7 @@ import ru.dbotthepony.kstarbound.world.entities.wire.WireConnection import java.io.DataOutputStream import java.util.HashMap -open class WorldObject(val config: Registry.Entry) : TileEntity(config.file?.computeDirectory() ?: "/") { +open class WorldObject(val config: Registry.Entry) : TileEntity() { open fun deserialize(data: JsonObject) { direction = data.get("direction", directions) { Direction.LEFT } orientationIndex = data.get("orientationIndex", -1).toLong() @@ -134,31 +134,43 @@ open class WorldObject(val config: Registry.Entry) : TileEntit return into } - override val metaBoundingBox: AABB by LazyData { - orientation?.metaBoundBox ?: orientation?.let { AABB(it.boundingBox.mins.toDoubleVector() + Vector2d.NEGATIVE_XY, it.boundingBox.maxs.toDoubleVector() + Vector2d(2.0, 2.0)) } ?: AABB.ZERO - } + protected val orientationLazies = ArrayList>() + protected val parametersLazies = ArrayList>() + protected val spacesLazies = ArrayList>() + + override val metaBoundingBox: AABB by ManualLazy { + (orientation?.metaBoundBox ?: orientation?.let { AABB(it.boundingBox.mins.toDoubleVector() + Vector2d.NEGATIVE_XY, it.boundingBox.maxs.toDoubleVector() + Vector2d(2.0, 2.0)) } ?: AABB.ZERO) + position + }.also { orientationLazies.add(it); spacesLazies.add(it) } val parameters = NetworkedMap(InternedStringCodec, JsonElementCodec).also { networkGroup.upstream.add(it) - it.addListener { invalidate() } + it.addListener { parametersUpdated() } } val orientation: ObjectOrientation? get() { return config.value.orientations.getOrNull(orientationIndex.toInt()) } - protected val mergedJson = LazyData { + protected val mergedJson = ManualLazy { val orientation = orientation if (orientation == null) { - mergeJson(config.jsonObject.deepCopy(), parameters) + mergeJson(config.jsonObject.deepCopy(), parameters) as JsonObject } else { - mergeJson(mergeJson(config.jsonObject.deepCopy(), orientation.json), parameters) + mergeJson(mergeJson(config.jsonObject.deepCopy(), orientation.json), parameters) as JsonObject } + }.also { orientationLazies.add(it); parametersLazies.add(it) } + + fun lookupProperty(path: JsonPath, orElse: () -> JsonElement): JsonElement { + return path.get(mergedJson.value, orElse) } - final override fun lookupProperty(path: JsonPath, orElse: () -> JsonElement): JsonElement { - return path.get(mergedJson.value, orElse) + fun lookupProperty(path: String, orElse: () -> JsonElement): JsonElement { + return mergedJson.value[path] ?: orElse.invoke() + } + + fun lookupProperty(path: String): JsonElement { + return mergedJson.value[path] ?: JsonNull.INSTANCE } init { @@ -166,36 +178,32 @@ open class WorldObject(val config: Registry.Entry) : TileEntit } var isInteractive by networkedBoolean().also { networkGroup.upstream.add(it) } - val networkedMaterialSpaces = NetworkedList(materialSpacesCodec, materialSpacesCodecLegacy).also { networkGroup.upstream.add(it) } + val declaredMaterialSpaces = NetworkedList(materialSpacesCodec, materialSpacesCodecLegacy).also { networkGroup.upstream.add(it) } - private val materialSpaces0 = LazyData { - networkedMaterialSpaces.map { (it.first + tilePosition) to it.second } - } + private val materialSpaces0 = ManualLazy { + declaredMaterialSpaces.map { world.geometry.wrap(it.first + tilePosition) to it.second } + }.also { orientationLazies.add(it); spacesLazies.add(it) } override val materialSpaces by materialSpaces0 - private val occupySpaces0 = LazyData { + override val occupySpaces: ImmutableSet by ManualLazy { (orientation?.occupySpaces ?: setOf()).stream().map { world.geometry.wrap(it + tilePosition) }.collect(ImmutableSet.toImmutableSet()) - } - - override val occupySpaces: ImmutableSet by occupySpaces0 + }.also { orientationLazies.add(it); spacesLazies.add(it) } override val roots: Set get() = setOf() - private val anchorPositions0 = LazyData { + val anchorPositions: ImmutableSet by ManualLazy { immutableSet { orientation?.anchors?.forEach { accept(it.position + tilePosition) } } - } - - val anchorPositions: ImmutableSet by anchorPositions0 + }.also { orientationLazies.add(it); spacesLazies.add(it) } init { networkGroup.upstream.add(xTilePositionNet) networkGroup.upstream.add(yTilePositionNet) - networkedMaterialSpaces.addListener { + declaredMaterialSpaces.addListener { materialSpaces0.invalidate() markSpacesDirty() } @@ -204,9 +212,9 @@ open class WorldObject(val config: Registry.Entry) : TileEntit var direction by networkedEnum(Direction.LEFT).also { networkGroup.upstream.add(it) } var health by networkedFloat(config.value.health).also { networkGroup.upstream.add(it) } - private var orientationIndex by networkedPointer(-1L).also { + var orientationIndex by networkedPointer(-1L).also { networkGroup.upstream.add(it) - it.addListener(Runnable { invalidate() }) + it.addListener(Runnable { orientationUpdated() }) } private val networkedRenderKeys = NetworkedMap(InternedStringCodec, InternedStringCodec).also { networkGroup.upstream.add(it) } @@ -230,13 +238,13 @@ open class WorldObject(val config: Registry.Entry) : TileEntit } } - val inputNodes: ImmutableList = lookupProperty(JsonPath("inputNodes")) { JsonArray() } + val inputNodes: ImmutableList = lookupProperty("inputNodes") { JsonArray() } .asJsonArray .stream() .map { WireNode(vectors.fromJsonTree(it), true) } .collect(ImmutableList.toImmutableList()) - val outputNodes: ImmutableList = lookupProperty(JsonPath("outputNodes")) { JsonArray() } + val outputNodes: ImmutableList = lookupProperty("outputNodes") { JsonArray() } .asJsonArray .stream() .map { WireNode(vectors.fromJsonTree(it), false) } @@ -275,17 +283,13 @@ open class WorldObject(val config: Registry.Entry) : TileEntit lua.globals["storage"] = lua.newTable() } - val unbreakable by LazyData { - lookupProperty(JsonPath("unbreakable")) { JsonPrimitive(false) }.asBoolean - } + val unbreakable by ManualLazy { + lookupProperty("unbreakable") { JsonPrimitive(false) }.asBoolean + }.also { parametersLazies.add(it) } override val type: EntityType get() = EntityType.OBJECT - override fun setProperty0(key: JsonPath, value: JsonElement) { - TODO("Not yet implemented") - } - override fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) { stream.writeBinaryString(config.key) stream.writeJsonElement(JsonObject().also { @@ -296,25 +300,10 @@ open class WorldObject(val config: Registry.Entry) : TileEntit } private var frameTimer = 0.0 - private var frame = 0 - set(value) { - if (field != value) { - field = value - drawablesCache.invalidate() - } - } val flickerPeriod = config.value.flickerPeriod?.copy() - // - // json driven properties - // - var color: TileColor by Property(JsonPath("color"), TileColor.DEFAULT) - - var imagePosition: Vector2i by Property(JsonPath("imagePosition"), Vector2i.ZERO) - var animationPosition: Vector2i by Property(JsonPath("animationPosition"), Vector2i.ZERO) - // ???????? val volumeBoundingBox: AABB get() { val orientation = orientation @@ -326,25 +315,14 @@ open class WorldObject(val config: Registry.Entry) : TileEntit } } - private val drawablesCache = LazyData { - orientation?.drawables?.map { it.with(::getRenderParam) } ?: listOf() + protected open fun parametersUpdated() { + parametersLazies.forEach { it.invalidate() } } - init { - networkedRenderKeys.addListener { drawablesCache.invalidate() } - } - - val drawables: List by drawablesCache - - private fun updateOrientation() { - setOrientation(config.value.findValidOrientation(world, tilePosition, direction)) - } - - fun setOrientation(index: Int) { - if (orientationIndex.toInt() == index) - return - - orientationIndex = index.toLong() + protected open fun orientationUpdated() { + parametersUpdated() + spacesLazies.forEach { it.invalidate() } + orientationLazies.forEach { it.invalidate() } val orientation = orientation @@ -353,19 +331,16 @@ open class WorldObject(val config: Registry.Entry) : TileEntit direction = orientation.directionAffinity } - networkedMaterialSpaces.clear() - networkedMaterialSpaces.addAll(orientation.materialSpaces) + declaredMaterialSpaces.clear() + declaredMaterialSpaces.addAll(orientation.materialSpaces) } else { - networkedMaterialSpaces.clear() + declaredMaterialSpaces.clear() } } override fun onPositionUpdated() { super.onPositionUpdated() - - occupySpaces0.invalidate() - anchorPositions0.invalidate() - materialSpaces0.invalidate() + spacesLazies.forEach { it.invalidate() } if (isInWorld && world.isServer) { updateMaterialSpaces(listOf()) // remove old spaces after moving before updating orientation @@ -378,9 +353,8 @@ open class WorldObject(val config: Registry.Entry) : TileEntit } } - override fun markSpacesDirty() { - super.markSpacesDirty() - materialSpaces0.invalidate() + private fun updateOrientation() { + orientationIndex = config.value.findValidOrientation(world, tilePosition, direction).toLong() } override fun onJoinWorld(world: World<*, *>) { @@ -395,16 +369,16 @@ open class WorldObject(val config: Registry.Entry) : TileEntit direction = orientation.directionAffinity } - networkedMaterialSpaces.clear() - networkedMaterialSpaces.addAll(orientation.materialSpaces) + declaredMaterialSpaces.clear() + declaredMaterialSpaces.addAll(orientation.materialSpaces) } if (isRemote) { } else { - setImageKey("color", lookupProperty(JsonPath("color")) { JsonPrimitive("default") }.asString) + setImageKey("color", lookupProperty("color") { JsonPrimitive("default") }.asString) - for ((k, v) in lookupProperty(JsonPath("animationParts")) { JsonObject() }.asJsonObject.entrySet()) { + for ((k, v) in lookupProperty("animationParts") { JsonObject() }.asJsonObject.entrySet()) { animator.setPartTag(k, "partImage", v.asString) } @@ -423,7 +397,7 @@ open class WorldObject(val config: Registry.Entry) : TileEntit // Don't animate the initial state when first spawned IF you're dumb, which by default // you would be, and don't know how to use transition and static states properly. Someday // I'll be brave and delete shit garbage entirely and we'll see what breaks. - if (lookupProperty(JsonPath("forceFinishAnimationsInInit")) { JsonPrimitive(true) }.asBoolean) { + if (lookupProperty("forceFinishAnimationsInInit") { JsonPrimitive(true) }.asBoolean) { animator.finishAnimations() } } @@ -435,22 +409,18 @@ open class WorldObject(val config: Registry.Entry) : TileEntit protected fun setImageKey(key: String, value: String) { val old = localRenderKeys.put(key, value) - if (old != value) { - drawablesCache.invalidate() - } - if (!isRemote && networkedRenderKeys[key] != value) { networkedRenderKeys[key] = value } } - private val interactAction by LazyData { - lookupProperty(JsonPath("interactAction")) { JsonNull.INSTANCE } - } + private val interactAction by ManualLazy { + lookupProperty("interactAction") { JsonNull.INSTANCE } + }.also { parametersLazies.add(it) } - private val interactData by LazyData { - lookupProperty(JsonPath("interactData")) { JsonNull.INSTANCE } - } + private val interactData by ManualLazy { + lookupProperty("interactData") { JsonNull.INSTANCE } + }.also { parametersLazies.add(it) } override fun interact(request: InteractRequest): InteractAction { val diff = world.geometry.diff(request.sourcePos, position) @@ -480,11 +450,6 @@ open class WorldObject(val config: Registry.Entry) : TileEntit return super.interact(request) } - override fun invalidate() { - super.invalidate() - drawablesCache.invalidate() - } - fun callBreak(smash: Boolean = false) { } @@ -551,44 +516,6 @@ open class WorldObject(val config: Registry.Entry) : TileEntit return tileHealth.isDead } - val lightColors: ImmutableMap by LazyData { - val lightColor = lookupProperty(lightColorPath) - - if (!lightColor.isJsonNull) { - return@LazyData ImmutableMap.of("default", colors0.fromJsonTree(lightColor)) - } - - val lightColors = lookupProperty(lightColorsPath) - - if (!lightColors.isJsonNull) { - return@LazyData colors1.fromJsonTree(lightColors) - } - - ImmutableMap.of() - } - - override fun addLights(lightCalculator: LightCalculator, xOffset: Int, yOffset: Int) { - var color = lightColors[color.lowercase] - - if (color != null) { - if (flickerPeriod != null) { - val sample = flickerPeriod.sinValue().toFloat() - color *= sample - } - - lightCalculator.addPointLight(tilePosition.x - xOffset, tilePosition.y - yOffset, color) - } - } - - override fun render(client: StarboundClient, layers: LayeredRenderer) { - val layer = layers.getLayer(orientation?.renderLayer ?: return) - - drawables.forEach { - val (x, y) = imagePosition - it.render(client, layer, position.x.toFloat() + x / PIXELS_IN_STARBOUND_UNITf, position.y.toFloat() + y / PIXELS_IN_STARBOUND_UNITf) - } - } - companion object { private val lightColorPath = JsonPath("lightColor") private val lightColorsPath = JsonPath("lightColors") diff --git a/src/test/kotlin/ru/dbotthepony/kstarbound/test/WorldTests.kt b/src/test/kotlin/ru/dbotthepony/kstarbound/test/WorldTests.kt index 29156827..9549e294 100644 --- a/src/test/kotlin/ru/dbotthepony/kstarbound/test/WorldTests.kt +++ b/src/test/kotlin/ru/dbotthepony/kstarbound/test/WorldTests.kt @@ -49,15 +49,7 @@ object WorldTests { val geometry = WorldGeometry(Vector2i(3000, 2000), true, false) val index = EntityIndex(geometry) - val entry = index.Entry(object : AbstractEntity("/") { - override fun lookupProperty(path: JsonPath, orElse: () -> JsonElement): JsonElement { - TODO("Not yet implemented") - } - - override fun setProperty0(key: JsonPath, value: JsonElement) { - TODO("Not yet implemented") - } - + val entry = index.Entry(object : AbstractEntity() { override val position: Vector2d get() = TODO("Not yet implemented") override val type: EntityType