PaperMC / PaperMC/Paper

Cancelling ProjectileHitEvent of arrow with piercing prevents it from hitting the next target

Open
#11,132 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

status: accepted type: bug version: 1.21
Dominant language
Java
Stars
12.7k
Forks
3.5k
Avg merge
3d 13h
Merged PRs (30d)
11

Description

Expected behavior

Cancelling ProjectileHitEvent of an arrow shouldn't affect its next target

Observed/Actual behavior

Once ProjectileHitEvent of an arrow with piercing has been cancelled, the next hit entity doesn't hurt at all

On AbstractArrow.java:

@Override
public ProjectileDeflection preHitTargetOrDeflectSelf(HitResult hitResult) {
    if (hitResult instanceof EntityHitResult entityHitResult && this.hitCancelled && this.getPierceLevel() > 0) {
        if (this.piercingIgnoreEntityIds == null) {
            this.piercingIgnoreEntityIds = new IntOpenHashSet(5);
        }
        this.piercingIgnoreEntityIds.add(entityHitResult.getEntity().getId());
    }
    return super.preHitTargetOrDeflectSelf(hitResult);
}

this.hitCancelled is checked to add the hit entity to piercing-ignoring entities, but it's BEFORE super.preHitTargetOrDeflectSelf

On Projectile.java:

public ProjectileDeflection preHitTargetOrDeflectSelf(HitResult movingobjectposition) { // Paper - protected -> public
    org.bukkit.event.entity.ProjectileHitEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, movingobjectposition);
    this.hitCancelled = event != null && event.isCancelled();
    if (movingobjectposition.getType() == HitResult.Type.BLOCK || !this.hitCancelled) {
        return this.hitTargetOrDeflectSelf(movingobjectposition);
    }
    return ProjectileDeflection.NONE;
}

And here 'this.hitCancelled' is set by the ProjectileHitEvent result, but it's AFTER adding piercing-ignoring entity.

On AbstractArrow::onHitEntity:

if (this.piercingIgnoreEntityIds.size() >= this.getPierceLevel() + 1) {
    this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
    return;
}
// ...hit the entity...

If the piercing-ignoring entities is enough, the arrow is discarded before hitting entity.

Steps/models to reproduce
@EventHandler
fun onProjectileHit(event: ProjectileHitEvent) {
  if (event.hitEntity is Enemy) {
    event.isCancelled = true
  }
}
Plugin and Datapack List

My plugin only

Paper version

This server is running Paper version 1.21-107-master@aa36ae6 (2024-07-21T10:39:28Z) (Implementing API version 1.21-R0.1-SNAPSHOT)
You are running the latest version
Previous version: 1.21-99-f1f01a1 (MC: 1.21)

Other

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing AbstractArrow.java and Projectile.java through preHitTargetOrDeflectSelf and onHitEntity, focusing on when hitCancelled and piercingIgnoreEntityIds are updated. Reproduce the Kotlin ProjectileHitEvent cancellation case and verify that cancelling one pierced hit does not prevent the arrow from damaging its next target.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.