Skip to content

Commit aff0cf4

Browse files
Block picking patch
Resolves #462
1 parent 9834506 commit aff0cf4

File tree

4 files changed

+63
-59
lines changed

4 files changed

+63
-59
lines changed

nova/src/main/kotlin/xyz/xenondevs/nova/context/param/DefaultContextParamTypes.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,4 +628,21 @@ object DefaultContextParamTypes {
628628
.optionalIn(BlockPlace)
629629
.build(false)
630630

631+
/**
632+
* Whether the data of the block should be included for creative-pick block interactions.
633+
*
634+
* Required in intentions: none
635+
*
636+
* Optional in intentions:
637+
* - [BlockInteract]
638+
*
639+
* Autofilled by: none
640+
*
641+
* Autofills: none
642+
*/
643+
val INCLUDE_DATA: DefaultingContextParamType<Boolean> =
644+
ContextParamType.builder<Boolean>("include_data")
645+
.optionalIn(BlockInteract)
646+
.build(false)
647+
631648
}

nova/src/main/kotlin/xyz/xenondevs/nova/patch/impl/block/BlockBehaviorPatches.kt

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ package xyz.xenondevs.nova.patch.impl.block
33
import net.minecraft.core.BlockPos
44
import net.minecraft.core.Direction
55
import net.minecraft.server.level.ServerLevel
6+
import net.minecraft.server.network.ServerGamePacketListenerImpl
67
import net.minecraft.util.RandomSource
78
import net.minecraft.world.entity.Entity
9+
import net.minecraft.world.entity.player.Player
10+
import net.minecraft.world.item.ItemStack
811
import net.minecraft.world.level.Level
912
import net.minecraft.world.level.LevelReader
1013
import net.minecraft.world.level.ScheduledTickAccess
@@ -14,11 +17,18 @@ import net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase
1417
import net.minecraft.world.level.block.state.BlockState
1518
import net.minecraft.world.level.redstone.Orientation
1619
import org.bukkit.block.data.BlockData
20+
import org.objectweb.asm.Opcodes
21+
import org.objectweb.asm.tree.MethodInsnNode
1722
import xyz.xenondevs.bytebase.jvm.VirtualClassPath
23+
import xyz.xenondevs.bytebase.util.replaceEvery
1824
import xyz.xenondevs.nova.LOGGER
25+
import xyz.xenondevs.nova.context.Context
26+
import xyz.xenondevs.nova.context.intention.DefaultContextIntentions
27+
import xyz.xenondevs.nova.context.param.DefaultContextParamTypes
1928
import xyz.xenondevs.nova.patch.MultiTransformer
2029
import xyz.xenondevs.nova.util.nmsBlockState
2130
import xyz.xenondevs.nova.util.toNovaPos
31+
import xyz.xenondevs.nova.util.unwrap
2232
import xyz.xenondevs.nova.world.block.state.model.BackingStateConfig
2333
import xyz.xenondevs.nova.world.block.state.model.DisplayEntityBlockModelData
2434
import xyz.xenondevs.nova.world.format.WorldDataManager
@@ -82,13 +92,21 @@ private val BLOCK_BEHAVIOR_ENTITY_INSIDE = BLOCK_BEHAVIOR_LOOKUP.findVirtual(
8292
)
8393
)
8494

85-
internal object BlockBehaviorPatches : MultiTransformer(BlockStateBase::class) {
95+
internal object BlockBehaviorPatches : MultiTransformer(BlockStateBase::class, ServerGamePacketListenerImpl::class) {
8696

8797
override fun transform() {
8898
VirtualClassPath[BlockStateBase::handleNeighborChanged].delegateStatic(::handleNeighborChanged)
8999
VirtualClassPath[BlockStateBase::updateShape].delegateStatic(::updateShape)
90100
VirtualClassPath[BlockStateBase::tick].delegateStatic(::tick)
91101
VirtualClassPath[BlockStateBase::entityInside].delegateStatic(::entityInside)
102+
VirtualClassPath[ServerGamePacketListenerImpl::handlePickItemFromBlock].replaceEvery(
103+
0, 0,
104+
{
105+
aLoad(0)
106+
getField(ServerGamePacketListenerImpl::player)
107+
invokeStatic(::getCloneItemStack)
108+
}
109+
) { it.opcode == Opcodes.INVOKEVIRTUAL && (it as MethodInsnNode).name == "getCloneItemStack" }
92110
}
93111

94112
@JvmStatic
@@ -169,4 +187,25 @@ internal object BlockBehaviorPatches : MultiTransformer(BlockStateBase::class) {
169187
}
170188
}
171189

190+
@JvmStatic
191+
fun getCloneItemStack(blockState: BlockState, world: ServerLevel, pos: BlockPos, includeData: Boolean, player: Player): ItemStack {
192+
val novaPos = pos.toNovaPos(world.world)
193+
val novaState = WorldDataManager.getBlockState(novaPos)
194+
if (novaState != null) {
195+
try {
196+
val ctx = Context.intention(DefaultContextIntentions.BlockInteract)
197+
.param(DefaultContextParamTypes.BLOCK_POS, novaPos)
198+
.param(DefaultContextParamTypes.BLOCK_STATE_NOVA, novaState)
199+
.param(DefaultContextParamTypes.INCLUDE_DATA, includeData)
200+
.param(DefaultContextParamTypes.SOURCE_ENTITY, player.bukkitEntity)
201+
.build()
202+
return novaState.block.pickBlockCreative(novaPos, novaState, ctx).unwrap()
203+
} catch (e: Exception) {
204+
LOGGER.error("Failed to get clone item stack for $novaState at $novaPos", e)
205+
}
206+
}
207+
208+
return blockState.getCloneItemStack(world, pos, includeData)
209+
}
210+
172211
}

nova/src/main/kotlin/xyz/xenondevs/nova/world/block/behavior/BlockBehavior.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import xyz.xenondevs.nova.context.Context
66
import xyz.xenondevs.nova.context.intention.DefaultContextIntentions.BlockBreak
77
import xyz.xenondevs.nova.context.intention.DefaultContextIntentions.BlockInteract
88
import xyz.xenondevs.nova.context.intention.DefaultContextIntentions.BlockPlace
9+
import xyz.xenondevs.nova.context.param.DefaultContextParamTypes
910
import xyz.xenondevs.nova.integration.protection.ProtectionManager
1011
import xyz.xenondevs.nova.world.BlockPos
1112
import xyz.xenondevs.nova.world.block.NovaBlock
@@ -95,6 +96,7 @@ interface BlockBehavior : BlockBehaviorHolder {
9596

9697
/**
9798
* 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.
99+
* @see DefaultContextParamTypes.INCLUDE_DATA
98100
*/
99101
fun pickBlockCreative(pos: BlockPos, state: NovaBlockState, ctx: Context<BlockInteract>): ItemStack? = null
100102

nova/src/main/kotlin/xyz/xenondevs/nova/world/block/logic/interact/BlockInteracting.kt

Lines changed: 4 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
package xyz.xenondevs.nova.world.block.logic.interact
22

3-
import net.minecraft.core.BlockPos
4-
import net.minecraft.world.level.LevelReader
5-
import net.minecraft.world.level.block.state.BlockBehaviour
6-
import net.minecraft.world.level.block.state.BlockState
7-
import org.bukkit.attribute.Attribute
83
import org.bukkit.block.Block
94
import org.bukkit.entity.EntityType
10-
import org.bukkit.entity.Player
115
import org.bukkit.event.EventHandler
126
import org.bukkit.event.EventPriority
137
import org.bukkit.event.Listener
@@ -17,10 +11,6 @@ import org.bukkit.event.block.BlockPistonExtendEvent
1711
import org.bukkit.event.block.BlockPistonRetractEvent
1812
import org.bukkit.event.entity.EntityChangeBlockEvent
1913
import org.bukkit.event.entity.EntityExplodeEvent
20-
import org.bukkit.event.inventory.InventoryCreativeEvent
21-
import org.bukkit.event.inventory.InventoryType
22-
import org.bukkit.inventory.EquipmentSlot
23-
import org.bukkit.inventory.ItemStack
2414
import xyz.xenondevs.nova.context.Context
2515
import xyz.xenondevs.nova.context.intention.DefaultContextIntentions
2616
import xyz.xenondevs.nova.context.intention.DefaultContextIntentions.BlockBreak
@@ -29,29 +19,24 @@ import xyz.xenondevs.nova.initialize.InitFun
2919
import xyz.xenondevs.nova.initialize.InternalInit
3020
import xyz.xenondevs.nova.initialize.InternalInitStage
3121
import xyz.xenondevs.nova.integration.protection.ProtectionManager
22+
import xyz.xenondevs.nova.network.event.PacketListener
23+
import xyz.xenondevs.nova.network.event.registerPacketListener
3224
import xyz.xenondevs.nova.util.BlockUtils
33-
import xyz.xenondevs.nova.util.nmsState
34-
import xyz.xenondevs.nova.util.reflection.ReflectionUtils
3525
import xyz.xenondevs.nova.util.registerEvents
36-
import xyz.xenondevs.nova.util.serverLevel
3726
import xyz.xenondevs.nova.world.format.WorldDataManager
3827
import xyz.xenondevs.nova.world.player.WrappedPlayerInteractEvent
3928
import xyz.xenondevs.nova.world.pos
40-
import net.minecraft.world.item.ItemStack as MojangStack
41-
42-
private val BLOCK_BEHAVIOR_GET_CLONE_ITEM_STACK = ReflectionUtils.getMethodHandle(
43-
BlockBehaviour::class, "getCloneItemStack", LevelReader::class, BlockPos::class, BlockState::class, Boolean::class
44-
)
4529

4630
@InternalInit(
4731
stage = InternalInitStage.POST_WORLD,
4832
dependsOn = [WorldDataManager::class]
4933
)
50-
internal object BlockInteracting : Listener {
34+
internal object BlockInteracting : Listener, PacketListener {
5135

5236
@InitFun
5337
private fun init() {
5438
registerEvents()
39+
registerPacketListener()
5540
}
5641

5742
@EventHandler(priority = EventPriority.LOW)
@@ -84,45 +69,6 @@ internal object BlockInteracting : Listener {
8469
}
8570
}
8671

87-
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
88-
private fun handleInventoryCreative(event: InventoryCreativeEvent) {
89-
if (event.slotType != InventoryType.SlotType.QUICKBAR)
90-
return
91-
92-
val player = event.whoClicked as Player
93-
val reach = player.getAttribute(Attribute.BLOCK_INTERACTION_RANGE)?.value ?: 8.0
94-
val rayTraceResult = player.rayTraceBlocks(reach)
95-
?: return
96-
val targetBlock = rayTraceResult.hitBlock
97-
?: return
98-
val targetPos = targetBlock.pos
99-
100-
val vanillaCloneStack = (BLOCK_BEHAVIOR_GET_CLONE_ITEM_STACK.invoke(
101-
targetBlock.world.serverLevel,
102-
targetBlock.pos.nmsPos,
103-
targetBlock.nmsState
104-
) as MojangStack).asBukkitMirror()
105-
106-
if (vanillaCloneStack != event.cursor)
107-
return
108-
109-
val novaBlockState = WorldDataManager.getBlockState(targetPos)
110-
?: return
111-
112-
val ctx = Context.intention(DefaultContextIntentions.BlockInteract)
113-
.param(DefaultContextParamTypes.BLOCK_POS, targetPos)
114-
.param(DefaultContextParamTypes.BLOCK_STATE_NOVA, novaBlockState)
115-
.param(DefaultContextParamTypes.SOURCE_ENTITY, player)
116-
.param(DefaultContextParamTypes.CLICKED_BLOCK_FACE, rayTraceResult.hitBlockFace)
117-
.param(DefaultContextParamTypes.INTERACTION_HAND, EquipmentSlot.HAND)
118-
.param(DefaultContextParamTypes.INTERACTION_ITEM_STACK, event.cursor)
119-
.build()
120-
121-
val novaCloneStack = novaBlockState.block.pickBlockCreative(targetPos, novaBlockState, ctx) ?: ItemStack.empty()
122-
event.cursor = novaCloneStack
123-
player.updateInventory()
124-
}
125-
12672
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
12773
private fun handlePistonExtend(event: BlockPistonExtendEvent) {
12874
if (event.blocks.any { WorldDataManager.getBlockState(it.pos) != null }) event.isCancelled = true

0 commit comments

Comments
 (0)