From 0373806e257abfa7f74953c79a6b11107860ef22 Mon Sep 17 00:00:00 2001 From: SashegDev Date: Wed, 30 Sep 2026 10:00:20 +0000 Subject: [PATCH] feat(delta): config persistence via Configurations.saveGlobalSection + decoupled sound range API --- paper-api/src/main/java/org/bukkit/World.java | 22 +++++++++++++ .../paper/configuration/Configurations.java | 17 ++++++++++ .../java/me/sashegdev/delta/DeltaCommand.java | 30 +++++++++++++----- .../org/bukkit/craftbukkit/CraftWorld.java | 31 +++++++++++++++++++ 4 files changed, 93 insertions(+), 7 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/World.java b/paper-api/src/main/java/org/bukkit/World.java index ae342a5ce..6e6f488ae 100644 --- a/paper-api/src/main/java/org/bukkit/World.java +++ b/paper-api/src/main/java/org/bukkit/World.java @@ -3616,6 +3616,28 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient */ void playSound(@NotNull Entity entity, @NotNull Sound sound, @NotNull SoundCategory category, float volume, float pitch, long seed); + /** + * Delta: plays a sound with an explicit audible range, decoupled from volume. + *

+ * In vanilla the audible distance is derived from volume (16 blocks per volume unit), + * so a sound that must be heard 150 blocks away requires volume ~10, which then clips + * badly for players standing next to the source. This overload lets you pick gain and + * distance independently. + *

+ * Requires the Delta core with {@code delta.decoupled-sound-range-enabled: true}; + * on any other server it silently falls back to vanilla behaviour. + * + * @param location the location to play the sound at + * @param sound the sound to play + * @param category the category of the sound + * @param volume the loudness at the source + * @param pitch the pitch of the sound + * @param range the audible radius in blocks, independent of volume + */ + default void playSound(@NotNull Location location, @NotNull Sound sound, @NotNull SoundCategory category, float volume, float pitch, float range) { + playSound(location, sound, category, volume, pitch); // fallback: range == f(volume) + } + /** * Play a Sound at the location of the provided entity in the World. For sounds * with multiple variations passing the same seed will always play the same diff --git a/paper-server/src/main/java/io/papermc/paper/configuration/Configurations.java b/paper-server/src/main/java/io/papermc/paper/configuration/Configurations.java index d0c4037c3..48ea39d7d 100644 --- a/paper-server/src/main/java/io/papermc/paper/configuration/Configurations.java +++ b/paper-server/src/main/java/io/papermc/paper/configuration/Configurations.java @@ -139,6 +139,23 @@ public abstract class Configurations { return instance; } + // Delta start - persist a single global config section + // Paper exposes no way to write paper-global.yml back after boot: reloadConfigs() + // re-reads from disk and would clobber any in-memory change. This writes just the + // requested section and leaves every other node exactly as it was parsed, so it can + // never damage unrelated settings. + public void saveGlobalSection(final RegistryAccess registryAccess, final String section, final Object value) throws ConfigurateException { + final Path configFile = this.globalFolder.resolve(this.globalConfigFileName); + final YamlConfigurationLoader loader = this.createGlobalLoaderBuilder(registryAccess) + .defaultOptions(this.applyObjectMapperFactory(this.createGlobalObjectMapperFactoryBuilder().build())) + .path(configFile) + .build(); + final ConfigurationNode node = loader.load(); + node.node(section).set(value); + trySaveFileNode(loader, node, configFile.toString()); + } + // Delta end + protected void verifyGlobalConfigVersion(final ConfigurationNode globalNode) { final ConfigurationNode version = globalNode.node(Configuration.VERSION_FIELD); if (version.virtual()) { 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 b2848145b..b6bce8484 100644 --- a/paper-server/src/main/java/me/sashegdev/delta/DeltaCommand.java +++ b/paper-server/src/main/java/me/sashegdev/delta/DeltaCommand.java @@ -73,17 +73,14 @@ public class DeltaCommand extends Command { cfg.displayTrackingRange = value; sender.sendMessage(Component.text("Display tracking range set to " + value + " blocks", NamedTextColor.GREEN)); } - // Paper не даёт публичного API для записи paper-global.yml, поэтому - // это живая правка только в памяти: после рестарта вернётся значение - // из конфига. Постоянно — правкой paper-global.yml -> delta. - sender.sendMessage(Component.text( - "Runtime-only. For a permanent change edit paper-global.yml -> delta.", - NamedTextColor.GRAY)); + // Paper не даёт публичного API для записи paper-global.yml — Delta добавляет + // saveGlobalSection(), который пишет только секцию delta и не трогает остальное. + persist(sender, "display range"); } case "clear" -> { var cfg = io.papermc.paper.configuration.GlobalConfiguration.get().delta; cfg.displayTrackingRangeEnabled = false; - sender.sendMessage(Component.text("Runtime override cleared, back to config values", NamedTextColor.YELLOW)); + persist(sender, "display range"); sender.sendMessage(line("display range ", DeltaTracking.describe())); } case "help" -> sender.sendMessage(Component.text( @@ -114,6 +111,25 @@ public class DeltaCommand extends Command { return List.of(); } + /** + * Пишет секцию {@code delta} обратно в paper-global.yml. + * Записывается только наша секция — остальные узлы файла остаются как были распарсены, + * поэтому сломать чужие настройки этим невозможно. + */ + private void persist(final CommandSender sender, final String what) { + final var server = Bukkit.getServer(); + try { + server.paperConfigurations().saveGlobalSection( + server.registryAccess(), "delta", + io.papermc.paper.configuration.GlobalConfiguration.get().delta); + sender.sendMessage(Component.text("Saved to paper-global.yml -> delta (" + what + ")", NamedTextColor.DARK_GREEN)); + } catch (Throwable t) { + sender.sendMessage(Component.text( + "Applied at runtime, but failed to persist: " + t.getMessage(), + NamedTextColor.RED)); + } + } + private Component header() { return Component.text("---- Delta ----", NamedTextColor.DARK_PURPLE); } 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 ee7a41690..9ba94246f 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -1889,6 +1889,37 @@ public class CraftWorld extends CraftRegionAccessor implements World { this.playSound(loc, sound, category, volume, pitch, this.getHandle().random.nextLong()); } + // Delta start - decoupled sound range + // Vanilla derives audible distance from volume (SoundEvent#getRange = 16 * volume), so the + // only way to make a boss ability audible at 150 blocks is volume=10, which then clips + // badly for anyone standing next to the source. This sends the same packet with an + // explicit radius, keeping gain and distance independent. + @Override + public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, float range) { + org.spigotmc.AsyncCatcher.catchOp("play sound (decoupled range)"); // Paper + if (loc == null || sound == null || category == null) return; + if (!io.papermc.paper.configuration.GlobalConfiguration.get().delta.decoupledSoundRangeEnabled) { + this.playSound(loc, sound, category, volume, pitch, this.getHandle().random.nextLong()); // feature off: stock behaviour + return; + } + + final double x = loc.getX(); + final double y = loc.getY(); + final double z = loc.getZ(); + + final net.minecraft.network.protocol.game.ClientboundSoundPacket packet = new net.minecraft.network.protocol.game.ClientboundSoundPacket( + CraftSound.bukkitToMinecraft(sound), + SoundSource.valueOf(category.name()), + x, y, z, + volume, pitch, this.getHandle().random.nextLong() + ); + + this.getServer().getHandle().getPlayerList().broadcast( + null, x, y, z, range, this.getHandle().dimension(), packet + ); + } + // Delta end + @Override public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper