feat(delta): config persistence via Configurations.saveGlobalSection + decoupled sound range API
Build Delta / Build paperclip jar (push) Failing after 5m52s

This commit is contained in:
SashegDev
2026-09-30 10:00:20 +00:00
parent f55129436b
commit 0373806e25
4 changed files with 93 additions and 7 deletions
@@ -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); 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.
* <p>
* 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.
* <p>
* 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 * 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 * with multiple variations passing the same seed will always play the same
@@ -139,6 +139,23 @@ public abstract class Configurations<G, W> {
return instance; 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) { protected void verifyGlobalConfigVersion(final ConfigurationNode globalNode) {
final ConfigurationNode version = globalNode.node(Configuration.VERSION_FIELD); final ConfigurationNode version = globalNode.node(Configuration.VERSION_FIELD);
if (version.virtual()) { if (version.virtual()) {
@@ -73,17 +73,14 @@ public class DeltaCommand extends Command {
cfg.displayTrackingRange = value; cfg.displayTrackingRange = value;
sender.sendMessage(Component.text("Display tracking range set to " + value + " blocks", NamedTextColor.GREEN)); sender.sendMessage(Component.text("Display tracking range set to " + value + " blocks", NamedTextColor.GREEN));
} }
// Paper не даёт публичного API для записи paper-global.yml, поэтому // Paper не даёт публичного API для записи paper-global.yml — Delta добавляет
// это живая правка только в памяти: после рестарта вернётся значение // saveGlobalSection(), который пишет только секцию delta и не трогает остальное.
// из конфига. Постоянно — правкой paper-global.yml -> delta. persist(sender, "display range");
sender.sendMessage(Component.text(
"Runtime-only. For a permanent change edit paper-global.yml -> delta.",
NamedTextColor.GRAY));
} }
case "clear" -> { case "clear" -> {
var cfg = io.papermc.paper.configuration.GlobalConfiguration.get().delta; var cfg = io.papermc.paper.configuration.GlobalConfiguration.get().delta;
cfg.displayTrackingRangeEnabled = false; 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())); sender.sendMessage(line("display range ", DeltaTracking.describe()));
} }
case "help" -> sender.sendMessage(Component.text( case "help" -> sender.sendMessage(Component.text(
@@ -114,6 +111,25 @@ public class DeltaCommand extends Command {
return List.of(); 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() { private Component header() {
return Component.text("---- Delta ----", NamedTextColor.DARK_PURPLE); return Component.text("---- Delta ----", NamedTextColor.DARK_PURPLE);
} }
@@ -1889,6 +1889,37 @@ public class CraftWorld extends CraftRegionAccessor implements World {
this.playSound(loc, sound, category, volume, pitch, this.getHandle().random.nextLong()); 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 @Override
public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) { public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) {
org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper