From 9b1ca17da5e45eda6af57535d49f08aa439b66df Mon Sep 17 00:00:00 2001 From: GamerDuck123 Date: Tue, 24 Jun 2025 02:37:25 -0400 Subject: [PATCH] Added ChestRule.kt which adds the proper storing of inventories in both single and double chests, as well as proper handling of breaking of chests (dropping items) and double chests (dropping items from the side that was broken and keeping the items in the other side intact) Edited ChestPlacementRule.kt to ensure that NBT data is transferred on update NOTE My code for ChestRule.kt was translated from Java code, so it might not be the prettiest looking thing, I tried to clean it up a bit, but I might have missed a few things, it is functional though. --- .../blocks/behavior/ChestRule.kt | 259 ++++++++++++++++++ .../blocks/group/VanillaBlockBehaviour.kt | 6 + .../blocks/placement/ChestPlacementRule.kt | 1 + 3 files changed, 266 insertions(+) create mode 100644 blocksandstuff-blocks/src/main/kotlin/org/everbuild/blocksandstuff/blocks/behavior/ChestRule.kt diff --git a/blocksandstuff-blocks/src/main/kotlin/org/everbuild/blocksandstuff/blocks/behavior/ChestRule.kt b/blocksandstuff-blocks/src/main/kotlin/org/everbuild/blocksandstuff/blocks/behavior/ChestRule.kt new file mode 100644 index 0000000..8a76d62 --- /dev/null +++ b/blocksandstuff-blocks/src/main/kotlin/org/everbuild/blocksandstuff/blocks/behavior/ChestRule.kt @@ -0,0 +1,259 @@ +package org.everbuild.blocksandstuff.blocks.behavior + +import net.kyori.adventure.key.Key +import net.kyori.adventure.nbt.CompoundBinaryTag +import net.kyori.adventure.text.Component +import net.minestom.server.MinecraftServer +import net.minestom.server.codec.Transcoder +import net.minestom.server.coordinate.Point +import net.minestom.server.coordinate.Vec +import net.minestom.server.entity.ItemEntity +import net.minestom.server.entity.Player +import net.minestom.server.event.EventListener +import net.minestom.server.event.inventory.InventoryCloseEvent +import net.minestom.server.instance.Instance +import net.minestom.server.instance.block.Block +import net.minestom.server.instance.block.BlockHandler +import net.minestom.server.instance.block.BlockHandler.Destroy +import net.minestom.server.inventory.Inventory +import net.minestom.server.inventory.InventoryType +import net.minestom.server.item.ItemStack +import net.minestom.server.network.packet.server.play.BlockActionPacket +import net.minestom.server.utils.time.TimeUnit +import java.util.* +import java.util.concurrent.ThreadLocalRandom +import java.util.concurrent.atomic.AtomicInteger +import java.util.function.Consumer +import kotlin.math.min + +class ChestRule(private val block: Block) : BlockHandler { + override fun getKey(): Key { + return block.key() + } + + override fun onInteract(interaction: BlockHandler.Interaction): Boolean { + val p: Player = interaction.player + if (p.isSneaking && !p.itemInMainHand.isAir + ) return onInteract(interaction) + + val clickedBlock = interaction.block + val blockPos = interaction.blockPosition + val instance = interaction.instance + + val inv = getBlockInventory(clickedBlock, blockPos, instance) + + p.openInventory(inv) + + MinecraftServer.getGlobalEventHandler().addListener( + EventListener.builder(InventoryCloseEvent::class.java) + .expireCount(1) + .handler( { event: InventoryCloseEvent? -> + if (event != null) { + updateBlockInventory(inv, clickedBlock, blockPos, instance) + updateBlockAction(p, blockPos, clickedBlock, 0.toByte()) + } + }) + .build() + ) + updateBlockAction(p, blockPos, clickedBlock, 1.toByte()) + return false + } + + override fun onDestroy(destroy: Destroy) { + val block = destroy.block + if (destroy.instance.getBlock(destroy.blockPosition) === Block.AIR && block.nbt() != null) { + if (block.nbt()!!.get("Inventory") != null) { + val tag = block.nbt()!!.get("Inventory") + val inventory = ITEMSTACK_CODEC.decode(Transcoder.NBT, tag!!).orElse( + listOf( + ItemStack.AIR + ) + ) + inventory.forEach(Consumer { itemStack: ItemStack? -> + val entity = ItemEntity(itemStack!!) + entity.setPickupDelay(1, TimeUnit.SECOND) + entity.scheduleRemove(5, TimeUnit.MINUTE) + entity.setVelocity( + Vec( + ThreadLocalRandom.current().nextDouble() * 2 - 1, + 2.0, + ThreadLocalRandom.current().nextDouble() * 2 - 1 + ) + ) + entity.setInstance(destroy.getInstance(), destroy.getBlockPosition().add(0.5, 0.5, 0.5)) + }) + } + } + } + + private fun updateBlockInventory(inv: Inventory, block: Block, blockPos: Point, instance: Instance) { + if (block.getProperty("type").equals("left") + || block.getProperty("type") == "right" + ) { + val fullItems = Arrays.asList(*inv.getItemStacks()) + + val pair: ChestPair? = getChestPair(block, blockPos, instance) + val leftChestPos: Point? = pair?.leftPos + val leftBlock: Block? = pair?.left + val rightChestPos: Point? = pair?.rightPos + val rightBlock: Block? = pair?.right + + val leftHalf = fullItems.subList(0, 27) + val rightHalf = fullItems.subList(27, 54) + + val leftNBT = CompoundBinaryTag.builder() + .put("Inventory", ITEMSTACK_CODEC.encode(Transcoder.NBT, leftHalf).orElseThrow()) + .build() + val rightNBT = CompoundBinaryTag.builder() + .put("Inventory", ITEMSTACK_CODEC.encode(Transcoder.NBT, rightHalf).orElseThrow()) + .build() + + if (leftChestPos != null && leftBlock != null) { + instance.setBlock(leftChestPos, leftBlock.withNbt(leftNBT)) + } + if (rightChestPos != null && rightBlock != null) { + instance.setBlock(rightChestPos, rightBlock.withNbt(rightNBT)) + } + } else { + instance.setBlock( + blockPos, + block.withNbt( + CompoundBinaryTag.builder().put( + "Inventory", + ITEMSTACK_CODEC.encode( + Transcoder.NBT, + listOf(*inv.getItemStacks()) + ).orElseThrow() + ).build() + ) + ) + } + } + + private fun getBlockInventory(block: Block, blockPos: Point, instance: Instance): Inventory { + val type = block.getProperty("type") + val inv: Inventory + if (type.equals("left") || + type.equals("right") + ) { + inv = Inventory(InventoryType.CHEST_6_ROW, Component.text("Large Chest")) + val items: MutableList = + ArrayList(listOf(*arrayOfNulls(54))) + items.fill(ItemStack.AIR) + + val pair: ChestPair? = getChestPair(block, blockPos, instance) + val leftBlock: Block? = pair?.left + val rightBlock: Block? = pair?.right + + if (leftBlock != null && leftBlock.nbt() != null && leftBlock.nbt()!!.get("Inventory") != null) { + val leftItems = + ITEMSTACK_CODEC.decode(Transcoder.NBT, leftBlock.nbt()!!.get("Inventory")!!) + .orElse(listOf(ItemStack.AIR)) + for (i in 0.. inv.setItemStack(slot.getAndIncrement(), i!!) }) + } else { + inv = Inventory(InventoryType.CHEST_3_ROW, Component.text("Chest")) + + if (block.nbt() != null) { + if (block.nbt()!!.get("Inventory") != null) { + val tag = block.nbt()!!.get("Inventory") + val inventory = ITEMSTACK_CODEC.decode(Transcoder.NBT, tag!!).orElse( + listOf( + ItemStack.AIR + ) + ) + val slot = AtomicInteger(0) + inventory.forEach(Consumer { i: ItemStack? -> inv.setItemStack(slot.getAndIncrement(), i!!) }) + } + } else { + instance.setBlock( + blockPos, + block.withNbt( + CompoundBinaryTag.builder().put( + "Inventory", + ITEMSTACK_CODEC.encode( + Transcoder.NBT, + Arrays.asList(*inv.getItemStacks()) + ).orElseThrow() + ).build() + ) + ) + } + } + + + return inv + } + + private fun updateBlockAction(p: Player, blockPos: Point?, clickedBlock: Block, value: Byte) { + p.sendPacketToViewersAndSelf( + BlockActionPacket( + blockPos, + 1.toByte(), + value, + clickedBlock + ) + ) + } + + private fun getConnectedChestPos(origin: Point, chestBlock: Block): Point { + val facing = chestBlock.getProperty("facing") + val type = chestBlock.getProperty("type") + + var dx = 0 + var dz = 0 + + when (facing) { + "north" -> { + dx = (if (type == "left") 1 else -1) + } + + "south" -> { + dx = (if (type == "left") -1 else 1) + } + + "east" -> { + dz = (if (type == "left") 1 else -1) + } + + "west" -> { + dz = (if (type == "left") -1 else 1) + } + } + + return origin.withX(origin.x() + dx).withZ(origin.z() + dz) + } + + @JvmRecord + internal data class ChestPair(val leftPos: Point?, val left: Block?, val rightPos: Point?, val right: Block?) + + private fun getChestPair(block: Block, origin: Point, instance: Instance): ChestPair? { + if (block.getProperty("type").equals("left")) { + val rightPos = getConnectedChestPos(origin, block) + return ChestPair(origin, block, rightPos, instance.getBlock(rightPos)) + } else if (block.getProperty("type").equals("right")) { + val leftPos = getConnectedChestPos(origin, block) + return ChestPair(leftPos, instance.getBlock(leftPos), origin, block) + } + return null + } + + companion object { + private val ITEMSTACK_CODEC = ItemStack.CODEC.list() + } +} \ No newline at end of file diff --git a/blocksandstuff-blocks/src/main/kotlin/org/everbuild/blocksandstuff/blocks/group/VanillaBlockBehaviour.kt b/blocksandstuff-blocks/src/main/kotlin/org/everbuild/blocksandstuff/blocks/group/VanillaBlockBehaviour.kt index f34928a..2a87595 100644 --- a/blocksandstuff-blocks/src/main/kotlin/org/everbuild/blocksandstuff/blocks/group/VanillaBlockBehaviour.kt +++ b/blocksandstuff-blocks/src/main/kotlin/org/everbuild/blocksandstuff/blocks/group/VanillaBlockBehaviour.kt @@ -134,6 +134,12 @@ object VanillaBlockBehaviour : VanillaRuleset BlockHa ::StrippingBehaviorRule ) + val CHEST = group( + byBlock(Block.CHEST), + ::ChestRule + ) + + override fun createGroup( blockGroup: BlockGroup, valueFunction: (Block) -> BlockHandler diff --git a/blocksandstuff-blocks/src/main/kotlin/org/everbuild/blocksandstuff/blocks/placement/ChestPlacementRule.kt b/blocksandstuff-blocks/src/main/kotlin/org/everbuild/blocksandstuff/blocks/placement/ChestPlacementRule.kt index 7bf4dfc..9255af8 100644 --- a/blocksandstuff-blocks/src/main/kotlin/org/everbuild/blocksandstuff/blocks/placement/ChestPlacementRule.kt +++ b/blocksandstuff-blocks/src/main/kotlin/org/everbuild/blocksandstuff/blocks/placement/ChestPlacementRule.kt @@ -71,6 +71,7 @@ class ChestPlacementRule(block: Block) : BlockPlacementRule(block) { ) { return updateState.currentBlock .withProperty("type", "single") + .withNbt(updateState.currentBlock.nbt()) } return super.blockUpdate(updateState)