Разработка Java-серверов на Paper · 6 мин чтения

Инвентарь мини-игры: сохраните исходные вещи до выдачи снаряжения

Начните с сохранения и сравнения инвентарного снимка, который переживает обычный рестарт. Пример ниже не очищает вещи: он позволяет проверить сериализацию предметов до разработки опасного перехода в мини-игру.

Редакция LonelyCraft · Обновлено

Схема: Сохранение → Сессия → Возврат
Устойчивый исходный снимок, этапы сессии и подтверждённый возврат после выхода. Авторская схема LonelyCraft.

Подготовьте контрольные предметы

Диагностический плагин ниже использует Paper 1.21.1 и JDK 21. Начните с Gradle-проекта первого плагина, оставьте пакет ru.example.trailnotes и замените весь главный класс TrailNotesPlugin. Команда /invsnapshot сохранит копии предметов в YAML и сравнит их с текущим инвентарём.

На отдельном тестовом мире возьмите именованный зачарованный инструмент, броню и щит во вторую руку. Сделайте скриншот слотов. В manifest добавьте команду invsnapshot с описанием Сохранить или проверить инвентарь; используйте только собственный тестовый аккаунт. Пример проверяет содержимое PlayerInventory, а здоровье, режим, эффекты и позиция требуют дополнительных полей при настоящем входе.

Сохраните полный инвентарный снимок в YAML

Класс целиком заменяет TrailNotesPlugin.java. /invsnapshot save клонирует каждый ItemStack и записывает файл с UUID; /invsnapshot check заново читает диск и сравнивает все слоты. getContents в данном PlayerInventory включает броню и дополнительный слот; для отдельных наборов есть getStorageContents/getArmorContents.

package ru.example.trailnotes;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;

public final class TrailNotesPlugin extends JavaPlugin {
    @Override public boolean onCommand(CommandSender sender, Command command,
                                        String label, String[] args) {
        if (!(sender instanceof Player player)) {
            sender.sendMessage("Нужен игрок с контрольным инвентарём.");
            return true;
        }
        if (args.length != 1 || !(args[0].equals("save") || args[0].equals("check"))) {
            sender.sendMessage("/invsnapshot save|check");
            return true;
        }
        File file = new File(getDataFolder(), player.getUniqueId() + ".yml");
        try {
            getDataFolder().mkdirs();
            if (args[0].equals("save")) {
                List<ItemStack> items = new ArrayList<>();
                for (ItemStack item : player.getInventory().getContents())
                    items.add(item == null ? null : item.clone());
                YamlConfiguration yaml = new YamlConfiguration();
                yaml.set("contents", items);
                yaml.save(file);
                player.sendMessage("Снимок записан: " + file.getName());
            } else {
                YamlConfiguration yaml = new YamlConfiguration();
                yaml.load(file);
                List<?> list = yaml.getList("contents");
                if (list == null) throw new IllegalArgumentException("Нет contents");
                ItemStack[] saved = new ItemStack[list.size()];
                for (int i = 0; i < list.size(); i++) {
                    Object item = list.get(i);
                    if (item != null && !(item instanceof ItemStack))
                        throw new IllegalArgumentException("Неверный предмет в слоте " + i);
                    saved[i] = item == null ? null : ((ItemStack) item).clone();
                }
                boolean same = Arrays.equals(saved, player.getInventory().getContents());
                player.sendMessage(same ? "Все слоты совпадают." : "Инвентарь отличается от снимка.");
            }
        } catch (Exception error) {
            player.sendMessage("Снимок не прочитан или не записан. Вещи не изменены.");
            getLogger().warning(error.getClass().getSimpleName() + ": " + file.getName());
        }
        return true;
    }
}

Испытайте сериализацию и повторное чтение

Соберите JAR и установите на остановленный тестовый Paper. Выполните /invsnapshot save, затем /invsnapshot check: сообщение подтверждает совпадение. Переложите предмет в другой слот и повторите check: результат должен измениться. Верните его и сравните снова.

Не меняя вещей, выйдите, выполните stop, запустите сервер и снова используйте /invsnapshot check. Имена, зачарования и число вещей должны участвовать в сравнении. Код выполняет маленький диагностический I/O синхронно; массовое сохранение игроков переносите в ограниченный поток, передавая уже снятый неизменяемый снимок.

Только после записи проектируйте восстановление

Наличие YAML не является законченной системой мини-игры. Перед очисткой обычных вещей устойчиво запишите снимок и этап SNAPSHOT_READY; при отказе записи не изменяйте инвентарь. Возврат должен применяться одним владельцем состояния и не конкурировать с Multiverse-Inventories или другим установленным менеджером.

Для паузы исходные вещи и игровой набор хранятся отдельно. Перед восстановлением закройте игровые события, удалите служебные предметы именно этой сессии и верните игрока по согласованному маршруту. На копии проверьте отключение на каждом этапе и рестарт; наш диагностический пример полезен для предметной сериализации, но сам не выдаёт гарантии аварийного восстановления.

Назначьте одного владельца обычного инвентаря

Если сервер уже использует многомировый менеджер инвентарей, собственный плагин мини-игры не должен параллельно восстанавливать те же вещи независимым способом. Выберите контракт: кто сохраняет обычное состояние, кто выдаёт временные предметы и в каком порядке происходит смена мира. Два корректных снимка могут конфликтовать, если применяются в разной последовательности.

Перечень состояния зависит от того, что меняет игра. Помимо содержимого инвентаря учитывайте броню, вторую руку, опыт, здоровье, голод, эффекты, режим и позицию. Предмет хранит метаданные: имя, зачарования, PDC и актуальные компоненты. Восстановленный материал DIAMOND_SWORD ещё не означает восстановленный уникальный меч игрока.

Снимок записывают до опасного изменения

Переход должен иметь устойчивое состояние вроде SNAPSHOT_READY перед очисткой вещей. Если запись не удалась, игра не начинается и исходный инвентарь остаётся на месте. Нельзя сначала очистить PlayerInventory, а затем надеяться, что сериализация успеет. При аварии между этими действиями единственная копия уже могла исчезнуть.

Идентификатор перехода и подтверждённая запись нужны для восстановления после рестарта. Устойчивые операции плагина.

Храните исходный и игровой снимки отдельно

Для режима с паузой есть два независимых набора: вещи до входа и снаряжение забега. На спавне применяется исходное состояние; при продолжении — игровой снимок. Повторная команда входа не должна заменить оригинал временными мечом и зельями. Проверяйте существующий незавершённый переход до создания нового.

Обычный выход завершает сессию, удаляет её служебные вещи и только затем передаёт игрока владельцу обычного инвентаря по согласованному маршруту. Телепортация в другой мир не равна очистке. Если плагин ещё считает игрока участником, поздний обработчик смерти или награды может затронуть уже восстановленный Survival-набор.

  1. Проверьте отсутствие конфликтующего перехода.
  2. Снимите полное исходное состояние.
  3. Устойчиво запишите снимок и идентификатор.
  4. Выдайте временный набор только после подтверждения.
  5. При выходе закройте игровые действия и очистите служебные предметы.
  6. Восстановите исходное состояние и подтвердите завершение.

Состояние определяет, когда повторное событие уже не должно запускать восстановление заново. Этапы игрового выхода.

Не отдавайте служебные предметы во внешний мир

Выходной компас, игровая палка и GUI-кнопка должны иметь устойчивую служебную метку. Отображаемое имя недостаточно: игрок может получить предмет с таким же текстом обычным способом. Запрещайте перенос служебных вещей в контейнер, выбрасывание и использование неподходящей команды, если это предусмотрено правилами режима.

При этом не очищайте все похожие материалы. Удаление каждого компаса или зелья способно затронуть обычные предметы игрока. Удаляйте только то, чем владеет конкретная сессия. Служебная метка помогает, но окончательная принадлежность связывается также с идентификатором операции и правилами перехода.

Отключение и повторный вход

Игрок может выйти сразу после сохранения, в ходе телепорта, после очистки или перед подтверждением восстановления. На новом входе проверьте устойчивый журнал и выберите допустимый следующий этап. Не доверяйте только онлайн-карте сессий, которая исчезает после рестарта. Восстановление должно быть идемпотентным относительно одного перехода.

Если авторизация управляет телепортом сохранённой позиции, согласуйте порядок с ней. Возврат на спавн должен происходить до попадания в старую арену без сессии и не прерывать уже начатую новую игру. Неправильная задержка в несколько секунд иногда выглядит рабочей, но остаётся гонкой событий входа.

Проверяйте свойства и места отказа

Создайте контрольный набор: именованный предмет, зачарование, вторая рука, броня, опыт и эффект. Войдите в игру и выйдите обычным способом, затем через команду, смерть и отключение. Сравнивайте свойства каждого элемента, а не только количество занятых слотов. Проверьте заполненный инвентарь, чтобы выдача служебного предмета не перезаписала существующий.

Имитируйте рестарт после устойчивой записи и до завершения возврата на отдельном сервере. Если восстановление требует ручного разбора, журнал должен указывать конкретный переход и сохранённый снимок. Не удаляйте такое состояние при обновлении как временный мусор: оно может быть единственным источником вещей игрока.

Частые вопросы

Можно ли сохранять только материалы и количество?

Это потеряет свойства предметов. Нужна сериализация, соответствующая версии и сохраняющая необходимые метаданные, зачарования и компоненты.

Почему нельзя иметь два независимых восстановителя?

Они могут применить снимки в разном порядке и перезаписать правильные вещи. Назначьте одного владельца обычного состояния и согласованный контракт перехода.

Что делать при заполненном инвентаре?

Выдача служебного предмета не должна перезаписывать слот. Предусмотрите свободное место, другой путь выхода или устойчивый резерв по правилам режима.

Зачем хранить незавершённое восстановление?

После рестарта память исчезает. Журнал и снимок позволяют вернуть исходное состояние в конкретном этапе, избегая потери или повторной выдачи.

Просмотры: … · Один просмотр с браузера за день

Было полезно?Общий счёт: …

Одна реакция с браузера. Нажмите повторно, чтобы снять её, или выберите другую.

Источники и документация

Minecraftразработка плагиновPaper

Читайте дальше