fix: keep active sounds from restarting during movement
This commit is contained in:
@@ -64,10 +64,11 @@ playback:
|
|||||||
leave-grace-ticks: 100
|
leave-grace-ticks: 100
|
||||||
blend-overlapping-zones: false
|
blend-overlapping-zones: false
|
||||||
follow-player: true
|
follow-player: true
|
||||||
|
fade-volume-updates: false
|
||||||
loop-restart-ticks: 2680
|
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
|
## Resource-pack looping note
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ import java.util.stream.Collectors;
|
|||||||
public final class PlaybackService {
|
public final class PlaybackService {
|
||||||
private final JavaPlugin plugin; private final ZoneIndex index = new ZoneIndex();
|
private final JavaPlugin plugin; private final ZoneIndex index = new ZoneIndex();
|
||||||
private final Map<UUID, Map<String, ActiveSound>> active = new HashMap<>();
|
private final Map<UUID, Map<String, ActiveSound>> 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 PlaybackService(JavaPlugin plugin) { this.plugin = plugin; }
|
||||||
public void configure(Collection<AudioZone> zones, int intervalTicks, long leaveGraceTicks, boolean blending,
|
public void configure(Collection<AudioZone> 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;
|
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);
|
if (taskId != -1) Bukkit.getScheduler().cancelTask(taskId);
|
||||||
taskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, this::tick, intervalTicks, intervalTicks);
|
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) {
|
} else if (zone.loop() && loopRestartTicks > 0 && now - sound.startedAtTick() >= loopRestartTicks) {
|
||||||
play(player, zone, volume);
|
play(player, zone, volume);
|
||||||
current.put(zone.normalizedName(), new ActiveSound(zone, Long.MAX_VALUE, now, now, 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
|
// 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.
|
// packet first; otherwise repeated entity sound packets overlap on the client.
|
||||||
play(player, zone, volume);
|
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());
|
else player.playSound(zone.originLocation(player.getWorld()), zone.sound(), zone.category(), zone.volume(), zone.pitch());
|
||||||
}
|
}
|
||||||
private float volumeFor(AudioZone zone, Player player) {
|
private float volumeFor(AudioZone zone, Player player) {
|
||||||
if (!followPlayer) return zone.volume();
|
if (!followPlayer || !fadeVolumeUpdates) return zone.volume();
|
||||||
double fade = zone.fadeDistance();
|
double fade = zone.fadeDistance();
|
||||||
double distance = Math.sqrt(zone.distanceToAudibleAreaSquared(player.getLocation()));
|
double distance = Math.sqrt(zone.distanceToAudibleAreaSquared(player.getLocation()));
|
||||||
double multiplier = fade == 0 ? (distance == 0 ? 1 : 0) : Math.max(0, 1 - distance / fade);
|
double multiplier = fade == 0 ? (distance == 0 ? 1 : 0) : Math.max(0, 1 - distance / fade);
|
||||||
|
|||||||
@@ -18,5 +18,5 @@ public final class ZoneService {
|
|||||||
public AudioZone get(String name) { return zones.get(name.toLowerCase(Locale.ROOT)); }
|
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 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; }
|
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))); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ playback:
|
|||||||
blend-overlapping-zones: false
|
blend-overlapping-zones: false
|
||||||
# Entity-attached sounds follow each player instead of remaining at origin.
|
# Entity-attached sounds follow each player instead of remaining at origin.
|
||||||
follow-player: true
|
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.
|
# 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.
|
# Set to the actual length of custom sounds; resource-pack loop=true is preferred.
|
||||||
loop-restart-ticks: 2680
|
loop-restart-ticks: 2680
|
||||||
|
|||||||
Reference in New Issue
Block a user