feat(voiddeath): muffled death in the void, local message only

Одиннадцатый этап, последний из задуманных.

Сообщение о смерти в пустоте не уходит в общий чат: забирается через
deathMessage(null) и отправляется только тем, кто в радиусе 100 блоков от места
смерти. Текст сообщения при этом тот же самый, что и обычно.

Убийство кого-то отдельным обработчиком ловить не пришлось: «X убит Y» — это
то же самое событие PlayerDeathEvent, что и «X умер». Различаются они только
текстом, который и так передаётся как есть.

Крик визера: громкость 0.2, дальность доставки 150 блоков. Отдельного
ENTITY_WITHER_SCREAM в enum нет — визer's крик в ванилле называется
entity.wither.spawn, то есть ENTITY_WITHER_SPAWN.

Про «дальность 150» честно: это радиус доставки пакета на сервере, то есть кто
его вообще получил. Громкость 0.2 — громкость пакета, а клиент дальше
применяет своё позиционное затухание: у источника слышно, в сотне метров
почти нет. Чтобы звук был одинаково громким на всех 150 блоках, громкость
пришлось бы поднять до 9.4, и тогда заглушало бы всех рядом с источником.
Именно для такой задачи в Delta есть раздельная дальность звука, и флаг
decoupled-sound-range-enabled на проде включён: без него World#playSound с
радиусом молча уходит в стоковое поведение, где радиус выводится из
громкости, то есть 0.2 даёт 3.2 блока.

Проверено: плагин грузится без ошибок, флаг виден в /delta как true. Сама
смерть в пустоте не проверялась — нужен игрок.
This commit is contained in:
SashegDev
2026-10-19 20:49:27 +00:00
parent 285bcbc282
commit 31d7753471
2 changed files with 72 additions and 0 deletions
@@ -49,6 +49,7 @@ public class BlackKnifePlugin extends JavaPlugin {
getServer().getPluginManager().registerEvents(new MoleHoleCoords(this), this); getServer().getPluginManager().registerEvents(new MoleHoleCoords(this), this);
getServer().getPluginManager().registerEvents(new VoidWorld(), this); getServer().getPluginManager().registerEvents(new VoidWorld(), this);
getServer().getPluginManager().registerEvents(new VoidExit(), this); getServer().getPluginManager().registerEvents(new VoidExit(), this);
getServer().getPluginManager().registerEvents(new VoidDeath(), this);
BlackKnife.init(this); BlackKnife.init(this);
// Имя измерения сервер выбирает сам, поэтому угадывать его в коде нельзя. // Имя измерения сервер выбирает сам, поэтому угадывать его в коде нельзя.
// Находим и печатаем: по этой строке видно, какой мир считается пустотой. // Находим и печатаем: по этой строке видно, какой мир считается пустотой.
@@ -0,0 +1,71 @@
package me.sashegdev.blackknife;
import net.kyori.adventure.text.Component;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.SoundCategory;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
/**
* Глухая смерть в пустоте.
*
* <p>По условию: в пустоте сообщение о смерти не уходит в общий чат, а достаёт
* только тех, кто рядом — в радиусе 100 блоков. Вместо сообщения слышен крик
* визера: громкость 0.2 от обычной, дальность 150 блоков. Убийство кого-то
* трактуется так же, как собственная смерть.
*
* <p>Убийство отдельным обработчиком ловить не нужно: сообщение «X убит Y»
* это то же самое событие {@link PlayerDeathEvent}, что и «X умер». Один
* обработчик покрывает оба случая, а различаются они только текстом сообщения,
* который мы и так передаём как есть.
*
* <p>Про «дальность 150» и «громкость 0.2» стоит понимать честно: 150 — это
* радиус доставки пакета на сервере, то есть кто его вообще получит. Громкость
* 0.2 — громкость самого пакета. Клиент дальше применяет своё позиционное
* затухание, поэтому вблизи источника звук слышен, а в сотне метров — уже
* почти нет. Сделать его одинаково громким на все 150 блоков можно было бы
* только громкостью от 9.4, и тогда заглушало бы всех, кто стоит рядом.
*/
public final class VoidDeath implements Listener {
public VoidDeath() {
}
/** Радиус, в котором сообщение о смерти ещё слышно. */
public static final double MESSAGE_RADIUS = 100.0D;
/** Дальность доставки звука, блоков. */
public static final float SOUND_RANGE = 150.0F;
/** Громкость крика визера. */
public static final float SOUND_VOLUME = 0.2F;
@EventHandler(priority = EventPriority.HIGHEST)
public void onDeath(PlayerDeathEvent e) {
final Player victim = e.getEntity();
if (!VoidWorld.inVoid(victim)) {
return;
}
// Забираем сообщение из общего чата. null означает «не показывать».
final Component message = e.deathMessage();
e.deathMessage(null);
final Location at = victim.getLocation();
final double r2 = MESSAGE_RADIUS * MESSAGE_RADIUS;
// Локально — только тем, кто рядом с местом смерти.
for (Player p : victim.getWorld().getPlayers()) {
if (p.getLocation().distanceSquared(at) <= r2 && message != null) {
p.sendMessage(message);
}
}
// Крик визера. Отдельного ENTITY_WITHER_SCREAM в enum нет: визer's
// крик в ванилле называется entity.wither.spawn.
victim.getWorld().playSound(at, Sound.ENTITY_WITHER_SPAWN,
SoundCategory.HOSTILE, SOUND_VOLUME, 1.0F, SOUND_RANGE);
}
}