Implement EnhancedContainer
This commit is contained in:
parent
efbc93e16a
commit
de79019f56
@ -0,0 +1,190 @@
|
||||
package ru.dbotthepony.mc.otm.container
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet
|
||||
import net.minecraft.core.HolderLookup.Provider
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.nbt.ListTag
|
||||
import net.minecraft.nbt.NbtOps
|
||||
import net.minecraft.nbt.Tag
|
||||
import net.minecraft.resources.RegistryOps
|
||||
import net.minecraft.world.SimpleContainer
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.neoforged.neoforge.common.util.INBTSerializable
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.mc.otm.container.slotted.SlottedContainer
|
||||
import ru.dbotthepony.mc.otm.core.isNotEmpty
|
||||
import ru.dbotthepony.mc.otm.core.nbt.set
|
||||
|
||||
/**
|
||||
* Flexible base implementation of [IEnhancedContainer], designed to be inherited, or used as-is
|
||||
* if no specific logic is required (this implementation is more efficient than one provided by [SlottedContainer.simple]).
|
||||
*
|
||||
* This is supposed to be counterpart to [SimpleContainer] of Minecraft itself, with more features
|
||||
* and improved performance (inside [IEnhancedContainer] defined methods).
|
||||
*/
|
||||
open class EnhancedContainer(private val size: Int) : IEnhancedContainer, INBTSerializable<CompoundTag> {
|
||||
private val items = Array(size) { ItemStack.EMPTY }
|
||||
private val observedItems = Array(size) { ItemStack.EMPTY }
|
||||
private val slots by lazy(LazyThreadSafetyMode.PUBLICATION) { Array(size) { IContainerSlot.Simple(it, this) } }
|
||||
|
||||
// can be safely overridden in subclasses, very little memory will be wasted
|
||||
override fun containerSlot(slot: Int): IContainerSlot {
|
||||
return slots[slot]
|
||||
}
|
||||
|
||||
protected open fun notifySlotChanged(slot: Int, old: ItemStack) {}
|
||||
|
||||
private fun observeSlotChanges(slot: Int) {
|
||||
if (items[slot].count != observedItems[slot].count || !ItemStack.isSameItemSameComponents(items[slot], observedItems[slot])) {
|
||||
notifySlotChanged(slot, observedItems[slot])
|
||||
|
||||
if (items[slot].isEmpty) {
|
||||
items[slot] = ItemStack.EMPTY
|
||||
observedItems[slot] = ItemStack.EMPTY
|
||||
} else {
|
||||
notifySlotChanged(slot, observedItems[slot])
|
||||
observedItems[slot] = items[slot].copy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun setChanged(slot: Int) {
|
||||
observeSlotChanges(slot)
|
||||
}
|
||||
|
||||
override fun clearContent() {
|
||||
items.fill(ItemStack.EMPTY)
|
||||
observedItems.fill(ItemStack.EMPTY)
|
||||
}
|
||||
|
||||
override fun setChanged() {
|
||||
for (slot in 0 until size)
|
||||
observeSlotChanges(slot)
|
||||
}
|
||||
|
||||
final override fun getContainerSize(): Int {
|
||||
return size
|
||||
}
|
||||
|
||||
final override fun getItem(slot: Int): ItemStack {
|
||||
return items[slot]
|
||||
}
|
||||
|
||||
final override fun removeItem(slot: Int, amount: Int): ItemStack {
|
||||
val item = items[slot]
|
||||
|
||||
if (item.isEmpty)
|
||||
return ItemStack.EMPTY
|
||||
else if (item.count >= amount)
|
||||
return removeItemNoUpdate(slot)
|
||||
else {
|
||||
val split = item.split(amount)
|
||||
notifySlotChanged(slot, observedItems[slot])
|
||||
observedItems[slot] = item.copy()
|
||||
return split
|
||||
}
|
||||
}
|
||||
|
||||
final override fun removeItemNoUpdate(slot: Int): ItemStack {
|
||||
val item = items[slot]
|
||||
|
||||
if (item.isEmpty)
|
||||
return ItemStack.EMPTY
|
||||
|
||||
items[slot] = ItemStack.EMPTY
|
||||
notifySlotChanged(slot, observedItems[slot])
|
||||
observedItems[slot] = ItemStack.EMPTY
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
final override fun setItem(slot: Int, itemStack: ItemStack) {
|
||||
if (itemStack.count != items[slot].count || !ItemStack.isSameItemSameComponents(itemStack, items[slot])) {
|
||||
items[slot] = itemStack
|
||||
notifySlotChanged(slot, observedItems[slot])
|
||||
observedItems[slot] = itemStack.copy()
|
||||
}
|
||||
}
|
||||
|
||||
override fun stillValid(player: Player): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from inside [serializeNBT] per slot, return true if you have written something into [nbt].
|
||||
* If returning false, and slot does not contain an item, tag entry is discarded
|
||||
*/
|
||||
protected open fun attachSlotData(provider: Provider, slot: Int, nbt: CompoundTag, ops: RegistryOps<Tag>): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
protected open fun loadSlotData(provider: Provider, slot: Int, nbt: CompoundTag, ops: RegistryOps<Tag>) {
|
||||
|
||||
}
|
||||
|
||||
override fun serializeNBT(provider: Provider): CompoundTag {
|
||||
val ops = provider.createSerializationContext(NbtOps.INSTANCE)
|
||||
|
||||
return CompoundTag().also {
|
||||
it["items"] = ListTag().also {
|
||||
for (i in 0 until size) {
|
||||
val tag = CompoundTag()
|
||||
var attached = attachSlotData(provider, i, tag, ops)
|
||||
|
||||
if (items[i].isNotEmpty) {
|
||||
attached = true
|
||||
tag["item"] = ItemStack.OPTIONAL_CODEC.encodeStart(ops, items[i])
|
||||
.getOrThrow { RuntimeException("Unable to serialize item ${items[i]} at slot $i: $it") }
|
||||
}
|
||||
|
||||
tag["slot"] = i
|
||||
|
||||
if (attached) {
|
||||
it.add(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun deserializeNBT(provider: Provider, nbt: CompoundTag) {
|
||||
val copy = observedItems.copyOf()
|
||||
items.fill(ItemStack.EMPTY)
|
||||
observedItems.fill(ItemStack.EMPTY)
|
||||
|
||||
val seenSlots = IntOpenHashSet()
|
||||
val ops = provider.createSerializationContext(NbtOps.INSTANCE)
|
||||
|
||||
if ("items" in nbt) {
|
||||
for (element in nbt.getList("items", Tag.TAG_COMPOUND.toInt())) {
|
||||
element as CompoundTag
|
||||
|
||||
if ("slot" !in element) continue
|
||||
val slot = element.getInt("slot")
|
||||
if (!seenSlots.add(slot)) continue
|
||||
|
||||
if ("item" in nbt) {
|
||||
ItemStack.OPTIONAL_CODEC.decode(ops, nbt["item"])
|
||||
.map { it.first }
|
||||
.ifError { LOGGER.error("Failed to deserialize item stack in slot $slot: ${it.message()}") }
|
||||
.ifSuccess {
|
||||
if (it.isNotEmpty) {
|
||||
items[slot] = it
|
||||
observedItems[slot] = it.copy()
|
||||
|
||||
if (it.count != copy[slot].count || !ItemStack.isSameItemSameComponents(it, copy[slot]))
|
||||
notifySlotChanged(slot, copy[slot])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadSlotData(provider, slot, element, ops)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
}
|
@ -3,9 +3,6 @@ package ru.dbotthepony.mc.otm.container.slotted
|
||||
import com.mojang.serialization.Codec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList
|
||||
import it.unimi.dsi.fastutil.ints.IntCollection
|
||||
import it.unimi.dsi.fastutil.ints.IntList
|
||||
import it.unimi.dsi.fastutil.ints.IntSet
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
@ -20,17 +17,25 @@ import net.minecraft.world.item.ItemStack
|
||||
import net.neoforged.neoforge.common.util.INBTSerializable
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.util.Either
|
||||
import ru.dbotthepony.mc.otm.container.EnhancedContainer
|
||||
import ru.dbotthepony.mc.otm.container.IAutomatedContainer
|
||||
import ru.dbotthepony.mc.otm.container.IAutomatedContainerSlot
|
||||
import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot
|
||||
import ru.dbotthepony.mc.otm.container.balance
|
||||
import ru.dbotthepony.mc.otm.container.slotRange
|
||||
import ru.dbotthepony.mc.otm.core.isNotEmpty
|
||||
import ru.dbotthepony.mc.otm.core.nbt.set
|
||||
import ru.dbotthepony.mc.otm.data.codec.minRange
|
||||
import java.util.function.Predicate
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Concrete container implementation, operating on slots.
|
||||
*
|
||||
* Due to inflexible constraints, it can not be inherited, and usually it is not required since
|
||||
* most logic is embedded inside [ContainerSlot].
|
||||
*
|
||||
* If you specifically need to inherit container implementation, use [EnhancedContainer], which was designed to be inherited.
|
||||
*/
|
||||
class SlottedContainer(
|
||||
slots: Collection<MarkedSlotProvider<*>>,
|
||||
private val stillValid: Predicate<Player>,
|
||||
@ -381,10 +386,12 @@ class SlottedContainer(
|
||||
return MultiTag(T::class)
|
||||
}
|
||||
|
||||
@Deprecated("Consider using EnhancedContainer", replaceWith = ReplaceWith("EnhancedContainer(size)", "ru.dbotthepony.mc.otm.container.EnhancedContainer"))
|
||||
fun simple(size: Int): SlottedContainer {
|
||||
return Builder().add(size, ::ContainerSlot).build()
|
||||
}
|
||||
|
||||
@Deprecated("Consider using EnhancedContainer", replaceWith = ReplaceWith("EnhancedContainer.withListener(size, listener)", "ru.dbotthepony.mc.otm.container.EnhancedContainer"))
|
||||
fun simple(size: Int, listener: Runnable): SlottedContainer {
|
||||
return Builder()
|
||||
.add(size, ::ContainerSlot)
|
||||
|
Loading…
Reference in New Issue
Block a user