KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/item/ItemStack.kt

218 lines
5.1 KiB
Kotlin

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
import com.google.gson.internal.bind.TypeAdapters
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import org.classdump.luna.Table
import org.classdump.luna.TableFactory
import ru.dbotthepony.kstarbound.Registry
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
/**
* Base class for instanced items in game
*/
open class ItemStack {
constructor() {
this.config = Registries.items.emptyRef
this.parameters = JsonObject()
}
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
/**
* 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) {
field = value.coerceAtLeast(0L)
}
val config: Registry.Ref<IItemDefinition>
val parameters: JsonObject
val isEmpty: Boolean
get() = count <= 0 || config.isEmpty
val isNotEmpty: Boolean
get() = count > 0 && config.isPresent
val maxStackSize: Long
get() = config.value?.maxStack ?: 0L
fun grow(amount: Long) {
count += amount
}
fun shrink(amount: Long) {
count -= amount
}
fun createDescriptor(): ItemDescriptor {
if (isEmpty)
return ItemDescriptor.EMPTY
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)
}
}
/**
* Возвращает null если этот предмет пуст
*/
fun conciseToNull(): ItemStack? {
if (isEmpty) {
return null
} else {
return this
}
}
fun mergeFrom(other: ItemStack, simulate: Boolean) {
if (isStackable(other)) {
val newCount = (count + other.count).coerceAtMost(maxStackSize)
val diff = newCount - count
other.count -= diff
if (!simulate)
count = newCount
}
}
fun lenientEquals(other: Any?): Boolean {
if (other !is ItemStack)
return false
if (isEmpty)
return other.isEmpty
return other.count == count && other.config == config
}
fun isStackable(other: ItemStack): Boolean {
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 {
if (other !is ItemStack)
return false
if (isEmpty)
return other.isEmpty
return other.count == count && other.config == config && other.parameters == parameters
}
override fun hashCode(): Int {
return config.hashCode()
}
override fun toString(): String {
if (isEmpty)
return "ItemStack.EMPTY"
return "ItemDescriptor[${config.value?.itemName}, count = $count, params = $parameters]"
}
fun copy(): ItemStack {
if (isEmpty)
return this
return ItemStack(ItemDescriptor(config, count, parameters.deepCopy()))
}
fun toJson(): JsonObject? {
if (isEmpty)
return null
return JsonObject().also {
it.add("name", JsonPrimitive(config.key.left()))
it.add("count", JsonPrimitive(count))
it.add("parameters", parameters.deepCopy())
}
}
fun toTable(allocator: TableFactory): Table? {
if (isEmpty) {
return null
}
return allocator.newTable(0, 3).also {
it.rawset("name", config.key.left())
it.rawset("count", count)
it.rawset("parameters", allocator.from(parameters))
}
}
class Adapter(val starbound: Starbound) : TypeAdapter<ItemStack>() {
override fun write(out: JsonWriter, value: ItemStack?) {
val json = value?.toJson()
if (json == null)
out.nullValue()
else
TypeAdapters.JSON_ELEMENT.write(out, json)
}
override fun read(`in`: JsonReader): ItemStack {
if (`in`.consumeNull())
return EMPTY
return starbound.item(TypeAdapters.JSON_ELEMENT.read(`in`))
}
}
companion object {
private val CHANGESET = AtomicLong()
@JvmField
val EMPTY = ItemStack()
fun create(descriptor: ItemDescriptor): ItemStack {
return EMPTY
}
}
}