Skip to content

Commit

Permalink
Block picking patch
Browse files Browse the repository at this point in the history
Resolves #462
  • Loading branch information
NichtStudioCode committed Dec 6, 2024
1 parent 9834506 commit aff0cf4
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<Boolean> =
ContextParamType.builder<Boolean>("include_data")
.optionalIn(BlockInteract)
.build(false)

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<BlockInteract>): ItemStack? = null

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit aff0cf4

Please sign in to comment.