Loungeable objects, some cleanup to entities in general

This commit is contained in:
DBotThePony 2024-04-14 20:59:01 +07:00
parent 403aac63de
commit 77aa05d9f3
Signed by: DBot
GPG Key ID: DCC23B5715498507
19 changed files with 228 additions and 344 deletions

View File

@ -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<Property<*>>()
private val lazies = ArrayList<LazyData<*>>()
/**
* [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<T>(private val initializer: () -> T) : Lazy<T> {
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<T>(
val name: JsonPath,
val default: Either<Supplier<T>, JsonElement>? = null,
private var adapter: TypeAdapter<T>? = null,
) : Delegate<T>, ReadWriteProperty<Any?, T> {
constructor(name: JsonPath, default: T, adapter: TypeAdapter<T>? = null) : this(name, Either.left(Supplier { default }), adapter)
constructor(name: JsonPath, default: Supplier<T>, adapter: TypeAdapter<T>? = null) : this(name, Either.left(default), adapter)
constructor(name: JsonPath, default: JsonElement, adapter: TypeAdapter<T>? = null) : this(name, Either.right(default), adapter)
init {
delegates.add(this)
}
private var value: Supplier<T> 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<T>
}
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<T>
}
return accept(value)
}
}
companion object {
private val mark = Any()
}
}

View File

@ -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!")

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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? ->

View File

@ -0,0 +1,16 @@
package ru.dbotthepony.kstarbound.util
class ManualLazy<T>(private val initializer: () -> T) : Lazy<T> {
private var parent: Lazy<T> = lazy(initializer)
override val value: T
get() = parent.value
override fun isInitialized(): Boolean {
return parent.isInitialized()
}
fun invalidate() {
parent = lazy(initializer)
}
}

View File

@ -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 <C : Comparable<C>, T : Any> Stream<Pair<C, T>>.binnedChoice(value: C): Opti
.filter { it.first <= value }
.findFirst().map { it.second }
}
fun <E : IStringSerializable> Collection<E>.valueOf(value: String): E {
return firstOrNull { it.match(value) } ?: throw NoSuchElementException("$value is not a valid element")
}

View File

@ -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
};

View File

@ -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<AbstractEntity> {
abstract class AbstractEntity : Comparable<AbstractEntity> {
abstract val position: Vector2d
var entityID: Int = 0

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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")
}
}

View File

@ -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<ObjectDefinition>) : WorldObject(config) {
init {
isInteractive = true
}
private val sitPositions = ArrayList<Vector2d>()
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<Either<StatModifier, String>>()
private val sitEffectEmitters = ObjectArraySet<String>()
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<ImmutableList<Vector2d>>() {}) }
private val statusEffects by lazy { Starbound.gson.getAdapter(object : TypeToken<ImmutableSet<Either<StatModifier, String>>>() {}) }
}
}

View File

@ -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() {

View File

@ -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<ObjectDefinition>) : TileEntity(config.file?.computeDirectory() ?: "/") {
open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : 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<ObjectDefinition>) : 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<ManualLazy<*>>()
protected val parametersLazies = ArrayList<ManualLazy<*>>()
protected val spacesLazies = ArrayList<ManualLazy<*>>()
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<ObjectDefinition>) : 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<Vector2i> by ManualLazy {
(orientation?.occupySpaces ?: setOf()).stream().map { world.geometry.wrap(it + tilePosition) }.collect(ImmutableSet.toImmutableSet())
}
override val occupySpaces: ImmutableSet<Vector2i> by occupySpaces0
}.also { orientationLazies.add(it); spacesLazies.add(it) }
override val roots: Set<Vector2i>
get() = setOf()
private val anchorPositions0 = LazyData {
val anchorPositions: ImmutableSet<Vector2i> by ManualLazy {
immutableSet {
orientation?.anchors?.forEach { accept(it.position + tilePosition) }
}
}
val anchorPositions: ImmutableSet<Vector2i> 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<ObjectDefinition>) : 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<ObjectDefinition>) : TileEntit
}
}
val inputNodes: ImmutableList<WireNode> = lookupProperty(JsonPath("inputNodes")) { JsonArray() }
val inputNodes: ImmutableList<WireNode> = lookupProperty("inputNodes") { JsonArray() }
.asJsonArray
.stream()
.map { WireNode(vectors.fromJsonTree(it), true) }
.collect(ImmutableList.toImmutableList())
val outputNodes: ImmutableList<WireNode> = lookupProperty(JsonPath("outputNodes")) { JsonArray() }
val outputNodes: ImmutableList<WireNode> = lookupProperty("outputNodes") { JsonArray() }
.asJsonArray
.stream()
.map { WireNode(vectors.fromJsonTree(it), false) }
@ -275,17 +283,13 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : 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<ObjectDefinition>) : 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<ObjectDefinition>) : 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<Drawable> 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<ObjectDefinition>) : 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<ObjectDefinition>) : 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<ObjectDefinition>) : 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<ObjectDefinition>) : 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<ObjectDefinition>) : 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<ObjectDefinition>) : 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<ObjectDefinition>) : TileEntit
return tileHealth.isDead
}
val lightColors: ImmutableMap<String, RGBAColor> 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")

View File

@ -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