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.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder import com.mojang.serialization.codecs.RecordCodecBuilder
import it.unimi.dsi.fastutil.ints.IntArrayList 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 it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import net.minecraft.core.HolderLookup import net.minecraft.core.HolderLookup
import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.core.registries.BuiltInRegistries
@ -20,17 +17,25 @@ import net.minecraft.world.item.ItemStack
import net.neoforged.neoforge.common.util.INBTSerializable import net.neoforged.neoforge.common.util.INBTSerializable
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.util.Either 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.IAutomatedContainer
import ru.dbotthepony.mc.otm.container.IAutomatedContainerSlot import ru.dbotthepony.mc.otm.container.IAutomatedContainerSlot
import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot import ru.dbotthepony.mc.otm.container.IFilteredContainerSlot
import ru.dbotthepony.mc.otm.container.balance 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.isNotEmpty
import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.data.codec.minRange import ru.dbotthepony.mc.otm.data.codec.minRange
import java.util.function.Predicate import java.util.function.Predicate
import kotlin.reflect.KClass 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( class SlottedContainer(
slots: Collection<MarkedSlotProvider<*>>, slots: Collection<MarkedSlotProvider<*>>,
private val stillValid: Predicate<Player>, private val stillValid: Predicate<Player>,
@ -381,10 +386,12 @@ class SlottedContainer(
return MultiTag(T::class) return MultiTag(T::class)
} }
@Deprecated("Consider using EnhancedContainer", replaceWith = ReplaceWith("EnhancedContainer(size)", "ru.dbotthepony.mc.otm.container.EnhancedContainer"))
fun simple(size: Int): SlottedContainer { fun simple(size: Int): SlottedContainer {
return Builder().add(size, ::ContainerSlot).build() 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 { fun simple(size: Int, listener: Runnable): SlottedContainer {
return Builder() return Builder()
.add(size, ::ContainerSlot) .add(size, ::ContainerSlot)