diff --git a/README.md b/README.md index ad3447e..978223e 100644 --- a/README.md +++ b/README.md @@ -64,10 +64,11 @@ playback: leave-grace-ticks: 100 blend-overlapping-zones: false follow-player: true + fade-volume-updates: false loop-restart-ticks: 2680 ``` -When a selected zone remains selected, Ambient Audio Zone normally sends no new sound packet. In follow-player mode it uses rate-limited fade updates and stops the previous packet before re-emitting, preventing layered copies of the sound. Bukkit has no mutable-volume packet, so these updates are necessarily discrete; resource-pack loops are the smoothest option. Leaving starts a configurable grace timer; returning before it expires preserves the existing client playback instead of restarting it. `loop-restart-ticks` is a fallback for sounds whose resource-pack definition does not loop; set it to the exact sound length, while resource-pack `loop: true` remains the seamless option. Reloading or editing a zone intentionally replaces its active sound. +When a selected zone remains selected, Ambient Audio Zone normally sends no new sound packet, so walking cannot restart or stack the audio. `fade-volume-updates` is disabled by default because Bukkit has no mutable-volume packet; enabling it approximates the fade by stopping and re-emitting the sound, which necessarily restarts it. For smooth native distance attenuation, disable `follow-player` and use a central `origin`. Leaving starts a configurable grace timer; returning before it expires preserves the existing client playback instead of restarting it. `loop-restart-ticks` is a fallback for sounds whose resource-pack definition does not loop; set it to the exact sound length, while resource-pack `loop: true` remains the seamless option. Reloading or editing a zone intentionally replaces its active sound. ## Resource-pack looping note diff --git a/src/main/java/com/lexian/ambientaudio/service/PlaybackService.java b/src/main/java/com/lexian/ambientaudio/service/PlaybackService.java index a3a6c0e..9432f22 100644 --- a/src/main/java/com/lexian/ambientaudio/service/PlaybackService.java +++ b/src/main/java/com/lexian/ambientaudio/service/PlaybackService.java @@ -12,13 +12,13 @@ import java.util.stream.Collectors; public final class PlaybackService { private final JavaPlugin plugin; private final ZoneIndex index = new ZoneIndex(); private final Map> active = new HashMap<>(); - private boolean blending; private boolean followPlayer; private long leaveGraceTicks, loopRestartTicks; private int taskId = -1; + private boolean blending; private boolean followPlayer, fadeVolumeUpdates; private long leaveGraceTicks, loopRestartTicks; private int taskId = -1; public PlaybackService(JavaPlugin plugin) { this.plugin = plugin; } public void configure(Collection zones, int intervalTicks, long leaveGraceTicks, boolean blending, - boolean followPlayer, long loopRestartTicks) { + boolean followPlayer, boolean fadeVolumeUpdates, long loopRestartTicks) { stopAll(); index.rebuild(zones); this.blending = blending; this.leaveGraceTicks = leaveGraceTicks; - this.followPlayer = followPlayer; this.loopRestartTicks = loopRestartTicks; + this.followPlayer = followPlayer; this.fadeVolumeUpdates = fadeVolumeUpdates; this.loopRestartTicks = loopRestartTicks; if (taskId != -1) Bukkit.getScheduler().cancelTask(taskId); taskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, this::tick, intervalTicks, intervalTicks); } @@ -39,7 +39,7 @@ public final class PlaybackService { } else if (zone.loop() && loopRestartTicks > 0 && now - sound.startedAtTick() >= loopRestartTicks) { play(player, zone, volume); current.put(zone.normalizedName(), new ActiveSound(zone, Long.MAX_VALUE, now, now, volume)); - } else if (followPlayer && now - sound.lastPacketTick() >= 40 && Math.abs(sound.volume() - volume) >= 0.10f) { + } else if (followPlayer && fadeVolumeUpdates && now - sound.lastPacketTick() >= 40 && Math.abs(sound.volume() - volume) >= 0.10f) { // Bukkit has no volume-update packet. Rate-limit fade packets and stop the previous // packet first; otherwise repeated entity sound packets overlap on the client. play(player, zone, volume); @@ -70,7 +70,7 @@ public final class PlaybackService { else player.playSound(zone.originLocation(player.getWorld()), zone.sound(), zone.category(), zone.volume(), zone.pitch()); } private float volumeFor(AudioZone zone, Player player) { - if (!followPlayer) return zone.volume(); + if (!followPlayer || !fadeVolumeUpdates) return zone.volume(); double fade = zone.fadeDistance(); double distance = Math.sqrt(zone.distanceToAudibleAreaSquared(player.getLocation())); double multiplier = fade == 0 ? (distance == 0 ? 1 : 0) : Math.max(0, 1 - distance / fade); diff --git a/src/main/java/com/lexian/ambientaudio/service/ZoneService.java b/src/main/java/com/lexian/ambientaudio/service/ZoneService.java index 5d5fab7..12d6d7b 100644 --- a/src/main/java/com/lexian/ambientaudio/service/ZoneService.java +++ b/src/main/java/com/lexian/ambientaudio/service/ZoneService.java @@ -18,5 +18,5 @@ public final class ZoneService { public AudioZone get(String name) { return zones.get(name.toLowerCase(Locale.ROOT)); } public void put(AudioZone zone) throws IOException { zones.put(zone.normalizedName(), zone); store.save(zones.values()); refreshPlayback(); } public AudioZone remove(String name) throws IOException { AudioZone removed = zones.remove(name.toLowerCase(Locale.ROOT)); if (removed != null) { store.save(zones.values()); refreshPlayback(); } return removed; } - private void refreshPlayback() { FileConfiguration c = plugin.getConfig(); playback.configure(zones.values(), Math.max(1, c.getInt("playback.check-interval-ticks", 10)), Math.max(0, c.getLong("playback.leave-grace-ticks", 100)), c.getBoolean("playback.blend-overlapping-zones", false), c.getBoolean("playback.follow-player", true), Math.max(0, c.getLong("playback.loop-restart-ticks", 2680))); } + private void refreshPlayback() { FileConfiguration c = plugin.getConfig(); playback.configure(zones.values(), Math.max(1, c.getInt("playback.check-interval-ticks", 10)), Math.max(0, c.getLong("playback.leave-grace-ticks", 100)), c.getBoolean("playback.blend-overlapping-zones", false), c.getBoolean("playback.follow-player", true), c.getBoolean("playback.fade-volume-updates", false), Math.max(0, c.getLong("playback.loop-restart-ticks", 2680))); } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index a11abf9..149bb4e 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -6,6 +6,9 @@ playback: blend-overlapping-zones: false # Entity-attached sounds follow each player instead of remaining at origin. follow-player: true + # Disabled by default: Bukkit has no mutable-volume packet, so enabling this + # necessarily restarts the sound when the calculated volume changes. + fade-volume-updates: false # Fallback replay interval for loop: true sounds. 2680 ticks is 134 seconds. # Set to the actual length of custom sounds; resource-pack loop=true is preferred. loop-restart-ticks: 2680