From 53ae2ba8003379b203a8fd1dc48ef0d1a6e46f1e Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Wed, 19 Mar 2025 00:48:24 -0400 Subject: [PATCH 1/2] PaperPR: Fix cancelled Projectile Events still consuming arrows --- ...elled-Projectile-Events-still-consum.patch | 242 ++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 leaf-server/minecraft-patches/features/0143-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch diff --git a/leaf-server/minecraft-patches/features/0143-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch b/leaf-server/minecraft-patches/features/0143-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch new file mode 100644 index 00000000..5f8594f2 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0143-PaperPR-Fix-cancelled-Projectile-Events-still-consum.patch @@ -0,0 +1,242 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tamion <70228790+notTamion@users.noreply.github.com> +Date: Sun, 16 Feb 2025 12:22:53 +0100 +Subject: [PATCH] PaperPR: Fix cancelled Projectile Events still consuming + arrows + +Original license: GPLv3 +Original project: https://github.com/PaperMC/Paper +Paper pull request: https://github.com/PaperMC/Paper/pull/12124 + +fixes https://github.com/PaperMC/Paper/issues/12123 + +diff --git a/net/minecraft/world/item/BowItem.java b/net/minecraft/world/item/BowItem.java +index b3e003694ce0da357e91ab3ce2b1380f9ab0a32a..4d5347a6a2fc8b10d11f89220f537c81a9a7be71 100644 +--- a/net/minecraft/world/item/BowItem.java ++++ b/net/minecraft/world/item/BowItem.java +@@ -41,9 +41,9 @@ public class BowItem extends ProjectileWeaponItem { + if (powerForTime < 0.1) { + return false; + } else { +- List list = draw(stack, projectile, player); ++ List list = draw(stack, projectile, player, ProjectileDrawingItemConsumption.MAYBE_LATER); // Paper - prevent item consumption for cancelled events + if (level instanceof ServerLevel serverLevel && !list.isEmpty()) { +- this.shoot(serverLevel, player, player.getUsedItemHand(), stack, list, powerForTime * 3.0F, (float) serverLevel.purpurConfig.bowProjectileOffset, powerForTime == 1.0F, null); // Purpur - Projectile offset config ++ if (!this.shoot(serverLevel, player, player.getUsedItemHand(), stack, new UnrealizedDrawResult(list, projectile), powerForTime * 3.0F, (float) serverLevel.purpurConfig.bowProjectileOffset, powerForTime == 1.0F, null)) return false; // Purpur - Projectile offset config // Paper - prevent item consumption for cancelled events + } + + level.playSound( +diff --git a/net/minecraft/world/item/CrossbowItem.java b/net/minecraft/world/item/CrossbowItem.java +index 318316d3aa14f1e9e863d435515c182619a81b3e..b13f03ae35e21088c2b4b388e2b12cf3fe3ef8c6 100644 +--- a/net/minecraft/world/item/CrossbowItem.java ++++ b/net/minecraft/world/item/CrossbowItem.java +@@ -124,7 +124,7 @@ public class CrossbowItem extends ProjectileWeaponItem { + return CrossbowItem.tryLoadProjectiles(shooter, crossbowStack, true); + } + private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack crossbowStack, boolean consume) { +- List list = draw(crossbowStack, shooter.getProjectile(crossbowStack), shooter, consume); ++ List list = draw(crossbowStack, shooter.getProjectile(crossbowStack), shooter, consume ? ProjectileDrawingItemConsumption.IMMEDIATELY : ProjectileDrawingItemConsumption.NEVER); // Paper - prevent item consumption for cancelled events + // Paper end - Add EntityLoadCrossbowEvent + if (!list.isEmpty()) { + crossbowStack.set(DataComponents.CHARGED_PROJECTILES, ChargedProjectiles.of(list)); +@@ -158,8 +158,10 @@ public class CrossbowItem extends ProjectileWeaponItem { + } + + projectile.shoot(projectileShotVector.x(), projectileShotVector.y(), projectileShotVector.z(), velocity, inaccuracy); +- float shotPitch = getShotPitch(shooter.getRandom(), index); +- shooter.level().playSound(null, shooter.getX(), shooter.getY(), shooter.getZ(), SoundEvents.CROSSBOW_SHOOT, shooter.getSoundSource(), 1.0F, shotPitch); ++ // Paper start - moved up to ensure events weren't cancelled ++ // float shotPitch = getShotPitch(shooter.getRandom(), index); ++ // shooter.level().playSound(null, shooter.getX(), shooter.getY(), shooter.getZ(), SoundEvents.CROSSBOW_SHOOT, shooter.getSoundSource(), 1.0F, shotPitch); ++ // Paper end - moved up to ensure events weren't cancelled + } + + private static Vector3f getProjectileShotVector(LivingEntity shooter, Vec3 distance, float angle) { +@@ -201,9 +203,9 @@ public class CrossbowItem extends ProjectileWeaponItem { + Level level, LivingEntity shooter, InteractionHand hand, ItemStack weapon, float velocity, float inaccuracy, @Nullable LivingEntity target + ) { + if (level instanceof ServerLevel serverLevel) { +- ChargedProjectiles chargedProjectiles = weapon.set(DataComponents.CHARGED_PROJECTILES, ChargedProjectiles.EMPTY); ++ ChargedProjectiles chargedProjectiles = weapon.get(DataComponents.CHARGED_PROJECTILES); // Paper - prevent item consumption for cancelled events + if (chargedProjectiles != null && !chargedProjectiles.isEmpty()) { +- this.shoot(serverLevel, shooter, hand, weapon, chargedProjectiles.getItems(), velocity, inaccuracy, shooter instanceof Player, target); ++ if (!this.shoot(serverLevel, shooter, hand, weapon, chargedProjectiles.getItems(), velocity, inaccuracy, shooter instanceof Player, target)) return; // Paper - prevent item consumption for cancelled events + if (shooter instanceof ServerPlayer serverPlayer) { + CriteriaTriggers.SHOT_CROSSBOW.trigger(serverPlayer, weapon); + serverPlayer.awardStat(Stats.ITEM_USED.get(weapon.getItem())); +diff --git a/net/minecraft/world/item/ProjectileWeaponItem.java b/net/minecraft/world/item/ProjectileWeaponItem.java +index f20c38c1ff978d00dc0c9810c050506deed44ebd..e58a40623c3a259c80d0f96686797445f54f3a6f 100644 +--- a/net/minecraft/world/item/ProjectileWeaponItem.java ++++ b/net/minecraft/world/item/ProjectileWeaponItem.java +@@ -40,7 +40,20 @@ public abstract class ProjectileWeaponItem extends Item { + + public abstract int getDefaultProjectileRange(); + +- protected void shoot( ++ // Paper start - prevent item consumption for cancelled events ++ protected record UnrealizedDrawResult( ++ List projectileStacks, ++ @Nullable ItemStack originalInPlayerInventory // Null in case the unrealised draw result is a noop (case of Crossbow) ++ ) { ++ public void consumeProjectilesFromPlayerInventory(int projectStackIndex) { ++ if (projectStackIndex != 0 || originalInPlayerInventory == null) return; ++ if (projectileStacks.isEmpty()) return; // Whatever happened here, nothing ++ final ItemStack nonIntangibleStack = projectileStacks.get(projectStackIndex); ++ originalInPlayerInventory.shrink(nonIntangibleStack.getCount()); ++ } ++ } ++ protected boolean shoot( ++ // Paper end - prevent item consumption for cancelled events + ServerLevel level, + LivingEntity shooter, + InteractionHand hand, +@@ -51,6 +64,23 @@ public abstract class ProjectileWeaponItem extends Item { + boolean isCrit, + @Nullable LivingEntity target + ) { ++ // Paper start - prevent item consumption for cancelled events ++ return shoot(level, shooter, hand, weapon, new UnrealizedDrawResult(projectileItems, null), velocity, inaccuracy, isCrit, target); ++ } ++ protected boolean shoot( ++ ServerLevel level, ++ LivingEntity shooter, ++ InteractionHand hand, ++ ItemStack weapon, ++ UnrealizedDrawResult unrealizedDrawResult, ++ float velocity, ++ float inaccuracy, ++ boolean isCrit, ++ @Nullable LivingEntity target ++ ) { ++ List projectileItems = unrealizedDrawResult.projectileStacks(); ++ boolean atLeastOneShootBowEventUncancelled = false; ++ // Paper end - prevent item consumption for cancelled events + float f = EnchantmentHelper.processProjectileSpread(level, weapon, shooter, 0.0F); + float f1 = projectileItems.size() == 1 ? 0.0F : 2.0F * f / (projectileItems.size() - 1); + float f2 = (projectileItems.size() - 1) % 2 * f1 / 2.0F; +@@ -66,11 +96,13 @@ public abstract class ProjectileWeaponItem extends Item { + Projectile projectile = this.createProjectile(level, shooter, weapon, itemStack, isCrit); + this.shootProjectile(shooter, projectile, i1, velocity, inaccuracy, f4, target); + +- org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(shooter, weapon, itemStack, projectile, hand, velocity, true); ++ boolean preConsumption = weapon.is(Items.CROSSBOW) || shooter.level().shouldConsumeArrow; ++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(shooter, weapon, itemStack, projectile, hand, velocity, preConsumption); + if (event.isCancelled()) { + event.getProjectile().remove(); +- return; ++ continue; // Paper - prevent item consumption for cancelled events; call for each shot projectile + } ++ atLeastOneShootBowEventUncancelled = true; // Paper - prevent item consumption for cancelled events + + if (event.getProjectile() == projectile.getBukkitEntity()) { + if (Projectile.spawnProjectile( +@@ -81,7 +113,25 @@ public abstract class ProjectileWeaponItem extends Item { + if (shooter instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { + serverPlayer.getBukkitEntity().updateInventory(); + } +- return; ++ // Paper start - prevent item consumption for cancelled events ++ continue; // call for each shot projectile ++ } ++ } ++ if (this instanceof CrossbowItem crossbow) { ++ // moved up to ensure events uncancelled ++ float shotPitch = crossbow.getShotPitch(shooter.getRandom(), i1); ++ shooter.level().playSound(null, shooter.getX(), shooter.getY(), shooter.getZ(), net.minecraft.sounds.SoundEvents.CROSSBOW_SHOOT, shooter.getSoundSource(), 1.0F, shotPitch); ++ } ++ if (!event.shouldConsumeItem() && projectile instanceof final AbstractArrow abstractArrow) ++ abstractArrow.pickup = AbstractArrow.Pickup.CREATIVE_ONLY; ++ if (event.shouldConsumeItem()) { ++ if (weapon.is(net.minecraft.world.item.Items.CROSSBOW)) { ++ List newProjectiles = new java.util.ArrayList<>(weapon.get(DataComponents.CHARGED_PROJECTILES).getItems()); ++ newProjectiles.remove(i - (projectileItems.size() - newProjectiles.size())); ++ weapon.set(DataComponents.CHARGED_PROJECTILES, net.minecraft.world.item.component.ChargedProjectiles.of(newProjectiles)); ++ } else if (level.shouldConsumeArrow) { ++ unrealizedDrawResult.consumeProjectilesFromPlayerInventory(i); ++ // Paper end - prevent item consumption for cancelled events + } + } + // CraftBukkit end +@@ -91,6 +141,7 @@ public abstract class ProjectileWeaponItem extends Item { + } + } + } ++ return atLeastOneShootBowEventUncancelled; // Paper - prevent item consumption for cancelled events + } + + protected int getDurabilityUse(ItemStack stack) { +@@ -114,11 +165,21 @@ public abstract class ProjectileWeaponItem extends Item { + } + + protected static List draw(ItemStack weapon, ItemStack ammo, LivingEntity shooter) { +- // Paper start +- return draw(weapon, ammo, shooter, true); ++ // Paper start - prevent item consumption for cancelled events ++ return draw(weapon, ammo, shooter, ProjectileDrawingItemConsumption.IMMEDIATELY); + } +- protected static List draw(ItemStack weapon, ItemStack ammo, LivingEntity shooter, boolean consume) { +- // Paper end ++ protected enum ProjectileDrawingItemConsumption { ++ // Will immediately consume from the passed projectile stack, like vanilla would ++ IMMEDIATELY, ++ // Will create a copyWithCount from the projectileStack, allowing for later reduction. ++ // The stacks yielded will adhere to vanilla's intangibility layout, with the first itemstack ++ // being tangible, allowing for it to be picked up once shot. ++ // Callers that do *not* consume later are responsible for marking the shot projectile as intangible. ++ MAYBE_LATER, ++ NEVER, ++ } ++ protected static List draw(ItemStack weapon, ItemStack ammo, LivingEntity shooter, final ProjectileDrawingItemConsumption consume) { ++ // Paper end - prevent item consumption for cancelled events + if (ammo.isEmpty()) { + return List.of(); + } else { +@@ -126,8 +187,9 @@ public abstract class ProjectileWeaponItem extends Item { + List list = new ArrayList<>(i); + ItemStack itemStack = ammo.copy(); + ++ shooter.level().shouldConsumeArrow = true; // Paper - prevent item consumption for cancelled events + for (int i1 = 0; i1 < i; i1++) { +- ItemStack itemStack1 = useAmmo(weapon, i1 == 0 ? ammo : itemStack, shooter, i1 > 0 || !consume); // Paper ++ ItemStack itemStack1 = useAmmo(weapon, i1 == 0 ? ammo : itemStack, shooter, i1 > 0, consume); // Paper + if (!itemStack1.isEmpty()) { + list.add(itemStack1); + } +@@ -138,17 +200,23 @@ public abstract class ProjectileWeaponItem extends Item { + } + + protected static ItemStack useAmmo(ItemStack weapon, ItemStack ammo, LivingEntity shooter, boolean intangable) { +- int i = !intangable && !shooter.hasInfiniteMaterials() && shooter.level() instanceof ServerLevel serverLevel ++ // Paper start - prevent item consumption for cancelled events ++ return useAmmo(weapon, ammo, shooter, intangable, ProjectileDrawingItemConsumption.IMMEDIATELY); ++ } ++ protected static ItemStack useAmmo(ItemStack weapon, ItemStack ammo, LivingEntity shooter, boolean intangable, final ProjectileDrawingItemConsumption consumption) { ++ int i = !intangable && consumption != ProjectileDrawingItemConsumption.NEVER && !shooter.hasInfiniteMaterials() && shooter.level() instanceof ServerLevel serverLevel + ? EnchantmentHelper.processAmmoUse(serverLevel, weapon, ammo, 1) + : 0; ++ // Paper end - prevent item consumption for cancelled events + if (i > ammo.getCount()) { + return ItemStack.EMPTY; + } else if (i == 0) { ++ if (!intangable) shooter.level().shouldConsumeArrow = false; // Paper - prevent item consumption for cancelled events + ItemStack itemStack = ammo.copyWithCount(1); + itemStack.set(DataComponents.INTANGIBLE_PROJECTILE, Unit.INSTANCE); + return itemStack; + } else { +- ItemStack itemStack = ammo.split(i); ++ ItemStack itemStack = consumption == ProjectileDrawingItemConsumption.MAYBE_LATER ? ammo.copyWithCount(i) : ammo.split(i); // Paper - prevent item consumption for cancelled events + if (ammo.isEmpty() && shooter instanceof Player player) { + player.getInventory().removeItem(ammo); + } +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index da579769cee175cfbf342efd643efe6f87e0a822..cfd66b5987a38bb3a4e32aa4c161e06a2d6be4be 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -183,6 +183,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + private int tileTickPosition; + public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions + public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here ++ public boolean shouldConsumeArrow = true; // Paper - prevent item consumption for cancelled shot events + + public final net.minecraft.world.level.levelgen.BitRandomSource simpleRandom = org.dreeam.leaf.config.modules.opt.FastRNG.enabled ? new org.dreeam.leaf.util.math.random.FasterRandomSource(net.minecraft.world.level.levelgen.RandomSupport.generateUniqueSeed()) : new ca.spottedleaf.moonrise.common.util.SimpleThreadUnsafeRandom(net.minecraft.world.level.levelgen.RandomSupport.generateUniqueSeed()); // Gale - Pufferfish - move random tick random // Leaf - Faster random generator + From c62c0bbd3d0e9d591360cba1f78749681dda70f3 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Wed, 19 Mar 2025 00:56:54 -0400 Subject: [PATCH 2/2] Fix build --- build-data/leaf.at | 1 + 1 file changed, 1 insertion(+) diff --git a/build-data/leaf.at b/build-data/leaf.at index 55828ad9..272f5d5a 100644 --- a/build-data/leaf.at +++ b/build-data/leaf.at @@ -10,6 +10,7 @@ public net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities nearbyE public net.minecraft.world.entity.decoration.ArmorStand noTickEquipmentDirty public net.minecraft.world.entity.monster.Shulker MAX_SCALE public net.minecraft.world.entity.player.Player canGlide()Z +public net.minecraft.world.item.CrossbowItem getShotPitch(Lnet/minecraft/util/RandomSource;I)F public net.minecraft.world.level.block.entity.FuelValues values public net.minecraft.world.level.chunk.storage.RegionFile getOversizedData(II)Lnet/minecraft/nbt/CompoundTag; public net.minecraft.world.level.chunk.storage.RegionFile isOversized(II)Z