diff --git a/paper-api/src/main/java/org/bukkit/World.java b/paper-api/src/main/java/org/bukkit/World.java index 181d6937a..8b0318ea6 100644 --- a/paper-api/src/main/java/org/bukkit/World.java +++ b/paper-api/src/main/java/org/bukkit/World.java @@ -4111,6 +4111,39 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); } + /** + * Переводит точку из этого мира в другой с учётом масштаба координат. + * + *
Масштаб самого мира Paper уже отдаёт через {@link #getCoordinateScale()}, + * не надо лезть в ядро. Но пересчёта координат в API нет ни в Paper, ни в + * Bukkit: {@code Player#teleport} просто переставляет игрока, потому что + * телепорт — не портал, и ванильная арифметика {@code coordinate_scale} к + * нему не применяется. + * + *
Поэтому переход между мирами с разным масштабом плагин обязан посчитать + * сам. Без этого игрок выходит не туда: пустота с масштабом 2.0 выкинет его + * в вдвое дальнюю точку оверворлда, чем он рассчитывал. + * + *
Множитель берётся ровно так же, как в + * {@code DimensionType#getTeleportationScale}: масштаб источника делим на + * масштаб назначения. Для перехода оверворлд → Незер это 1.0 / 8.0 = 0.125, + * то есть x и z уменьшаются в восемь раз. + * + *
Высота не пересчитывается: по вертикали измерения не масштабируются, и + * так же поступает ванильная арифметика порталов. + * + *
По умолчанию точка переносится как есть: для миров с одинаковым + * масштабом это и есть правильный ответ, а реализация {@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 diff --git a/paper-server/src/main/java/me/sashegdev/delta/DeltaCommand.java b/paper-server/src/main/java/me/sashegdev/delta/DeltaCommand.java index 3718294b0..87b46a2fa 100644 --- a/paper-server/src/main/java/me/sashegdev/delta/DeltaCommand.java +++ b/paper-server/src/main/java/me/sashegdev/delta/DeltaCommand.java @@ -46,6 +46,8 @@ public class DeltaCommand extends Command { ? "off" : cfg.soundBudgetPerPlayerPerSecond + " pkts/player/sec")); sender.sendMessage(line("extended distance ", cfg.extendedParticleDistanceEnabled ? cfg.extendedParticleDistance + " blocks" : "off")); + sender.sendMessage(line("coord scale ", "overworld=" + overworldScale() + + " void=" + voidScale())); sender.sendMessage(line("decoupled sound ", String.valueOf(cfg.decoupledSoundRangeEnabled))); sender.sendMessage(line("display lead ", DeltaDisplay.describe())); if (sender instanceof Player p) { @@ -169,4 +171,35 @@ public class DeltaCommand extends Command { private Component line(String key, String value) { return Component.text(key, NamedTextColor.GRAY).append(Component.text(value, NamedTextColor.WHITE)); } + + /** + * Масштаб координат мира, читается через Bukkit-API. + * + *
Нужен, чтобы видеть глазами, что кастомное измерение реально получило
+ * coordinate_scale 2.0 из датапака, а не осталось со значением по умолчанию.
+ * Знание о масштабе берётся из World#getCoordinateScale, который по умолчанию
+ * возвращает 1, то есть «1» означает «метод не переопределён».
+ */
+ private static double overworldScale() {
+ final org.bukkit.World w = Bukkit.getWorlds().isEmpty() ? null : Bukkit.getWorlds().get(0);
+ return w == null ? -1.0D : w.getCoordinateScale();
+ }
+
+ private static double voidScale() {
+ // Измерение, добавленное датапаком, не обязано появляться в
+ // Bukkit.getWorlds(): там только миры Bukkit, а уровни измерений живут
+ // отдельно. Поэтому идём к уровням сервера напрямую.
+ try {
+ final var handle = ((org.bukkit.craftbukkit.CraftServer) Bukkit.getServer()).getHandle();
+ for (final var level : handle.getServer().getAllLevels()) {
+ final String key = level.dimension().location().toString();
+ if (key.contains("void")) {
+ return level.dimensionType().coordinateScale();
+ }
+ }
+ } catch (Throwable t) {
+ return -1.0D;
+ }
+ return -1.0D;
+ }
}
\ No newline at end of file
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 8fab42586..af38dec7c 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -2313,17 +2313,29 @@ public class CraftWorld extends CraftRegionAccessor implements World {
}
final double limit = Math.min(viewDistance, cfg.extendedParticleDistance);
- // Дистанция применяется ЗДЕСЬ, до вызова в ServerLevel, а не патчем
- // самого ServerLevel. Причина: sendParticlesSource принимает готовый список
- // получателей, поэтому достаточно отфильтровать его. Ванильная проверка
- // (longDistance ? 512 : 32) остаётся нетронутой и просто пропускает всех,
- // кто прошёл наш фильтр.
+ // ЗАПАД РЕШАЮЩИЙ, без него ничего не работало.
+ //
+ // Фильтр сравнивает квадрат расстояния ПО МЕНЬШЕМУ радиуса, строго.
+ // Но радиус приходит из вызова как расстояние ОТ ИГРОКА ДО ТОЧКИ
+ // ПРИЦЕЛА. Значит единственный игрок, ради которого вызов и сделан,
+ // стоит ровно на границе: distSqr == limit², а нужно < limit². Он
+ // отсекался всегда, и в лог падало "0 receivers" на каждом вызове.
+ // Замер: requested=41.7, capped=41.7, игрок 1, получателей 0.
+ //
+ // Плюс блок запаса, чтобы округление и подбор высоты точки прицела
+ // не выбрасывали игрока в самом пограничном случае.
+ final double marginSq = (limit + 1.0D) * (limit + 1.0D);
+
+ // Фильтр получателей. Он нужен не для того, чтобы обойти 32 блока —
+ // этим занимается alwaysShow, — а чтобы ограничить вызов ТЕМ расстоянием,
+ // которое запросил плагин. Без него любой вызов улетел бы всем, до кого
+ // дотягивается предел в 512, а не туда, куда целился игрок.
final List Сам масштаб Paper уже отдаёт через {@code World#getCoordinateScale()}.
+ * Не хватает только арифметики: {@code Player#teleport} пересчёт не делает,
+ * потому что телепорт не портал.
+ *
+ * Множитель тот же, что в {@code DimensionType#getTeleportationScale}:
+ * масштаб источника делим на масштаб назначения. Оверворлд → Незер это
+ * 1.0 / 8.0 = 0.125. Высота не трогается, как и в ванильной арифметике
+ * порталов.
+ */
+ @Override
+ public org.bukkit.Location convertLocation(org.bukkit.Location target, org.bukkit.Location loc) {
+ if (target == null || loc == null) {
+ return loc;
+ }
+ final org.bukkit.World tw = target.getWorld();
+ if (tw == null) {
+ return loc;
+ }
+ final double from = this.getCoordinateScale();
+ final double to = tw.getCoordinateScale();
+ if (from == to || from == 0.0D || to == 0.0D) {
+ return loc.clone();
+ }
+ final double k = from / to;
+ final org.bukkit.Location out = loc.clone();
+ out.setWorld(tw);
+ out.setX(loc.getX() * k);
+ out.setZ(loc.getZ() * k);
+ return out;
+ }
+
+ // CraftBukkit end - coordinate scale
+
/** Конфиг Delta, либо null если он ещё не инициализирован. */
private static me.sashegdev.delta.DeltaConfig deltaConfig() {
try {