Создайте ресурс и команду
Проверку конфигурации можно повторить на Paper 1.21.1 с JDK 21. Подготовьте базовый проект плагина, поместите код ниже вместо TrailNotesPlugin.java в пакете ru.example.trailnotes и создайте ресурс src/main/resources/config.yml перед сборкой JAR.
В src/main/resources/config.yml сохраните cooldown-seconds: 30. В plugin.yml добавьте commands: → checkconfig: → description: Проверить новый конфиг и permission: trailnotes.config, а в permissions: задайте trailnotes.config: → default: op. Это административная операция нашего примера.
При первом включении saveDefaultConfig создаёт plugins/TrailNotes/config.yml. Обычная конфигурация Paper-плагина поддерживает defaults, но смысл нуля и диапазона должен задать ваш код. Здесь 0 допустим и означает отсутствие ожидания; максимум — 3600 секунд.
Проверьте новый файл, не меняя прежний снимок
Сохраните полный класс ниже. Строгий YamlConfiguration.load позволяет поймать ошибку чтения; loadConfiguration с удобным поведением не должен скрывать неудачу как пустые настройки. Временная модель candidate присваивается полю только после всех проверок.
package ru.example.trailnotes;
import java.io.File;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
public final class TrailNotesPlugin extends JavaPlugin {
private record Settings(int cooldownSeconds) {}
private Settings settings = new Settings(30);
private Settings readSettings() throws Exception {
YamlConfiguration yaml = new YamlConfiguration();
yaml.load(new File(getDataFolder(), "config.yml"));
Object raw = yaml.contains("cooldown-seconds")
? yaml.get("cooldown-seconds") : Integer.valueOf(30);
if (!(raw instanceof Integer value) || value < 0 || value > 3600)
throw new IllegalArgumentException("cooldown-seconds: нужен integer 0..3600");
return new Settings(value);
}
@Override public void onEnable() {
saveDefaultConfig();
try {
settings = readSettings();
} catch (Exception error) {
getLogger().severe("config.yml не прошёл проверку: " + error.getClass().getSimpleName());
getServer().getPluginManager().disablePlugin(this);
}
}
@Override public boolean onCommand(CommandSender sender, Command command,
String label, String[] args) {
if (!sender.hasPermission("trailnotes.config")) {
sender.sendMessage("Нет права trailnotes.config.");
return true;
}
try {
Settings candidate = readSettings();
settings = candidate;
sender.sendMessage("Применено: " + settings.cooldownSeconds() + " секунд.");
} catch (Exception error) {
sender.sendMessage("Отказ. Проверьте cooldown-seconds: integer 0..3600.");
sender.sendMessage("Прежнее значение: " + settings.cooldownSeconds() + " секунд.");
}
return true;
}
}Выполните проверки явного нуля и отказа
Соберите и установите JAR. В консоли выполните checkconfig: должно быть 30 секунд. В созданном runtime config.yml замените число на 0, сохраните и повторите команду: должно примениться 0, а не default 30. Для проверки синтаксиса/типов используйте следующий порядок.
- Замените значение на
-1: ожидается отказ и прежний 0. - Поставьте
"30"в кавычках: это строка, поэтому ожидается отказ, а не скрытое преобразование. - Поставьте
3601: верхняя граница не проходит; сохраните затем 60 и подтвердите успешное применение. - Удалите строку целиком: выбранный контракт возвращает default 30. Если поле должно быть обязательным, замените эту политику явным отказом в readSettings.
Расширяйте валидацию по связям
Для min/max игроков проверяйте не только каждое число, но и min <= max. Для каталога наград сверяйте стабильные ID, существование ссылок и положительную сумму весов. При нескольких независимых ошибках удобно собрать список конкретных путей вместо одного огромного stack trace.
Уже идущая игра должна следовать выбранному снимку Settings, даже если новая команда загрузила другую цену. Определите это при создании сессии. Для меняющейся структуры используйте schema-version и отдельную миграцию с копией файла, а не перезапись авторских значений новым defaults.
Проверка синтаксиса и смысла — разные этапы
YAML-парсер подтверждает, что файл можно прочитать, но не знает правил вашей игры. Цена -10, пустой список режимов или интервал в одну миллисекунду могут быть корректным YAML и неправильной конфигурацией. После разбора создайте типизированную модель, проверьте диапазоны и зависимости полей, а лишь затем публикуйте настройки для рабочих сессий.
Отступы используйте последовательно, без табуляции. Строку, похожую на логическое значение или число, оформляйте осознанно. Если поле ожидает список, не принимайте строку через удобное автоматическое преобразование. Особенно осторожно работайте с дублирующимися ключами: выберите политику парсера и проверку, чтобы повторная строка не скрыла прежнее значение незаметно.
Контракт одного поля
Для cooldown-seconds опишите тип integer, минимум 0, максимум по разумному пределу и значение нуля. Для max-players — положительное число в пределах ресурсов. Для reward-amount — точный диапазон и валюту. Отдельно задайте default при отсутствии. Если администратор явно поставил 0, нельзя без объяснения заменить его значением 30 как «пустое».
Число секунд и тиков отличается не только коэффициентом 20. При перегрузке тики растягиваются по реальному времени. Если настройка означает срок приглашения, сохраняйте соответствующую семантику. Если частоту игровых проверок — используйте тики с явным именем. Непонятный ключ interval заставляет владельца угадывать поведение.
Выбор единиц определяется механикой, а не удобством одного планировщика. Тики и реальное время.
Проверяйте связи полей
Начальная цена должна быть не больше максимальной. Минимальное число игроков — не больше ёмкости лобби. Список режимов — содержать доступный стартовый режим. Для весового выбора нужна положительная сумма доступных весов; все нулевые элементы означают отсутствие выбора, а не случайный безопасный вариант.
Для шаблона комнаты проверьте идентификатор, тип цели, границы геометрии и наличие необходимого выхода. Для награды — допустимый тип и полезную величину. Не позволяйте конфигурации исполнять произвольную консольную команду, если достаточно закрытого набора gold, case, title. Типизированный контракт упрощает и проверку, и сообщение об ошибке.
Новый конфиг не должен менять смысл уже сохранённого забега без явной политики. Настройки активной сессии.
Ошибка должна показывать конкретный путь
Сообщение «неверная настройка» бесполезно для большого каталога. Укажите tasks.first.reward.amount, ожидаемый тип и допустимый диапазон. Можно собрать несколько независимых ошибок за одну загрузку, чтобы администратор исправил их вместе. Однако секретные значения подключения не выводите целиком в журнал; для них достаточно имени поля и причины.
Во время перезагрузки не изменяйте глобальные коллекции по одному элементу. Игровой обработчик иначе может увидеть половину нового каталога и половину старого. Создайте целую проверенную модель и публикуйте её атомарно в правильном контексте. Это относится также к асинхронному чтению файла и применению результата.
- Прочитайте файл в отдельную временную модель.
- Проверьте синтаксис и типы значений.
- Проверьте диапазоны и зависимости.
- Подготовьте все необходимые неизменяемые структуры.
- Только после успеха замените рабочий снимок настроек.
- При отказе сохраните прежний рабочий снимок и покажите ошибки.
Версия схемы и сохранённые идентификаторы
Если меняется структура, храните schema-version и выполняйте явную миграцию. Новый шаблон defaults не должен стирать авторские цены и тексты. Перед миграцией сохраните копию, затем покажите преобразование и результат валидации. Номер схемы относится к формату, а дата файла не заменяет его.
Для каталога достижений сохраняйте stable ID даже при смене заголовка. Удаление старого ID может сделать историю наград недоступной, а новый — создать повторную цель. Для шаблонов, используемых текущими сессиями, определите поддержку старых ссылок или безопасное завершение. Переименование для красоты не должно случайно сбрасывать прогресс.
Набор полезных тестов
Проверьте отсутствующее поле, явный ноль, отрицательное число, слишком большой предел, строку вместо списка, пустой список и неизвестный тип. Добавьте неправильное соотношение двух полей и конфиг прежней схемы. Тесты должны подтверждать правила механики, а не только повторять строки getters.
При ручном редактировании сравните нормальную загрузку и неудачный reload: вторая должна сохранить прежнее рабочее состояние. Открытая мини-игра должна следовать выбранной политике, а новая — читать новый снимок. Тогда ошибка настройки остаётся исправимой административной задачей, а не превращается в потерянную сессию игрока.



