
Здравствуйте, SE7EN! Меня зовут Владимир Туров, я работаю разработчиком в Selectel. В этом году команда Paper объявила о том, что проект становиться полностью автономным. С каждым новым выпуском Minecraft API Spigot и Paper могут всё больше расходиться, поэтому настало время освоить разработку и отладку плагинов для ядра Paper.
На примере задачи по синхронизации погоды в лобби Minecraft с реальной погодой Санкт-Петербурга я покажу весь процесс: от создания базового прототипа до управления временем и погодой в игровом мире.
Введение
Среди альтернатив официальному серверу Minecraft наиболее популярным стал Paper — оптимизированная и более безопасная ветка Spigot. Spigot сам по себе представляет собой набор патчей над «ванильным» сервером Minecraft: лицензия игры разрешает дорабатывать серверное ПО, но запрещает распространять его в изменённом виде.
Накопление патчей заставляло команду Paper ждать выхода новых версий Spigot, чтобы применить собственные исправления. Недавний «hard fork» устранил эту зависимость: Paper перешёл в статус независимого проекта. При этом разработчики гарантируют сохранение совместимости с API Spigot и конфигурационными файлами по крайней мере на первых этапах, обещая при этом более частые релизы.
Постановка задачи
Для демонстрации работы я подготовил лобби в сказочном стиле и настроил в нём раздачу промокодов на услуги Selectel. Мне показалось забавным синхронизировать смену дня и ночи в лобби с фактическим циклом суток в Санкт-Петербурге.
Да, мы действительно выдаём промокоды просто за вход на наш сервер Minecraft.
Существуют готовые решения для синхронизации мира Minecraft с реальным временем: WorldSync, CustomDay и другие. Но мы пойдём своим путём и создадим собственный плагин.

Играйте в Minecraft и получайте бонусы в панели управления!
Накопленными бонусами можно оплатить облачный сервер для сайта, приложения или бота.
Исходные данные
В Minecraft реализованы циклы дня и ночи длительностью 20 минут (1 200 секунд или 24 000 тактов) и три типа погоды: ясная, дождь и гроза. Смена времени происходит плавно: Солнце и Луна движутся по небосводу, проходя через зенит в положенные моменты.

Вот ключевые точки игрового времени (в тактах от начала цикла):
- 0 — рассвет;
- 6 000 — Солнце в зените;
- 12 000 — начало заката;
- 13 000 — ночь вступает в силу;
- 13 702 — Солнце полностью ушло за горизонт;
- 18 000 — Луна в зените;
- 22 000 — рассвет начинается;
- 22 300 — Солнце появляется на востоке;
- 23 216 — рассвет завершён;
- 24 000 — начало нового цикла.

Для ручного вмешательства доступны команды:
/time set <такты>
gamerule doDaylightCycle false

Погода глобальная для всего мира, виды осадков управляются командами:
/weather <clear|rain|thunder>
gamerule doWeatherCycle false
В своём Telegram-канале я публикую заметки о феноменах Minecraft и занимательные мини-посты. По пятницам — мемы!
Итак, у нас есть базовый цикл дня, базовые команды для времени и погоды — время переходить к прототипированию плагина.
Прототип плагина
Для начала создайте в IDE (VS Code или IntelliJ IDEA) новый плагин по шаблону Bukkit/Spigot, добавьте в pom.xml зависимость Paper API и переименуйте plugin.yml в paper-plugin.yml:
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21.5-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>

Соберите проект, скачайте сервер Paper с официального сайта, поместите .jar в папку plugins/ и запустите сервер. В списке плагинов вы увидите наш модуль как Paper-плагин.
Класс для отладки
Создадим singleton-класс WorldState, который будет хранить времена рассвета и заката, а также текущее время суток в секундах:
public class WorldState {
private static final WorldState INSTANCE = new WorldState();
private long sunriseOfDay;
private long sunsetOfDay;
private WorldState() { }
public static WorldState getInstance() {
return INSTANCE;
}
public long getSecondsOfDay() {
return LocalTime.now().toSecondOfDay();
}
public void setSunrise(String time) {
LocalTime t = LocalTime.parse(time, DateTimeFormatter.ofPattern("h:mm a"));
sunriseOfDay = t.toSecondOfDay();
}
public void setSunset(String time) {
LocalTime t = LocalTime.parse(time, DateTimeFormatter.ofPattern("h:mm a"));
sunsetOfDay = t.toSecondOfDay();
}
public long getSunriseOfDay() { return sunriseOfDay; }
public long getSunsetOfDay() { return sunsetOfDay; }
}
Далее добавим команду /weathersync с подкомандами version, show, setSunrise <time> и setSunset <time> через Command API Paper:
weathersync
├─ version — показать версию
├─ show — отобразить настройки
├─ setSunrise
Синхронизация солнца
Чтобы плавно изменять положение Солнца, заведём в методе onEnable() периодическую задачу, которая каждую игровую «тик» будет продвигать время мира:
BukkitScheduler scheduler = getServer().getScheduler();
sun = scheduler.runTaskTimer(this, () -> {
World w = getServer().getWorld("world");
if (w != null) w.setFullTime(w.getTime() + 20);
}, 0, 1);
Чтобы соотнести реальные секунды с игровыми тактами, используем времена рассвета и заката как опорные точки. Разделим сутки на участки «ночь—рассвет», «рассвет—закат» и «закат—полночь», вычислим линейные преобразования и назначим world.setFullTime(newWorldTime). Пример вычислений:
if (now <= sunrise) {
// ночь
newTime = midnightTick + (sunriseTick - midnightTick) * now / sunrise;
} else if (now <= sunset) {
// день
newTime = sunriseTick + (sunsetTick - sunriseTick) * (now - sunrise) / (sunset - sunrise);
} else {
// вечер
newTime = sunsetTick + (midnightTick - sunsetTick) * (now - sunset) / (86400 - sunset);
}
world.setFullTime(newTime);
Настройка поставщиков данных
Для получения времени рассвета, заката и текущей погоды я выбрал сервис weatherapi.com с бесплатным тарифом. Добавим в resources/config.yml параметры:
enabled: true
api-key: ""
city: ""
world: "world"
fetch-interval: 3600
В onEnable():
@Override
public void onEnable() {
saveDefaultConfig();
WeatherProvider.API_KEY = getConfig().getString("api-key");
WeatherProvider.CITY = getConfig().getString("city");
getServer().getScheduler().runTaskTimerAsynchronously(this, () -> {
if (WeatherProvider.fetchAstronomy()) {
WorldState st = WorldState.getInstance();
st.setSunrise(WeatherProvider.getSunrise());
st.setSunset(WeatherProvider.getSunset());
}
}, 0, getConfig().getInt("fetch-interval")); // по умолчанию раз в час
}
Управление погодой
В Minecraft есть три базовых погодных режима: ясно, дождь и гроза. API предоставляет методы:
world.setStorm(false);
world.setThundering(false);
Фактически погодных состояний четыре: ясно, дождь, гроза и дождь с грозой. Бонусом учтём «магические биомы»: снег вместо дождя в холодных зонах и пасмурную пустыню без осадков. Для смены биома используется world.setBiome(x, y, z, biome), но это стоит делать осторожно — массовая замена биомов может вызвать просадки TPS и не всегда корректно отображается клиентом.
- Изменение биома требует основного потока, большие объёмы влияют на производительность.
- Клиент не всегда обновляет текстуры сразу, иногда нужен повторный вход.
- Некоторые механики (замерзание воды, спавн мобов) завязаны на тип биома.
Добавьте в конфигурацию координаты региона для биомов и доработайте команды, обработав ошибки и уведомления об успехе.
Заключение
Хотя Command API Paper поначалу кажется громоздким по сравнению с Bukkit/Spigot, он открывает гибкие возможности регистрации деревьев команд и управления задачами. Плагин легко расширить: добавить фазы Луны, локальные осадки («местами дождь»), смешанные погодные эффекты и многое другое.
Исходный код плагина доступен на GitHub. Попробовать его работу можно на нашем сервере: подключиться →.



