From 79a935fd61ed7947ea9c90517e38dc43be66f066 Mon Sep 17 00:00:00 2001 From: BRanulf Date: Mon, 28 Apr 2025 18:50:46 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BA=9F=E4=BA=86=EF=BC=8C=E5=9B=9E=E9=80=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle.properties | 2 +- .../semmiedev/disc_jockey_revive/Main.java | 64 +++++++++++++++++ .../disc_jockey_revive/ModConfig.java | 24 +++++++ .../disc_jockey_revive/SongPlayer.java | 69 +++++++++++++++---- .../gui/screen/DiscJockeyScreen.java | 60 +++++++++++----- .../assets/disc_jockey/lang/en_us.json | 12 +++- .../assets/disc_jockey/lang/zh_cn.json | 12 +++- 7 files changed, 211 insertions(+), 32 deletions(-) diff --git a/gradle.properties b/gradle.properties index dc69aa4..dc9a287 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.21.4 yarn_mappings=1.21.4+build.8 loader_version=0.16.10 # Mod Properties -mod_version=1.14.514.021 +mod_version=1.14.514.022 maven_group=semmiedev archives_base_name=disc_jockey_revive # Dependencies diff --git a/src/main/java/semmiedev/disc_jockey_revive/Main.java b/src/main/java/semmiedev/disc_jockey_revive/Main.java index 9fcbd6e..bf8bfaf 100644 --- a/src/main/java/semmiedev/disc_jockey_revive/Main.java +++ b/src/main/java/semmiedev/disc_jockey_revive/Main.java @@ -20,6 +20,7 @@ import net.minecraft.util.Formatting; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.lwjgl.glfw.GLFW; +import semmiedev.disc_jockey_revive.gui.SongListWidget; import semmiedev.disc_jockey_revive.gui.hud.BlocksOverlay; import semmiedev.disc_jockey_revive.gui.screen.DiscJockeyScreen; @@ -38,6 +39,15 @@ public class Main implements ClientModInitializer { public static ModConfig config; public static ConfigHolder configHolder; + public static KeyBinding nextSongKeyBind; +// public static KeyBinding prevSongKeyBind; + static KeyBinding playStopKeyBind; + public static KeyBinding reloadKeyBind; + + private static long lastNextSongPress = 0; + public MinecraftClient client; + + @Override public void onInitializeClient() { configHolder = AutoConfig.register(ModConfig.class, JanksonConfigSerializer::new); @@ -48,8 +58,40 @@ public class Main implements ClientModInitializer { SongLoader.loadSongs(); + // 打开菜单 KeyBinding openScreenKeyBind = KeyBindingHelper.registerKeyBinding(new KeyBinding(MOD_ID+".key_bind.open_screen", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_J, "key.category."+MOD_ID)); + // 下一首和上一首快捷键 + nextSongKeyBind = KeyBindingHelper.registerKeyBinding(new KeyBinding( + MOD_ID+".key_bind.next_song", + InputUtil.Type.KEYSYM, + InputUtil.UNKNOWN_KEY.getCode(), // Default to unbound + "key.category."+MOD_ID + )); + // 上一首不要了,没啥用 +// prevSongKeyBind = KeyBindingHelper.registerKeyBinding(new KeyBinding( +// MOD_ID+".key_bind.prev_song", +// InputUtil.Type.KEYSYM, +// InputUtil.UNKNOWN_KEY.getCode(), // Default to unbound +// "key.category."+MOD_ID +// )); + + // 停止 + playStopKeyBind = KeyBindingHelper.registerKeyBinding(new KeyBinding( + MOD_ID+".key_bind.play_stop", + InputUtil.Type.KEYSYM, + InputUtil.UNKNOWN_KEY.getCode(), // Default unbound + "key.category."+MOD_ID + )); + + // 重载 + reloadKeyBind = KeyBindingHelper.registerKeyBinding(new KeyBinding( + MOD_ID+".key_bind.reload", + InputUtil.Type.KEYSYM, + InputUtil.UNKNOWN_KEY.getCode(), // Default unbound + "key.category."+MOD_ID + )); + ClientTickEvents.START_CLIENT_TICK.register(new ClientTickEvents.StartTick() { private ClientWorld prevWorld; @@ -69,9 +111,31 @@ public class Main implements ClientModInitializer { client.setScreen(new DiscJockeyScreen()); } } + if (nextSongKeyBind.wasPressed()) { + if (SONG_PLAYER.running) { + SONG_PLAYER.playNextSong(); + } + } +// if (prevSongKeyBind.wasPressed()) { +// SONG_PLAYER.playPreviousSong(); +// } + if (playStopKeyBind.wasPressed()) { + if (SONG_PLAYER.running) { + SONG_PLAYER.stop(); + } + } + if (reloadKeyBind.wasPressed()) { + SongLoader.loadSongs(); + if (client.currentScreen instanceof DiscJockeyScreen) { + client.setScreen(new DiscJockeyScreen()); + } + } } }); + + + ClientTickEvents.START_WORLD_TICK.register(world -> { for (ClientTickEvents.StartWorldTick listener : TICK_LISTENERS) listener.onStartTick(world); }); diff --git a/src/main/java/semmiedev/disc_jockey_revive/ModConfig.java b/src/main/java/semmiedev/disc_jockey_revive/ModConfig.java index 2cf4404..a9cefcc 100644 --- a/src/main/java/semmiedev/disc_jockey_revive/ModConfig.java +++ b/src/main/java/semmiedev/disc_jockey_revive/ModConfig.java @@ -2,6 +2,7 @@ package semmiedev.disc_jockey_revive; import me.shedaniel.autoconfig.ConfigData; import me.shedaniel.autoconfig.annotation.ConfigEntry; +import net.minecraft.text.Text; import java.util.ArrayList; @@ -37,6 +38,29 @@ public class ModConfig implements ConfigData { } } + public enum ErrorHandlingMode { + STOP_PLAYBACK(Text.translatable(Main.MOD_ID+".config.error_handling.stop")), + PLAY_NEXT(Text.translatable(Main.MOD_ID+".config.error_handling.next")); + + private final Text displayText; + + ErrorHandlingMode(Text displayText) { + this.displayText = displayText; + } + + public Text getDisplayText() { + return displayText; + } + + @Override + public String toString() { + return displayText.getString(); + } + } + + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) + public ErrorHandlingMode errorHandlingMode = ErrorHandlingMode.STOP_PLAYBACK; + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) @ConfigEntry.Gui.Tooltip(count = 4) public ExpectedServerVersion expectedServerVersion = ExpectedServerVersion.All; diff --git a/src/main/java/semmiedev/disc_jockey_revive/SongPlayer.java b/src/main/java/semmiedev/disc_jockey_revive/SongPlayer.java index abb4ac0..b29f0d3 100644 --- a/src/main/java/semmiedev/disc_jockey_revive/SongPlayer.java +++ b/src/main/java/semmiedev/disc_jockey_revive/SongPlayer.java @@ -26,6 +26,7 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class SongPlayer implements ClientTickEvents.StartWorldTick { @@ -98,7 +99,7 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick { STOP_AFTER // 播完就停 } - private PlayMode playMode = PlayMode.STOP_AFTER; + public PlayMode playMode = PlayMode.STOP_AFTER; private boolean isRandomPlaying = false; private int randomIndex = -1; @@ -160,6 +161,27 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick { didSongReachEnd = false; // Change after running stop() if actually ended cleanly } + + public synchronized void playPreviousSong() { + if (!running || song == null) return; + + if (SongLoader.currentFolder == null || SongLoader.currentFolder.songs.isEmpty()) return; + + int currentIndex = SongLoader.currentFolder.songs.indexOf(song); + if (currentIndex == -1) return; + + if (playMode == PlayMode.RANDOM) { + int newIndex; + do { + newIndex = (int) (Math.random() * SongLoader.currentFolder.songs.size()); + } while (newIndex == currentIndex && SongLoader.currentFolder.songs.size() > 1); + start(SongLoader.currentFolder.songs.get(newIndex)); + } else { + int prevIndex = (currentIndex - 1 + SongLoader.currentFolder.songs.size()) % SongLoader.currentFolder.songs.size(); + start(SongLoader.currentFolder.songs.get(prevIndex)); + } + } + public synchronized void tickPlayback() { if (!running) { lastPlaybackTickAt = -1L; @@ -197,8 +219,14 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick { continue; } if (!canInteractWith(client.player, blockPos)) { - stop(); - client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".player.to_far").formatted(Formatting.RED)); + if (Main.config.errorHandlingMode == ModConfig.ErrorHandlingMode.PLAY_NEXT) { + playNextSong(); + } else { + stop(); + if (client.player != null) { + client.player.sendMessage(Text.translatable(Main.MOD_ID+".player.to_far").formatted(Formatting.RED), false); + } + } return; } Vec3d unit = Vec3d.ofCenter(blockPos, 0.5).subtract(client.player.getEyePos()).normalize(); @@ -256,24 +284,39 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick { } } - private void playNextSong() { - if (SongLoader.currentFolder == null || SongLoader.currentFolder.songs.isEmpty()) return; + public synchronized void playNextSong() { + if (!running || song == null) return; - int currentIndex = SongLoader.currentFolder.songs.indexOf(song); - if (currentIndex == -1) return; + if (SongLoader.currentFolder == null || + (SongLoader.currentFolder.songs.isEmpty() && SongLoader.SONGS.isEmpty())) { + return; + } + + List availableSongs = SongLoader.currentFolder != null ? + SongLoader.currentFolder.songs : SongLoader.SONGS; + + if (availableSongs.isEmpty()) return; if (playMode == PlayMode.RANDOM) { + // 随机播放 int newIndex; do { - newIndex = (int) (Math.random() * SongLoader.currentFolder.songs.size()); - } while (newIndex == currentIndex && SongLoader.currentFolder.songs.size() > 1); - start(SongLoader.currentFolder.songs.get(newIndex)); - } else if (playMode == PlayMode.LIST_LOOP) { - int nextIndex = (currentIndex + 1) % SongLoader.currentFolder.songs.size(); - start(SongLoader.currentFolder.songs.get(nextIndex)); + newIndex = (int)(Math.random() * availableSongs.size()); + } while (availableSongs.size() > 1 && + availableSongs.get(newIndex).fileName.equals(song.fileName)); + + start(availableSongs.get(newIndex)); + } + else { // 列表循环和单曲循环 + int currentIndex = availableSongs.indexOf(song); + if (currentIndex == -1) return; + + int nextIndex = (currentIndex + 1) % availableSongs.size(); + start(availableSongs.get(nextIndex)); } } + public synchronized void setPlayMode(PlayMode mode) { this.playMode = mode; this.loopSong = mode == PlayMode.SINGLE_LOOP; diff --git a/src/main/java/semmiedev/disc_jockey_revive/gui/screen/DiscJockeyScreen.java b/src/main/java/semmiedev/disc_jockey_revive/gui/screen/DiscJockeyScreen.java index 33a4127..ba00455 100644 --- a/src/main/java/semmiedev/disc_jockey_revive/gui/screen/DiscJockeyScreen.java +++ b/src/main/java/semmiedev/disc_jockey_revive/gui/screen/DiscJockeyScreen.java @@ -9,10 +9,7 @@ import net.minecraft.item.ItemStack; import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; -import semmiedev.disc_jockey_revive.Main; -import semmiedev.disc_jockey_revive.Note; -import semmiedev.disc_jockey_revive.Song; -import semmiedev.disc_jockey_revive.SongLoader; +import semmiedev.disc_jockey_revive.*; import semmiedev.disc_jockey_revive.gui.SongListWidget; import semmiedev.disc_jockey_revive.gui.hud.BlocksOverlay; @@ -34,26 +31,25 @@ public class DiscJockeyScreen extends Screen { PLAY_STOP = Text.translatable(Main.MOD_ID+".screen.play.stop"), PREVIEW = Text.translatable(Main.MOD_ID+".screen.preview"), PREVIEW_STOP = Text.translatable(Main.MOD_ID+".screen.preview.stop"), - DROP_HINT = Text.translatable(Main.MOD_ID+".screen.drop_hint").formatted(Formatting.GRAY) - ; + DROP_HINT = Text.translatable(Main.MOD_ID+".screen.drop_hint").formatted(Formatting.GRAY), - private SongListWidget songListWidget; - private ButtonWidget playButton, previewButton; - public boolean shouldFilter; - private String query = ""; - - private static final MutableText FOLDER_UP = Text.literal("↑"), CURRENT_FOLDER = Text.translatable(Main.MOD_ID+".screen.current_folder"), PLAY_MODE = Text.translatable(Main.MOD_ID+".screen.play_mode"), MODE_SINGLE = Text.translatable(Main.MOD_ID+".screen.mode_single"), MODE_LIST = Text.translatable(Main.MOD_ID+".screen.mode_list"), MODE_RANDOM = Text.translatable(Main.MOD_ID+".screen.mode_random"), - MODE_STOP = Text.translatable(Main.MOD_ID+".screen.mode_stop"); - - private static final MutableText + MODE_STOP = Text.translatable(Main.MOD_ID+".screen.mode_stop"), OPEN_FOLDER = Text.translatable(Main.MOD_ID+".screen.open_folder"), - RELOAD = Text.translatable(Main.MOD_ID+".screen.reload"); + RELOAD = Text.translatable(Main.MOD_ID+".screen.reload"), + NEXT_SONG = Text.translatable(Main.MOD_ID+".screen.next_song"), + PREV_SONG = Text.translatable(Main.MOD_ID+".screen.prev_song"); + + public SongListWidget songListWidget; + private ButtonWidget playButton, previewButton; + public boolean shouldFilter; + private String query = ""; + private ButtonWidget folderUpButton, playModeButton; public SongFolder currentFolder; @@ -209,6 +205,38 @@ public class DiscJockeyScreen extends Screen { client.setScreen(null); }).dimensions(width / 2 + 60, height - 31, 100, 20).build()); + // 下一首 + addDrawableChild(ButtonWidget.builder(NEXT_SONG, button -> { + SongPlayer player = Main.SONG_PLAYER; + if (player.running) { + player.playNextSong(); + } else { + SongListWidget.SongEntry entry = songListWidget.getSelectedSongOrNull(); + if (entry != null) { + List availableSongs = SongLoader.currentFolder != null ? + SongLoader.currentFolder.songs : SongLoader.SONGS; + + int currentIndex = availableSongs.indexOf(entry.song); + if (currentIndex != -1) { + int nextIndex; + if (player.playMode == PlayMode.RANDOM) { + // Get random song in menu too + do { + nextIndex = (int)(Math.random() * availableSongs.size()); + } while (availableSongs.size() > 1 && nextIndex == currentIndex); + } else { + nextIndex = (currentIndex + 1) % availableSongs.size(); + } + songListWidget.setSelected(availableSongs.get(nextIndex).entry); + } + } + } + }).dimensions(width / 2 + 60 + 110 + 5, height - 61, 100, 20).build()); + + + + + // 搜索框 TextFieldWidget searchBar = new TextFieldWidget(textRenderer, width / 2 - 50, height - 31, 100, 20, Text.translatable(Main.MOD_ID+".screen.search")); searchBar.setChangedListener(query -> { query = query.toLowerCase().replaceAll("\\s", ""); diff --git a/src/main/resources/assets/disc_jockey/lang/en_us.json b/src/main/resources/assets/disc_jockey/lang/en_us.json index c5bb33f..c582703 100644 --- a/src/main/resources/assets/disc_jockey/lang/en_us.json +++ b/src/main/resources/assets/disc_jockey/lang/en_us.json @@ -61,5 +61,15 @@ "disc_jockey_revive.screen.open_folder": "Open Folder", "disc_jockey_revive.screen.open_folder_failed": "Failed to open folder", "disc_jockey_revive.screen.reload": "Reload Songs", - "disc_jockey_revive.screen.reloading": "Reloading songs..." + "disc_jockey_revive.screen.reloading": "Reloading songs...", + "disc_jockey_revive.screen.next_song": "Next Song", + "disc_jockey_revive.screen.prev_song": "Previous Song", + "disc_jockey_revive.key_bind.next_song": "Next Song", + "disc_jockey_revive.key_bind.prev_song": "Previous Song", + "text.autoconfig.disc_jockey_revive.option.errorHandlingMode": "Error Handling", + "text.autoconfig.disc_jockey_revive.option.errorHandlingMode.@Tooltip": "What to do when errors occur", + "disc_jockey_revive.config.error_handling.stop": "Stop Playback", + "disc_jockey_revive.config.error_handling.next": "Play Next Song", + "disc_jockey_revive.key_bind.play_stop": "Play/Stop", + "disc_jockey_revive.key_bind.reload": "Reload" } \ No newline at end of file diff --git a/src/main/resources/assets/disc_jockey/lang/zh_cn.json b/src/main/resources/assets/disc_jockey/lang/zh_cn.json index 0a5cfb8..bce2efe 100644 --- a/src/main/resources/assets/disc_jockey/lang/zh_cn.json +++ b/src/main/resources/assets/disc_jockey/lang/zh_cn.json @@ -60,5 +60,15 @@ "disc_jockey_revive.screen.mode_stop": "播完停止","disc_jockey_revive.screen.open_folder": "打开文件夹", "disc_jockey_revive.screen.open_folder_failed": "无法打开文件夹", "disc_jockey_revive.screen.reload": "重新加载", - "disc_jockey_revive.screen.reloading": "正在重新加载..." + "disc_jockey_revive.screen.reloading": "正在重新加载...", + "disc_jockey_revive.screen.next_song": "下一首", + "disc_jockey_revive.screen.prev_song": "上一首", + "disc_jockey_revive.key_bind.next_song": "下一首", + "disc_jockey_revive.key_bind.prev_song": "上一首", + "text.autoconfig.disc_jockey_revive.option.errorHandlingMode": "错误处理", + "text.autoconfig.disc_jockey_revive.option.errorHandlingMode.@Tooltip": "播放错误时的处理方式", + "disc_jockey_revive.config.error_handling.stop": "停止播放", + "disc_jockey_revive.config.error_handling.next": "播放下一首", + "disc_jockey_revive.key_bind.play_stop": "播放/停止", + "disc_jockey_revive.key_bind.reload": "重新加载" } \ No newline at end of file