fix: follow players and restart looping sounds
This commit is contained in:
@@ -12,11 +12,13 @@ import java.util.stream.Collectors;
|
||||
public final class PlaybackService {
|
||||
private final JavaPlugin plugin; private final ZoneIndex index = new ZoneIndex();
|
||||
private final Map<UUID, Map<String, ActiveSound>> active = new HashMap<>();
|
||||
private boolean blending; private long leaveGraceTicks; private int taskId = -1;
|
||||
private boolean blending; private boolean followPlayer; private long leaveGraceTicks, loopRestartTicks; private int taskId = -1;
|
||||
|
||||
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) {
|
||||
stopAll(); index.rebuild(zones); this.blending = blending; this.leaveGraceTicks = leaveGraceTicks;
|
||||
this.followPlayer = followPlayer; this.loopRestartTicks = loopRestartTicks;
|
||||
if (taskId != -1) Bukkit.getScheduler().cancelTask(taskId);
|
||||
taskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, this::tick, intervalTicks, intervalTicks);
|
||||
}
|
||||
@@ -28,14 +30,26 @@ public final class PlaybackService {
|
||||
if (!blending && !audible.isEmpty()) audible = List.of(audible.getFirst());
|
||||
Set<String> desired = audible.stream().map(AudioZone::normalizedName).collect(Collectors.toSet());
|
||||
Map<String, ActiveSound> current = active.computeIfAbsent(player.getUniqueId(), unused -> new HashMap<>());
|
||||
for (AudioZone zone : audible) if (!current.containsKey(zone.normalizedName())) {
|
||||
player.playSound(zone.originLocation(player.getWorld()), zone.sound(), zone.category(), zone.volume(), zone.pitch());
|
||||
current.put(zone.normalizedName(), new ActiveSound(zone, Long.MAX_VALUE));
|
||||
for (AudioZone zone : audible) {
|
||||
ActiveSound sound = current.get(zone.normalizedName());
|
||||
float volume = volumeFor(zone, player);
|
||||
if (sound == null) {
|
||||
play(player, zone, volume);
|
||||
current.put(zone.normalizedName(), new ActiveSound(zone, Long.MAX_VALUE, 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.
|
||||
play(player, zone, volume);
|
||||
current.put(zone.normalizedName(), new ActiveSound(zone, Long.MAX_VALUE, sound.startedAtTick(), 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)); continue; }
|
||||
if (sound.stopAtTick() == Long.MAX_VALUE) { entry.setValue(new ActiveSound(sound.zone(), now + leaveGraceTicks)); continue; }
|
||||
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 (now >= sound.stopAtTick()) { player.stopSound(sound.zone().sound(), sound.zone().category()); it.remove(); }
|
||||
}
|
||||
if (current.isEmpty()) active.remove(player.getUniqueId());
|
||||
@@ -48,5 +62,16 @@ public final class PlaybackService {
|
||||
Map<String, ActiveSound> sounds = active.get(player.getUniqueId()); if (sounds != null) sounds.values().forEach(sound -> player.stopSound(sound.zone().sound(), sound.zone().category()));
|
||||
} active.clear();
|
||||
}
|
||||
private record ActiveSound(AudioZone zone, long stopAtTick) { }
|
||||
private void play(Player player, AudioZone zone, float volume) {
|
||||
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());
|
||||
}
|
||||
private float volumeFor(AudioZone zone, Player player) {
|
||||
if (!followPlayer) 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);
|
||||
return (float) Math.max(0.001, zone.volume() * multiplier);
|
||||
}
|
||||
private record ActiveSound(AudioZone zone, long stopAtTick, long startedAtTick, float volume) { }
|
||||
}
|
||||
|
||||
@@ -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)); }
|
||||
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))); }
|
||||
}
|
||||
|
||||
@@ -4,3 +4,8 @@ playback:
|
||||
check-interval-ticks: 10
|
||||
leave-grace-ticks: 100
|
||||
blend-overlapping-zones: false
|
||||
# Entity-attached sounds follow each player instead of remaining at origin.
|
||||
follow-player: true
|
||||
# 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
|
||||
|
||||
Reference in New Issue
Block a user