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
Next revision
Previous revision
tutorial:persistent_states [2023/09/28 05:37] – Add missing imports jmanc3tutorial:persistent_states [2025/03/13 14:52] (current) – world.getServer() only once, harmonized MOD_ID lakazatong
Line 19: Line 19:
 First, since the data will be saved on the 'server', (NOTE: that there is always a 'server' running even when you play offline, so don't be scared about that word), let's send a simple packet to the player when the server detects the player breaks a dirt block, and print it to the chat. First, since the data will be saved on the 'server', (NOTE: that there is always a 'server' running even when you play offline, so don't be scared about that word), let's send a simple packet to the player when the server detects the player breaks a dirt block, and print it to the chat.
  
-Modify your class which ''**implements ModInitializer**'' as follows:+For that, we need to define a Payload which ''**implements CustomPayload**'' as follows: 
 + 
 +<code java> 
 +import net.minecraft.network.PacketByteBuf; 
 +import net.minecraft.network.codec.PacketCodec; 
 +import net.minecraft.network.codec.PacketCodecs; 
 +import net.minecraft.network.packet.CustomPayload; 
 +import net.minecraft.util.Identifier; 
 + 
 +public record DirtBrokenPayload(Integer totalDirtBlocksBroken) implements CustomPayload { 
 +    public static final Identifier DIRT_BROKEN_ID = Identifier.of(MOD_ID, "dirt_broken"); 
 +    public static final CustomPayload.Id<DirtBrokenPayload> ID = new CustomPayload.Id<>(DIRT_BROKEN_ID); 
 +    public static final PacketCodec<PacketByteBuf, DirtBrokenPayload> CODEC = PacketCodec.tuple( 
 +            PacketCodecs.INTEGER, DirtBrokenPayload::totalDirtBlocksBroken, 
 +            DirtBrokenPayload::new); 
 + 
 +    @Override 
 +    public Id<? extends CustomPayload> getId() { 
 +        return ID; 
 +    } 
 +
 +</code> 
 + 
 +Next, modify your class which ''**implements ModInitializer**'' as follows:
  
 <code java> <code java>
 import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
 import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
-import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
-import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;+
 import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
 import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
-import net.minecraft.network.PacketByteBuf; 
 import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
 import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
-import net.minecraft.util.Identifier; 
  
 public class ExampleMod implements ModInitializer { public class ExampleMod implements ModInitializer {
  
-    public static final String MOD_ID = "your_unique_mod_id_change_me_please"+    public static final String MOD_ID = "examplemod";
- +
-    public static final Identifier DIRT_BROKEN = new Identifier(MOD_ID, "dirt_broken");+
  
     private Integer totalDirtBlocksBroken = 0;     private Integer totalDirtBlocksBroken = 0;
  
-    @Override 
     public void onInitialize() {     public void onInitialize() {
 +        PayloadTypeRegistry.playS2C().register(DirtBrokenPayload.ID, DirtBrokenPayload.CODEC);
 +
         PlayerBlockBreakEvents.AFTER.register((world, player, pos, state, entity) -> {         PlayerBlockBreakEvents.AFTER.register((world, player, pos, state, entity) -> {
             if (state.getBlock() == Blocks.GRASS_BLOCK || state.getBlock() == Blocks.DIRT) {             if (state.getBlock() == Blocks.GRASS_BLOCK || state.getBlock() == Blocks.DIRT) {
-                // Increment the amount of dirt blocks that have been broken 
-                totalDirtBlocksBroken += 1; 
- 
                 // Send a packet to the client                 // Send a packet to the client
                 MinecraftServer server = world.getServer();                 MinecraftServer server = world.getServer();
 +                assert server != null;
  
-                PacketByteBuf data = PacketByteBufs.create(); +                // Increment the amount of dirt blocks that have been broken 
-                data.writeInt(totalDirtBlocksBroken);+                totalDirtBlocksBroken += 1;
  
                 ServerPlayerEntity playerEntity = server.getPlayerManager().getPlayer(player.getUuid());                 ServerPlayerEntity playerEntity = server.getPlayerManager().getPlayer(player.getUuid());
                 server.execute(() -> {                 server.execute(() -> {
-                    ServerPlayNetworking.send(playerEntity, DIRT_BROKEN, data);+                    assert playerEntity != null; 
 +                    ServerPlayNetworking.send(playerEntity, new DirtBrokenPayload(totalDirtBlocksBroken));
                 });                 });
             }             }
Line 67: Line 85:
     * To send a packet we use: ''**ServerPlayNetworking.send**''. It takes three arguments; The ''**ServerPlayerEntity**''. An ''**Identifer**'' which is needed when sending a packet between server and client or client and server, and a ''**PacketByteBuf**'' which we are able to fill with arbritary data (that is: bools, ints, arrays, strings, and more). The ''**PacketByteBuf**'' is where we stuff the data we want to send across the line. In this case we send the total amount of dirt blocks that the server has seen broken.     * To send a packet we use: ''**ServerPlayNetworking.send**''. It takes three arguments; The ''**ServerPlayerEntity**''. An ''**Identifer**'' which is needed when sending a packet between server and client or client and server, and a ''**PacketByteBuf**'' which we are able to fill with arbritary data (that is: bools, ints, arrays, strings, and more). The ''**PacketByteBuf**'' is where we stuff the data we want to send across the line. In this case we send the total amount of dirt blocks that the server has seen broken.
  
-Next modify your class which ''**implements ClientModInitializer**'' as follows:+Finally, modify your class which ''**implements ClientModInitializer**'' as follows:
  
 <code java> <code java>
 import net.fabricmc.api.ClientModInitializer; import net.fabricmc.api.ClientModInitializer;
 import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
-import net.minecraft.text.Text; +import net.minecraft.client.network.ClientPlayerEntity; 
 +import net.minecraft.text.Text;
  
 public class ExampleModClient implements ClientModInitializer { public class ExampleModClient implements ClientModInitializer {
 +    private static void handleDirtBrokenPayload(DirtBrokenPayload payload, ClientPlayNetworking.Context context) {
 +        ClientPlayerEntity player = context.client().player;
 +        assert player != null;
 +        player.sendMessage(Text.literal("Total dirt blocks broken: " + payload.totalDirtBlocksBroken()), false);
 +    }
  
     @Override     @Override
     public void onInitializeClient() {     public void onInitializeClient() {
-        ClientPlayNetworking.registerGlobalReceiver(ExampleMod.DIRT_BROKEN(client, handler, buf, responseSender) -> { +        ClientPlayNetworking.registerGlobalReceiver(DirtBrokenPayload.IDExampleModClient::handleDirtBrokenPayload);
-            int totalDirtBlocksBroken = buf.readInt(); +
-            client.execute(() -> { +
-                client.player.sendMessage(Text.literal("Total dirt blocks broken" + totalDirtBlocksBroken)); +
-            }); +
-        });+
     }     }
 } }
Line 100: Line 119:
 <code java> <code java>
 import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
 +import net.minecraft.registry.RegistryWrapper;
 import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
 +import net.minecraft.server.world.ServerWorld;
 import net.minecraft.world.PersistentState; import net.minecraft.world.PersistentState;
-import net.minecraft.world.PersistentStateManager; 
 import net.minecraft.world.World; import net.minecraft.world.World;
-import net.minecraft.datafixer.DataFixTypes; 
  
 public class StateSaverAndLoader extends PersistentState { public class StateSaverAndLoader extends PersistentState {
Line 111: Line 130:
  
     @Override     @Override
-    public NbtCompound writeNbt(NbtCompound nbt) {+    public NbtCompound writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registries) {
         nbt.putInt("totalDirtBlocksBroken", totalDirtBlocksBroken);         nbt.putInt("totalDirtBlocksBroken", totalDirtBlocksBroken);
         return nbt;         return nbt;
Line 122: Line 141:
   * ''**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!)   * ''**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 function to that same file:+Next add the following functions to that same file:
  
 <code java> <code java>
Line 129: Line 148:
     // ... (Previously written code)     // ... (Previously written code)
  
-    public static StateSaverAndLoader createFromNbt(NbtCompound tag) {+    public static StateSaverAndLoader createFromNbt(NbtCompound tag, RegistryWrapper.WrapperLookup registryLookup) {
         StateSaverAndLoader state = new StateSaverAndLoader();         StateSaverAndLoader state = new StateSaverAndLoader();
         state.totalDirtBlocksBroken = tag.getInt("totalDirtBlocksBroken");         state.totalDirtBlocksBroken = tag.getInt("totalDirtBlocksBroken");
 +        return state;
 +    }
 +
 +    public static StateSaverAndLoader createNew() {
 +        StateSaverAndLoader state = new StateSaverAndLoader();
 +        state.totalDirtBlocksBroken = 0;
         return state;         return state;
     }     }
Line 137: Line 162:
 </code> </code>
  
-This function does the opposite of ''**writeNbt**''. It takes in an ''**NbtCompound**'' (the same one we wrote in ''**writeNbt**''), creates a brand ''**new StateSaverAndLoader**'' and stuffs it with the data inside the ''**NbtCompound**''.+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**''.   * 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**''.
  
-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**''. Ultimately the function returns the ''**StateSaverAndLoader**'' for the given ''**MinecraftServer**''.+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**''.
  
 <code java> <code java>
Line 147: Line 174:
  
     // ... (Previously written code)     // ... (Previously written code)
 +
 +    private static final Type<StateSaverAndLoader> type = new Type<>(
 +            StateSaverAndLoader::createNew, // If there's no 'StateSaverAndLoader' yet create one and refresh variables
 +            StateSaverAndLoader::createFromNbt, // If there is a 'StateSaverAndLoader' NBT, parse it with 'createFromNbt'
 +            null // Supposed to be an 'DataFixTypes' enum, but we can just pass null
 +    );
  
     public static StateSaverAndLoader getServerState(MinecraftServer server) {     public static StateSaverAndLoader getServerState(MinecraftServer server) {
         // (Note: arbitrary choice to use 'World.OVERWORLD' instead of 'World.END' or 'World.NETHER' Any work)         // (Note: arbitrary choice to use 'World.OVERWORLD' instead of 'World.END' or 'World.NETHER' Any work)
-        PersistentStateManager persistentStateManager = server.getWorld(World.OVERWORLD).getPersistentStateManager();+        ServerWorld serverWorld = server.getWorld(World.OVERWORLD)
 +        assert serverWorld != null;
  
         // 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 our function 'StateSaverAndLoader::createFromNbt'.
-        // +        StateSaverAndLoader state = serverWorld.getPersistentStateManager().getOrCreate(type, ExampleMod.MOD_ID);
-        // (Note: 'DataFixTypes.LEVEL' is used as it's a required parameter but the decision to use 'DataFixTypes.LEVEL' specifically is arbitrary.) +
-        StateSaverAndLoader state = persistentStateManager.getOrCreate( +
-                new Type<>(StateSaverAndLoader::new, StateSaverAndLoader::createFromNbt, DataFixTypes.LEVEL), +
-                ExampleMod.MOD_ID);+
  
         // 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, when Minecraft closes, 'writeNbt' won't be called and therefore nothing will be saved.
Line 177: Line 207:
 <code java> <code java>
 import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtCompound;
 +import net.minecraft.registry.RegistryWrapper;
 import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
 +import net.minecraft.server.world.ServerWorld;
 import net.minecraft.world.PersistentState; import net.minecraft.world.PersistentState;
-import net.minecraft.world.PersistentStateManager; 
 import net.minecraft.world.World; import net.minecraft.world.World;
-import net.minecraft.datafixer.DataFixTypes; 
  
 public class StateSaverAndLoader extends PersistentState { public class StateSaverAndLoader extends PersistentState {
Line 188: Line 218:
  
     @Override     @Override
-    public NbtCompound writeNbt(NbtCompound nbt) {+    public NbtCompound writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registries) {
         nbt.putInt("totalDirtBlocksBroken", totalDirtBlocksBroken);         nbt.putInt("totalDirtBlocksBroken", totalDirtBlocksBroken);
         return nbt;         return nbt;
     }     }
  
-    public static StateSaverAndLoader createFromNbt(NbtCompound tag) {+    public static StateSaverAndLoader createFromNbt(NbtCompound tag, RegistryWrapper.WrapperLookup registryLookup) {
         StateSaverAndLoader state = new StateSaverAndLoader();         StateSaverAndLoader state = new StateSaverAndLoader();
         state.totalDirtBlocksBroken = tag.getInt("totalDirtBlocksBroken");         state.totalDirtBlocksBroken = tag.getInt("totalDirtBlocksBroken");
         return state;         return state;
     }     }
 +
 +    public static StateSaverAndLoader createNew() {
 +        StateSaverAndLoader state = new StateSaverAndLoader();
 +        state.totalDirtBlocksBroken = 0;
 +        return state;
 +    }
 +
 +    private static final Type<StateSaverAndLoader> type = new Type<>(
 +            StateSaverAndLoader::createNew, // If there's no 'StateSaverAndLoader' yet create one and refresh variables
 +            StateSaverAndLoader::createFromNbt, // If there is a 'StateSaverAndLoader' NBT, parse it with 'createFromNbt'
 +            null // Supposed to be an 'DataFixTypes' enum, but we can just pass null
 +    );
  
     public static StateSaverAndLoader getServerState(MinecraftServer server) {     public static StateSaverAndLoader getServerState(MinecraftServer server) {
         // (Note: arbitrary choice to use 'World.OVERWORLD' instead of 'World.END' or 'World.NETHER' Any work)         // (Note: arbitrary choice to use 'World.OVERWORLD' instead of 'World.END' or 'World.NETHER' Any work)
-        PersistentStateManager persistentStateManager = server.getWorld(World.OVERWORLD).getPersistentStateManager();+        ServerWorld serverWorld = server.getWorld(World.OVERWORLD)
 +        assert serverWorld != null;
  
         // 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 our function 'StateSaverAndLoader::createFromNbt'.
-        // +        StateSaverAndLoader state = serverWorld.getPersistentStateManager().getOrCreate(type, ExampleMod.MOD_ID);
-        // (Note: 'DataFixTypes.LEVEL' is used as it's a required parameter but the decision to use 'DataFixTypes.LEVEL' specifically is arbitrary.) +
-        StateSaverAndLoader state = persistentStateManager.getOrCreate( +
-                new Type<>(StateSaverAndLoader::new, StateSaverAndLoader::createFromNbt, DataFixTypes.LEVEL), +
-                ExampleMod.MOD_ID);+
  
         // 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, when Minecraft closes, 'writeNbt' won't be called and therefore nothing will be saved.
Line 229: Line 268:
 import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
 import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
-import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
-import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;+
 import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
 import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
-import net.minecraft.network.PacketByteBuf; 
 import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
 import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
-import net.minecraft.util.Identifier; 
  
 public class ExampleMod implements ModInitializer { public class ExampleMod implements ModInitializer {
  
-    public static final String MOD_ID = "your_unique_mod_id_change_me_please";+    public static final String MOD_ID = "examplemod";
  
-    public static final Identifier DIRT_BROKEN = new Identifier(MOD_ID"dirt_broken");+    public void onInitialize() { 
 +        PayloadTypeRegistry.playS2C().register(DirtBrokenPayload.IDDirtBrokenPayload.CODEC);
  
-    @Override 
-    public void onInitialize() { 
         PlayerBlockBreakEvents.AFTER.register((world, player, pos, state, entity) -> {         PlayerBlockBreakEvents.AFTER.register((world, player, pos, state, entity) -> {
             if (state.getBlock() == Blocks.GRASS_BLOCK || state.getBlock() == Blocks.DIRT) {             if (state.getBlock() == Blocks.GRASS_BLOCK || state.getBlock() == Blocks.DIRT) {
-                StateSaverAndLoader serverState = StateSaverAndLoader.getServerState(world.getServer()); 
-                // Increment the amount of dirt blocks that have been broken 
-                serverState.totalDirtBlocksBroken += 1; 
- 
                 // Send a packet to the client                 // Send a packet to the client
                 MinecraftServer server = world.getServer();                 MinecraftServer server = world.getServer();
 +                assert server != null;
  
-                PacketByteBuf data PacketByteBufs.create(); +                // Increment the amount of dirt blocks that have been broken 
-                data.writeInt(serverState.totalDirtBlocksBroken);+                StateSaverAndLoader serverState StateSaverAndLoader.getServerState(server); 
 +                serverState.totalDirtBlocksBroken += 1;
  
                 ServerPlayerEntity playerEntity = server.getPlayerManager().getPlayer(player.getUuid());                 ServerPlayerEntity playerEntity = server.getPlayerManager().getPlayer(player.getUuid());
                 server.execute(() -> {                 server.execute(() -> {
-                    ServerPlayNetworking.send(playerEntity, DIRT_BROKEN, data);+                    assert playerEntity != null; 
 +                    ServerPlayNetworking.send(playerEntity, new DirtBrokenPayload(serverState.totalDirtBlocksBroken));
                 });                 });
             }             }
Line 344: Line 378:
 public class ExampleMod implements ModInitializer { public class ExampleMod implements ModInitializer {
  
-    public static final String MOD_ID = "your_unique_mod_id_change_me_please";+    public static final String MOD_ID = "examplemod";
  
     public static final Identifier DIRT_BROKEN = new Identifier(MOD_ID, "dirt_broken");     public static final Identifier DIRT_BROKEN = new Identifier(MOD_ID, "dirt_broken");
Line 352: Line 386:
         PlayerBlockBreakEvents.AFTER.register((world, player, pos, state, entity) -> {         PlayerBlockBreakEvents.AFTER.register((world, player, pos, state, entity) -> {
             if (state.getBlock() == Blocks.GRASS_BLOCK || state.getBlock() == Blocks.DIRT) {             if (state.getBlock() == Blocks.GRASS_BLOCK || state.getBlock() == Blocks.DIRT) {
-                StateSaverAndLoader serverState = StateSaverAndLoader.getServerState(world.getServer());+                // Send a packet to the client 
 +                MinecraftServer server = world.getServer(); 
 +                assert server != null; 
 +                 
 +                StateSaverAndLoader serverState = StateSaverAndLoader.getServerState(server);
                 // Increment the amount of dirt blocks that have been broken                 // Increment the amount of dirt blocks that have been broken
                 serverState.totalDirtBlocksBroken += 1;                 serverState.totalDirtBlocksBroken += 1;
Line 358: Line 396:
                 PlayerData playerState = StateSaverAndLoader.getPlayerState(player);                 PlayerData playerState = StateSaverAndLoader.getPlayerState(player);
                 playerState.dirtBlocksBroken += 1;                 playerState.dirtBlocksBroken += 1;
- 
-                // Send a packet to the client 
-                MinecraftServer server = world.getServer(); 
  
                 PacketByteBuf data = PacketByteBufs.create();                 PacketByteBuf data = PacketByteBufs.create();
Line 400: Line 435:
 </code> </code>
  
-If you ran the client now, it would seem as if everything is working, but we are forgetting a crucial step: We haven't updated our ''**writeNbt**'' and ''**createFromNbt**'' to save and load our hashmap.+If you ran the client now, it would seem as if everything is working, but we are forgetting a crucial step: We haven't updated our ''**writeNbt**''''**createFromNbt**'' and ''**createNew**'' to save and load our hashmap.
  
 The updated functions are as follows: The updated functions are as follows:
Line 426: Line 461:
     }     }
  
-    public static StateSaverAndLoader createFromNbt(NbtCompound tag) {+    public static StateSaverAndLoader createFromNbt(NbtCompound tag, RegistryWrapper.WrapperLookup registryLookup) {
         StateSaverAndLoader state = new StateSaverAndLoader();         StateSaverAndLoader state = new StateSaverAndLoader();
         state.totalDirtBlocksBroken = tag.getInt("totalDirtBlocksBroken");         state.totalDirtBlocksBroken = tag.getInt("totalDirtBlocksBroken");
Line 440: Line 475:
         });         });
  
 +        return state;
 +    }
 +    
 +    public static StateSaverAndLoader createNew() {
 +        StateSaverAndLoader state = new StateSaverAndLoader();
 +        state.totalDirtBlocksBroken = 0;
 +        state.players = new HashMap<>();
         return state;         return state;
     }     }
Line 457: Line 499:
 import net.minecraft.world.PersistentStateManager; import net.minecraft.world.PersistentStateManager;
 import net.minecraft.world.World; import net.minecraft.world.World;
-import net.minecraft.datafixer.DataFixTypes; 
  
 import java.util.HashMap; import java.util.HashMap;
Line 485: Line 526:
     }     }
  
-    public static StateSaverAndLoader createFromNbt(NbtCompound tag) {+    public static StateSaverAndLoader createFromNbt(NbtCompound tag, RegistryWrapper.WrapperLookup registryLookup) {
         StateSaverAndLoader state = new StateSaverAndLoader();         StateSaverAndLoader state = new StateSaverAndLoader();
         state.totalDirtBlocksBroken = tag.getInt("totalDirtBlocksBroken");         state.totalDirtBlocksBroken = tag.getInt("totalDirtBlocksBroken");
Line 501: Line 542:
         return state;         return state;
     }     }
 +    
 +    public static StateSaverAndLoader createNew() {
 +        StateSaverAndLoader state = new StateSaverAndLoader();
 +        state.totalDirtBlocksBroken = 0;
 +        state.players = new HashMap<>();
 +        return state;
 +    }
 +
 +    private static Type<StateSaverAndLoader> type = new Type<>(
 +            StateSaverAndLoader::createNew, // If there's no 'StateSaverAndLoader' yet create one and refresh variables
 +            StateSaverAndLoader::createFromNbt, // If there is a 'StateSaverAndLoader' NBT, parse it with 'createFromNbt'
 +            null // Supposed to be an 'DataFixTypes' enum, but we can just pass null
 +    );
  
     public static StateSaverAndLoader getServerState(MinecraftServer server) {     public static StateSaverAndLoader getServerState(MinecraftServer server) {
Line 509: Line 563:
         // 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 our function 'StateSaverAndLoader::createFromNbt'.
-        // +        StateSaverAndLoader state = persistentStateManager.getOrCreate(type, ExampleMod.MOD_ID);
-        // (Note: 'DataFixTypes.LEVEL' is used as it's a required parameter but the decision to use 'DataFixTypes.LEVEL' specifically is arbitrary.) +
-        StateSaverAndLoader state = persistentStateManager.getOrCreate( +
-                new Type<>(StateSaverAndLoader::new, StateSaverAndLoader::createFromNbt, DataFixTypes.LEVEL), +
-                ExampleMod.MOD_ID);+
  
         // 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, when Minecraft closes, 'writeNbt' won't be called and therefore nothing will be saved.
Line 538: Line 588:
 Running the client now, all our player-specific data is correctly saved. Running the client now, all our player-specific data is correctly saved.
  
-  Note: each time you restart the minecraft client with fabric, you're assigned a new UUID, so it may seem like it'not working, but that'just because of the developer environment(If you run the fabric multiplayer server, 'Minecraft Server' and use authme, you could verify it does indeed work as it's supposed to.)+==== Important Caveat ==== 
 + 
 +  Each time you restart the minecraft client with fabric, you're assigned a new random UUID each launch, so it may seem like our code is not working because it'pulling data for a new UUID never before seen. If you want to verify everything is working correctlydownload [[https://www.curseforge.com/minecraft/mc-mods/auth-me|AuthMe]] and drop the ''AuthMe.jar'' into run/mods. Login to your Minecraft account from the multiplayer screen. This will now change the random UUID to your actual UUID and therefore you can test if the data is correctly saved and associated with one UUID even across restarts. (You don't have to keep doing this, once you've verified once that, if the UUID is the same across launches, the data is correctly saved, you can just develop the mod normally safe in the knowledge that your data will be saved)
  
 Just remember if you add new fields to ''**PlayerData**'' or ''**StateSaveAndLoader**'' you need to correctly do the work of writing and loading those fields in the ''**writeNbt**'' and ''**createFromNbt**'' functions //always//. If you forget this step, your data won't be properly saved or loaded from disk. Just remember if you add new fields to ''**PlayerData**'' or ''**StateSaveAndLoader**'' you need to correctly do the work of writing and loading those fields in the ''**writeNbt**'' and ''**createFromNbt**'' functions //always//. If you forget this step, your data won't be properly saved or loaded from disk.
Line 562: Line 614:
 public class ExampleMod implements ModInitializer { public class ExampleMod implements ModInitializer {
  
-    public static final String MOD_ID = "your_unique_mod_id_change_me_please";+    public static final String MOD_ID = "examplemod";
  
     public static final Identifier DIRT_BROKEN = new Identifier(MOD_ID, "dirt_broken");     public static final Identifier DIRT_BROKEN = new Identifier(MOD_ID, "dirt_broken");
Line 581: Line 633:
         PlayerBlockBreakEvents.AFTER.register((world, player, pos, state, entity) -> {         PlayerBlockBreakEvents.AFTER.register((world, player, pos, state, entity) -> {
             if (state.getBlock() == Blocks.GRASS_BLOCK || state.getBlock() == Blocks.DIRT) {             if (state.getBlock() == Blocks.GRASS_BLOCK || state.getBlock() == Blocks.DIRT) {
-                StateSaverAndLoader serverState = StateSaverAndLoader.getServerState(world.getServer());+                // Send a packet to the client 
 +                MinecraftServer server = world.getServer(); 
 +                assert server != null; 
 +                 
 +                StateSaverAndLoader serverState = StateSaverAndLoader.getServerState(server);
                 // Increment the amount of dirt blocks that have been broken                 // Increment the amount of dirt blocks that have been broken
                 serverState.totalDirtBlocksBroken += 1;                 serverState.totalDirtBlocksBroken += 1;
Line 587: Line 643:
                 PlayerData playerState = StateSaverAndLoader.getPlayerState(player);                 PlayerData playerState = StateSaverAndLoader.getPlayerState(player);
                 playerState.dirtBlocksBroken += 1;                 playerState.dirtBlocksBroken += 1;
- 
-                // Send a packet to the client 
-                MinecraftServer server = world.getServer(); 
  
                 PacketByteBuf data = PacketByteBufs.create();                 PacketByteBuf data = PacketByteBufs.create();
Line 643: Line 696:
   * Note: The ''**playerData**'' we created isn't the up-to-date one that lives on the server. We simply create our own copy of ''**PlayerData**'' client-side and update it as we receive packets. Since it's ''**public static**'' that means you can access it from anywhere on the client.   * Note: The ''**playerData**'' we created isn't the up-to-date one that lives on the server. We simply create our own copy of ''**PlayerData**'' client-side and update it as we receive packets. Since it's ''**public static**'' that means you can access it from anywhere on the client.
  
-  Note: each time you restart the minecraft client with fabric, you're assigned a new UUID, so it may seem like it'not working, but that'just because of the developer environment(If you run the fabric multiplayer server, 'Minecraft Server' and use authme, you could verify it does indeed work as it's supposed to.)+==== Important Caveat ==== 
 + 
 +  Each time you restart the minecraft client with fabric, you're assigned a new random UUID each launch, so it may seem like our code is not working because it'pulling data for a new UUID never before seen. If you want to verify everything is working correctlydownload [[https://www.curseforge.com/minecraft/mc-mods/auth-me|AuthMe]] and drop the ''AuthMe.jar'' into run/mods. Login to your Minecraft account from the multiplayer screen. This will now change the random UUID to your actual UUID and therefore you can test if the data is correctly saved and associated with one UUID even across restarts. (You don't have to keep doing this, once you've verified once that, if the UUID is the same across launches, the data is correctly saved, you can just develop the mod normally safe in the knowledge that your data will be saved)
  
 ====== More Involved Player Data ====== ====== More Involved Player Data ======
Line 674: Line 729:
 import net.minecraft.world.PersistentStateManager; import net.minecraft.world.PersistentStateManager;
 import net.minecraft.world.World; import net.minecraft.world.World;
-import net.minecraft.datafixer.DataFixTypes; 
  
 import java.util.HashMap; import java.util.HashMap;
Line 708: Line 762:
     }     }
  
-    public static StateSaverAndLoader createFromNbt(NbtCompound tag) {+    public static StateSaverAndLoader createFromNbt(NbtCompound tag, RegistryWrapper.WrapperLookup registryLookup) {
         StateSaverAndLoader state = new StateSaverAndLoader();         StateSaverAndLoader state = new StateSaverAndLoader();
         state.totalDirtBlocksBroken = tag.getInt("totalDirtBlocksBroken");         state.totalDirtBlocksBroken = tag.getInt("totalDirtBlocksBroken");
Line 735: Line 789:
         return state;         return state;
     }     }
 +    
 +    public static StateSaverAndLoader createNew() {
 +        StateSaverAndLoader state = new StateSaverAndLoader();
 +        state.totalDirtBlocksBroken = 0;
 +        state.players = new HashMap<>();
 +        return state;
 +    }
 +
 +    private static Type<StateSaverAndLoader> type = new Type<>(
 +            StateSaverAndLoader::createNew, // If there's no 'StateSaverAndLoader' yet create one and refresh variables
 +            StateSaverAndLoader::createFromNbt, // If there is a 'StateSaverAndLoader' NBT, parse it with 'createFromNbt'
 +            null // Supposed to be an 'DataFixTypes' enum, but we can just pass null
 +    );
  
     public static StateSaverAndLoader getServerState(MinecraftServer server) {     public static StateSaverAndLoader getServerState(MinecraftServer server) {
Line 743: Line 810:
         // 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 our function 'StateSaverAndLoader::createFromNbt'.
-        // +        StateSaverAndLoader state = persistentStateManager.getOrCreate(type, ExampleMod.MOD_ID);
-        // (Note: 'DataFixTypes.LEVEL' is used as it's a required parameter but the decision to use 'DataFixTypes.LEVEL' specifically is arbitrary.) +
-        StateSaverAndLoader state = persistentStateManager.getOrCreate( +
-                new Type<>(StateSaverAndLoader::new, StateSaverAndLoader::createFromNbt, DataFixTypes.LEVEL), +
-                ExampleMod.MOD_ID);+
  
         // 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, when Minecraft closes, 'writeNbt' won't be called and therefore nothing will be saved.
Line 819: Line 882:
 public class ExampleMod implements ModInitializer { public class ExampleMod implements ModInitializer {
  
-    public static final String MOD_ID = "your_unique_mod_id_change_me_please";+    public static final String MOD_ID = "examplemod";
  
     public static final Identifier DIRT_BROKEN = new Identifier(MOD_ID, "dirt_broken");     public static final Identifier DIRT_BROKEN = new Identifier(MOD_ID, "dirt_broken");
Line 838: Line 901:
         PlayerBlockBreakEvents.AFTER.register((world, player, pos, state, entity) -> {         PlayerBlockBreakEvents.AFTER.register((world, player, pos, state, entity) -> {
             if (state.getBlock() == Blocks.GRASS_BLOCK || state.getBlock() == Blocks.DIRT) {             if (state.getBlock() == Blocks.GRASS_BLOCK || state.getBlock() == Blocks.DIRT) {
-                StateSaverAndLoader serverState = StateSaverAndLoader.getServerState(world.getServer());+                // Send a packet to the client 
 +                MinecraftServer server = world.getServer(); 
 +                assert server != null; 
 +                 
 +                StateSaverAndLoader serverState = StateSaverAndLoader.getServerState(server);
                 // Increment the amount of dirt blocks that have been broken                 // Increment the amount of dirt blocks that have been broken
                 serverState.totalDirtBlocksBroken += 1;                 serverState.totalDirtBlocksBroken += 1;
Line 844: Line 911:
                 PlayerData playerState = StateSaverAndLoader.getPlayerState(player);                 PlayerData playerState = StateSaverAndLoader.getPlayerState(player);
                 playerState.dirtBlocksBroken += 1;                 playerState.dirtBlocksBroken += 1;
- 
-                // Send a packet to the client 
-                MinecraftServer server = world.getServer(); 
  
                 PacketByteBuf data = PacketByteBufs.create();                 PacketByteBuf data = PacketByteBufs.create();
tutorial/persistent_states.1695879458.txt.gz · Last modified: 2023/09/28 05:37 by jmanc3