diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/context/param/DefaultContextParamTypes.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/context/param/DefaultContextParamTypes.kt index 345044785b..0a6150bb88 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/context/param/DefaultContextParamTypes.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/context/param/DefaultContextParamTypes.kt @@ -628,4 +628,21 @@ object DefaultContextParamTypes { .optionalIn(BlockPlace) .build(false) + /** + * Whether the data of the block should be included for creative-pick block interactions. + * + * Required in intentions: none + * + * Optional in intentions: + * - [BlockInteract] + * + * Autofilled by: none + * + * Autofills: none + */ + val INCLUDE_DATA: DefaultingContextParamType = + ContextParamType.builder("include_data") + .optionalIn(BlockInteract) + .build(false) + } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/block/BlockBehaviorPatches.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/block/BlockBehaviorPatches.kt index 5206511e26..c053de5ee5 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/block/BlockBehaviorPatches.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/block/BlockBehaviorPatches.kt @@ -3,8 +3,11 @@ package xyz.xenondevs.nova.patch.impl.block import net.minecraft.core.BlockPos import net.minecraft.core.Direction import net.minecraft.server.level.ServerLevel +import net.minecraft.server.network.ServerGamePacketListenerImpl import net.minecraft.util.RandomSource import net.minecraft.world.entity.Entity +import net.minecraft.world.entity.player.Player +import net.minecraft.world.item.ItemStack import net.minecraft.world.level.Level import net.minecraft.world.level.LevelReader import net.minecraft.world.level.ScheduledTickAccess @@ -14,11 +17,18 @@ import net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.redstone.Orientation import org.bukkit.block.data.BlockData +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.MethodInsnNode import xyz.xenondevs.bytebase.jvm.VirtualClassPath +import xyz.xenondevs.bytebase.util.replaceEvery import xyz.xenondevs.nova.LOGGER +import xyz.xenondevs.nova.context.Context +import xyz.xenondevs.nova.context.intention.DefaultContextIntentions +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes import xyz.xenondevs.nova.patch.MultiTransformer import xyz.xenondevs.nova.util.nmsBlockState import xyz.xenondevs.nova.util.toNovaPos +import xyz.xenondevs.nova.util.unwrap import xyz.xenondevs.nova.world.block.state.model.BackingStateConfig import xyz.xenondevs.nova.world.block.state.model.DisplayEntityBlockModelData import xyz.xenondevs.nova.world.format.WorldDataManager @@ -82,13 +92,21 @@ private val BLOCK_BEHAVIOR_ENTITY_INSIDE = BLOCK_BEHAVIOR_LOOKUP.findVirtual( ) ) -internal object BlockBehaviorPatches : MultiTransformer(BlockStateBase::class) { +internal object BlockBehaviorPatches : MultiTransformer(BlockStateBase::class, ServerGamePacketListenerImpl::class) { override fun transform() { VirtualClassPath[BlockStateBase::handleNeighborChanged].delegateStatic(::handleNeighborChanged) VirtualClassPath[BlockStateBase::updateShape].delegateStatic(::updateShape) VirtualClassPath[BlockStateBase::tick].delegateStatic(::tick) VirtualClassPath[BlockStateBase::entityInside].delegateStatic(::entityInside) + VirtualClassPath[ServerGamePacketListenerImpl::handlePickItemFromBlock].replaceEvery( + 0, 0, + { + aLoad(0) + getField(ServerGamePacketListenerImpl::player) + invokeStatic(::getCloneItemStack) + } + ) { it.opcode == Opcodes.INVOKEVIRTUAL && (it as MethodInsnNode).name == "getCloneItemStack" } } @JvmStatic @@ -169,4 +187,25 @@ internal object BlockBehaviorPatches : MultiTransformer(BlockStateBase::class) { } } + @JvmStatic + fun getCloneItemStack(blockState: BlockState, world: ServerLevel, pos: BlockPos, includeData: Boolean, player: Player): ItemStack { + val novaPos = pos.toNovaPos(world.world) + val novaState = WorldDataManager.getBlockState(novaPos) + if (novaState != null) { + try { + val ctx = Context.intention(DefaultContextIntentions.BlockInteract) + .param(DefaultContextParamTypes.BLOCK_POS, novaPos) + .param(DefaultContextParamTypes.BLOCK_STATE_NOVA, novaState) + .param(DefaultContextParamTypes.INCLUDE_DATA, includeData) + .param(DefaultContextParamTypes.SOURCE_ENTITY, player.bukkitEntity) + .build() + return novaState.block.pickBlockCreative(novaPos, novaState, ctx).unwrap() + } catch (e: Exception) { + LOGGER.error("Failed to get clone item stack for $novaState at $novaPos", e) + } + } + + return blockState.getCloneItemStack(world, pos, includeData) + } + } \ No newline at end of file diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/BlockBehavior.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/BlockBehavior.kt index 6916ad636d..e4607015af 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/BlockBehavior.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/BlockBehavior.kt @@ -6,6 +6,7 @@ import xyz.xenondevs.nova.context.Context import xyz.xenondevs.nova.context.intention.DefaultContextIntentions.BlockBreak import xyz.xenondevs.nova.context.intention.DefaultContextIntentions.BlockInteract import xyz.xenondevs.nova.context.intention.DefaultContextIntentions.BlockPlace +import xyz.xenondevs.nova.context.param.DefaultContextParamTypes import xyz.xenondevs.nova.integration.protection.ProtectionManager import xyz.xenondevs.nova.world.BlockPos import xyz.xenondevs.nova.world.block.NovaBlock @@ -95,6 +96,7 @@ interface BlockBehavior : BlockBehaviorHolder { /** * Chooses the [ItemStack] that should be given to the player when mid-clicking a block of [state] at [pos] with the given [ctx] in creative mode. + * @see DefaultContextParamTypes.INCLUDE_DATA */ fun pickBlockCreative(pos: BlockPos, state: NovaBlockState, ctx: Context): ItemStack? = null diff --git a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/interact/BlockInteracting.kt b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/interact/BlockInteracting.kt index 2ec3c51c0e..9232a9471d 100644 --- a/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/interact/BlockInteracting.kt +++ b/nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/interact/BlockInteracting.kt @@ -1,13 +1,7 @@ package xyz.xenondevs.nova.world.block.logic.interact -import net.minecraft.core.BlockPos -import net.minecraft.world.level.LevelReader -import net.minecraft.world.level.block.state.BlockBehaviour -import net.minecraft.world.level.block.state.BlockState -import org.bukkit.attribute.Attribute import org.bukkit.block.Block import org.bukkit.entity.EntityType -import org.bukkit.entity.Player import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority import org.bukkit.event.Listener @@ -17,10 +11,6 @@ import org.bukkit.event.block.BlockPistonExtendEvent import org.bukkit.event.block.BlockPistonRetractEvent import org.bukkit.event.entity.EntityChangeBlockEvent import org.bukkit.event.entity.EntityExplodeEvent -import org.bukkit.event.inventory.InventoryCreativeEvent -import org.bukkit.event.inventory.InventoryType -import org.bukkit.inventory.EquipmentSlot -import org.bukkit.inventory.ItemStack import xyz.xenondevs.nova.context.Context import xyz.xenondevs.nova.context.intention.DefaultContextIntentions import xyz.xenondevs.nova.context.intention.DefaultContextIntentions.BlockBreak @@ -29,29 +19,24 @@ import xyz.xenondevs.nova.initialize.InitFun import xyz.xenondevs.nova.initialize.InternalInit import xyz.xenondevs.nova.initialize.InternalInitStage import xyz.xenondevs.nova.integration.protection.ProtectionManager +import xyz.xenondevs.nova.network.event.PacketListener +import xyz.xenondevs.nova.network.event.registerPacketListener import xyz.xenondevs.nova.util.BlockUtils -import xyz.xenondevs.nova.util.nmsState -import xyz.xenondevs.nova.util.reflection.ReflectionUtils import xyz.xenondevs.nova.util.registerEvents -import xyz.xenondevs.nova.util.serverLevel import xyz.xenondevs.nova.world.format.WorldDataManager import xyz.xenondevs.nova.world.player.WrappedPlayerInteractEvent import xyz.xenondevs.nova.world.pos -import net.minecraft.world.item.ItemStack as MojangStack - -private val BLOCK_BEHAVIOR_GET_CLONE_ITEM_STACK = ReflectionUtils.getMethodHandle( - BlockBehaviour::class, "getCloneItemStack", LevelReader::class, BlockPos::class, BlockState::class, Boolean::class -) @InternalInit( stage = InternalInitStage.POST_WORLD, dependsOn = [WorldDataManager::class] ) -internal object BlockInteracting : Listener { +internal object BlockInteracting : Listener, PacketListener { @InitFun private fun init() { registerEvents() + registerPacketListener() } @EventHandler(priority = EventPriority.LOW) @@ -84,45 +69,6 @@ internal object BlockInteracting : Listener { } } - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - private fun handleInventoryCreative(event: InventoryCreativeEvent) { - if (event.slotType != InventoryType.SlotType.QUICKBAR) - return - - val player = event.whoClicked as Player - val reach = player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE)?.value ?: 8.0 - val rayTraceResult = player.rayTraceBlocks(reach) - ?: return - val targetBlock = rayTraceResult.hitBlock - ?: return - val targetPos = targetBlock.pos - - val vanillaCloneStack = (BLOCK_BEHAVIOR_GET_CLONE_ITEM_STACK.invoke( - targetBlock.world.serverLevel, - targetBlock.pos.nmsPos, - targetBlock.nmsState - ) as MojangStack).asBukkitMirror() - - if (vanillaCloneStack != event.cursor) - return - - val novaBlockState = WorldDataManager.getBlockState(targetPos) - ?: return - - val ctx = Context.intention(DefaultContextIntentions.BlockInteract) - .param(DefaultContextParamTypes.BLOCK_POS, targetPos) - .param(DefaultContextParamTypes.BLOCK_STATE_NOVA, novaBlockState) - .param(DefaultContextParamTypes.SOURCE_ENTITY, player) - .param(DefaultContextParamTypes.CLICKED_BLOCK_FACE, rayTraceResult.hitBlockFace) - .param(DefaultContextParamTypes.INTERACTION_HAND, EquipmentSlot.HAND) - .param(DefaultContextParamTypes.INTERACTION_ITEM_STACK, event.cursor) - .build() - - val novaCloneStack = novaBlockState.block.pickBlockCreative(targetPos, novaBlockState, ctx) ?: ItemStack.empty() - event.cursor = novaCloneStack - player.updateInventory() - } - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) private fun handlePistonExtend(event: BlockPistonExtendEvent) { if (event.blocks.any { WorldDataManager.getBlockState(it.pos) != null }) event.isCancelled = true