Мир пустоты как кастомное измерение датапаком и пересчёт координат между
мирами с разным масштабом.
Механизм масштаба — тот же, что у Незера: поле coordinate_scale в типе
измерения, а пересчёт при переходе равен делению масштаба источника на
масштаб назначения. Для пустоты нужно ровно 2.0, то есть один блок пустоты
на два блока оверворлда. Проверено на сервере: coord scale overworld=1.0
void=2.0.
Биом сделан своим (delta_void:void), а не переопределением ванильного
minecraft:the_void. Переопределение трогало бы данные Mojang и конфликтовало
с любыми другими датапаками. Отличия от ванильного — только цвета:
sky_color и fog_color в ноль, плюс выключенный туман и вода.
Что потребовалось по дороге, все три ошибки поймал сервер, а не я:
1. У типа измерения в 1.21.8 обязательное поле logical_height. Без него
сервер отказывается стартовать: "No key logical_height".
2. Файл измерения не принимает дискриминатор "type": "minecraft:dimension".
Кодек LevelStem читает поле type как ССЫЛКУ на тип измерения, и мой
дискриминатор уходил в неё как имя типа. Правильно — "type" со значением
delta_void:void.
3. getAllLevels() живёт на MinecraftServer, а не на DedicatedServer, который
отдаёт CraftServer#getHandle(). И dimensionType() в Paper уже разворачивает
Holder, так что .value() там не нужен и ломает компиляцию.
Отдельно: World#getCoordinateScale() в Paper УЖЕ есть, причём реализован
ровно так, как я собрался писать. Для чтения масштаба ядро менять не нужно.
А вот пересчёта координат в API нет ни в Paper, ни в Bukkit: Player#teleport
его не делает, потому что телепорт не портал. Добавлен default-метод
World#convertLocation с той же формулой, что и ванильная, и без изменения Y.
Хорошая новость про отказ сервера: на битый датапак он не стартует, а
падает в safeMode. Тихая загрузка сломанной генерации была бы хуже.
Диагностика частиц всё ещё в CraftWorld, снимается после проверки прицела.
Частицы не долетали дальше 32 блоков, при том что механика способностей бьёт
на 120. Игрок дальше 32 блоков не видел прицела вообще, но удар приходил.
Причина оказалась на стороне сервера, а не клиента. В ServerLevel есть
if (blockPos.closerToCenterThan(pos, longDistance ? 512.0 : 32.0))
где longDistance это параметр alwaysShow. По умолчанию он false, то есть 32
блока. Но Bukkit-API этот флаг не открывает: в CraftWorld.spawnParticle он
захардкожен в false, а параметр force уходит в overrideLimiter. То есть ни
один плагин на Paper не мог отправить частицу дальше 32 блоков.
Само поле alwaysShow сериализуется в ClientboundLevelParticlesPacket, поэтому
клиент со своей стороны отсечку тоже снимает — тем же механизмом, что и
ванильная команда /particle в режиме display.
Что сделано:
1. World#spawnParticleLongDistance(...) — default-метод, а не абстрактный.
Причина: абстрактный метод в интерфейсе World ломает бинарную совместимость
с любой сторонней реализацией. Фолбэк здесь ведёт себя как обычный
spawnParticle, то есть деградация — «слишком короткая дальность», а не
«частиц нет».
2. CraftWorld переопределяет его. Дистанция применяется фильтрацией списка
получателей ДО вызова ServerLevel, а не патчем самого ServerLevel:
sendParticlesSource принимает готовый receivers, поэтому достаточно отсечь
его самим. Ванильная проверка остаётся нетронутой. Это принципиально —
меньше правок в ванильных патчах, меньше риска конфликтов.
3. Конфиг extended-particle-distance перестал быть мёртвым. Раньше он был
объявлен в DeltaConfig и выводился в /delta, но нигде не читался при отправке
частиц, поэтому переключатель не делал ничего. Теперь он работает как
потолок: плагин запрашивает сколько нужно, сервер режет до 128.
extendedParticleDistanceEnabled=false возвращает стоковые 32.
Замер: /delta теперь показывает "extended distance 128.0 blocks" вместо "off".
This implements a solution that preemptively exits the tick loop if we have already marked the connection as disconnecting. This avoids changing the result of Connection#isConnected in order to avoid other possibly unintentional changes. Fundamentally it should be investigated if closing the connection async is really still needed.
This also additionally removes the login disconnecting logic for server stopping, as this was also a possible issue in the config stage but also shouldn't be an issue as connections are closed on server stop very early.
Additionally, do not check for isConnecting() on VERIFYING, as that seemed to be an old diff originally trying to resolve this code, however isConnected is not updated at this point so it pretty much was useless.