container objects "functional"

This commit is contained in:
DBotThePony 2024-04-15 21:28:43 +07:00
parent 77aa05d9f3
commit 9f52e2314d
Signed by: DBot
GPG Key ID: DCC23B5715498507
9 changed files with 465 additions and 32 deletions

View File

@ -234,7 +234,10 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
throw NoSuchElementException("Script $path does not exist") throw NoSuchElementException("Script $path does not exist")
} }
return loader.compileTextChunk(path, find.readToString()) val time = System.nanoTime()
val result = loader.compileTextChunk(path, find.readToString())
LOGGER.debug("Compiled {} in {} ms", path, (System.nanoTime() - time) / 1_000_000L)
return result
} }
fun loadScript(path: String): ChunkFactory { fun loadScript(path: String): ChunkFactory {

View File

@ -35,6 +35,7 @@ import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.item.ItemStack import ru.dbotthepony.kstarbound.item.ItemStack
import ru.dbotthepony.kstarbound.json.readJsonElement import ru.dbotthepony.kstarbound.json.readJsonElement
import ru.dbotthepony.kstarbound.json.writeJsonElement import ru.dbotthepony.kstarbound.json.writeJsonElement
import ru.dbotthepony.kstarbound.lua.get
import ru.dbotthepony.kstarbound.lua.indexNoYield import ru.dbotthepony.kstarbound.lua.indexNoYield
import ru.dbotthepony.kstarbound.lua.toJson import ru.dbotthepony.kstarbound.lua.toJson
import java.io.DataInputStream import java.io.DataInputStream
@ -109,6 +110,24 @@ fun ExecutionContext.ItemDescriptor(data: Table): ItemDescriptor {
} }
} }
@Deprecated("Does not obey meta methods, need to find replacement where possible")
fun ItemDescriptor(data: Table): ItemDescriptor {
val name = data[1L] ?: data["name"] ?: data["item"]
val count = data[2L] ?: data["count"] ?: 1L
val parameters = data[3L] ?: data["parameters"] ?: data["data"]
if (name !is ByteString) throw LuaRuntimeException("Invalid item descriptor name (${name})")
if (count !is Number) throw LuaRuntimeException("Invalid item descriptor count (${count})")
if (parameters == null) {
return ItemDescriptor(name.decode(), count.toLong())
} else if (parameters is Table) {
return ItemDescriptor(name.decode(), count.toLong(), parameters.toJson(true) as JsonObject)
} else {
throw LuaRuntimeException("Invalid item descriptor parameters ($parameters)")
}
}
fun ItemDescriptor(stream: DataInputStream): ItemDescriptor { fun ItemDescriptor(stream: DataInputStream): ItemDescriptor {
val name = stream.readInternedString() val name = stream.readInternedString()
val count = stream.readVarLong() val count = stream.readVarLong()
@ -135,6 +154,7 @@ data class ItemDescriptor(
constructor(ref: Registry.Ref<IItemDefinition>, count: Long, parameters: JsonObject) : this(ref.key.left(), count, parameters) constructor(ref: Registry.Ref<IItemDefinition>, count: Long, parameters: JsonObject) : this(ref.key.left(), count, parameters)
val isEmpty get() = count <= 0L || name == "" || ref.isEmpty val isEmpty get() = count <= 0L || name == "" || ref.isEmpty
val isNotEmpty get() = !isEmpty
val ref by lazy { if (name == "") Registries.items.emptyRef else Registries.items.ref(name) } val ref by lazy { if (name == "") Registries.items.emptyRef else Registries.items.ref(name) }
override fun toString(): String { override fun toString(): String {

View File

@ -13,6 +13,7 @@ import ru.dbotthepony.kstarbound.Globals
import ru.dbotthepony.kstarbound.Registries import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.AssetPath import ru.dbotthepony.kstarbound.defs.AssetPath
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyLiquid
import ru.dbotthepony.kstarbound.io.readColor import ru.dbotthepony.kstarbound.io.readColor
import ru.dbotthepony.kstarbound.io.writeColor import ru.dbotthepony.kstarbound.io.writeColor
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
@ -178,10 +179,8 @@ data class SkyParameters(
// If the planet has water, then draw the corresponding water image as the // If the planet has water, then draw the corresponding water image as the
// base layer, otherwise use the bottom most mask image. // base layer, otherwise use the bottom most mask image.
val surfaceLiquid = visitable.surfaceLiquid.entry?.key if (visitable.surfaceLiquid.isNotEmptyLiquid && liquidImages.isNotBlank()) {
layers.add(Layer(liquidImages.replace("<liquid>", visitable.surfaceLiquid.entry!!.key), imageScale))
if (surfaceLiquid != null && liquidImages.isNotBlank()) {
layers.add(Layer(liquidImages.replace("<liquid>", surfaceLiquid), imageScale))
} else { } else {
if (baseCount > 0) { if (baseCount > 0) {
layers.add(Layer("${baseImages.replace("<biome>", visitable.primaryBiome).replace("<num>", baseCount.toString())}?hueshift=${visitable.hueShift}", imageScale)) layers.add(Layer("${baseImages.replace("<biome>", visitable.primaryBiome).replace("<num>", baseCount.toString())}?hueshift=${visitable.hueShift}", imageScale))
@ -293,15 +292,13 @@ data class SkyParameters(
val biomeHueShift = "?hueshift=${visitable.hueShift.toInt()}" val biomeHueShift = "?hueshift=${visitable.hueShift.toInt()}"
val surfaceLiquid = visitable.surfaceLiquid.entry?.key if (visitable.surfaceLiquid.isNotEmptyLiquid) {
if (surfaceLiquid != null) {
val random = random(parameters.seed) val random = random(parameters.seed)
for (i in 0 until 23) for (i in 0 until 23)
random.nextInt() random.nextInt()
images.add(getLR(liquidTextures.replace("<liquid>", surfaceLiquid))) images.add(getLR(liquidTextures.replace("<liquid>", visitable.surfaceLiquid.entry!!.key)))
val masksL = ArrayList<String>() val masksL = ArrayList<String>()
val masksR = ArrayList<String>() val masksR = ArrayList<String>()

View File

@ -1,13 +0,0 @@
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
}
}

View File

@ -1,7 +1,152 @@
package ru.dbotthepony.kstarbound.item package ru.dbotthepony.kstarbound.item
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
interface IContainer { interface IContainer {
val size: Int var size: Int
operator fun get(index: Int): ItemStack operator fun get(index: Int): ItemStack
operator fun set(index: Int, value: ItemStack) operator fun set(index: Int, value: ItemStack)
fun ageItems(by: Double): Boolean {
var any = false
for (i in 0 until size) {
val (newItem, hasAdvanced) = this[i].advanceAge(by)
any = hasAdvanced || any
if (newItem != null) {
this[i] = newItem
}
}
return any
}
// puts item into container, returns remaining not put items
fun add(item: ItemStack, simulate: Boolean = false): ItemStack {
val copy = item.copy()
// first, try to put into not empty slots first
for (i in 0 until size) {
val itemThere = this[i]
if (itemThere.isStackable(copy)) {
val newCount = (itemThere.size + copy.size).coerceAtMost(itemThere.maxStackSize)
val diff = newCount - itemThere.size
copy.size -= diff
if (!simulate)
itemThere.size += diff
if (copy.isEmpty)
return ItemStack.EMPTY
}
}
// then try to move into empty slots
for (i in 0 until size) {
val itemThere = this[i]
if (itemThere.isEmpty) {
if (copy.size > copy.maxStackSize) {
if (!simulate)
this[i] = copy.copy(copy.maxStackSize)
copy.size -= copy.maxStackSize
} else {
if (!simulate)
this[i] = copy
return ItemStack.EMPTY
}
}
}
return copy
}
fun clear()
fun toJson(store: Boolean = false): JsonArray {
val result = JsonArray(size)
if (store) {
Starbound.storeJson {
for (i in 0 until size) {
result.add(this[i].toJson())
}
}
} else {
for (i in 0 until size) {
result.add(this[i].toJson())
}
}
return result
}
fun fromJson(json: JsonElement, resize: Boolean = false): List<ItemStack> {
clear()
val read = ArrayList<ItemStack>()
val array = json.asJsonArray
var nonEmpty = 0
for (i in 0 until array.size()) {
val item = ItemDescriptor(array[i])
if (item.isNotEmpty)
nonEmpty++
read.add(ItemStack.create(item))
}
if (read.size > size) {
// we have a problem
if (resize) {
// no problem
size = read.size
for ((i, item) in read.withIndex()) {
this[i] = item
}
return emptyList()
} else {
// first, put items to their place if they are within container size
for (i in 0 until size) {
this[i] = read[i]
}
// second, try to move remaining item into empty or stackable slots
val lost = ArrayList<ItemStack>()
for (i in size until read.size) {
val remaining = add(read[i])
if (remaining.isNotEmpty) {
lost.add(remaining)
}
}
// if we weren't capable of putting excess items into container, then what a shame.
return lost
}
} else {
if (read.size < size && resize)
size = read.size
for ((i, item) in read.withIndex()) {
this[i] = item
}
return emptyList()
}
}
} }

View File

@ -18,6 +18,7 @@ import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.io.writeVarLong import ru.dbotthepony.kommons.io.writeVarLong
import ru.dbotthepony.kstarbound.Registries import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.json.writeJsonElement import ru.dbotthepony.kstarbound.json.writeJsonElement
import ru.dbotthepony.kstarbound.lua.LuaEnvironment
import ru.dbotthepony.kstarbound.lua.from import ru.dbotthepony.kstarbound.lua.from
import java.io.DataOutputStream import java.io.DataOutputStream
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
@ -62,7 +63,8 @@ open class ItemStack {
} }
val config: Registry.Ref<IItemDefinition> val config: Registry.Ref<IItemDefinition>
val parameters: JsonObject var parameters: JsonObject
protected set
val isEmpty: Boolean val isEmpty: Boolean
get() = size <= 0 || config.isEmpty get() = size <= 0 || config.isEmpty
@ -81,6 +83,38 @@ open class ItemStack {
size -= amount size -= amount
} }
data class AgingResult(val new: ItemStack?, val ageUpdated: Boolean)
private fun config() = config
private val agingScripts: LuaEnvironment? by lazy {
val config = config().value ?: return@lazy null
//if (config.itemTags)
null
}
open fun advanceAge(by: Double): AgingResult {
val agingScripts = agingScripts ?: return AgingResult(null, false)
val descriptor = createDescriptor()
val updated = ItemDescriptor(agingScripts.invokeGlobal("ageItem", descriptor.toTable(agingScripts), by)[0] as Table)
if (descriptor != updated) {
if (descriptor.name == updated.name) {
// only parameters got changed
this.parameters = descriptor.parameters
this.size = descriptor.count
changeset = CHANGESET.incrementAndGet()
return AgingResult(null, true)
} else {
// item got replaced by something else
return AgingResult(create(updated), true)
}
}
return AgingResult(null, false)
}
fun createDescriptor(): ItemDescriptor { fun createDescriptor(): ItemDescriptor {
if (isEmpty) if (isEmpty)
return ItemDescriptor.EMPTY return ItemDescriptor.EMPTY
@ -151,7 +185,7 @@ open class ItemStack {
return "ItemDescriptor[${config.value?.itemName}, count = $size, params = $parameters]" return "ItemDescriptor[${config.value?.itemName}, count = $size, params = $parameters]"
} }
fun copy(): ItemStack { fun copy(size: Long = this.size): ItemStack {
if (isEmpty) if (isEmpty)
return this return this
@ -206,6 +240,9 @@ open class ItemStack {
val EMPTY = ItemStack() val EMPTY = ItemStack()
fun create(descriptor: ItemDescriptor): ItemStack { fun create(descriptor: ItemDescriptor): ItemStack {
if (descriptor.isEmpty)
return EMPTY
return ItemStack(descriptor) return ItemStack(descriptor)
} }
} }

View File

@ -28,7 +28,14 @@ import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
class PlayerInventory { class PlayerInventory {
inner class Bag(override val size: Int) : IContainer { inner class Bag(size: Int) : IContainer {
override var size: Int = size
set(value) { throw UnsupportedOperationException() }
override fun clear() {
slots.forEach { it.accept(ItemStack.EMPTY) }
}
val slots = immutableList(size) { networkedItem() } val slots = immutableList(size) { networkedItem() }
override fun get(index: Int): ItemStack { override fun get(index: Int): ItemStack {

View File

@ -1,23 +1,259 @@
package ru.dbotthepony.kstarbound.world.entities.tile package ru.dbotthepony.kstarbound.world.entities.tile
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.set
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.kommons.util.getValue import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.kommons.util.setValue import ru.dbotthepony.kommons.util.setValue
import ru.dbotthepony.kstarbound.Registry import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.defs.InteractAction
import ru.dbotthepony.kstarbound.defs.InteractRequest
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
import ru.dbotthepony.kstarbound.item.IContainer
import ru.dbotthepony.kstarbound.item.ItemStack
import ru.dbotthepony.kstarbound.math.Interpolator import ru.dbotthepony.kstarbound.math.Interpolator
import ru.dbotthepony.kstarbound.network.syncher.NetworkedElement
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
import ru.dbotthepony.kstarbound.network.syncher.networkedBytes import ru.dbotthepony.kstarbound.network.syncher.networkedBytes
import ru.dbotthepony.kstarbound.network.syncher.networkedFloat import ru.dbotthepony.kstarbound.network.syncher.networkedFloat
import ru.dbotthepony.kstarbound.network.syncher.networkedSignedInt import ru.dbotthepony.kstarbound.network.syncher.networkedSignedInt
import ru.dbotthepony.kstarbound.util.ManualLazy
import ru.dbotthepony.kstarbound.util.RelativeClock
import ru.dbotthepony.kstarbound.world.entities.ItemDropEntity
import java.io.DataInputStream
import java.io.DataOutputStream
class ContainerObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(config) { class ContainerObject(config: Registry.Entry<ObjectDefinition>) : WorldObject(config) {
var opened by networkedSignedInt().also { networkGroup.upstream.add(it) } var opened by networkedSignedInt().also { networkGroup.upstream.add(it) }
var isCrafting by networkedBoolean().also { networkGroup.upstream.add(it) } var isCrafting by networkedBoolean().also { networkGroup.upstream.add(it) }
var craftingProgress by networkedFloat().also { networkGroup.upstream.add(it); it.interpolator = Interpolator.Linear } var craftingProgress by networkedFloat().also { networkGroup.upstream.add(it); it.interpolator = Interpolator.Linear }
// i have no words. val items = Container(0).also { networkGroup.upstream.add(it) }
// this field embeds ENTIRE net state of 'ItemBag',
// and each time container is updated, its contents are networked fully // whenever set loot seed, put initial items, etc.
// each. damn. time. private var isInitialized = false
// placeholder data, container size 40, read 0 items
var itemsNetState by networkedBytes(byteArrayOf(40, 0)).also { networkGroup.upstream.add(it) } private val lostItems = ArrayList<ItemStack>()
private val ageItemsTimer = RelativeClock()
private val ageItemsEvery = ManualLazy {
lookupProperty("ageItemsEvery") { JsonPrimitive(10.0) }.asDouble
}.also { parametersLazies.add(it) }
private val itemAgeMultiplier = ManualLazy {
lookupProperty("itemAgeMultiplier") { JsonPrimitive(1.0) }.asDouble
}.also { parametersLazies.add(it) }
override fun tick(delta: Double) {
super.tick(delta)
if (world.isServer) {
for (item in lostItems) {
val entity = ItemDropEntity(item)
entity.position = position
entity.joinWorld(world)
}
lostItems.clear()
ageItemsTimer.update(world.sky.time)
if (ageItemsTimer.time >= ageItemsEvery.value) {
items.ageItems(ageItemsTimer.time * itemAgeMultiplier.value)
ageItemsTimer.set(0.0)
}
}
}
override fun interact(request: InteractRequest): InteractAction {
return InteractAction(InteractAction.Type.OPEN_CONTAINER, entityID)
}
override fun deserialize(data: JsonObject) {
super.deserialize(data)
opened = data.get("opened", 0)
isCrafting = data.get("crafting", false)
craftingProgress = data.get("craftingProgress", 0.0)
isInitialized = data.get("initialized", true)
items.fromJson(data.get("items", JsonArray()), resize = true)
}
override fun serialize(): JsonObject {
val data = super.serialize()
// required by original engine
data["currentState"] = 0
data["opened"] = opened
data["crafting"] = isCrafting
data["craftingProgress"] = craftingProgress
data["initialized"] = isInitialized
data["items"] = items.toJson(true)
return data
}
// Networking of this container to legacy clients is incredibly stupid,
// and networks entire state each time something has changed.
inner class Container(size: Int) : NetworkedElement(), IContainer {
private var items = Array(size) { ItemStack.EMPTY }
private var itemNetVersions = LongArray(size)
private var itemObservedVersion = LongArray(size)
private var lastSizeChange = 0L
private fun observeItems() {
for (i in 0 until size) {
if (itemObservedVersion[i] != items[i].changeset) {
itemObservedVersion[i] = items[i].changeset
itemNetVersions[i] = currentVersion()
}
}
}
override var size: Int = size
set(value) {
if (field == value) return
lastSizeChange = currentVersion()
if (value > field) {
items = items.copyOf(value) as Array<ItemStack>
for (i in field until value)
items[i] = ItemStack.EMPTY
} else {
for (i in value until field) {
lostItems.add(items[i])
}
items = items.copyOf(value) as Array<ItemStack>
}
field = value
itemObservedVersion = itemObservedVersion.copyOf(value)
itemNetVersions = itemNetVersions.copyOf(value)
}
override fun get(index: Int): ItemStack {
return items[index]
}
override fun set(index: Int, value: ItemStack) {
items[index] = value.copy()
}
override fun clear() {
items.fill(ItemStack.EMPTY)
}
override fun readInitial(data: DataInputStream, isLegacy: Boolean) {
if (isLegacy) {
val stream = DataInputStream(FastByteArrayInputStream(data.readByteArray()))
size = stream.readVarInt()
val setItemsSize = stream.readVarInt()
items.fill(ItemStack.EMPTY)
for (i in 0 until setItemsSize) {
items[i] = ItemStack(ItemDescriptor(stream))
}
} else {
size = data.readVarInt()
items.fill(ItemStack.EMPTY)
while (true) {
val index = data.readVarInt() - 1
if (index == -1) break
items[index] = ItemStack(ItemDescriptor(data))
}
}
}
override fun writeInitial(data: DataOutputStream, isLegacy: Boolean) {
if (isLegacy) {
val wrapper = FastByteArrayOutputStream()
val stream = DataOutputStream(wrapper)
stream.writeVarInt(size)
var setItemsSize = items.indexOfLast { it.isNotEmpty }
if (setItemsSize == -1)
setItemsSize = 0
stream.writeVarInt(setItemsSize)
for (i in 0 until setItemsSize) {
items[i].write(stream)
}
data.writeByteArray(wrapper.array, 0, wrapper.length)
} else {
data.writeVarInt(size)
for (i in 0 until size) {
if (items[i].isNotEmpty) {
data.writeVarInt(i + 1)
items[i].write(data)
}
}
data.writeVarInt(0)
}
}
override fun readDelta(data: DataInputStream, interpolationDelay: Double, isLegacy: Boolean) {
if (isLegacy) {
readInitial(data, true)
} else {
if (data.readBoolean()) {
readInitial(data, false)
} else {
while (true) {
val index = data.readVarInt() - 1
if (index == -1) break
items[index] = ItemStack(ItemDescriptor(data))
}
}
}
}
override fun writeDelta(data: DataOutputStream, remoteVersion: Long, isLegacy: Boolean) {
if (isLegacy) {
writeInitial(data, true)
} else {
if (lastSizeChange >= remoteVersion) {
data.writeBoolean(true)
writeInitial(data, false)
} else {
observeItems()
data.writeBoolean(false)
for (i in 0 until size) {
if (itemNetVersions[i] >= remoteVersion) {
data.writeVarInt(i + 1)
items[i].write(data)
}
}
data.writeVarInt(0)
}
}
}
override fun hasChangedSince(version: Long): Boolean {
observeItems()
return lastSizeChange >= version || itemNetVersions.any { it >= version }
}
override fun readBlankDelta(interpolationDelay: Double) {}
override fun enableInterpolation(extrapolation: Double) {}
override fun disableInterpolation() {}
override fun tickInterpolation(delta: Double) {}
}
} }

View File

@ -504,6 +504,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
} }
if (shouldBreak) { if (shouldBreak) {
callBreak(false)
} }
} }
} }