fix: prevent overlapping fade and loop playback
This commit is contained in:
@@ -67,7 +67,7 @@ playback:
|
||||
loop-restart-ticks: 2680
|
||||
```
|
||||
|
||||
When a selected zone remains selected, Ambient Audio Zone normally sends no new sound packet. In follow-player mode it re-emits only when the calculated fade changes materially, and it uses an entity-attached packet so the sound follows the player. 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. 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.
|
||||
|
||||
## Resource-pack looping note
|
||||
|
||||
|
||||
@@ -35,21 +35,21 @@ public final class PlaybackService {
|
||||
float volume = volumeFor(zone, player);
|
||||
if (sound == null) {
|
||||
play(player, zone, volume);
|
||||
current.put(zone.normalizedName(), new ActiveSound(zone, Long.MAX_VALUE, now, volume));
|
||||
current.put(zone.normalizedName(), new ActiveSound(zone, Long.MAX_VALUE, now, now, volume));
|
||||
} 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, volume));
|
||||
} else if (followPlayer && Math.abs(sound.volume() - volume) >= 0.05f) {
|
||||
// A sound packet has no mutable volume. Re-emit only when the fade changed materially;
|
||||
// inside the zone the packet remains attached to the player and is not restarted.
|
||||
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) {
|
||||
// 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);
|
||||
current.put(zone.normalizedName(), new ActiveSound(zone, Long.MAX_VALUE, sound.startedAtTick(), volume));
|
||||
current.put(zone.normalizedName(), new ActiveSound(zone, Long.MAX_VALUE, sound.startedAtTick(), now, volume));
|
||||
}
|
||||
}
|
||||
for (Iterator<Map.Entry<String, ActiveSound>> it = current.entrySet().iterator(); it.hasNext();) {
|
||||
Map.Entry<String, ActiveSound> entry = it.next(); ActiveSound sound = entry.getValue();
|
||||
if (desired.contains(entry.getKey())) { entry.setValue(new ActiveSound(sound.zone(), Long.MAX_VALUE, sound.startedAtTick(), sound.volume())); continue; }
|
||||
if (sound.stopAtTick() == Long.MAX_VALUE) { entry.setValue(new ActiveSound(sound.zone(), now + leaveGraceTicks, sound.startedAtTick(), sound.volume())); continue; }
|
||||
if (desired.contains(entry.getKey())) { entry.setValue(new ActiveSound(sound.zone(), Long.MAX_VALUE, sound.startedAtTick(), sound.lastPacketTick(), sound.volume())); continue; }
|
||||
if (sound.stopAtTick() == Long.MAX_VALUE) { entry.setValue(new ActiveSound(sound.zone(), now + leaveGraceTicks, sound.startedAtTick(), sound.lastPacketTick(), sound.volume())); continue; }
|
||||
if (now >= sound.stopAtTick()) { player.stopSound(sound.zone().sound(), sound.zone().category()); it.remove(); }
|
||||
}
|
||||
if (current.isEmpty()) active.remove(player.getUniqueId());
|
||||
@@ -63,6 +63,9 @@ public final class PlaybackService {
|
||||
} active.clear();
|
||||
}
|
||||
private void play(Player player, AudioZone zone, float volume) {
|
||||
// A sound event is not a handle. Stop the matching event before re-emitting it so
|
||||
// fade updates and loop restarts cannot layer copies of the same sound.
|
||||
player.stopSound(zone.sound(), zone.category());
|
||||
if (followPlayer) player.playSound(player, zone.sound(), zone.category(), volume, zone.pitch());
|
||||
else player.playSound(zone.originLocation(player.getWorld()), zone.sound(), zone.category(), zone.volume(), zone.pitch());
|
||||
}
|
||||
@@ -73,5 +76,5 @@ public final class PlaybackService {
|
||||
double multiplier = fade == 0 ? (distance == 0 ? 1 : 0) : Math.max(0, 1 - distance / fade);
|
||||
return (float) Math.max(0.001, zone.volume() * multiplier);
|
||||
}
|
||||
private record ActiveSound(AudioZone zone, long stopAtTick, long startedAtTick, float volume) { }
|
||||
private record ActiveSound(AudioZone zone, long stopAtTick, long startedAtTick, long lastPacketTick, float volume) { }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user