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