User Tools

Site Tools


tutorial:persistent_states

Differences

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

Link to this comparison view

Both sides previous revisionPrevious revision
tutorial:persistent_states [2025/03/13 14:52] – world.getServer() only once, harmonized MOD_ID lakazatongtutorial:persistent_states [2025/07/28 20:33] (current) – Begin update to 1.21.5 the...why...even...how
Line 128: Line 128:
  
     public Integer totalDirtBlocksBroken = 0;     public Integer totalDirtBlocksBroken = 0;
- 
-    @Override 
-    public NbtCompound writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registries) { 
-        nbt.putInt("totalDirtBlocksBroken", totalDirtBlocksBroken); 
-        return nbt; 
-    } 
 } }
 </code> </code>
  
-Note: ''**writeNbt**'' must be implemented when extending ''**PersistentState**''In the function, you get passed in an ''**NbtCompound**'' which we are supposed to pack with the data we want saved to disk. In our case, we moved the ''**public Integer totalDirtBlocksBroken**'' variable we had created earlier into this file. +Note:  you must provide a `Codec` for your state when extending  `**PersistentState**`The `Codec` will translate the state to and from nbt when saving and loading from the disk. In our case, we moved the  `**public Integer totalDirtBlocksBroken**`  variable we had created earlier into this file.
- +
-  * ''**NbtCompound**'' doesn't just store ''**Integers**''. It has functions for strings, arrays, bools, floats, importantly other ''**NbtCompound**'''s as you'll see soon enough, and even arbitrary bytes. That means, if you want to store some ''**SuperCustomClass**'', what you should do is create a ''**new NbtCompound**'' and pack that new ''**NbtCompound**'' with the fields of your ''**SuperCustomClass**'' and then store it in the main ''**NbtCompound**'' you get passed in. (We're about to do just that!)+
  
-Next add the following functions to that same file:+-   `Codec`s  doesn't just store  `**Integers**`. Default `Codec`s are provided for for strings, bools, floats, and other primitive building blocks, and you can write your own for custom types. That means, if you want to store some  `**SuperCustomClass**`, what you should do is create a  `**Codec**` for that class, and use that codec in the codec for your state class.
  
 <code java> <code java>
Line 148: Line 140:
     // ... (Previously written code)     // ... (Previously written code)
  
-    public static StateSaverAndLoader createFromNbt(NbtCompound tag, RegistryWrapper.WrapperLookup registryLookup) { +    private StateSaverAndLoader() {
-        StateSaverAndLoader state = new StateSaverAndLoader(); +
-        state.totalDirtBlocksBroken = tag.getInt("totalDirtBlocksBroken"); +
-        return state;+
     }     }
- +  
-    public static StateSaverAndLoader createNew() { +    private StateSaverAndLoader(int totalDirtBlocksBroken) { 
-        StateSaverAndLoader state = new StateSaverAndLoader(); +        this.totalDirtBlocksBroken = totalDirtBlocksBroken;
-        state.totalDirtBlocksBroken = 0; +
-        return state;+
     }     }
 + 
 +    private int getTotalDirtBlocksBroken() {
 +        return totalDirtBlocksBroken;
 +    }
 + 
 +    private static final Codec<StateSaverAndLoader> CODEC = Codec.INT.fieldOf("totalDirtBlocksBroken").codec().xmap(
 +            StateSaverAndLoader::new, // create a new 'StateSaverAndLoader' from the stored number
 +            StateSaverAndLoader::getTotalDirtBlocksBroken // return the number from the 'StateSaverAndLoader' to be saved
 +    );
 } }
 </code> </code>
- 
-First function does the opposite of ''**writeNbt**''. It takes in an ''**NbtCompound**'' (the same one we wrote in ''**writeNbt**'') and a ''**RegistryWrapper.WrapperLookup**'', creates a brand ''**new StateSaverAndLoader**'' and stuffs it with the data inside the ''**NbtCompound**''. 
- 
-  * Note: how we pull out the int we stored earlier with ''**getInt**'' and how the string we pass in is the same one we used in ''**writeNbt**''. 
- 
-Second function refreshing variables ''**totalDirtBlocksBroken**'' to avoid transfering data from another world to new one. 
- 
 Now we just need to add one more utility function which hooks everything up together. This function will take a ''**MinecraftServer**'' and from it, get the ''**PersistentStateManager**''. ''**PersistentStateManager**'' has a function ''**getOrCreate**'' which will use our ''**MOD_ID**'' as a key to see if it has an instance of our ''**StateSaverAndLoader**'' or if it needs to create one. If it needs to create one, it'll call the function we just wrote ''**createFromNbt**'' passing in the previously saved-to-disk ''**NbtCompound**'' and a ''**RegistryWrapper.WrapperLookup**''. Ultimately the function returns the ''**StateSaverAndLoader**'' for the given ''**MinecraftServer**''. Now we just need to add one more utility function which hooks everything up together. This function will take a ''**MinecraftServer**'' and from it, get the ''**PersistentStateManager**''. ''**PersistentStateManager**'' has a function ''**getOrCreate**'' which will use our ''**MOD_ID**'' as a key to see if it has an instance of our ''**StateSaverAndLoader**'' or if it needs to create one. If it needs to create one, it'll call the function we just wrote ''**createFromNbt**'' passing in the previously saved-to-disk ''**NbtCompound**'' and a ''**RegistryWrapper.WrapperLookup**''. Ultimately the function returns the ''**StateSaverAndLoader**'' for the given ''**MinecraftServer**''.
  
Line 175: Line 164:
     // ... (Previously written code)     // ... (Previously written code)
  
-    private static final Type<StateSaverAndLoader> type = new Type<>( +    private static final PersistentStateType<StateSaverAndLoader> type = new PersistentStateType<>( 
-            StateSaverAndLoader::createNew, // If there's no 'StateSaverAndLoader' yet create one and refresh variables +            (String) ExampleMod.MOD_ID, 
-            StateSaverAndLoader::createFromNbt, // If there is a 'StateSaverAndLoader' NBT, parse it with 'createFromNbt'+            StateSaverAndLoader::new, // If there's no 'StateSaverAndLoader' yet create one and refresh variables 
 +            CODEC, // If there is a 'StateSaverAndLoader' NBT, parse it with 'CODEC'
             null // Supposed to be an 'DataFixTypes' enum, but we can just pass null             null // Supposed to be an 'DataFixTypes' enum, but we can just pass null
     );     );
Line 188: Line 178:
         // The first time the following 'getOrCreate' function is called, it creates a brand new 'StateSaverAndLoader' and         // The first time the following 'getOrCreate' function is called, it creates a brand new 'StateSaverAndLoader' and
         // stores it inside the 'PersistentStateManager'. The subsequent calls to 'getOrCreate' pass in the saved         // stores it inside the 'PersistentStateManager'. The subsequent calls to 'getOrCreate' pass in the saved
-        // 'StateSaverAndLoader' NBT on disk to our function 'StateSaverAndLoader::createFromNbt'. +        // 'StateSaverAndLoader' NBT on disk to the codec in our type, using the codec to decode the nbt into our state 
-        StateSaverAndLoader state = serverWorld.getPersistentStateManager().getOrCreate(type, ExampleMod.MOD_ID);+        StateSaverAndLoader state = serverWorld.getPersistentStateManager().getOrCreate(type);
  
-        // If state is not marked dirty, when Minecraft closes, 'writeNbt' won't be called and therefore nothing will be saved.+        // If state is not marked dirty, nothing will be saved when Minecraft closes.
         // Technically it's 'cleaner' if you only mark state as dirty when there was actually a change, but the vast majority         // Technically it's 'cleaner' if you only mark state as dirty when there was actually a change, but the vast majority
         // of mod writers are just going to be confused when their data isn't being saved, and so it's best just to 'markDirty' for them.         // of mod writers are just going to be confused when their data isn't being saved, and so it's best just to 'markDirty' for them.
Line 206: Line 196:
  
 <code java> <code java>
-import net.minecraft.nbt.NbtCompound+import com.mojang.serialization.Codec
-import net.minecraft.registry.RegistryWrapper;+
 import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
 import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
 import net.minecraft.world.PersistentState; import net.minecraft.world.PersistentState;
 +import net.minecraft.world.PersistentStateType;
 import net.minecraft.world.World; import net.minecraft.world.World;
  
Line 217: Line 208:
     public Integer totalDirtBlocksBroken = 0;     public Integer totalDirtBlocksBroken = 0;
  
-    @Override +    private StateSaverAndLoader() {
-    public NbtCompound writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registries) { +
-        nbt.putInt("totalDirtBlocksBroken", totalDirtBlocksBroken); +
-        return nbt;+
     }     }
  
-    public static StateSaverAndLoader createFromNbt(NbtCompound tag, RegistryWrapper.WrapperLookup registryLookup) { +    private StateSaverAndLoader(int totalDirtBlocksBroken) { 
-        StateSaverAndLoader state = new StateSaverAndLoader(); +        this.totalDirtBlocksBroken = totalDirtBlocksBroken;
-        state.totalDirtBlocksBroken = tag.getInt("totalDirtBlocksBroken"); +
-        return state;+
     }     }
  
-    public static StateSaverAndLoader createNew() { +    private int getTotalDirtBlocksBroken() { 
-        StateSaverAndLoader state = new StateSaverAndLoader(); +        return totalDirtBlocksBroken;
-        state.totalDirtBlocksBroken = 0; +
-        return state;+
     }     }
  
-    private static final Type<StateSaverAndLoader> type = new Type<>( +    private static final Codec<StateSaverAndLoader> CODEC = Codec.INT.fieldOf("totalDirtBlocksBroken").codec().xmap( 
-            StateSaverAndLoader::createNew, // If there's no 'StateSaverAndLoader' yet create one and refresh variables +            StateSaverAndLoader::new, // create a new 'StateSaverAndLoader' from the stored number 
-            StateSaverAndLoader::createFromNbt, // If there is a 'StateSaverAndLoader' NBT, parse it with 'createFromNbt'+            StateSaverAndLoader::getTotalDirtBlocksBroken // return the number from the 'StateSaverAndLoader' to be saved 
 +    ); 
 + 
 +    private static final PersistentStateType<StateSaverAndLoader> type = new PersistentStateType<>( 
 +            (String) ExampleMod.MOD_ID, 
 +            StateSaverAndLoader::new, // If there's no 'StateSaverAndLoader' yet create one and refresh variables 
 +            CODEC, // If there is a 'StateSaverAndLoader' NBT, parse it with 'CODEC'
             null // Supposed to be an 'DataFixTypes' enum, but we can just pass null             null // Supposed to be an 'DataFixTypes' enum, but we can just pass null
     );     );
Line 248: Line 238:
         // The first time the following 'getOrCreate' function is called, it creates a brand new 'StateSaverAndLoader' and         // The first time the following 'getOrCreate' function is called, it creates a brand new 'StateSaverAndLoader' and
         // stores it inside the 'PersistentStateManager'. The subsequent calls to 'getOrCreate' pass in the saved         // stores it inside the 'PersistentStateManager'. The subsequent calls to 'getOrCreate' pass in the saved
-        // 'StateSaverAndLoader' NBT on disk to our function 'StateSaverAndLoader::createFromNbt'. +        // 'StateSaverAndLoader' NBT on disk to the codec in our type, using the codec to decode the nbt into our state 
-        StateSaverAndLoader state = serverWorld.getPersistentStateManager().getOrCreate(type, ExampleMod.MOD_ID);+        StateSaverAndLoader state = serverWorld.getPersistentStateManager().getOrCreate(type);
  
-        // If state is not marked dirty, when Minecraft closes, 'writeNbt' won't be called and therefore nothing will be saved.+        // If state is not marked dirty, nothing will be saved when Minecraft closes.
         // Technically it's 'cleaner' if you only mark state as dirty when there was actually a change, but the vast majority         // Technically it's 'cleaner' if you only mark state as dirty when there was actually a change, but the vast majority
         // of mod writers are just going to be confused when their data isn't being saved, and so it's best just to 'markDirty' for them.         // of mod writers are just going to be confused when their data isn't being saved, and so it's best just to 'markDirty' for them.
tutorial/persistent_states.txt · Last modified: 2025/07/28 20:33 by the...why...even...how