feat(delta): let plugins send particles beyond the vanilla 32-block cut
Build Delta / Build paperclip jar (push) Successful in 8m22s
Build Delta / Build paperclip jar (push) Successful in 8m22s
Частицы не долетали дальше 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 commit is contained in:
@@ -4064,6 +4064,53 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
|
|||||||
*/
|
*/
|
||||||
public <T> void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, @Nullable T data, boolean force);
|
public <T> void spawnParticle(@NotNull Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, @Nullable T data, boolean force);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawns a particle that players further away can still see.
|
||||||
|
*
|
||||||
|
* <p>Vanilla refuses to send particles beyond {@code 32} blocks from the
|
||||||
|
* receiving player's own position, and the vanilla client applies the same
|
||||||
|
* cut-off when rendering. Both limits exist so that a single particle cannot
|
||||||
|
* cost bandwidth to the whole server, but for anything a player is meant to
|
||||||
|
* aim with, the effect is that the ability silently stops being visible
|
||||||
|
* while still hitting: the reticle disappears past 32 blocks, yet the strike
|
||||||
|
* lands. The mechanic reaches 120 and the player sees nothing past 32.
|
||||||
|
*
|
||||||
|
* <p>{@link #spawnParticle} cannot express this. Its {@code force} parameter
|
||||||
|
* maps to vanilla's {@code overrideLimiter}, not to {@code alwaysShow}, and
|
||||||
|
* CraftBukkit hardcodes {@code alwaysShow = false}, so no Bukkit plugin has
|
||||||
|
* ever been able to send a particle further than 32 blocks.
|
||||||
|
*
|
||||||
|
* <p>The distance is per call, because the caller knows what the particle is
|
||||||
|
* for. Server-side it is applied by pre-filtering the receiver list; the
|
||||||
|
* {@code alwaysShow} flag then additionally lifts the client's own render
|
||||||
|
* cut-off, so the particle is drawn at the requested range instead of being
|
||||||
|
* received and dropped.
|
||||||
|
*
|
||||||
|
* <p>Declared {@code default} on purpose: adding an abstract method to this
|
||||||
|
* interface would break every third-party {@link World} implementation at
|
||||||
|
* runtime. The fallback here is the stock 32-block behaviour, so an
|
||||||
|
* implementation that does not override this degrades to "too short a
|
||||||
|
* range", never to "no particles".
|
||||||
|
*
|
||||||
|
* @param particle the particle type
|
||||||
|
* @param x the x coordinate of the particle
|
||||||
|
* @param y the y coordinate of the particle
|
||||||
|
* @param z the z coordinate of the particle
|
||||||
|
* @param count the amount of particles to spawn
|
||||||
|
* @param offsetX the maximum random offset on the X axis
|
||||||
|
* @param offsetY the maximum random offset on the Y axis
|
||||||
|
* @param offsetZ the maximum random offset on the Z axis
|
||||||
|
* @param extra the extra data for this particle, depends on the
|
||||||
|
* particle used (normally speed)
|
||||||
|
* @param data the data to use for the particle or null,
|
||||||
|
* the type of this depends on {@link Particle#getDataType()}
|
||||||
|
* @param viewDistance how far away, in blocks, players may be and still
|
||||||
|
* receive this particle
|
||||||
|
*/
|
||||||
|
public default <T> void spawnParticleLongDistance(@NotNull Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, @Nullable T data, double viewDistance) {
|
||||||
|
spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the closest nearby structure of a given {@link StructureType}.
|
* Find the closest nearby structure of a given {@link StructureType}.
|
||||||
* Finding unexplored structures can, and will, block if the world is
|
* Finding unexplored structures can, and will, block if the world is
|
||||||
|
|||||||
@@ -2296,6 +2296,64 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void spawnParticleLongDistance(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, double viewDistance) {
|
||||||
|
// CraftBukkit start - extended particle range
|
||||||
|
data = CraftParticle.convertLegacy(data);
|
||||||
|
if (data != null) {
|
||||||
|
Preconditions.checkArgument(particle.getDataType().isInstance(data), "data (%s) should be %s", data.getClass(), particle.getDataType());
|
||||||
|
}
|
||||||
|
|
||||||
|
final me.sashegdev.delta.DeltaConfig cfg = deltaConfig();
|
||||||
|
// Выключатель и потолок из DeltaConfig. Пока extendedParticleDistanceEnabled
|
||||||
|
// выключен, поведение остаётся стоковым, и метод работает как spawnParticle.
|
||||||
|
if (cfg == null || !cfg.extendedParticleDistanceEnabled) {
|
||||||
|
this.spawnParticle(particle, null, null, x, y, z, count, offsetX, offsetY, offsetZ, extra, data, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final double limit = Math.min(viewDistance, cfg.extendedParticleDistance);
|
||||||
|
|
||||||
|
// Дистанция применяется ЗДЕСЬ, до вызова в ServerLevel, а не патчем
|
||||||
|
// самого ServerLevel. Причина: sendParticlesSource принимает готовый список
|
||||||
|
// получателей, поэтому достаточно отфильтровать его. Ванильная проверка
|
||||||
|
// (longDistance ? 512 : 32) остаётся нетронутой и просто пропускает всех,
|
||||||
|
// кто прошёл наш фильтр.
|
||||||
|
final List<net.minecraft.server.level.ServerPlayer> targets = new java.util.ArrayList<>();
|
||||||
|
for (net.minecraft.server.level.ServerPlayer handle : this.getHandle().players()) {
|
||||||
|
if (handle.level() != this.getHandle()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (handle.blockPosition().closerToCenterThan(new net.minecraft.world.phys.Vec3(x, y, z), limit)) {
|
||||||
|
targets.add(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (targets.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getHandle().sendParticlesSource(
|
||||||
|
targets,
|
||||||
|
null,
|
||||||
|
CraftParticle.createParticleParam(particle, data),
|
||||||
|
false, // overrideLimiter: прицелы редкие, форсировать не нужно
|
||||||
|
true, // alwaysShow: снимает 32-блочную отсечку на клиенте
|
||||||
|
x, y, z,
|
||||||
|
count,
|
||||||
|
offsetX, offsetY, offsetZ,
|
||||||
|
extra
|
||||||
|
);
|
||||||
|
// CraftBukkit end
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Конфиг Delta, либо null если он ещё не инициализирован. */
|
||||||
|
private static me.sashegdev.delta.DeltaConfig deltaConfig() {
|
||||||
|
try {
|
||||||
|
return io.papermc.paper.configuration.GlobalConfiguration.get().delta;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public Location locateNearestStructure(Location origin, org.bukkit.StructureType structureType, int radius, boolean findUnexplored) {
|
public Location locateNearestStructure(Location origin, org.bukkit.StructureType structureType, int radius, boolean findUnexplored) {
|
||||||
|
|||||||
Reference in New Issue
Block a user