Skip to content

Assign BlockProjectileSource before dispenser projectiles spawn - #14201

Open
charlierichards13 wants to merge 1 commit into
PaperMC:mainfrom
charlierichards13:fix-dispenser-projectile-source
Open

Assign BlockProjectileSource before dispenser projectiles spawn#14201
charlierichards13 wants to merge 1 commit into
PaperMC:mainfrom
charlierichards13:fix-dispenser-projectile-source

Conversation

@charlierichards13

Copy link
Copy Markdown

Fixes #14194.

Problem

ProjectileLaunchEvent#getEntity().getShooter() returned null for projectiles fired from a dispenser, even though the expected shooter is a BlockProjectileSource. This PR addresses the dispenser path specifically, in ProjectileDispenseBehavior.

Root cause

ProjectileDispenseBehavior used Projectile.spawnProjectileUsingShoot(...), which delegates to the delayed implementation and then immediately calls .spawn(). That spawn call goes through addFreshEntity(...), the point at which the projectile enters the world and ProjectileLaunchEvent fires.

Only once that call returned did the dispenser behavior assign:

projectile.projectileSource = new CraftBlockProjectileSource(...);

So the effective ordering was:

create/configure projectile
  -> spawn projectile
  -> ProjectileLaunchEvent fires
  -> projectileSource is assigned

The event therefore observed a shooter that had not been set yet.

Changes

Creation/configuration is now split from the actual spawn. The projectile is created via asProjectile(...), configured with Projectile.spawnProjectileUsingShootDelayed(...) (which sets it up without inserting it into the world), assigned its CraftBlockProjectileSource while still unspawned, and only then spawned via delayedProjectile.spawn().

New ordering:

create projectile
  -> configure launch velocity
  -> assign CraftBlockProjectileSource
  -> spawn projectile
  -> ProjectileLaunchEvent fires

This reuses Paper's existing delayed-projectile mechanism rather than introducing new API or a new spawning path. Launch velocity calculation is unchanged; spawnProjectileUsingShootDelayed runs the same shoot(...) setup; it just postpones the final world insertion until after the source has been attached.

Scope is one source patch against ProjectileDispenseBehavior. No API changes and no persistent test-plugin changes.

Testing

Manually verified the change against a dev server: temporarily enabled Paper's local test-plugin, registered a ProjectileLaunchEvent listener, started :paper-server:runDevServer, connected with Minecraft 26.2, and fired a snowball from a placed dispenser. During the event, the listener logged:

ProjectileLaunchEvent: projectile=SNOWBALL, shooter=org.bukkit.craftbukkit.projectiles.CraftBlockProjectileSource

That confirms the source is available during the event rather than only after the projectile has spawned. The dispenser also continued to launch the snowball normally during the test. The temporary listener was removed and test-plugin disabled again afterward.

Source patches rebuild and reapply cleanly, and a full ./gradlew build passes.

ProjectileDispenseBehavior assigned projectileSource only after
spawnProjectileUsingShoot had already inserted the entity into the
world, so ProjectileLaunchEvent observed a null shooter. Use the
existing delayed spawn path instead: create and configure the
projectile, attach the CraftBlockProjectileSource, then spawn it.
@charlierichards13
charlierichards13 requested a review from a team as a code owner August 20, 2026 08:09
@github-project-automation github-project-automation Bot moved this to Awaiting review in Paper PR Queue Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Awaiting review

Development

Successfully merging this pull request may close these issues.

ProjectileLaunchEvent ProjectileSource is null when shot by a dispenser.

1 participant