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 ce1ce18410fc1d47d999c918a8f880b43bf9797c..b9d31b0cbeabd5c62b810e50f612878624975d4e 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, powerForTime); // Paper - Pass draw strength // 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, powerForTime)) return false; // Paper - Pass draw strength // 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 2ab3da301f7edbb811c2e055f75434a799ef093a..74fd1472d6840b7106d8f424319170e2e891bbac 100644 --- a/net/minecraft/world/item/CrossbowItem.java +++ b/net/minecraft/world/item/CrossbowItem.java @@ -95,7 +95,7 @@ public class CrossbowItem extends ProjectileWeaponItem { } 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)); @@ -129,8 +129,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) { @@ -172,9 +174,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, 1); // Paper - Pass draw strength + if (!this.shoot(serverLevel, shooter, hand, weapon, chargedProjectiles.getItems(), velocity, inaccuracy, shooter instanceof Player, target, 1)) return; // Paper - Pass draw strength // 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 fb5077450aa9f7b7a03dd20c27a68dfdaab5ef06..f37fd3b9ab725e5b8eb7fccf9b35bbc09e3a2d0c 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, @@ -52,6 +65,24 @@ public abstract class ProjectileWeaponItem extends Item { @Nullable LivingEntity target ,float drawStrength // Paper - Pass draw strength ) { + // Paper start - prevent item consumption for cancelled events + return shoot(level, shooter, hand, weapon, new UnrealizedDrawResult(projectileItems, null), velocity, inaccuracy, isCrit, target, drawStrength); + } + protected boolean shoot( + ServerLevel level, + LivingEntity shooter, + InteractionHand hand, + ItemStack weapon, + UnrealizedDrawResult unrealizedDrawResult, + float velocity, + float inaccuracy, + boolean isCrit, + @Nullable LivingEntity target + ,float drawStrength // Paper - Pass draw strength + ) { + 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; @@ -67,11 +98,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, drawStrength, 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, drawStrength, 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( @@ -82,7 +115,25 @@ public abstract class ProjectileWeaponItem extends Item { if (shooter instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { serverPlayer.containerMenu.sendAllDataToRemote(); } - 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 @@ -92,6 +143,7 @@ public abstract class ProjectileWeaponItem extends Item { } } } + return atLeastOneShootBowEventUncancelled; // Paper - prevent item consumption for cancelled events } protected int getDurabilityUse(ItemStack stack) { @@ -115,11 +167,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 { @@ -127,8 +189,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); } @@ -139,17 +202,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 c8219508cf94da71143b8672661592c66b341782..6278fe737a555f47625098828a03952cf9b859a7 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -171,6 +171,7 @@ public abstract class Level implements LevelAccessor, UUIDLookup, AutoCl public final Map explosionDensityCache = new java.util.HashMap<>(); // Paper - Optimize explosions public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here 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 + public boolean shouldConsumeArrow = true; // Paper - prevent item consumption for cancelled shot events // Purpur start - Add adjustable breeding cooldown to config private com.google.common.cache.Cache playerBreedingCooldowns;