User Tools

Site Tools


tutorial:blockentity_sync_itemstack

Syncing BlockEntity data with ItemStack

Introduction

When you create a block with block entity, you might want to place the block with predefined NBT data from an ItemStack (of your BlockItem), or save the BlockEntity data in the ItemStack after breaking the block.

In this tutorial, we assume you have created a block (and block item) and the block entity, and stored data for it.

Block drops with data

For a block to drop an ItemStack with the NBT or data components from the BlockEntity of the broken block, we only need to change the loot table.

For versions since 1.20.5

As data components have been introduced, you need to store the block entity data in the data components of the block item. This requires you to store nbt data as data components. See using_data_components about it.

If you'are using versions before 1.21, replace the word loot_table with loot_tables in the path.

src/main/resources/data/tutorial/loot_table/blocks/demo_block.json
{
  "pools": [
    {
      "rolls": 1.0,
      "bonus_rolls": 0.0,
      "entries": [
        {
          "name": "tutorial:demo_block",
          "type": "minecraft:item"
        }
      ],
      "conditions": [
        {
          "condition": "minecraft:survives_explosion"
        }
      ]
    }
  ],
  "functions": [
    {
      "source": "block_entity",
      "include": [
        "tutorial:number"
      ],
      "function": "minecraft:copy_components"
    }
  ]
}

where:

  • "include": the IDs of the data component types.

For versions before 1.20.5

Before version 1.20.5, data components were not introduced. Therefore, just copy nbt.

src/main/resources/data/tutorial/loot_tables/blocks/demo_block.json
{
  "type": "minecraft:block",
  "pools": [
    {
      "rolls": 1.0,
      "entries": [
        {
          "type": "minecraft:item",
          "name": "tutorial:demo_block",
          "functions": [
            {
              "function": "minecraft:copy_nbt",
              "source": "block_entity",
              "ops": [
                {
                  "source": "number",
                  "target": "BlockEntityTag.number",
                  "op": "replace"
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

where:

  • "source" is the key of the nbt we used in the writeNbt and readNbt methods in our DemoBlockEntity class – "number"
  • "target" is the hierarchy in the nbt of dropped ItemStack (source prefixed with the "BlockEntityTag" key, which is needed for placing the block back with the saved data) – "BlockEntityTag.number"

To save more fields, just add more replace operations (with source, target and op) to the %“ops”%% array.

Reading saved data in the tooltip

For versions since 1.20.5, to get the BlockEntity's data stored in the item stack, we can use get its data component.

For versions since 1.20.5:

DemoBlock.class
public class DemoBlock extends BlockWithEntity {
 
    [...]
 
    @Override
    public void appendTooltip(ItemStack stack, Item.TooltipContext context, List<Text> tooltip, TooltipType options) {
        final Integer i = stack.get(TutorialDataComponentTypes.NUMBER);
        if (i == null) return;
 
        tooltip.add(Text.literal("Number: " + i));
    }
}

For versions before 1.20.5, we call getBlockEntityNbt, which internally calls getSubNbt.

For versions before 1.20.5:

DemoBlock.class
public class DemoBlock extends BlockWithEntity {
 
    [...]
 
    @Override
    public void appendTooltip(ItemStack stack, BlockView world, List<Text> tooltip, TooltipContext context) {
        NbtCompound nbt = BlockItem.getBlockEntityNbt(stack);
        if (nbt == null) return;
 
        tooltip.add(Text.literal("Number: " + nbt.getInt("number"))
    }
}

Similarly, you can also modify the getName method so the data can be displayed directly in the name. In this case, you have to modify how your block item is registered to create DemoBlockItem instead of BlockItem.

DemoBlockItem.class
public class DemoBlockItem extends BlockItem {
  public DemoBlockItem(Block block, Settings settings) {
    super(block, settings);
  }
 
  @Override
  public Text getName(ItemStack stack) {
    final MutableText name = Text.translatable(stack.getTranslationKey());
    if (stack.contains(TutorialDataComponentTypes.NUMBER)) {
      name.append(" - number=" + stack.get(TutorialDataComponentTypes.NUMBER));
    }
    return name;
  }
}

Pick item with data

In Creative Mode, when you pick item (by pressing the mouse wheel) while holding Ctrl, you can get an item stack with all block entity data. However, if you don't press Ctrl, you can only get one without data, unless you modify getPickStack method in the DemoBlock class:

  @Override
  public ItemStack getPickStack(WorldView world, BlockPos pos, BlockState state) {
    final ItemStack pickStack = super.getPickStack(world, pos, state);
    final BlockEntity blockEntity = world.getBlockEntity(pos);
    if (blockEntity instanceof DemoBlockEntity demoBlockEntity) {
      pickStack.applyComponentsFrom(demoBlockEntity.createComponentMap());
    }
    return pickStack;
  }
Note: When Ctrl is not pressed down, pick-stack happend only on client side. Therefore, you should modify toUpdatePacket and toInitialChunkDataNbt, and call updateListeners when modifying data (see sync data from server to client).

Helpful Reference

More examples can bee seen in vanilla codes, such as ShulkerBoxBlock and ShulkerBoxBlockEntity, which implements Nameable interface and has code to save custom names through ItemStack and BlockEntity. There are also other useful information:

tutorial/blockentity_sync_itemstack.txt · Last modified: 2024/08/27 02:37 by solidblock