User Tools

Site Tools


zh_cn:tutorial:persistent_states

Differences

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

Link to this comparison view

Next revision
Previous revision
zh_cn:tutorial:persistent_states [2023/12/15 08:10] – created & translation started dreamuniversezh_cn:tutorial:persistent_states [2023/12/23 02:39] (current) – fix incorrect spelling&character dreamuniverse
Line 1: Line 1:
 ====== 状态持久化 ====== ====== 状态持久化 ======
 +
 +===== 引言 =====
  
 通常情况下,我们的模组中带有与玩家相关的信息,或是在玩家死亡、服务器重启时我们希望保留的世界信息。\\ 通常情况下,我们的模组中带有与玩家相关的信息,或是在玩家死亡、服务器重启时我们希望保留的世界信息。\\
Line 16: Line 18:
 那么,我们如何能让 Fabric 保存这些信息,或者其他我们想保存的模组或游戏数据,以供下次玩家加载世界或重新登录时能读取到这些信息? 那么,我们如何能让 Fabric 保存这些信息,或者其他我们想保存的模组或游戏数据,以供下次玩家加载世界或重新登录时能读取到这些信息?
  
-====== 简单的消息发送——数据包 ======+===== 简单的消息发送——数据包 =====
  
 首先,既然我们的游戏保存在逻辑服务器上,那么我们就让服务器在检测到玩家挖掘泥土方块时向玩家发送一个数据包,并将其显示在玩家的聊天区内。\\ 首先,既然我们的游戏保存在逻辑服务器上,那么我们就让服务器在检测到玩家挖掘泥土方块时向玩家发送一个数据包,并将其显示在玩家的聊天区内。\\
Line 103: Line 105:
 那么,如果我们把上次游戏结束时的计数写入到了本地(如硬盘某处),我们就可以在游戏启动时读取并加载它,作为计数器的初始值。这样一来我们就可以继续统计下去。\\ 那么,如果我们把上次游戏结束时的计数写入到了本地(如硬盘某处),我们就可以在游戏启动时读取并加载它,作为计数器的初始值。这样一来我们就可以继续统计下去。\\
 但是,我们**必须**要做到这一点:数值要在游戏关闭时保存,在游戏启动时读取并加载。\\ 但是,我们**必须**要做到这一点:数值要在游戏关闭时保存,在游戏启动时读取并加载。\\
-想要达到这一目的,方法千千万,不过我们在这里使用 Minecraft 提供给我们的方法:实现一个扩展了 '**PersistentState**' 的类。+想要达到这一目的,方法千千万,不过我们在这里使用 Minecraft 提供给我们的方法:实现一个继承了 ''**PersistentState**'' 的类。
  
-====== 状态持久化 ======+===== 状态持久化详述 =====
  
 首先,我们在项目目录新建一个名为 ''**StateSaverAndLoader.java**'' 的文件,它需要实现 ''**ModInitializer**'' 类: 首先,我们在项目目录新建一个名为 ''**StateSaverAndLoader.java**'' 的文件,它需要实现 ''**ModInitializer**'' 类:
Line 128: Line 130:
 </code> </code>
  
-注:在扩展 ''**PersistentState**'' 类时,必须实现 ''**writeNbt**''。从功能上讲,我们通过 ''**NbtCompound**'' 将我们要存储到本地的数据进行打包。在本例中,我们将先前创建的 "**public Integer totalDirtBlocksBroken**移入了这个文件。+注:在继承 ''**PersistentState**'' 类时,必须实现 ''**writeNbt**''。从功能上讲,我们通过 ''**NbtCompound**'' 将我们要存储到本地的数据进行打包。在本例中,我们将先前创建的 ''**public Integer totalDirtBlocksBroken**'' 移入了这个文件。
  
-  * ''**NbtCompound**''能直接保存 ''**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**''只是保存**整数值**,它还保存着其他类型的数据,甚至是其他的 ''**NbtCompound**'' 数据或者任意的字节数据。 
 +  因此,如果您希望存储一个自定义类,那么您应当新建一个 ''**NbtCompound**'' 子对象,然后将您自定义类的相应字段打包进这个 ''**NbtCompound**'' 子对象中,再传入其父级 ''**NbtCompound**'' 对象。(我们正在为此努力!)
  
-Next add the following function to that same file:+接下来,将这个方法添加到同一个文件中:
  
 <code java> <code java>
 public class StateSaverAndLoader extends PersistentState { public class StateSaverAndLoader extends PersistentState {
  
-    // ... (Previously written code)+    // ... (先前写好的代码部分)
  
     public static StateSaverAndLoader createFromNbt(NbtCompound tag) {     public static StateSaverAndLoader createFromNbt(NbtCompound tag) {
Line 147: Line 150:
 </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**''.+这个方法的作用与 ''**writeNbt**'' 相反,它需要传入一个 ''**NbtCompound**'' (在 ''**writeNbt**'' 时写入的同一个对象),之后新建一个 ''**StateSaverAndLoader**'' 以供我们传入在 ''**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**''.+  * 注:与 ''**getInt**'' 读取整数的方式类似,''**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**''.+现在我们再添加一个工具类方法,这个方法需要导入 ''**MinecraftServer.PersistentStateManager**''''**PersistentStateManager**'' 有一个名为 ''**getOrCreate**'' 的方法,其中 ''**MOD_ID**'' 作为字段传递给 ''**StateSaverAndLoader**'' 以确定是否有其实例,若没有则创建。\\ 
 +当创建时,其调用我们刚刚写到的 ''**createFromNbt**'' 并将我们已经存储在本地的 ''**NbtCompound**'' 传入进去。最后,这个方法会向给定的 ''**MinecraftServer**'' 返回 ''**StateSaverAndLoader**''
  
 <code java> <code java>
 public class StateSaverAndLoader extends PersistentState { public class StateSaverAndLoader extends PersistentState {
  
-    // ... (Previously written code)+    // ... (先前写好的代码部分)
  
     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::new, // 若不存在 'StateSaverAndLoader' 则创建 
-            StateSaverAndLoader::createFromNbt, // If there is a 'StateSaverAndLoader' NBT, parse it with 'createFromNbt' +            StateSaverAndLoader::createFromNbt, // 若存在 'StateSaverAndLoader' NBT, 则调用 'createFromNbt' 传入参数 
-            null // Supposed to be an 'DataFixTypes' enum, but we can just pass null+            null // 此处理论上应为 'DataFixTypes' 的枚举,但我们直接传递为空(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)+        // (注:如需在任意维度生效,请使用 'World.OVERWORLD' ,不要使用 'World.END' 或 'World.NETHER')
         PersistentStateManager persistentStateManager = server.getWorld(World.OVERWORLD).getPersistentStateManager();         PersistentStateManager persistentStateManager = server.getWorld(World.OVERWORLD).getPersistentStateManager();
  
-        // The first time the following 'getOrCreate' function is called, it creates a brand new 'StateSaverAndLoader' and +        // 当第一次调用了方法 'getOrCreate' 后,它会创建新的 'StateSaverAndLoader' 并将其存储于  'PersistentStateManager' 中。 
-        // stores it inside the 'PersistentStateManager'. The subsequent calls to 'getOrCreate' pass in the saved +        //  'getOrCreate' 的后续调用将本地的 'StateSaverAndLoader' NBT 传递给 'StateSaverAndLoader::createFromNbt'
-        // 'StateSaverAndLoader' NBT on disk to our function 'StateSaverAndLoader::createFromNbt'.+
         StateSaverAndLoader state = persistentStateManager.getOrCreate(type, ExampleMod.MOD_ID);         StateSaverAndLoader state = persistentStateManager.getOrCreate(type, ExampleMod.MOD_ID);
  
-        // If state is not marked dirty, when Minecraft closes, 'writeNbt' won't be called and therefore nothing will be saved. +        // 若状态未标记为脏(dirty),当 Minecraft 关闭时, 'writeNbt' 不会被调用,相应地,没有数据会被保存。 
-        // Technically it's 'cleaner' if you only mark state as dirty when there was actually a change, but the vast majority +        // 从技术上讲,只有在事实上发生数据变更时才应当将状态标记为脏(dirty)。 
-        // 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. +        // 但大多数开发者和模组作者会对他们的数据未能保存而感到困惑,所以不妨直接使用 'markDirty'  
-        // Besides, it's literally just setting a bool to true, and the only time there's a 'cost' is when the file is written to disk when +        // 另外,这只将对应的布尔值设定为 TRUE,代价是文件写入磁盘时模组的状态不会有任何改变。(这种情况非常少见)
-        // there were no actual change to any of the mods state (INCREDIBLY RARE).+
         state.markDirty();         state.markDirty();
  
Line 185: Line 187:
 </code> </code>
  
-Your ''**StateSaverAndLoader**'' file should look as follows:+现在,您的 ''**StateSaverAndLoader**'' 文件应如下所示:
  
 <code java> <code java>
Line 211: Line 213:
  
     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::new, // 若不存在 'StateSaverAndLoader' 则创建 
-            StateSaverAndLoader::createFromNbt, // If there is a 'StateSaverAndLoader' NBT, parse it with 'createFromNbt' +            StateSaverAndLoader::createFromNbt, // 若存在 'StateSaverAndLoader' NBT, 则调用 'createFromNbt' 传入参数 
-            null // Supposed to be an 'DataFixTypes' enum, but we can just pass null+            null // 此处理论上应为 'DataFixTypes' 的枚举,但我们直接传递为空(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)+        // (注:如需在任意维度生效,请使用 'World.OVERWORLD' ,不要使用 'World.END' 或 'World.NETHER')
         PersistentStateManager persistentStateManager = server.getWorld(World.OVERWORLD).getPersistentStateManager();         PersistentStateManager persistentStateManager = server.getWorld(World.OVERWORLD).getPersistentStateManager();
  
-        // The first time the following 'getOrCreate' function is called, it creates a brand new 'StateSaverAndLoader' and +        // 当第一次调用了方法 'getOrCreate' 后,它会创建新的 'StateSaverAndLoader' 并将其存储于  'PersistentStateManager' 中。 
-        // stores it inside the 'PersistentStateManager'. The subsequent calls to 'getOrCreate' pass in the saved +        //  'getOrCreate' 的后续调用将本地的 'StateSaverAndLoader' NBT 传递给 'StateSaverAndLoader::createFromNbt'
-        // 'StateSaverAndLoader' NBT on disk to our function 'StateSaverAndLoader::createFromNbt'.+
         StateSaverAndLoader state = persistentStateManager.getOrCreate(type, ExampleMod.MOD_ID);         StateSaverAndLoader state = persistentStateManager.getOrCreate(type, ExampleMod.MOD_ID);
  
-        // If state is not marked dirty, when Minecraft closes, 'writeNbt' won't be called and therefore nothing will be saved. +        // 若状态未标记为脏(dirty),当 Minecraft 关闭时, 'writeNbt' 不会被调用,相应地,没有数据会被保存。 
-        // Technically it's 'cleaner' if you only mark state as dirty when there was actually a change, but the vast majority +        // 从技术上讲,只有在事实上发生数据变更时才应当将状态标记为脏(dirty)。 
-        // 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. +        // 但大多数开发者和模组作者会对他们的数据未能保存而感到困惑,所以不妨直接使用 'markDirty'  
-        // Besides, it's literally just setting a bool to true, and the only time there's a 'cost' is when the file is written to disk when +        // 另外,这只将对应的布尔值设定为 TRUE,代价是文件写入磁盘时模组的状态不会有任何改变。(这种情况非常少见)
-        // there were no actual change to any of the mods state (INCREDIBLY RARE).+
         state.markDirty();         state.markDirty();
  
Line 237: Line 237:
 </code> </code>
  
-You'll also have to update your class which ''**implements ModInitializer**'' so that it's as follows:+相应地,您也需要将实现了 ''**ModInitializer**'' 的类做如下改动:
  
 <code java> <code java>
Line 253: Line 253:
 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 = "您的MOD_ID";
  
     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 262: Line 262:
             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());                 StateSaverAndLoader serverState = StateSaverAndLoader.getServerState(world.getServer());
-                // Increment the amount of dirt blocks that have been broken+                // 当泥土方块被挖掘时增加计数
                 serverState.totalDirtBlocksBroken += 1;                 serverState.totalDirtBlocksBroken += 1;
  
-                // Send a packet to the client+                // 向客户端发送数据包
                 MinecraftServer server = world.getServer();                 MinecraftServer server = world.getServer();
  
Line 281: Line 281:
 </code> </code>
  
-If you run your game now, you should see the counter going up, but now, if you fully close Minecraft, and open it again, you should see the number keeps increasing from where it left off.+如果您现在运行游戏,您会发现计数器正常递增,但现在即使您关闭游戏并重新启动,计数器也会从退出时的位置继续递增。 
 + 
 +有一点您有可能会忽略,''**totalDirtBlocksBroken**'' 不是玩家限定的,这意味着若这个模组运行在服务器侧,并同时有好几个玩家挖掘泥土方块,他们都会收到相同的统计数字。\\ 
 +这对我们模组中的特定类型数据是很好的,表明它们能正常工作。但更多时候,我们期望数据是玩家限定(因人而异)的。 
 +正如我们开篇所提,如果我们想要保留**任意玩家**的**特定方块**的挖掘数据,这时我们要怎么做?
  
-What you might or might not expect is that ''**totalDirtBlocksBroken**'' is not specific to a player, that is, if you made a server with this mod, and a few people were running around breaking dirt blocks, all of them would increment that same number. This is fine for certain types of data we'd like to store for our mods, but most of the time, we want to store player-specific data. For instance, as we mentioned at the start of this article, what if we would like to store how many dirt blocks any //specific// player has broken? +===== 因玩家而异的持久化状态 =====
-====== Player Specific Persistent State ======+
  
-We can store player-specific data by extending what we already wrote. +我们可以将我们所写的代码延伸一下,这样就可以存储每个玩家的特定数据了。
  
-First write a new class ''**PlayerData.java**'' (again placing it in the same folder as our class which ''**implements ModInitializer**'').+首先,新建一个名为 ''**PlayerData.java**'' 的类(需要和实现了 ''**ModInitializer**'' 的类位于同一层次的软件包中).
  
-  * Extremely important noteSince we'll be creating a HashMap which seemingly stores ''**PlayerData**'''sit'll be tempting when you want to know some ''**PlayerData**'' client-side to use that hashmap, **but**, you won't be able to, **because** this data only exists on the server. If you want the client to see some or all the player's data. You'll have to send it through a packet. (We'll create an ''**INITIAL_SYNC**'' packet later which we will use to demonstrate this).+  * 特别提示我们会创建 HashMap 用以存储 ''**PlayerData**'' 数据,当客户端侧希望获知 ''**PlayerData**'' 时非常有效, **但是**若在服务器侧如法炮制,客户端将**无法获知任何数据**,因为这些数据**只存在于服务器上**。\\ 
 +  如果您期望在客户端侧获知部分或全部的 ''**PlayerData**'' 数据,则必须使用数据包进行传递。(稍后我们会创建一个内联同步(''**INITIAL_SYNC**'')数据包以作演示).
  
 <code java> <code java>
Line 298: Line 302:
 </code> </code>
  
-To simplify, we're just continuing with our simple example, but you could put any fields you'd like in the ''**PlayerData**'' (and it's expected that you will).+为尽可能简明地说明问题,在示例代码中我们依然使用这个简单的例子,但您在实际应用中可以向 ''**PlayerData**'' 内放入您所期望使用的任意字段。
  
-Next, we'll modify the top of our ''**StateSaverAndLoader.java**'' class as follows:+接下来,将 ''**StateSaverAndLoader.java**'' 类的头部做如下改动:
  
 <code java> <code java>
-// ... (Previous imports)+// ... (其他的引用包)
 import java.util.HashMap; import java.util.HashMap;
 import java.util.UUID; import java.util.UUID;
Line 313: Line 317:
     public HashMap<UUID, PlayerData> players = new HashMap<>();     public HashMap<UUID, PlayerData> players = new HashMap<>();
  
-    // ... (Rest of the code)+    // ... (代码的剩余部分)
  
 } }
 </code> </code>
  
-Note: We create a ''**HashMap**'' of ''**UUID**'''s to ''**PlayereData**'''s. If you don't know what a hashmap does: you give them a 'key', in our case a ''**UUID**'' and they give you back something, in our case ''**PlayerData**''. The reason we use ''**UUID**'''s is because every player that connects to our 'server' has a unique ''**UUID**'' that //only// they are associated with. This lets us differentiate between different players and lets us 'pull' the right data for them. (Or create it if it doesn't exist yet).+注:我们创建了一个关于 ''**UUID**'' 数据的 ''**HashMap**'' 并将其存储于 ''**PlayerData**'' 中。\\ 
 +Hashmap 即哈希表,简单而言,在本例中,您向表中给出一个特定的“键值”(key),表从 ''**PlayerData**'' 中返回对应“键值”的 ''**UUID**''。\\ 
 +我们使用 ''**UUID**''进行记录的原因是每位连接到服务器的玩家的 ''**UUID**'' 必定是//唯一//的。这就使得我们得以区分不同玩家,并针对其返回相对应的数据。如对应玩家的数据不存在,则创建之。
  
-Let's add a utility function to ''**StateSaverAndLoader**'' which will take a ''**LivingEntity**'' and return the associated ''**PlayerData**'' in our '''**HashMap**'''.+接下来我们向 ''**StateSaverAndLoader**'' 类内添加一个方法,其接受一个传入参数 ''**LivingEntity**'' 并从我们的哈希表中返回对应的 ''**PlayerData**''
  
 <code java> <code java>
 public class StateSaverAndLoader extends PersistentState { public class StateSaverAndLoader extends PersistentState {
  
-    // ... (Previously written code)+    // ... (先前写好的代码部分)
  
     public static PlayerData getPlayerState(LivingEntity player) {     public static PlayerData getPlayerState(LivingEntity player) {
         StateSaverAndLoader serverState = getServerState(player.getWorld().getServer());         StateSaverAndLoader serverState = getServerState(player.getWorld().getServer());
  
-        // Either get the player by the uuid, or we don't have data for him yet, make a new player state+        // 根据 UUID 获取对应玩家的状态,如果没有该玩家的数据,就创建一个新的玩家状态。
         PlayerData playerState = serverState.players.computeIfAbsent(player.getUuid(), uuid -> new PlayerData());         PlayerData playerState = serverState.players.computeIfAbsent(player.getUuid(), uuid -> new PlayerData());
  
Line 339: Line 345:
  
  
-  * If our ''**StateSaverAndLoader**'' has never seen the ''**UUID**'' before then it makes sense that our ''**HashMap**'' won't return anything. That's why we use the convenience function ''**computeIfAbsent**'' so that in the case where //that// ''**UUID**'' doesn't exist in the hashmap yet, it will automatically create a new ''**PlayerData**'' and puts it in our hashmap, using //that// players specific ''**UUID**'' as the key so that it can be retrieved later.+  * 若类 ''**StateSaverAndLoader**'' 从未处理过对应玩家的 ''**UUID**'' ,那么哈希表的返回值将会为空。这就是为何我们使用了 ''**computeIfAbsent**'' 方法,这样当该 ''**UUID**'' 未在表中存储时,模组会自动创建一个对应该玩家的新 ''**PlayerData**'' 并存入表中。我们使用 ''**UUID**'' 作为键值以便稍后从表中检索。
  
-Now update the class which ''**implements ModInitializer**'' as follows:+接下来将实现了 ''**ModInitializer**'' 的类再改动一下:
  
 <code java> <code java>
Line 357: Line 363:
 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 = "您的MOD_ID";
  
     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 363: Line 369:
     @Override     @Override
     public void onInitialize() {     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());                 StateSaverAndLoader serverState = StateSaverAndLoader.getServerState(world.getServer());
-                // Increment the amount of dirt blocks that have been broken+                // 当泥土方块被挖掘时增加计数
                 serverState.totalDirtBlocksBroken += 1;                 serverState.totalDirtBlocksBroken += 1;
  
Line 372: Line 379:
                 playerState.dirtBlocksBroken += 1;                 playerState.dirtBlocksBroken += 1;
  
-                // Send a packet to the client+                // 向客户端发送数据包
                 MinecraftServer server = world.getServer();                 MinecraftServer server = world.getServer();
  
Line 389: Line 396:
 </code> </code>
  
-You'll also have to modify the class which ''**implements ClientModInitializer**'' as follows:+您同时需要将实现了 ''**ClientModInitializer**'' 的类做如下改动:
  
 <code java> <code java>
Line 405: Line 412:
  
             client.execute(() -> {             client.execute(() -> {
-                client.player.sendMessage(Text.literal("Total dirt blocks broken: " + totalDirtBlocksBroken)); +                client.player.sendMessage(Text.literal("总计被挖掘的泥土方块数:" + totalDirtBlocksBroken)); 
-                client.player.sendMessage(Text.literal("Player specific dirt blocks broken: " + playerSpecificDirtBlocksBroken));+                client.player.sendMessage(Text.literal("当前玩家挖掘的泥土方块数:" + playerSpecificDirtBlocksBroken));
             });             });
         });         });
Line 413: Line 420:
 </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.+如果您现在运行客户端,看上去一切工作正常,但我们忘记了非常关键的一点:我们没有使用 ''**writeNbt**'' 和 ''**createFromNbt**'' 方法来存储、加载我们的哈希表。
  
-The updated functions are as follows:+改动后的方法代码如下所示:
  
 <code java> <code java>
 public class StateSaverAndLoader extends PersistentState { public class StateSaverAndLoader extends PersistentState {
  
-    // ... (Rest of code)+    // ... (代码的剩余部分)
  
     @Override     @Override
Line 456: Line 463:
     }     }
  
-    // ... (Rest of code)+    // ... (代码的剩余部分)
  
 } }
 </code> </code>
  
-The final ''**StateSaverAndLoader**'' should be as follows:+最终的 ''**StateSaverAndLoader**'' 类代码如下所示:
  
 <code java> <code java>
Line 515: Line 522:
  
     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::new, // 若不存在 'StateSaverAndLoader' 则创建 
-            StateSaverAndLoader::createFromNbt, // If there is a 'StateSaverAndLoader' NBT, parse it with 'createFromNbt' +            StateSaverAndLoader::createFromNbt, // 若存在 'StateSaverAndLoader' NBT, 则调用 'createFromNbt' 传入参数 
-            null // Supposed to be an 'DataFixTypes' enum, but we can just pass null+            null // 此处理论上应为 'DataFixTypes' 的枚举,但我们直接传递为空(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)+        // (注:如需在任意维度生效,请使用 'World.OVERWORLD' ,不要使用 'World.END' 或 'World.NETHER')
         PersistentStateManager persistentStateManager = server.getWorld(World.OVERWORLD).getPersistentStateManager();         PersistentStateManager persistentStateManager = server.getWorld(World.OVERWORLD).getPersistentStateManager();
  
-        // The first time the following 'getOrCreate' function is called, it creates a brand new 'StateSaverAndLoader' and +        // 当第一次调用了方法 'getOrCreate' 后,它会创建新的 'StateSaverAndLoader' 并将其存储于  'PersistentStateManager' 中。 
-        // stores it inside the 'PersistentStateManager'. The subsequent calls to 'getOrCreate' pass in the saved +        //  'getOrCreate' 的后续调用将本地的 'StateSaverAndLoader' NBT 传递给 'StateSaverAndLoader::createFromNbt'
-        // 'StateSaverAndLoader' NBT on disk to our function 'StateSaverAndLoader::createFromNbt'.+
         StateSaverAndLoader state = persistentStateManager.getOrCreate(type, ExampleMod.MOD_ID);         StateSaverAndLoader state = persistentStateManager.getOrCreate(type, ExampleMod.MOD_ID);
  
-        // If state is not marked dirty, when Minecraft closes, 'writeNbt' won't be called and therefore nothing will be saved. +        // 若状态未标记为脏(dirty),当 Minecraft 关闭时, 'writeNbt' 不会被调用,相应地,没有数据会被保存。 
-        // Technically it's 'cleaner' if you only mark state as dirty when there was actually a change, but the vast majority +        // 从技术上讲,只有在事实上发生数据变更时才应当将状态标记为脏(dirty)。 
-        // 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. +        // 但大多数开发者和模组作者会对他们的数据未能保存而感到困惑,所以不妨直接使用 'markDirty'  
-        // Besides, it's literally just setting a bool to true, and the only time there's a 'cost' is when the file is written to disk when +        // 另外,这只将对应的布尔值设定为 TRUE,代价是文件写入磁盘时模组的状态不会有任何改变。(这种情况非常少见)
-        // there were no actual change to any of the mods state (INCREDIBLY RARE).+
         state.markDirty();         state.markDirty();
  
Line 542: Line 547:
         StateSaverAndLoader serverState = getServerState(player.getWorld().getServer());         StateSaverAndLoader serverState = getServerState(player.getWorld().getServer());
  
-        // Either get the player by the uuid, or we don't have data for him yet, make a new player state+        // 根据 UUID 获取对应玩家的状态,如果没有该玩家的数据,就创建一个新的玩家状态。
         PlayerData playerState = serverState.players.computeIfAbsent(player.getUuid(), uuid -> new PlayerData());         PlayerData playerState = serverState.players.computeIfAbsent(player.getUuid(), uuid -> new PlayerData());
  
Line 550: Line 555:
 </code> </code>
  
-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's not working, but that's 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.)+  * 注:每次您使用 Fabric 调试启动客户端时,您所分配到的 UUID 都是不一样的,所以您可能会认为代码没有正确工作,但这只是因为您使用的开发环境使然。(如果您在生产环境运行一个 Minecraft 服务器并使用 Authme,您就可以确认模组在按照我们的预期目的工作了。)
  
-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.+请谨记,只要您向 ''**PlayerData**'' 或 ''**StateSaveAndLoader**'' 添加了新的字段,您//就需要//将对应的字段调用 ''**writeNbt**'' 和 ''**createFromNbt**'' 方法。如果您忘记了这一步,那么数据就无法被正确地保存到磁盘或从磁盘读取。
  
-====== Initial Sync ======+===== 内联同步 =====
  
-What if it's important for our mod that as soon as a player joins they receive some or all the PlayerData associated with them? For this, we will crate a new packet ''**INITIAL_SYNC**'' which will send the player, their specific player data as soon as they join the world.+那么,当玩家加入服务器时,他们应当收到与他们相关的部分或全部玩家数据(''**PlayerData**''),而这一点对我们的模组至关重要。\\ 
 +对于这一点,当玩家加入世界时,我们会向玩家发送一个用于内联同步(''**INITIAL_SYNC**'')的数据包,从而指示服务器发送该玩家的数据。
  
-Modify your class which ''**implements ModInitializer**'' as follows:+现在,我们将实现了''**ModInitializer**'' 的类做如下改动:
  
 <code java> <code java>
Line 576: Line 582:
 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 = "您的MOD_ID";
  
     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 596: Line 602:
             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());                 StateSaverAndLoader serverState = StateSaverAndLoader.getServerState(world.getServer());
-                // Increment the amount of dirt blocks that have been broken+                // 当泥土方块被挖掘时增加计数
                 serverState.totalDirtBlocksBroken += 1;                 serverState.totalDirtBlocksBroken += 1;
  
Line 602: Line 608:
                 playerState.dirtBlocksBroken += 1;                 playerState.dirtBlocksBroken += 1;
  
-                // Send a packet to the client+                // 向客户端发送数据包
                 MinecraftServer server = world.getServer();                 MinecraftServer server = world.getServer();
  
Line 619: Line 625:
 </code> </code>
  
-Then modify your class which ''**implements ClientModInitializer**'' as follows:+随后,再将实现了 ''**ClientModInitializer**'' 的类做如下改动:
  
 <code java> <code java>
Line 637: Line 643:
  
             client.execute(() -> {             client.execute(() -> {
-                client.player.sendMessage(Text.literal("Total dirt blocks broken: " + totalDirtBlocksBroken)); +                client.player.sendMessage(Text.literal("总计被挖掘的泥土方块数:" + totalDirtBlocksBroken)); 
-                client.player.sendMessage(Text.literal("Player specific dirt blocks broken: " + playerData.dirtBlocksBroken));+                client.player.sendMessage(Text.literal("当前玩家挖掘的泥土方块数:" + playerData.dirtBlocksBroken));
             });             });
         });         });
Line 646: Line 652:
  
             client.execute(() -> {             client.execute(() -> {
-                client.player.sendMessage(Text.literal("Initial specific dirt blocks broken: " + playerData.dirtBlocksBroken));+                client.player.sendMessage(Text.literal("初始化的当前玩家挖掘的泥土方块数:" + playerData.dirtBlocksBroken));
             });             });
         });         });
Line 653: Line 659:
 </code> </code>
  
-As soon as you join the world/server you should see a message popup telling you the amount of dirt blocks you've specifically broken.+这样一来,当您进入本地世界或服务器时,您就会看到有一条消息提示您已经挖掘了多少泥土方块。
  
-  * 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'''**public static**'' that means you can access it from anywhere on the client.+  * 注①:我们在客户端侧所创建的 ''**PlayerData**'' 并非与服务器侧实时同步。我们只是从服务器侧接收并在客户端侧创建了相应副本,并在客户端侧刷新。由于其关键字为 ''**public static**'',这意味着您可以在客户端的任意类中访问。
  
-  * Note: each time you restart the minecraft client with fabric, you're assigned a new UUID, so it may seem like it's not working, but that's 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.)+  * 注②:每次您使用 Fabric 调试启动客户端时,您所分配到的 UUID 都是不一样的,所以您可能会认为代码没有正确工作,但这只是因为您使用的开发环境使然。(如果您在生产环境运行一个 Minecraft 服务器并使用 Authme,您就可以确认模组在按照我们的预期目的工作了。)
  
-====== More Involved Player Data ======+===== 更复杂的玩家数据 =====
  
-And just for good measure, let's see an example of how our ''**StateSaverAndLoader**'' class would look if our ''**PlayerData**'' has more than primitives, like lists, and even its own hashmap. How would that look?+现在我们已经不能满足单一方块的统计需求了,我们来看另一个例子:如果我们的 ''**PlayerData**'' 数据更多(包含列表甚至前文所提到的哈希表),我们的 ''**StateSaverAndLoader**'' 类需要怎么编写?
  
-Let's say this is our ''**PlayerData**''':+假设我们的 ''**PlayerData**'' 是这样的:
  
 <code java> <code java>
Line 679: Line 685:
 </code> </code>
  
-This would be our ''**StateSaverAndLoader**'':+我们的 ''**StateSaverAndLoader**'' 如下所示:
  
 <code java> <code java>
Line 750: Line 756:
  
     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::new, // 若不存在 'StateSaverAndLoader' 则创建 
-            StateSaverAndLoader::createFromNbt, // If there is a 'StateSaverAndLoader' NBT, parse it with 'createFromNbt' +            StateSaverAndLoader::createFromNbt, // 若存在 'StateSaverAndLoader' NBT, 则调用 'createFromNbt' 传入参数 
-            null // Supposed to be an 'DataFixTypes' enum, but we can just pass null+            null // 此处理论上应为 'DataFixTypes' 的枚举,但我们直接传递为空(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)+        // (注:如需在任意维度生效,请使用 'World.OVERWORLD' ,不要使用 'World.END' 或 'World.NETHER')
         PersistentStateManager persistentStateManager = server.getWorld(World.OVERWORLD).getPersistentStateManager();         PersistentStateManager persistentStateManager = server.getWorld(World.OVERWORLD).getPersistentStateManager();
- +  
-        // The first time the following 'getOrCreate' function is called, it creates a brand new 'StateSaverAndLoader' and +        // 当第一次调用了方法 'getOrCreate' 后,它会创建新的 'StateSaverAndLoader' 并将其存储于  'PersistentStateManager' 中。 
-        // stores it inside the 'PersistentStateManager'. The subsequent calls to 'getOrCreate' pass in the saved +        //  'getOrCreate' 的后续调用将本地的 'StateSaverAndLoader' NBT 传递给 'StateSaverAndLoader::createFromNbt'
-        // 'StateSaverAndLoader' NBT on disk to our function 'StateSaverAndLoader::createFromNbt'.+
         StateSaverAndLoader state = persistentStateManager.getOrCreate(type, ExampleMod.MOD_ID);         StateSaverAndLoader state = persistentStateManager.getOrCreate(type, ExampleMod.MOD_ID);
- +  
-        // If state is not marked dirty, when Minecraft closes, 'writeNbt' won't be called and therefore nothing will be saved. +        // 若状态未标记为脏(dirty),当 Minecraft 关闭时, 'writeNbt' 不会被调用,相应地,没有数据会被保存。 
-        // Technically it's 'cleaner' if you only mark state as dirty when there was actually a change, but the vast majority +        // 从技术上讲,只有在事实上发生数据变更时才应当将状态标记为脏(dirty)。 
-        // 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. +        // 但大多数开发者和模组作者会对他们的数据未能保存而感到困惑,所以不妨直接使用 'markDirty'  
-        // Besides, it's literally just setting a bool to true, and the only time there's a 'cost' is when the file is written to disk when +        // 另外,这只将对应的布尔值设定为 TRUE,代价是文件写入磁盘时模组的状态不会有任何改变。(这种情况非常少见)
-        // there were no actual change to any of the mods state (INCREDIBLY RARE).+
         state.markDirty();         state.markDirty();
 + 
         return state;         return state;
     }     }
Line 777: Line 781:
         StateSaverAndLoader serverState = getServerState(player.getWorld().getServer());         StateSaverAndLoader serverState = getServerState(player.getWorld().getServer());
  
-        // Either get the player by the uuid, or we don't have data for him yet, make a new player state+        // 根据 UUID 获取对应玩家的状态,如果没有该玩家的数据,就创建一个新的玩家状态。
         PlayerData playerState = serverState.players.computeIfAbsent(player.getUuid(), uuid -> new PlayerData());         PlayerData playerState = serverState.players.computeIfAbsent(player.getUuid(), uuid -> new PlayerData());
  
Line 785: Line 789:
 </code> </code>
  
-Our classes which implement ''**ClientModInitializer**'' and ''**ModInitializer**'' would be the same as before, but we include them here so that this section of the article is easy to copy and paste into you program and experiment with persistent state.+对已经实现了 ''**ClientModInitializer**'' 和 ''**ModInitializer**'' 的类无需改动,但我们保留了其代码,这样一来您就可以直接复制粘贴,直观地体会状态持久化带来的效果。
  
 <code java> <code java>
Line 803: Line 807:
  
             client.execute(() -> {             client.execute(() -> {
-                client.player.sendMessage(Text.literal("Total dirt blocks broken: " + totalDirtBlocksBroken)); +                client.player.sendMessage(Text.literal("总计被挖掘的泥土方块数:" + totalDirtBlocksBroken)); 
-                client.player.sendMessage(Text.literal("Player specific dirt blocks broken: " + playerData.dirtBlocksBroken));+                client.player.sendMessage(Text.literal("当前玩家挖掘的泥土方块数:" + playerData.dirtBlocksBroken));
             });             });
         });         });
Line 812: Line 816:
  
             client.execute(() -> {             client.execute(() -> {
-                client.player.sendMessage(Text.literal("Initial specific dirt blocks broken: " + playerData.dirtBlocksBroken));+                client.player.sendMessage(Text.literal("初始化的当前玩家挖掘的泥土方块数:" + playerData.dirtBlocksBroken));
             });             });
         });         });
Line 834: Line 838:
 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 = "您的MOD_ID";
  
     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 854: Line 858:
             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());                 StateSaverAndLoader serverState = StateSaverAndLoader.getServerState(world.getServer());
-                // Increment the amount of dirt blocks that have been broken+                // 当泥土方块被挖掘时增加计数
                 serverState.totalDirtBlocksBroken += 1;                 serverState.totalDirtBlocksBroken += 1;
  
Line 860: Line 864:
                 playerState.dirtBlocksBroken += 1;                 playerState.dirtBlocksBroken += 1;
  
-                // Send a packet to the client+                // 向客户端发送数据包
                 MinecraftServer server = world.getServer();                 MinecraftServer server = world.getServer();
  
zh_cn/tutorial/persistent_states.1702627824.txt.gz · Last modified: 2023/12/15 08:10 by dreamuniverse