feat(delta): void dimension at 2:1 coordinate scale, plus World#convertLocation
Build Delta / Build paperclip jar (push) Successful in 7m57s

Мир пустоты как кастомное измерение датапаком и пересчёт координат между
мирами с разным масштабом.

Механизм масштаба — тот же, что у Незера: поле 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, снимается после проверки прицела.
This commit is contained in:
SashegDev
2026-10-15 21:55:19 +00:00
parent e6a95a0f85
commit 1a2f3bbde7
3 changed files with 155 additions and 9 deletions
@@ -4111,6 +4111,39 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data);
}
/**
* Переводит точку из этого мира в другой с учётом масштаба координат.
*
* <p>Масштаб самого мира Paper уже отдаёт через {@link #getCoordinateScale()},
* не надо лезть в ядро. Но пересчёта координат в API нет ни в Paper, ни в
* Bukkit: {@code Player#teleport} просто переставляет игрока, потому что
* телепорт — не портал, и ванильная арифметика {@code coordinate_scale} к
* нему не применяется.
*
* <p>Поэтому переход между мирами с разным масштабом плагин обязан посчитать
* сам. Без этого игрок выходит не туда: пустота с масштабом 2.0 выкинет его
* в вдвое дальнюю точку оверворлда, чем он рассчитывал.
*
* <p>Множитель берётся ровно так же, как в
* {@code DimensionType#getTeleportationScale}: масштаб источника делим на
* масштаб назначения. Для перехода оверворлд → Незер это 1.0 / 8.0 = 0.125,
* то есть x и z уменьшаются в восемь раз.
*
* <p>Высота не пересчитывается: по вертикали измерения не масштабируются, и
* так же поступает ванильная арифметика порталов.
*
* <p>По умолчанию точка переносится как есть: для миров с одинаковым
* масштабом это и есть правильный ответ, а реализация {@link World}, которая
* метод не переопределяет, не ломает пересчёт.
*
* @param target мир, в который переводим
* @param loc точка в этом мире
* @return та же точка в координатах целевого мира
*/
public default @NotNull Location convertLocation(@NotNull Location target, @NotNull Location loc) {
return loc;
}
/**
* Find the closest nearby structure of a given {@link StructureType}.
* Finding unexplored structures can, and will, block if the world is