User Tools

Site Tools


tutorial:blockentity_modify_data

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
tutorial:blockentity_modify_data [2024/08/26 02:04] – [Using data components] solidblocktutorial:blockentity_modify_data [2025/04/01 12:48] (current) – [Some notes about NbtCompound] solidblock
Line 3: Line 3:
 In the previous tutorial, we have created a [[blockentity|block entity]]. But they are too boring as they do not have any data. Therefore, we try to add some data to it, and define ways of serializing and deserializing data. In the previous tutorial, we have created a [[blockentity|block entity]]. But they are too boring as they do not have any data. Therefore, we try to add some data to it, and define ways of serializing and deserializing data.
  
 +===== Some notes about NbtCompound =====
 +
 +Since 1.21.5, methods of ''NbtCompound'' have been changed. The getter methods return ''Optional'' objects, unless you specify another parameter as a default value used when the field does not exist. For example:
 +
 +<code java>
 +// no default value specified, returns Optional
 +Optional<Integer> value = nbt.getInt("value"); // not OptionalInt
 +
 +// default value specified, returns the default value when the field does not exist
 +int value = nbt.getInt("value", 1000);
 +</code>
 +
 +For collection types, such as compounds and lists, an empty object can be returned as a default value, for example:
 +<code java>
 +// no default value specified, returns Optional
 +Optional<NbtCompound> config = nbt.getCompound("config");
 +
 +// when the field does not exist, returns an empty compound
 +NbtCompound config = nbt.getCompoundOrEmpty("config");
 +</code>
 +
 +In versions before 1.21.5, they return zero or empty values. If you need to specify a default value, you need to judge with ''contains'' method beforehand. For example:
 +<code java>
 +// returns 0 if the field does not exist:
 +int value = nbt.getInt("value");
 +
 +// returns an empty compound when the field does not exist:
 +NbtCompound config = nbt.getCompound("config");
 +</code>
 +
 +Besides, since 1.21.5, the whole NBT compound and its fields can be decoded directly with [[codec]]s, such as:
 +<code java>
 +NbtCompound nbt = new NbtCompound();
 +nbt.put("Id", Identifier.CODEC, Identifier.of("tutorial", "example_id"));
 +Optional<Identifier> id = nbt.get("Id", Identifier.CODEC);
 +</code>
 +
 +<code java>
 +NbtCompound nbt = new NbtCompound();
 +// have to use the RegistryOps since an item is a registry entry
 +nbt.copyFromCodec(ItemStack.MAP_CODEC, wrapperLookup.getOps(NbtOps.INSTANCE), new ItemStack(Items.WHEAT));
 +Optional<ItemStack> stack = nbt.decode(ItemStack.MAP_CODEC, wrapperLookup.getOps(NbtOps.INSTANCE));
 +</code>
 +
 +Besides, since 1.21.5, lists support mixing elements of different types. In previous versions, mixing elements of different types in a list results in exceptions.
 +
 +More information about NBT changes, see [[https://fabricmc.net/2025/03/24/1215.html|Fabric for Minecraft 1.21.5]].
 ===== Serializing Data ===== ===== Serializing Data =====
  
 If you want to store any data in your ''BlockEntity'', you will need to save and load it, or it will only be held while the ''BlockEntity'' is loaded, and the data will reset whenever you come back to it. Luckily, saving and loading is quite simple - you only need to override ''writeNbt()'' and ''readNbt()'' If you want to store any data in your ''BlockEntity'', you will need to save and load it, or it will only be held while the ''BlockEntity'' is loaded, and the data will reset whenever you come back to it. Luckily, saving and loading is quite simple - you only need to override ''writeNbt()'' and ''readNbt()''
  
-''writeNbt()'' modifies the parameter ''nbt'', which should contain all of the data in your block entity. It usually does not modify the block entity object itself. The NBT is saved to the disk, and if you need to sync your block entity data with clients, also sent through packets. It is very important to call ''super.writeNbt'', which saves the position and id of the block entity to the nbt. Without this, any further data you try and save will be lost as it is not associated with a position and ''BlockEntityType''.+''writeNbt()'' modifies the parameter ''nbt'', which should contain all of the data in your block entity. It usually does not modify the block entity object itself. The NBT is saved to the disk, and if you need to sync your block entity data with clients, also sent through packets. 
 + 
 +In older versions, it was very important to call ''super.writeNbt'', which saves the position and id of the block entity to the nbt. Without this, any further data you try and save would be lost as it is not associated with a position and ''BlockEntityType''. However, latest versions do not require it, as they will be handled through ''createNbt()'' method.
  
 Knowing this, the example below demonstrates saving an integer from your ''BlockEntity'' to the nbt. In the example, the integer is saved under the key ''"number"'' - you can replace this with any string, but you can only have one entry for each key in your nbt, and you will need to remember the key in order to read the data later. Knowing this, the example below demonstrates saving an integer from your ''BlockEntity'' to the nbt. In the example, the integer is saved under the key ''"number"'' - you can replace this with any string, but you can only have one entry for each key in your nbt, and you will need to remember the key in order to read the data later.
Line 15: Line 64:
  
     // Store the current value of the number     // Store the current value of the number
-    private int number = 7;+    private int number = 0;
        
     public DemoBlockEntity(BlockPos pos, BlockState state) {     public DemoBlockEntity(BlockPos pos, BlockState state) {
Line 23: Line 72:
     // Serialize the BlockEntity     // Serialize the BlockEntity
     @Override     @Override
-    public void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapper) {+    public void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registries) {
         // Save the current value of the number to the nbt         // Save the current value of the number to the nbt
         nbt.putInt("number", number);         nbt.putInt("number", number);
  
-        super.writeNbt(nbt, wrapper);+        super.writeNbt(nbt, registries);
     }     }
 } }
 </code> </code>
  
-In order to read the data, you will also need to override ''readNbt''. This method is the opposite of ''writeNbt'' - instead of saving your data to a ''NBTCompound'', you are given the nbt data which you saved earlier, enabling you to retrieve any data that you need. It modifies the block entity object itself, instead of the ''nbt''. As with ''writeNbt'', it is essential that you call ''super.readNbt'', and you will need to use the same keys to retrieve data that you saved. To readthe number we saved earlier in the nbt, see the example below.+In order to read the data, you will also need to override ''readNbt''. This method is the opposite of ''writeNbt'' - instead of saving your data to a ''NBTCompound'', you are given the nbt data which you saved earlier, enabling you to retrieve any data that you need. It modifies the block entity object itself, instead of the ''nbt''. To read the number we saved earlier in the nbt, see the example below.
  
 <code java> <code java>
     // Deserialize the BlockEntity     // Deserialize the BlockEntity
     @Override     @Override
-    public void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup wrapper) { +    public void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registries) { 
-        super.readNbt(nbt, wrapper);+        super.readNbt(nbt, registries);
                  
-        number = nbt.getInt("number");+        number = nbt.getInt("number", 0);
     }     }
 </code> </code>
  
-Once you have implemented the ''writeNbt'' and ''readNbt'' methods, you simply need to ensure that they are called when needed. Whenever your block entity is modified and needs to be saved, call ''markDirty()''This will force the ''writeNbt'' method to be called when the world is next saved by marking the chunk in which your block is as dirty. As a general rule of thumbsimply call ''markDirty()'' whenever you modify any custom variable in your ''BlockEntity'' class, otherwise after you exit and re-enter the world, the block entity appears as if the modification had not been done.+To get the NBT data for the block entity, call ''createNbt(registries)''. It will also handle some data like position and components. 
 + 
 +For old versions, calling ''super.readNbt()'' and ''super.writeNbt()'' was essential, because handling data such as positions is requiredIn old versions, if ''createNbt'' does not existtry ''writeNbt(new NbtCompound())''.
  
 ===== Sync data from server to client ===== ===== Sync data from server to client =====
Line 64: Line 115:
   }   }
 </code> </code>
- 
-**Warning**: Need to call ''world.updateListeners(pos, state, state, Block.NOTIFY_LISTENERS);'' to trigger the update, otherwise the client does not know that the block entity has been changed. 
  
 <code java DemoBlockEntity.class> <code java DemoBlockEntity.class>
Line 77: Line 126:
    
     @Override     @Override
-    public void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {+    public void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registries) {
         nbt.putInt("number", number);         nbt.putInt("number", number);
    
-        super.writeNbt(nbt, registryLookup);+        super.writeNbt(nbt, registries);
     }     }
          
     @Override     @Override
-    public void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) { +    public void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registries) { 
-        super.readNbt(nbt);+        super.readNbt(nbt, registries);
    
-        number = nbt.getInt("number", registryLookup);+        // for versions before 1.21.5, use nbt.getInt("number"); 
 +        number = nbt.getInt("number", 0);
     }     }
 } }
 </code> </code>
  
-Make sure the ''number'' field is **public**, as we are going to change it in the ''DemoBlock'' class. You could also make getter and setter methods, but we just expose the field here for simplicity..+Make sure the ''number'' field is **public**, as we are going to change it in the ''DemoBlock'' class. You could also make getter and setter methods, but we just expose the field here for simplicity.
  
 ===== Modifying the data ===== ===== Modifying the data =====
Line 110: Line 160:
                 demoBlockEntity.number++;                 demoBlockEntity.number++;
                 player.sendMessage(Text.literal("Number is " + demoBlockEntity.number));                 player.sendMessage(Text.literal("Number is " + demoBlockEntity.number));
 +                demoBlockEntity.markDirty();
             }             }
         }         }
                  
-        return ActionResult.success(world.isClient);+        return ActionResult.SUCCESS;
     }     }
 } }
 +</code>
 +
 +The method ''markDirty()'' should be called whenever the block entity is modified. This will mark the position "dirty" to force the ''writeNbt'' method to be called when the world is next saved. As a general rule of thumb, simply call ''markDirty()'' whenever you modify any custom variable in your ''BlockEntity'' class, otherwise after you exit and re-enter the world, the block entity appears as if the modification had not been done.
 +
 +If you want clients to know the update (for example, they may be need to render, or pick-stack without holding ''Ctrl''), add an extra line after the ''markDirty'' call, so that the client will know the block entity has been changed:
 +<code java>
 +                world.updateListeners(pos, state, state, 0);
 </code> </code>
  
 ===== Using data components ===== ===== Using data components =====
  
-Since 1.20.5, you can also use data components to store data. In this case, as data components have codecs that can serialize and deserialize themselves, you do not need to write ''readNbt'' and ''writeNbt'' methods for them.+Since 1.20.5, you can also use data components to store data. This is required if you want to write the data into item stacks. You still need to write ''readNbt'' and ''writeNbt'' methods for them to save the block entities correctly.
  
-If you want to have a try, //remove// the ''readNbt'' and ''writeNbt'' methods before. And then create a data component type in a separate class ''TutorialDataComponentTypes'' for it. More information about registering data components can be seen in [[https://docs.fabricmc.net/develop/items/custom-data-components|Fabric Docs page]].+Create a data component type in a separate class ''TutorialDataComponentTypes'' for it. More information about registering data components can be seen in [[https://docs.fabricmc.net/develop/items/custom-data-components|Fabric Docs page]].
  
 <code java TutorialDataComponentTypes.java> <code java TutorialDataComponentTypes.java>
Line 155: Line 213:
  
 <code java DemoBlockEntity.java> <code java DemoBlockEntity.java>
-  // removed the `readNbt` and `writeNbt` methods. 
- 
   @Override   @Override
   protected void readComponents(ComponentsAccess components) {   protected void readComponents(ComponentsAccess components) {
Line 168: Line 224:
     componentMapBuilder.add(ExampleMod.NUMBER, number);     componentMapBuilder.add(ExampleMod.NUMBER, number);
   }   }
-</code> 
  
-Now these components can also be stored normally. If you pick stack (which means press the mouse wheel to the block write pressing ''Ctrl''), the components will be transferred to the stack.+  @Override 
 +  public void removeFromCopiedStackNbt(NbtCompound nbt
 +    nbt.remove("number"); 
 +  } 
 +</code>
  
-In some cases, you can also retain the ''readNbt'' and ''writeNbt'' methodsand override ''removeFromCopiedStackNbt'' method to remove the ''%%"number"%%'' field from the nbt.+The usage of ''removeFromCopiedStackNbt'' iswhen copying item stacks, as the components are already copied, the nbts are no longer needed. If you pick stack (which means press the mouse wheel to the block write pressing ''Ctrl''), the components will be transferred to the stack. If you need to transfer the components when pick blocks even if you do not hold ''Ctrl'' (like the behavior of vanilla banner blocks), see [[blockentity_sync_itemstack]].
tutorial/blockentity_modify_data.1724637858.txt.gz · Last modified: 2024/08/26 02:04 by solidblock