From c62c431873cf4306a93326950c07db89cfdf7ecb Mon Sep 17 00:00:00 2001 From: Andrei <139968939+Andrei965@users.noreply.github.com> Date: Sat, 28 Jun 2025 18:22:13 +0300 Subject: [PATCH 1/4] feat: add methods to pause/resume song --- src/main/java/dev/emortal/nbstom/NBS.java | 136 +++++++++++++++++----- 1 file changed, 109 insertions(+), 27 deletions(-) diff --git a/src/main/java/dev/emortal/nbstom/NBS.java b/src/main/java/dev/emortal/nbstom/NBS.java index de9ec21..8b3e672 100644 --- a/src/main/java/dev/emortal/nbstom/NBS.java +++ b/src/main/java/dev/emortal/nbstom/NBS.java @@ -11,11 +11,64 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Supplier; public class NBS { - public static final Map playingTaskMap = new ConcurrentHashMap<>(); + private static final Map playingSongs = new ConcurrentHashMap<>(); + + private static class NBSPlayer { + final NBSSong song; + final Audience audience; + final Scheduler scheduler; + final UUID stopId; + + Task task; + int tick = 0; + + NBSPlayer(NBSSong song, Audience audience, Scheduler scheduler, UUID stopId) { + this.song = song; + this.audience = audience; + this.scheduler = scheduler; + this.stopId = stopId; + } + + void schedule() { + if (this.task != null && this.task.isAlive()) { + this.task.cancel(); + } + + this.task = this.scheduler.submitTask(() -> { + if (tick > song.getLength() + 1) { + playingSongs.remove(this.stopId); + return TaskSchedule.stop(); + } + + List sounds = song.getTicks().get(tick); + if (sounds != null) { + for (Sound sound : sounds) { + audience.playSound(sound, Sound.Emitter.self()); + } + } + + tick++; + + return TaskSchedule.millis((long) (1000.0 / song.getTps())); + }); + } + + void pause() { + if (this.task != null) { + this.task.cancel(); + } + } + + void stop() { + if (this.task != null) { + this.task.cancel(); + } + } + } + /** * Plays this NBS song to an audience @@ -36,38 +89,67 @@ public static void play(NBSSong song, Player player) { * @param stopId The id for use with {@link #stop(UUID)} later */ public static void play(NBSSong song, Audience audience, Scheduler scheduler, UUID stopId) { - playingTaskMap.put(stopId, scheduler.submitTask(new Supplier<>() { - int tick = 0; - - @Override - public TaskSchedule get() { - if (tick > song.getLength() + 1) { - return TaskSchedule.stop(); - } + stop(stopId); - List sounds = song.getTicks().get(tick); - if (sounds != null) { - for (Sound sound : sounds) { - audience.playSound(sound, Sound.Emitter.self()); - } - } - - tick++; - - return TaskSchedule.millis((long) (1000.0 / song.getTps())); - } - })); + NBSPlayer nbsPlayer = new NBSPlayer(song, audience, scheduler, stopId); + playingSongs.put(stopId, nbsPlayer); + nbsPlayer.schedule(); } public static void stop(Player player) { stop(player.getUuid()); } + public static void stop(UUID stopId) { - Task task = playingTaskMap.get(stopId); - if (task != null) { - task.cancel(); + NBSPlayer nbsPlayer = playingSongs.remove(stopId); + if (nbsPlayer != null) { + nbsPlayer.stop(); + } + } + + /** + * Pauses a song for a specific player. + * The song can be resumed using {@link #resume(Player)}. + * + * @param player The player to pause the song for. + */ + public static void pause(Player player) { + pause(player.getUuid()); + } + + /** + * Pauses a song for a specific stopId. + * The song can be resumed using {@link #resume(UUID)}. + * + * @param stopId The id of the song to pause. + */ + public static void pause(UUID stopId) { + NBSPlayer nbsPlayer = playingSongs.get(stopId); + if (nbsPlayer != null) { + nbsPlayer.pause(); } - playingTaskMap.remove(stopId); } -} + /** + * Resumes a paused song for a specific player. + * + * @param player The player to resume the song for. + */ + public static void resume(Player player) { + resume(player.getUuid()); + } + + /** + * Resumes a paused song for a specific stopId. + * + * @param stopId The id of the song to resume. + */ + public static void resume(UUID stopId) { + NBSPlayer nbsPlayer = playingSongs.get(stopId); + if (nbsPlayer != null) { + if (nbsPlayer.task == null || !nbsPlayer.task.isAlive()) { + nbsPlayer.schedule(); + } + } + } +} \ No newline at end of file From f8891b6902e0e3868c59e73e68feed7156b4d1bd Mon Sep 17 00:00:00 2001 From: Andrei <139968939+Andrei965@users.noreply.github.com> Date: Sat, 28 Jun 2025 18:35:41 +0300 Subject: [PATCH 2/4] feat: implement looping --- src/main/java/dev/emortal/nbstom/NBS.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/dev/emortal/nbstom/NBS.java b/src/main/java/dev/emortal/nbstom/NBS.java index 8b3e672..f9a9189 100644 --- a/src/main/java/dev/emortal/nbstom/NBS.java +++ b/src/main/java/dev/emortal/nbstom/NBS.java @@ -24,6 +24,7 @@ private static class NBSPlayer { Task task; int tick = 0; + int loops = 0; NBSPlayer(NBSSong song, Audience audience, Scheduler scheduler, UUID stopId) { this.song = song; @@ -38,9 +39,14 @@ void schedule() { } this.task = this.scheduler.submitTask(() -> { - if (tick > song.getLength() + 1) { - playingSongs.remove(this.stopId); - return TaskSchedule.stop(); + if (tick > song.getLength()) { + if (song.isLoop() && (song.getMaxLoopCount() == 0 || loops < song.getMaxLoopCount())) { + this.loops++; + this.tick = song.getLoopStart(); + } else { + playingSongs.remove(this.stopId); + return TaskSchedule.stop(); + } } List sounds = song.getTicks().get(tick); From 26b231c3d48288cbba557fcfbda522f7191d97ed Mon Sep 17 00:00:00 2001 From: Andrei <139968939+Andrei965@users.noreply.github.com> Date: Sat, 28 Jun 2025 18:59:22 +0300 Subject: [PATCH 3/4] feat: return future that completes when song ends --- src/main/java/dev/emortal/nbstom/NBS.java | 37 +++++++++++++++++------ 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/main/java/dev/emortal/nbstom/NBS.java b/src/main/java/dev/emortal/nbstom/NBS.java index f9a9189..6b224fe 100644 --- a/src/main/java/dev/emortal/nbstom/NBS.java +++ b/src/main/java/dev/emortal/nbstom/NBS.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; public class NBS { @@ -21,16 +22,18 @@ private static class NBSPlayer { final Audience audience; final Scheduler scheduler; final UUID stopId; + final CompletableFuture onFinishFuture; Task task; int tick = 0; int loops = 0; - NBSPlayer(NBSSong song, Audience audience, Scheduler scheduler, UUID stopId) { + NBSPlayer(NBSSong song, Audience audience, Scheduler scheduler, UUID stopId, CompletableFuture onFinishFuture) { this.song = song; this.audience = audience; this.scheduler = scheduler; this.stopId = stopId; + this.onFinishFuture = onFinishFuture; } void schedule() { @@ -45,6 +48,7 @@ void schedule() { this.tick = song.getLoopStart(); } else { playingSongs.remove(this.stopId); + this.onFinishFuture.complete(null); return TaskSchedule.stop(); } } @@ -72,34 +76,37 @@ void stop() { if (this.task != null) { this.task.cancel(); } + this.onFinishFuture.cancel(false); } } - /** - * Plays this NBS song to an audience - * Can be cancelled by using {@link #stop(UUID)} with the player's UUID. This stops automatically when the player leaves. + * Plays this NBS song to a player and returns a future that completes when the song is finished. * * @param player The player to play the song to + * @return A {@link CompletableFuture} that completes when the song finishes. */ - public static void play(NBSSong song, Player player) { - play(song, player, player.scheduler(), player.getUuid()); + public static CompletableFuture play(NBSSong song, Player player) { + return play(song, player, player.scheduler(), player.getUuid()); } /** - * Plays this NBS song to an audience - * Can be cancelled by using {@link #stop(UUID)} with the same stopId or by cancelling via the scheduler + * Plays this NBS song to an audience and returns a future that completes when the song is finished. * * @param audience The audience to play the song to * @param scheduler The scheduler to tick the song on * @param stopId The id for use with {@link #stop(UUID)} later + * @return A {@link CompletableFuture} that completes when the song finishes. */ - public static void play(NBSSong song, Audience audience, Scheduler scheduler, UUID stopId) { + public static CompletableFuture play(NBSSong song, Audience audience, Scheduler scheduler, UUID stopId) { stop(stopId); - NBSPlayer nbsPlayer = new NBSPlayer(song, audience, scheduler, stopId); + CompletableFuture future = new CompletableFuture<>(); + NBSPlayer nbsPlayer = new NBSPlayer(song, audience, scheduler, stopId, future); playingSongs.put(stopId, nbsPlayer); nbsPlayer.schedule(); + + return future; } public static void stop(Player player) { @@ -113,6 +120,16 @@ public static void stop(UUID stopId) { } } + /** + * Checks if a song is currently playing for the given ID. + * + * @param stopId The ID to check. + * @return true if a song is playing, false otherwise. + */ + public static boolean isPlaying(UUID stopId) { + return playingSongs.containsKey(stopId); + } + /** * Pauses a song for a specific player. * The song can be resumed using {@link #resume(Player)}. From 10a795f12ce5e8081cda81530cd91ed13822d76e Mon Sep 17 00:00:00 2001 From: Andrei <139968939+Andrei965@users.noreply.github.com> Date: Sun, 29 Jun 2025 14:09:53 +0300 Subject: [PATCH 4/4] fix song tempo --- src/main/java/dev/emortal/nbstom/NBS.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/dev/emortal/nbstom/NBS.java b/src/main/java/dev/emortal/nbstom/NBS.java index 6b224fe..d5601cb 100644 --- a/src/main/java/dev/emortal/nbstom/NBS.java +++ b/src/main/java/dev/emortal/nbstom/NBS.java @@ -62,7 +62,13 @@ void schedule() { tick++; - return TaskSchedule.millis((long) (1000.0 / song.getTps())); + long delayInTicks = (long) (20.0 / song.getTps()); + + if (delayInTicks < 1) { + delayInTicks = 1; + } + + return TaskSchedule.tick((int)delayInTicks); }); }