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)