Repository navigation
BlockDispenseEvent is called twice since 26.3 for most blocks #14415
Description
Expected behavior
BlockDispenseEvent is only called once when a dispenser throws out a block
Observed/Actual behavior
When a block is dispensed from a dispenser, it used to go through the usual DefaultDispenseItemBehavior.
With the Sulfur Cubes that changed for most blocks (DispenserBlock line 121):
} else if (itemStack.is(ItemTags.SULFUR_CUBE_SWALLOWABLE)) {
return SulfurCubeBlockDispenseItemBehavior.INSTANCE;
}SulfurCubeBlockDispenseItemBehavior#execute (vanilla) falls back to super.execute if no Sulfur Cube took the item:
@Override
protected ItemStack execute(final BlockSource source, final ItemStack dispensed) {
return dispenseBlock(source.level(), source.pos().relative(source.state().getValue(DispenserBlock.FACING)), dispensed, source, this) // Paper - Call BlockDispenseEvent
? dispensed
: super.execute(source, dispensed);
}Paper calls handleBlockDispenseEvent at the start of dispenseBlock, before it checks whether a Sulfur Cube is in front:
ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(source, pos, dispensed, currentBehavior);If there is none, dispenseBlock returns false and vanilla falls back to super.execute, which fires the event again in DefaultDispenseItemBehavior.spawnItem.
The same applies to TNT: the TNT behavior also calls SulfurCubeBlockDispenseItemBehavior.dispenseBlock first and then fires its own BlockDispenseEvent. The TNT is only primed once, but the event is called twice for a single dispense.
sequenceDiagram
autonumber
participant D as DispenserBlock
participant S as SulfurCubeBlockDispenseItemBehavior
participant DD as DefaultDispenseItemBehavior (super)
participant F as CraftEventFactory
participant L as Plugin Listener
D->>D: getDefaultDispenseMethod(itemStack)
Note over D: itemStack.is(SULFUR_CUBE_SWALLOWABLE)<br/>→ SulfurCubeBlockDispenseItemBehavior.INSTANCE
D->>S: dispense(source, stack)
S->>S: execute(source, stack)
S->>S: dispenseBlock(level, pos, stack, source, this)
S->>F: handleBlockDispenseEvent(source, pos, stack, this)
F->>L: BlockDispenseEvent (1st call)
L-->>F: not cancelled
F-->>S: null
S->>S: getEntitiesOfClass(SulfurCube, AABB(pos))
Note over S: no Sulfur Cube in front<br/>→ dispenseBlock returns false
S->>DD: super.execute(source, stack)
DD->>DD: spawnItem(level, stack, 6, direction, position, source)
DD->>L: BlockDispenseEvent (2nd call)
L-->>DD: not cancelled
DD->>DD: level.addFreshEntity(itemEntity)
Steps/models to reproduce
Watch the BlockDispenseEvent with a event listener and see two logs for the same dispense.
Plugin and Datapack List
Paper version
This server is running Paper version 26.3-209-main@03af8b4 (2026-10-20T21:49:44Z) (Implementing API version 26.3.build.209-stable)
Other
No response
Metadata
Metadata
Assignees
Labels
Type
Fields
MC Issue
Activity
[-]Sulfur Cubes introduced Dispenser Events for blocks being called twice[/-][+]BlockDispenseEvent is called twice since 26.3 for most blocks[/+]