[SPIGOT-4376] Add some way to get the drops caused by a broken block Created: 15/Sep/18  Updated: 31/Oct/18  Resolved: 31/Oct/18

Status: Resolved
Project: Spigot
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: New Feature Priority: Minor
Reporter: Anda Block Assignee: Unassigned
Resolution: Fixed Votes: 0
Labels: BlockBreakEvent, Items

Attachments: PNG File 2018-09-15_21.27.18.png    
Issue Links:
Relates
relates to SPIGOT-891 get/setDrops method in BlockBreakEvent Resolved
Version: 1.13.1
Guidelines Read: Yes

 Description   

Hello,

I need a way to get all drops caused by a block break, for example if a torch is placed on top of a stone block, I need both drops, the stone drop and the torch drop. This also needs to respect if the block was mined with silktouch, fortune, bare hand or something else. The BlockBreakEvent can't give away this information, because it is called before the breaking calculation. If my understanding of the Code is correct, the thing I need is already in the PlayerInteractManager, (Decompiled Lines 315 to 326 in Method public boolean breakBlock(BlockPosition))

this.world.captureDrops = new ArrayList();
boolean flag = this.c(blockposition);
if (event.isDropItems()) {
    Iterator var21 = this.world.captureDrops.iterator();

    while(var21.hasNext()) {
        EntityItem item = (EntityItem)var21.next();
        this.world.addEntity(item);
    }
}

this.world.captureDrops = null;

this.world.captureDrops seems to store all drops caused by the block to break, so they can be prevented dropping, if isDropItems was set to false. It would be awesome if some sort of event could be added on this place, something like BlockBreakDropItemsEvent(Player, Block, List<Items>), to access the excact drops caused by the breaking of this block.

 

Best regards



 Comments   
Comment by md_5 [ 08/Oct/18 ]

You’re welcome to try yourself

Comment by Anda Block [ 08/Oct/18 ]

It has been a while, has anyone decided yet if this has a chance of going into the api?

 

Best Regards

Comment by Ugleh [ 28/Sep/18 ]

I'll look into that when I have time.

Comment by Anda Block [ 28/Sep/18 ]

The one from the event only returns what the block COULD drop if it was borken with an appropriate tool, it does not return the items the will actually drop, it does not account for silk touch or fortune.

 

Best Regards

Comment by Ugleh [ 28/Sep/18 ]

To get the drop of the some wouldn't you just use getDrops from the event and add it yourself? Prevents is from having to modify a ton of nms

Comment by Anda Block [ 28/Sep/18 ]

I did some more digging on this, downloaded build tools and tested a bit, this seems to work:

// CraftBukkit start
world.captureDrops = new ArrayList<>(); //start capturing all drops happening in the world
//remember the data of the block to break for the event later on
BlockData blockData = this.world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()).getBlockData();

boolean flag = this.c(blockposition); //call the physic update that causes all drops except the ones of the block itself
//if (event.isDropItems()) {
//    for (EntityItem item : world.captureDrops) {
//        world.addEntity(item); //don't drop them here
//    }
//}
//world.captureDrops = null; //keep capturing drops
// CraftBukkit end

if (!this.isCreative()) { //let all the calls run to break the block itself while still capturing drops
    ItemStack itemstack1 = this.player.getItemInMainHand();
    boolean flag1 = this.player.hasBlock(iblockdata);

    itemstack1.a(this.world, iblockdata, blockposition, this.player);
    // CraftBukkit start - Check if block should drop items
    if (flag && flag1 && event.isDropItems()) {
        ItemStack itemstack2 = itemstack1.isEmpty() ? ItemStack.a : itemstack1.cloneItemStack();

        iblockdata.getBlock().a(this.world, this.player, blockposition, iblockdata, tileentity, itemstack2);
    }
    // CraftBukkit end
}
//now captureDrops also has all the drops of the broken block in it
if (event.isDropItems()) {
    for (EntityItem item : world.captureDrops) {
        //call some sort of event for every? drop. since the broken block does not exist anymore on this point
        //i used the remembered BlockData from above and the location as an option to identify the block
        //that break if you have a listener for this event in a plugin. also contains the player and the drop itself
        BlockBreakDropItemEvent blockBreakDropItemEvent = new BlockBreakDropItemEvent(
                (org.bukkit.entity.Item) item.getBukkitEntity(), this.player.getBukkitEntity(),
                blockData, new Location(world.getWorld(), blockposition.getX(), blockposition.getY(), blockposition.getZ())
        );
        this.world.getServer().getPluginManager().callEvent(blockBreakDropItemEvent);
        if(!blockBreakDropItemEvent.isCancelled()) { //if the event did not canceled, drop the item
            world.addEntity(item);
        }
    }
}
world.captureDrops = null; //stop capturing the drops

This worked with the stuff I tested:

 - breaking the carpet contraption from above

 - breaking redstone ore and coal ore with:

   - an unanchanted diamond pickaxe

   - a silk touch diamond pickaxe

   - a fortune diamond pickaxe

 

I hope my understanding of the code is correct, if not, please tell me.

 

I don't have an irc account, so i can't do requests.

 

Best Regards

Comment by Senmori [ 27/Sep/18 ]

I don't see how this could be fixed without rewriting how Minecraft drops items.
In your example, the stone block is not causing those drops, it is triggering a physics update which causes the drops. There is a distinction between them in the actual source code.
As well, Minecraft does not have a fully defined way to retrieving the drops from a block. The way it calculates fortune into block drops is different from what items crops should drop.

Comment by Anda Block [ 16/Sep/18 ]

I tested it, and can confirm, that the stone block is not in the List. Maybe they could move the whole captureDrops part below this

if (!this.isCreative()) {
    ItemStack itemstack1 = this.player.getItemInMainHand();
    boolean flag1 = this.player.hasBlock(iblockdata);
    itemstack1.a(this.world, iblockdata, blockposition, this.player);
    if (flag && flag1 && event.isDropItems()) {
        ItemStack itemstack2 = itemstack1.isEmpty() ? ItemStack.a : itemstack1.cloneItemStack();
        iblockdata.getBlock().a(this.world, this.player, blockposition, iblockdata, tileentity, itemstack2);
    }
}

so it could capture the drops of the block itself, too?

 

If no new event would be added, this would be a nice workaround, but I am aware off that nms uses are not supported.

 

Best regards

Comment by Ugleh [ 16/Sep/18 ]

I think the solution for developers is to add captureDrops to the API, and then you can grab it during ItemSpawnEvent. That or a new event called BlockBreakSpawnItemEvent like you said. For example, this returns a list of torches and yellow carpets, however not the stone block.

 

 

boolean hasRan = false;
@EventHandler
 public void itemSpawnEvent(ItemSpawnEvent e)
 {
 List<EntityItem> entityList = ((CraftWorld)e.getEntity().getWorld()).getHandle().captureDrops;
 if(entityList == null) return;
 if(hasRan) return;
 
 hasRan = true;
 for(EntityItem i : entityList)
 {
 Bukkit.broadcastMessage(i.getBukkitEntity().getName());
 }
 }

 

Comment by Anda Block [ 15/Sep/18 ]

I'm pretty sure, that those lines contain the torch. If I set isDropItems to false in a BlockBreakEvent, and I break the stone of a construct like this:

nothing will drop. This commit added, that every Item dropped after a block breaks gets stored in the list and only dropped if isDropItems is true.

 

Best regards

Comment by Black Hole [ 15/Sep/18 ]

The drops in those lines won't contain the torch. After the block is broken, neighbor blocks gets notified and decide if they'll break, too.

Generated at Sat Dec 13 13:42:52 UTC 2025 using Jira 10.3.13#10030013-sha1:56dd970ae30ebfeda3a697d25be1f6388b68a422.