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
Next revision
Previous revision
tutorial:persistent_states [2024/04/25 14:06] mayaqqtutorial:persistent_states [2025/07/28 20:33] (current) – Begin update to 1.21.5 the...why...even...how
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;
  
Line 108: Line 128:
  
     public Integer totalDirtBlocksBroken = 0;     public Integer totalDirtBlocksBroken = 0;
- 
-    @Override 
-    public NbtCompound writeNbt(NbtCompound nbt) { 
-        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!) +  `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.
- +
-Next add the following function to that same file:+
  
 <code java> <code java>
Line 128: 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;+
     }     }
 + 
 +    private StateSaverAndLoader(int totalDirtBlocksBroken) {
 +        this.totalDirtBlocksBroken = totalDirtBlocksBroken;
 +    }
 + 
 +    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>
- 
-This 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**''. 
- 
 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 147: Line 164:
     // ... (Previously written code)     // ... (Previously written code)
  
-    private static Type<StateSaverAndLoader> type = new Type<>( +    private static final PersistentStateType<StateSaverAndLoader> type = new PersistentStateType<>( 
-            StateSaverAndLoader::new, // If there's no 'StateSaverAndLoader' yet create one +            (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 155: Line 173:
     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 the codec in our type, using the codec to decode the nbt into our state 
-        StateSaverAndLoader state = persistentStateManager.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 177: Line 196:
  
 <code java> <code java>
-import net.minecraft.nbt.NbtCompound;+import com.mojang.serialization.Codec; 
 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.PersistentStateType;
 import net.minecraft.world.World; import net.minecraft.world.World;
  
Line 187: Line 208:
     public Integer totalDirtBlocksBroken = 0;     public Integer totalDirtBlocksBroken = 0;
  
-    @Override +    private StateSaverAndLoader() {
-    public NbtCompound writeNbt(NbtCompound nbt) { +
-        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;+
     }     }
  
-    private static Type<StateSaverAndLoader> type = new Type<>( +    private int getTotalDirtBlocksBroken() { 
-            StateSaverAndLoader::new, // If there's no 'StateSaverAndLoader' yet create one +        return totalDirtBlocksBroken; 
-            StateSaverAndLoader::createFromNbt, // If there is a 'StateSaverAndLoader' NBT, parse it with 'createFromNbt'+    } 
 + 
 +    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 
 +    ); 
 + 
 +    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 207: Line 233:
     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 the codec in our type, using the codec to decode the nbt into our state 
-        StateSaverAndLoader state = persistentStateManager.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 231: Line 258:
 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 346: Line 368:
 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 354: Line 376:
         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 360: Line 386:
                 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 402: Line 425:
 </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 442: Line 465:
         });         });
  
 +        return state;
 +    }
 +    
 +    public static StateSaverAndLoader createNew() {
 +        StateSaverAndLoader state = new StateSaverAndLoader();
 +        state.totalDirtBlocksBroken = 0;
 +        state.players = new HashMap<>();
         return state;         return state;
     }     }
Line 500: Line 530:
         });         });
  
 +        return state;
 +    }
 +    
 +    public static StateSaverAndLoader createNew() {
 +        StateSaverAndLoader state = new StateSaverAndLoader();
 +        state.totalDirtBlocksBroken = 0;
 +        state.players = new HashMap<>();
         return state;         return state;
     }     }
  
     private static Type<StateSaverAndLoader> type = new Type<>(     private static Type<StateSaverAndLoader> type = new Type<>(
-            StateSaverAndLoader::new, // If there's no 'StateSaverAndLoader' yet create one+            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'             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             null // Supposed to be an 'DataFixTypes' enum, but we can just pass null
Line 567: Line 604:
 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 586: Line 623:
         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 592: Line 633:
                 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 739: Line 777:
         });         });
  
 +        return state;
 +    }
 +    
 +    public static StateSaverAndLoader createNew() {
 +        StateSaverAndLoader state = new StateSaverAndLoader();
 +        state.totalDirtBlocksBroken = 0;
 +        state.players = new HashMap<>();
         return state;         return state;
     }     }
  
     private static Type<StateSaverAndLoader> type = new Type<>(     private static Type<StateSaverAndLoader> type = new Type<>(
-            StateSaverAndLoader::new, // If there's no 'StateSaverAndLoader' yet create one+            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'             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             null // Supposed to be an 'DataFixTypes' enum, but we can just pass null
Line 827: Line 872:
 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 846: Line 891:
         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 852: Line 901:
                 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.1714053985.txt.gz · Last modified: 2024/04/25 14:06 by mayaqq