Saved Data 26.1.2
Сохранение данных между игровыми сессиями.
Saved Data — это встроенное в Minecraft решение для сохранения данных между сессиями.
Данные сохраняются на диске и загружаются заново при закрытии и повторном открытии игры. Эти данные обычно имеют определенный объем (например, уровень). Данные записываются на диск в формате NBT, а для сериализации/десериализации этих данных используются кодеки.
Рассмотрим простой сценарий, в котором нам нужно сохранить кол-во блоков, разрушенных игроком. Мы можем хранить этот счет на логическом сервере.
Мы можем использовать событие PlayerBlockBreakEvents.AFTER с простым статическим целочисленным полем для хранения этого значения и отправки его в виде сообщения в чате.
java
private static int blocksBroken = 0; // keeps track of the number of blocks broken
PlayerBlockBreakEvents.AFTER.register((level, player, pos, state, blockEntity) -> {
blocksBroken++; // increment the counter each time a block is broken
player.displayClientMessage(Component.literal("Blocks broken: " + blocksBroken), false);
});1
2
3
4
5
6
2
3
4
5
6
Теперь, когда вы разбиваете блок, вы увидите сообщение с кол-вом.

Если вы перезапустите Minecraft, загрузите мир и начнете разрушать блоки, вы заметите, что счетчик сбросился. Здесь нам понадобятся Saved Data. Затем мы можем сохранить это значение, чтобы при следующей загрузке мира мы могли получить сохраненное значение и начать его инкрементировать с этого момента.
Saving Data
SavedData — основной класс, отвечающий за управление сохранением/загрузкой данных. Поскольку это абстрактный класс, от вас ожидается предоставление реализации.
Настройка класса данных
Назовем наш класс данных SavedBlockData и сделаем его расширением SavedData.
Этот класс будет содержать поле для отслеживания кол-ва разбитых блоков, а также метод для получения и метод для увеличения этого числа.
java
public class SavedBlockData extends SavedData {
private int blocksBroken = 0;
public SavedBlockData() {
}
public int getBlocksBroken() {
return this.blocksBroken;
}
public void incrementBlocksBroken() {
this.blocksBroken++;
// If saved data is not marked dirty, nothing will be saved when Minecraft closes.
setDirty();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Для сериализации и десериализации этих данных нам необходимо определить кодек. Мы можем создать кодек, используя различные примитивные кодеки, предоставляемые Minecraft.
Для инициализации класса потребуется конструктор с аргументом int.
java
public SavedBlockData(int count) {
this.blocksBroken = count;
}1
2
3
2
3
Затем мы можем создать кодек.
java
private static final Codec<SavedBlockData> CODEC = Codec.INT.xmap(
SavedBlockData::new, // Create a new 'SavedBlockData' from the stored number.
SavedBlockData::getBlocksBroken // Return the number from the 'SavedBlockData' to be saved/
);1
2
3
4
2
3
4
Мы должны вызывать метод setDirty(), когда данные действительно изменяются, чтобы Minecraft знал, что их необходимо сохранить на диск.
java
public void incrementBlocksBroken() {
this.blocksBroken++;
// If saved data is not marked dirty, nothing will be saved when Minecraft closes.
setDirty();
}1
2
3
4
5
6
2
3
4
5
6
Наконец, нам необходимо иметь SavedDataType, который описывает наши сохраненные данные. Первый аргумент соответствует имени файла, который будет создан в каталоге data мира.
java
private static final SavedDataType<SavedBlockData> TYPE = new SavedDataType<>(
Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "saved_block_data"), // The unique name for this saved data.
SavedBlockData::new, // If there's no 'SavedBlockData', yet create one and refresh fields.
CODEC, // The codec used for serialization/deserialization.
null // A data fixer, which is not needed here.
);1
2
3
4
5
6
2
3
4
5
6
Доступ к Saved Data
Как упоминалось ранее, сохраненные данные могут быть связаны с областью действия, такой как текущий уровень. В данном случае наши данные будут частью данных уровня. Мы можем получить DimensionDataStorage уровня, чтобы добавить и изменить наши данные.
Мы поместим эту логику в служебный метод.
java
public static SavedBlockData getSavedBlockData(MinecraftServer server) {
// This could be either the overworld or another dimension.
ServerLevel level = server.getLevel(ServerLevel.OVERWORLD);
if (level == null) {
return new SavedBlockData(); // Return a new instance if the level is null.
}
// The first time the following 'computeIfAbsent' function is called, it creates a new 'SavedBlockData'
// instance and stores it inside the 'DimensionDataStorage'.
// Subsequent calls to 'computeIfAbsent' returns the saved 'SavedBlockData' NBT on disk to the Codec in our type,
// using the Codec to decode the NBT into our saved data.
return level.getDataStorage().computeIfAbsent(TYPE);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
Использование Saved Data
Теперь, когда все настроено, давайте сохраним некоторые данные.
Мы можем повторно использовать первый сценарий и вместо инкрементирования поля вызвать нашу функцию incrementBlocksBroken из нашего SavedBlockData.
java
PlayerBlockBreakEvents.AFTER.register((level, player, pos, state, blockEntity) -> {
MinecraftServer server = level.getServer();
if (server == null) {
return;
}
// Retrieve the saved block data from the server.
SavedBlockData savedData = SavedBlockData.getSavedBlockData(server);
savedData.incrementBlocksBroken(); // Increment the counter each time a block is broken.
player.sendSystemMessage(Component.literal("Blocks broken: " + savedData.getBlocksBroken()));
});1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
Это должно увеличить значение и сохранить его на диске.
Если вы перезапустите Minecraft, загрузите мир и разрушите блок, вы увидите, что ранее сохраненное кол-во теперь увеличилось.
Если вы зайдете в папку «data» мира, то увидите файл «.dat» с именем «saved_block_data.dat». Открыв этот файл в программе чтения NBT, вы увидите, как в нем сохраняются наши данные.

