From aec9b7a2b98889e2d1169261d34450dd739a75c2 Mon Sep 17 00:00:00 2001 From: bluelhf Date: Sun, 12 Apr 2026 23:47:23 +0300 Subject: [PATCH 1/3] fix(interact-event): trigger player interact event for piercing weapon clicks on blocks --- .../server/network/ServerGamePacketListenerImpl.java.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch index d182a76be888..cab970a3228c 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch @@ -1711,7 +1711,7 @@ + CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelectedItem(), InteractionHand.MAIN_HAND); + } else { // Paper start - Call interact event + GameType gameType = this.player.gameMode.getGameModeForPlayer(); -+ if (gameType == GameType.ADVENTURE && result.getHitBlock() != null) { ++ if ((gameType == GameType.ADVENTURE || this.player.getInventory().getSelectedItem().has(DataComponents.PIERCING_WEAPON)) && result.getHitBlock() != null) { + CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, ((org.bukkit.craftbukkit.block.CraftBlock) result.getHitBlock()).getPosition(), org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(result.getHitBlockFace()), this.player.getInventory().getSelectedItem(), InteractionHand.MAIN_HAND); + } else if (gameType != GameType.CREATIVE && result.getHitEntity() != null && origin.toVector().distanceSquared(result.getHitPosition()) > this.player.entityInteractionRange() * this.player.entityInteractionRange()) { + CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelectedItem(), InteractionHand.MAIN_HAND); From 111ea77c5051c18541c2af1cf9c9eb9307b4fa03 Mon Sep 17 00:00:00 2001 From: bluelhf Date: Sun, 26 Apr 2026 19:02:35 +0300 Subject: [PATCH 2/3] fix(game-packets): extract common code for stab and swing events to method --- .../ServerGamePacketListenerImpl.java.patch | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch index cab970a3228c..0f827f0d24c6 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch @@ -1089,6 +1089,14 @@ if (this.hasClientLoaded()) { BlockPos pos = packet.getPos(); this.player.resetLastActionTime(); +@@ -1282,6 +_,7 @@ + PiercingWeapon piercingWeapon = itemInHand.get(DataComponents.PIERCING_WEAPON); + if (piercingWeapon != null) { + piercingWeapon.attack(this.player, EquipmentSlot.MAINHAND); ++ fireRayTracedInteractEvent(); + } + + return; @@ -1289,32 +_,95 @@ case SWAP_ITEM_WITH_OFFHAND: if (!this.player.isSpectator()) { @@ -1692,42 +1700,46 @@ } } } -@@ -1694,7 +_,34 @@ +@@ -1694,14 +_,60 @@ @Override public void handleAnimate(final ServerboundSwingPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level()); + if (this.player.isImmobile()) return; // CraftBukkit this.player.resetLastActionTime(); ++ fireRayTracedInteractEvent(); ++ ++ // Arm swing animation ++ io.papermc.paper.event.player.PlayerArmSwingEvent event = new io.papermc.paper.event.player.PlayerArmSwingEvent(this.getCraftPlayer(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(packet.getHand())); // Paper - Add PlayerArmSwingEvent ++ this.cserver.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) return; ++ // CraftBukkit end + this.player.swing(packet.getHand()); + } + ++ private void fireRayTracedInteractEvent() { // Paper start - Extract interact ray trace + // CraftBukkit start - Raytrace to look for 'rogue armswings' -+ Location origin = CraftLocation.toBukkit(this.player.getEyePosition(), this.player.level(), this.player.getYRot(), this.player.getXRot()); ++ org.bukkit.Location origin = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(this.player.getEyePosition(), this.player.level(), this.player.getYRot(), this.player.getXRot()); + double maxRange = Math.max(this.player.blockInteractionRange(), this.player.entityInteractionRange()); // todo flawed + // SPIGOT-5607: Only call interact event if no block or entity is being clicked. Use bukkit ray trace method, because it handles blocks and entities at the same time + // SPIGOT-7429: Make sure to call PlayerInteractEvent for spectators and non-pickable entities + org.bukkit.util.RayTraceResult result = this.player.level().getWorld().rayTrace(origin, origin.getDirection(), maxRange, org.bukkit.FluidCollisionMode.NEVER, false, 0.0, entity -> { // Paper - Call interact event; change raySize from 0.1 to 0.0 -+ Entity handle = ((CraftEntity) entity).getHandle(); ++ net.minecraft.world.entity.Entity handle = ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle(); + return entity != this.player.getBukkitEntity() && this.player.getBukkitEntity().canSee(entity) && !handle.isSpectator() && handle.isPickable() && !handle.isPassengerOfSameVehicle(this.player); + }); + if (result == null) { -+ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelectedItem(), InteractionHand.MAIN_HAND); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(this.player, org.bukkit.event.block.Action.LEFT_CLICK_AIR, this.player.getInventory().getSelectedItem(), net.minecraft.world.InteractionHand.MAIN_HAND); + } else { // Paper start - Call interact event -+ GameType gameType = this.player.gameMode.getGameModeForPlayer(); -+ if ((gameType == GameType.ADVENTURE || this.player.getInventory().getSelectedItem().has(DataComponents.PIERCING_WEAPON)) && result.getHitBlock() != null) { -+ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, ((org.bukkit.craftbukkit.block.CraftBlock) result.getHitBlock()).getPosition(), org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(result.getHitBlockFace()), this.player.getInventory().getSelectedItem(), InteractionHand.MAIN_HAND); -+ } else if (gameType != GameType.CREATIVE && result.getHitEntity() != null && origin.toVector().distanceSquared(result.getHitPosition()) > this.player.entityInteractionRange() * this.player.entityInteractionRange()) { -+ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelectedItem(), InteractionHand.MAIN_HAND); ++ net.minecraft.world.level.GameType gameType = this.player.gameMode.getGameModeForPlayer(); ++ if (gameType == net.minecraft.world.level.GameType.ADVENTURE && result.getHitBlock() != null) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(this.player, org.bukkit.event.block.Action.LEFT_CLICK_BLOCK, ((org.bukkit.craftbukkit.block.CraftBlock) result.getHitBlock()).getPosition(), org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(result.getHitBlockFace()), this.player.getInventory().getSelectedItem(), net.minecraft.world.InteractionHand.MAIN_HAND); ++ } else if (gameType != net.minecraft.world.level.GameType.CREATIVE && result.getHitEntity() != null && origin.toVector().distanceSquared(result.getHitPosition()) > this.player.entityInteractionRange() * this.player.entityInteractionRange()) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(this.player, org.bukkit.event.block.Action.LEFT_CLICK_AIR, this.player.getInventory().getSelectedItem(), net.minecraft.world.InteractionHand.MAIN_HAND); + } + } // Paper end - Call interact event ++ } // Paper end - Extract interact ray trace + -+ // Arm swing animation -+ io.papermc.paper.event.player.PlayerArmSwingEvent event = new io.papermc.paper.event.player.PlayerArmSwingEvent(this.getCraftPlayer(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(packet.getHand())); // Paper - Add PlayerArmSwingEvent -+ this.cserver.getPluginManager().callEvent(event); -+ -+ if (event.isCancelled()) return; -+ // CraftBukkit end - this.player.swing(packet.getHand()); - } - -@@ -1702,6 +_,21 @@ + @Override public void handlePlayerCommand(final ServerboundPlayerCommandPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level()); if (this.hasClientLoaded()) { From f894cd4ec907fe1992429b6984eb7b7664b53299 Mon Sep 17 00:00:00 2001 From: bluelhf Date: Sun, 26 Apr 2026 20:04:10 +0300 Subject: [PATCH 3/3] fix(interact-event): trigger interact event properly the only remaining bug is because of the problematic maxRange calculation (which was that way before) --- .../ServerGamePacketListenerImpl.java.patch | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch index 0f827f0d24c6..ee3004c4f3b5 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch @@ -1089,11 +1089,18 @@ if (this.hasClientLoaded()) { BlockPos pos = packet.getPos(); this.player.resetLastActionTime(); -@@ -1282,6 +_,7 @@ +@@ -1282,6 +_,14 @@ PiercingWeapon piercingWeapon = itemInHand.get(DataComponents.PIERCING_WEAPON); if (piercingWeapon != null) { piercingWeapon.attack(this.player, EquipmentSlot.MAINHAND); -+ fireRayTracedInteractEvent(); ++ ++ // Paper start - Call interact event ++ Location origin = CraftLocation.toBukkit(this.player.getEyePosition(), this.player.level(), this.player.getYRot(), this.player.getXRot()); ++ org.bukkit.util.RayTraceResult result = doInteractionRayTrace(origin); ++ ++ if (result != null && result.getHitBlock() != null) { ++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, ((org.bukkit.craftbukkit.block.CraftBlock) result.getHitBlock()).getPosition(), org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(result.getHitBlockFace()), itemInHand, InteractionHand.MAIN_HAND); ++ } // Paper end - Call interact event } return; @@ -1706,7 +1713,19 @@ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level()); + if (this.player.isImmobile()) return; // CraftBukkit this.player.resetLastActionTime(); -+ fireRayTracedInteractEvent(); ++ // CraftBukkit start - Raytrace to look for 'rogue armswings' ++ Location origin = CraftLocation.toBukkit(this.player.getEyePosition(), this.player.level(), this.player.getYRot(), this.player.getXRot()); ++ org.bukkit.util.RayTraceResult result = doInteractionRayTrace(origin); ++ if (result == null) { ++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelectedItem(), InteractionHand.MAIN_HAND); ++ } else { // Paper start - Call interact event ++ GameType gameType = this.player.gameMode.getGameModeForPlayer(); ++ if (gameType == GameType.ADVENTURE && result.getHitBlock() != null) { ++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, ((org.bukkit.craftbukkit.block.CraftBlock) result.getHitBlock()).getPosition(), org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(result.getHitBlockFace()), this.player.getInventory().getSelectedItem(), InteractionHand.MAIN_HAND); ++ } else if (gameType != GameType.CREATIVE && result.getHitEntity() != null && origin.toVector().distanceSquared(result.getHitPosition()) > this.player.entityInteractionRange() * this.player.entityInteractionRange()) { ++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelectedItem(), InteractionHand.MAIN_HAND); ++ } ++ } // Paper end - Call interact event + + // Arm swing animation + io.papermc.paper.event.player.PlayerArmSwingEvent event = new io.papermc.paper.event.player.PlayerArmSwingEvent(this.getCraftPlayer(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(packet.getHand())); // Paper - Add PlayerArmSwingEvent @@ -1717,27 +1736,15 @@ this.player.swing(packet.getHand()); } -+ private void fireRayTracedInteractEvent() { // Paper start - Extract interact ray trace -+ // CraftBukkit start - Raytrace to look for 'rogue armswings' -+ org.bukkit.Location origin = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(this.player.getEyePosition(), this.player.level(), this.player.getYRot(), this.player.getXRot()); ++ private org.bukkit.util.@org.jspecify.annotations.Nullable RayTraceResult doInteractionRayTrace(org.bukkit.Location origin) { + double maxRange = Math.max(this.player.blockInteractionRange(), this.player.entityInteractionRange()); // todo flawed + // SPIGOT-5607: Only call interact event if no block or entity is being clicked. Use bukkit ray trace method, because it handles blocks and entities at the same time + // SPIGOT-7429: Make sure to call PlayerInteractEvent for spectators and non-pickable entities -+ org.bukkit.util.RayTraceResult result = this.player.level().getWorld().rayTrace(origin, origin.getDirection(), maxRange, org.bukkit.FluidCollisionMode.NEVER, false, 0.0, entity -> { // Paper - Call interact event; change raySize from 0.1 to 0.0 ++ return this.player.level().getWorld().rayTrace(origin, origin.getDirection(), maxRange, org.bukkit.FluidCollisionMode.NEVER, false, 0.0, entity -> { // Paper - Call interact event; change raySize from 0.1 to 0.0 + net.minecraft.world.entity.Entity handle = ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle(); + return entity != this.player.getBukkitEntity() && this.player.getBukkitEntity().canSee(entity) && !handle.isSpectator() && handle.isPickable() && !handle.isPassengerOfSameVehicle(this.player); + }); -+ if (result == null) { -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(this.player, org.bukkit.event.block.Action.LEFT_CLICK_AIR, this.player.getInventory().getSelectedItem(), net.minecraft.world.InteractionHand.MAIN_HAND); -+ } else { // Paper start - Call interact event -+ net.minecraft.world.level.GameType gameType = this.player.gameMode.getGameModeForPlayer(); -+ if (gameType == net.minecraft.world.level.GameType.ADVENTURE && result.getHitBlock() != null) { -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(this.player, org.bukkit.event.block.Action.LEFT_CLICK_BLOCK, ((org.bukkit.craftbukkit.block.CraftBlock) result.getHitBlock()).getPosition(), org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(result.getHitBlockFace()), this.player.getInventory().getSelectedItem(), net.minecraft.world.InteractionHand.MAIN_HAND); -+ } else if (gameType != net.minecraft.world.level.GameType.CREATIVE && result.getHitEntity() != null && origin.toVector().distanceSquared(result.getHitPosition()) > this.player.entityInteractionRange() * this.player.entityInteractionRange()) { -+ org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(this.player, org.bukkit.event.block.Action.LEFT_CLICK_AIR, this.player.getInventory().getSelectedItem(), net.minecraft.world.InteractionHand.MAIN_HAND); -+ } -+ } // Paper end - Call interact event -+ } // Paper end - Extract interact ray trace ++ } + @Override public void handlePlayerCommand(final ServerboundPlayerCommandPacket packet) {