9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2026-01-06 15:51:31 +00:00
Files
Leaf/leaf-server/minecraft-patches/features/0211-Paper-PR-Fix-cancelled-Projectile-Events-still-consu.patch
Dreeam 9a4efaa230 Drop patch that causes performance regression
Originally vanilla logic is to use stream, and Mojang switched it to Guava's Collections2
since 1.21.4. It is much faster than using stream or manually adding to a new ArrayList.
Manually adding to a new ArrayList requires allocating a new object array. However, the Collections2
lazy handles filter condition on iteration, so much better.
2025-08-04 19:25:56 +08:00

246 lines
17 KiB
Diff

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] Paper PR: 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..ef8b4bbbd24e1e91cad4fdb028388721c993fc73 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<ItemStack> list = draw(stack, projectile, player);
+ List<ItemStack> list = draw(stack, projectile, player, ProjectileDrawingItemConsumption.MAYBE_LATER); // Paper PR - 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 PR - 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 d49a5360d4a21e5b15bac94a823831e25d242a3d..04286fdba0c22d5b6d7b3ab71ee1a1fe3ed92e6d 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<ItemStack> list = draw(crossbowStack, shooter.getProjectile(crossbowStack), shooter, consume);
+ List<ItemStack> list = draw(crossbowStack, shooter.getProjectile(crossbowStack), shooter, consume ? ProjectileDrawingItemConsumption.IMMEDIATELY : ProjectileDrawingItemConsumption.NEVER); // Paper PR - 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 PR 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 PR 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 PR - 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 PR - 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 f12b9e4e8a78c713782af548d1cb15ef363305b4..1ac688cc89c32168bcb3ecda2a4e1f993098b3aa 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 PR start - prevent item consumption for cancelled events
+ protected record UnrealizedDrawResult(
+ List<ItemStack> 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 PR 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 PR 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<ItemStack> projectileItems = unrealizedDrawResult.projectileStacks();
+ boolean atLeastOneShootBowEventUncancelled = false;
+ // Paper PR 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,15 @@ 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);
+ // Paper PR start - prevent item consumption for cancelled events; call for each shot projectile
+ 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);
+ // Paper PR end - prevent item consumption for cancelled events; call for each shot projectile
if (event.isCancelled()) {
event.getProjectile().remove();
- return;
+ continue; // Paper PR - prevent item consumption for cancelled events; call for each shot projectile
}
+ atLeastOneShootBowEventUncancelled = true; // Paper PR - prevent item consumption for cancelled events
if (event.getProjectile() == projectile.getBukkitEntity()) {
if (Projectile.spawnProjectile(
@@ -79,7 +114,25 @@ public abstract class ProjectileWeaponItem extends Item {
level,
itemStack
).isRemoved()) {
- return;
+ // Paper PR 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<ItemStack> 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 PR end - prevent item consumption for cancelled events
}
}
// CraftBukkit end
@@ -89,6 +142,7 @@ public abstract class ProjectileWeaponItem extends Item {
}
}
}
+ return atLeastOneShootBowEventUncancelled; // Paper PR - prevent item consumption for cancelled events
}
protected int getDurabilityUse(ItemStack stack) {
@@ -112,11 +166,21 @@ public abstract class ProjectileWeaponItem extends Item {
}
protected static List<ItemStack> draw(ItemStack weapon, ItemStack ammo, LivingEntity shooter) {
- // Paper start
- return draw(weapon, ammo, shooter, true);
+ // Paper PR start - prevent item consumption for cancelled events
+ return draw(weapon, ammo, shooter, ProjectileDrawingItemConsumption.IMMEDIATELY);
}
- protected static List<ItemStack> 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<ItemStack> draw(ItemStack weapon, ItemStack ammo, LivingEntity shooter, final ProjectileDrawingItemConsumption consume) {
+ // Paper PR end - prevent item consumption for cancelled events
if (ammo.isEmpty()) {
return List.of();
} else {
@@ -124,8 +188,9 @@ public abstract class ProjectileWeaponItem extends Item {
List<ItemStack> list = new ArrayList<>(i);
ItemStack itemStack = ammo.copy();
+ shooter.level().shouldConsumeArrow = true; // Paper PR - 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 PR - prevent item consumption for cancelled events
if (!itemStack1.isEmpty()) {
list.add(itemStack1);
}
@@ -136,17 +201,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 PR 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 PR 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 PR - 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 PR - 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 93be887352ac5995672a18b7289e5f4d0ca25870..f881866e460b1361f3691c5a999277673845f64e 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<Entity>, AutoCl
public final Map<ServerExplosion.CacheKey, Float> explosionDensityCache = new java.util.HashMap<>(); // Paper - Optimize explosions
public java.util.ArrayDeque<net.minecraft.world.level.block.RedstoneTorchBlock.Toggle> 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 PR - prevent item consumption for cancelled shot events
// Purpur start - Add adjustable breeding cooldown to config
private com.google.common.cache.Cache<BreedingCooldownPair, Object> playerBreedingCooldowns;