PaperMC / PaperMC/Paper

Cancelling BlockDestroyEvent and BlockMultiPlaceEvent causes item duplication in certain situations

Open
#13,586 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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

Description

Expected behavior

Placing container blocks in places such as the end portal and the end podium, and cancelling the destruction of those containers doesn't duplicate the items within the container

Additionally, placing blocks in places such as the end podium and end platform, and cancelling the destruction of those blocks actually prevents their destruction.

Observed/Actual behavior

Cancelling BlockDestroyEvent and the subsequent BlockMultiPlaceEvent when a container is placed within the area the end portal generates when placing the last eye in an end portal frame, or around/inside the end podium when the Ender Dragon is respawned, causes the items inside the container to drop, but also remain within the container itself, resulting in duplicate items.

This is because Paper stores the blocks modified during an item use (in this case, eye of ender or end crystal), and fires a BlockMultiPlaceEvent with those modified blocks. Cancelling the event causes Paper to restore the blocks as they were before the item use. However, the blocks are still temporarily modified. In particular, LevelChunk.setBlockState is called, which checks if a block entity is being replaced, and if so, calls BlockEntity.preRemoveSideEffects, which for containers runs Containers.dropContents.

This means that before BlockMultiPlaceEvent is fired, all the items from containers are already dropped and in the world. Reverting the modified blocks restores the contents of those containers in addition to the already dropped items, since Paper saved the block data from before they were replaced.

Additionally, cancelling BlockDestroyEvent when a block is placed around/inside the end podium when the Ender Dragon dies, or in the area of the end platform when a player enters the end portal, doesn't prevent the blocks from actually being destroyed. No item duplication can happen here, but the items are still spilled and not prevented from being destroyed. BlockMultiPlaceEvent isn't fired here as there is no player to attribute the changes to.

There may be more ways to trigger this, but what all these scenarios have in common is that they call Level.destroyBlock followed by Level.setBlock. Thanks to Paper events, the destroyBlock may be cancelled, but vanilla code still assumes that the blocks are empty. setBlock causes the item drops, since instead of overwriting air, an actual block may still exist there. This action doesn't call any events, unless an item is currently being used.

There is no API method to prevent these blocks from being overridden without duplication.

Steps/models to reproduce
    @EventHandler
    public void onBlockDestroy(BlockDestroyEvent e) {
        if (e.getBlock().getType() == Material.BARREL) {
            e.setCancelled(true);
        }
    }

    @EventHandler
    public void onBlockPlace(BlockMultiPlaceEvent e) {
        if (e.getReplacedBlockStates().stream().anyMatch(i -> i.getType() == Material.BARREL)) {
            e.setCancelled(true);
        }
    }

Place a barrel with items within the end fountain, podium, or platform, and observe either item duplication, or the barrel being destroyed regardless of destruction being cancelled.

Plugin and Datapack List

Paper-Test-Plugin

Paper version
This server is running Paper version 1.21.11-DEV-main@4873e3f (1970-01-01T00:00:00Z) (Implementing API version 1.21.11-R0.1-SNAPSHOT)

(4873e3f)

Other

Fixing https://github.com/PaperMC/Paper/issues/12576 would cause end fountains to move from "duplication" to "ignoring destruction"

https://github.com/PaperMC/Paper/issues/13536 is a similar issue (duplication due to event system)

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 with the interaction among Level.destroyBlock, Level.setBlock, and LevelChunk.setBlockState, then trace when BlockDestroyEvent and BlockMultiPlaceEvent are fired for the end fountain, podium, and platform scenarios. Use the provided barrel reproduction with cancelled events to verify that container contents are not duplicated and that cancelled destruction is actually preserved.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend, game-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.