Implement EnhancedContainer

This commit is contained in:
DBotThePony 2025-03-06 16:06:04 +07:00
parent efbc93e16a
commit de79019f56
Signed by: DBot
GPG Key ID: DCC23B5715498507
2 changed files with 201 additions and 4 deletions

View File

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

View File

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