This commit is contained in:
BRanulf 2025-07-05 23:03:55 +08:00
parent 54c9cc4acb
commit 8cc8f6a427
10 changed files with 192 additions and 27 deletions

View File

@ -6,7 +6,7 @@ minecraft_version=1.21.4
yarn_mappings=1.21.4+build.8 yarn_mappings=1.21.4+build.8
loader_version=0.16.10 loader_version=0.16.10
# Mod Properties # Mod Properties
mod_version=1.14.514.032 mod_version=1.14.514.034
maven_group=semmiedev maven_group=semmiedev
archives_base_name=disc_jockey_revive archives_base_name=disc_jockey_revive
# Dependencies # Dependencies

View File

@ -21,6 +21,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import semmiedev.disc_jockey_revive.gui.hud.BlocksOverlay; import semmiedev.disc_jockey_revive.gui.hud.BlocksOverlay;
import semmiedev.disc_jockey_revive.gui.hud.PlaybackProgressOverlay;
import semmiedev.disc_jockey_revive.gui.screen.DiscJockeyScreen; import semmiedev.disc_jockey_revive.gui.screen.DiscJockeyScreen;
import java.io.File; import java.io.File;
@ -85,6 +86,6 @@ public class Main implements ClientModInitializer {
SONG_PLAYER.stop(); SONG_PLAYER.stop();
}); });
HudRenderCallback.EVENT.register(BlocksOverlay::render); HudRenderCallback.EVENT.register(new PlaybackProgressOverlay());
} }
} }

View File

@ -46,4 +46,7 @@ public class ModConfig implements ConfigData {
@ConfigEntry.Gui.Excluded @ConfigEntry.Gui.Excluded
public ArrayList<String> favorites = new ArrayList<>(); public ArrayList<String> favorites = new ArrayList<>();
@ConfigEntry.Gui.Tooltip(count = 1)
public boolean showHudProgressBar = true;
} }

View File

@ -79,7 +79,7 @@ public class SongLoader {
song.folder = songFolder; song.folder = songFolder;
} }
} catch (Exception exception) { } catch (Exception exception) {
Main.LOGGER.error("Unable to read or parse song {}", file.getName(), exception); Main.LOGGER.error("无法读取或解析歌曲 {}", file.getName(), exception);
} }
} }
} }

View File

@ -215,7 +215,7 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
client.player.networkHandler.sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.START_DESTROY_BLOCK, blockPos, Direction.UP, 0)); client.player.networkHandler.sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.START_DESTROY_BLOCK, blockPos, Direction.UP, 0));
last100MsSpanEstimatedPackets++; last100MsSpanEstimatedPackets++;
}else if(last100MsSpanEstimatedPackets >= last100MsStopPacketsAfter) { }else if(last100MsSpanEstimatedPackets >= last100MsStopPacketsAfter) {
Main.LOGGER.info("Stopping all packets for a bit!"); Main.LOGGER.info("短暂暂停所有数据包!");
stopPacketsUntil = Math.max(stopPacketsUntil, now + 250); stopPacketsUntil = Math.max(stopPacketsUntil, now + 250);
reducePacketsUntil = Math.max(reducePacketsUntil, now + 10000); reducePacketsUntil = Math.max(reducePacketsUntil, now + 10000);
} }
@ -256,29 +256,62 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
} }
} }
private void playNextSong() {
public synchronized void setPlayMode(PlayMode mode) {
this.playMode = mode;
this.loopSong = mode == PlayMode.SINGLE_LOOP;
}
// 获取当前播放进度 (0.0 - 1.0)
public synchronized float getProgress() {
if (song == null || !running) return 0;
return Math.min(1.0f, (float) (tick / song.length));
}
// 获取格式化时间字符串
public synchronized String getFormattedTime() {
if (song == null) return "00:00 / 00:00";
double totalSeconds = song.getLengthInSeconds();
double currentSeconds = song.ticksToMilliseconds(tick) / 1000.0;
return formatTime((int) currentSeconds) + " / " + formatTime((int) totalSeconds);
}
// 格式化时间为 mm:ss
private String formatTime(int seconds) {
int min = seconds / 60;
int sec = seconds % 60;
return String.format("%02d:%02d", min, sec);
}
// 播放上一首
public synchronized void playPreviousSong() {
if (SongLoader.currentFolder == null || SongLoader.currentFolder.songs.isEmpty()) return; if (SongLoader.currentFolder == null || SongLoader.currentFolder.songs.isEmpty()) return;
int currentIndex = SongLoader.currentFolder.songs.indexOf(song); int currentIndex = SongLoader.currentFolder.songs.indexOf(song);
if (currentIndex == -1) return; if (currentIndex == -1) return;
int prevIndex = (currentIndex - 1 + SongLoader.currentFolder.songs.size()) % SongLoader.currentFolder.songs.size();
start(SongLoader.currentFolder.songs.get(prevIndex));
}
// 播放下一首
public synchronized void playNextSong() {
if (playMode == PlayMode.RANDOM) { if (playMode == PlayMode.RANDOM) {
int newIndex; randomIndex = getRandomSongIndex();
do { start(SongLoader.currentFolder.songs.get(randomIndex));
newIndex = (int) (Math.random() * SongLoader.currentFolder.songs.size()); } else {
} while (newIndex == currentIndex && SongLoader.currentFolder.songs.size() > 1); if (SongLoader.currentFolder == null || SongLoader.currentFolder.songs.isEmpty()) return;
start(SongLoader.currentFolder.songs.get(newIndex));
} else if (playMode == PlayMode.LIST_LOOP) { int currentIndex = SongLoader.currentFolder.songs.indexOf(song);
if (currentIndex == -1) return;
int nextIndex = (currentIndex + 1) % SongLoader.currentFolder.songs.size(); int nextIndex = (currentIndex + 1) % SongLoader.currentFolder.songs.size();
start(SongLoader.currentFolder.songs.get(nextIndex)); start(SongLoader.currentFolder.songs.get(nextIndex));
} }
} }
public synchronized void setPlayMode(PlayMode mode) {
this.playMode = mode;
this.loopSong = mode == PlayMode.SINGLE_LOOP;
}
// this is the original authors comment, i dont wanna delete it // this is the original authors comment, i dont wanna delete it
// TODO: 6/2/2022 Play note blocks every song tick, instead of every tick. That way the song will sound better // TODO: 6/2/2022 Play note blocks every song tick, instead of every tick. That way the song will sound better
// 11/1/2023 Playback now done in separate thread. Not ideal but better especially when FPS are low. // 11/1/2023 Playback now done in separate thread. Not ideal but better especially when FPS are low.

View File

@ -0,0 +1,23 @@
package semmiedev.disc_jockey_revive.gui;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
public class ProgressBarRenderer {
private static final int BACKGROUND_COLOR = 0x80808080;
private static final int PROGRESS_COLOR = 0x8000FF00; // 绿色
private static final int TEXT_COLOR = 0xFFFFFF;
public void renderProgressBar(DrawContext context, int x, int y, int width, int height, float progress, String timeText) {
context.fill(x, y, x + width, y + height, BACKGROUND_COLOR);
int progressWidth = (int) (width * progress);
context.fill(x, y, x + progressWidth, y + height, PROGRESS_COLOR);
MinecraftClient client = MinecraftClient.getInstance();
int textX = x + (width - client.textRenderer.getWidth(timeText)) / 2;
int textY = y - client.textRenderer.fontHeight - 2;
context.drawTextWithShadow(client.textRenderer, timeText, textX, textY, TEXT_COLOR);
}
}

View File

@ -0,0 +1,46 @@
package semmiedev.disc_jockey_revive.gui.hud;
import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.render.RenderTickCounter;
import semmiedev.disc_jockey_revive.Main;
import semmiedev.disc_jockey_revive.gui.ProgressBarRenderer;
import net.minecraft.client.gui.screen.ConfirmScreen;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.TextFieldWidget;
import static net.minecraft.client.toast.TutorialToast.PROGRESS_BAR_WIDTH;
public class PlaybackProgressOverlay implements HudRenderCallback {
private final ProgressBarRenderer progressBarRenderer = new ProgressBarRenderer();
@Override
public void onHudRender(DrawContext context, RenderTickCounter tickCounter) {
if (!Main.config.showHudProgressBar) return;
MinecraftClient client = MinecraftClient.getInstance();
boolean isInGame = client.currentScreen == null;
if (Main.SONG_PLAYER.running && Main.SONG_PLAYER.song != null && isInGame) {
int screenWidth = context.getScaledWindowWidth();
int screenHeight = context.getScaledWindowHeight();
int barX = screenWidth / 2 - PROGRESS_BAR_WIDTH / 2;
int barY = screenHeight - 50;
progressBarRenderer.renderProgressBar(
context,
barX,
barY,
PROGRESS_BAR_WIDTH,
5,
Main.SONG_PLAYER.getProgress(),
Main.SONG_PLAYER.getFormattedTime()
);
}
}
}

View File

@ -13,6 +13,7 @@ import semmiedev.disc_jockey_revive.Main;
import semmiedev.disc_jockey_revive.Note; import semmiedev.disc_jockey_revive.Note;
import semmiedev.disc_jockey_revive.Song; import semmiedev.disc_jockey_revive.Song;
import semmiedev.disc_jockey_revive.SongLoader; import semmiedev.disc_jockey_revive.SongLoader;
import semmiedev.disc_jockey_revive.gui.ProgressBarRenderer;
import semmiedev.disc_jockey_revive.gui.SongListWidget; import semmiedev.disc_jockey_revive.gui.SongListWidget;
import semmiedev.disc_jockey_revive.gui.hud.BlocksOverlay; import semmiedev.disc_jockey_revive.gui.hud.BlocksOverlay;
@ -27,6 +28,8 @@ import java.util.stream.Collectors;
import semmiedev.disc_jockey_revive.SongLoader.SongFolder; import semmiedev.disc_jockey_revive.SongLoader.SongFolder;
import semmiedev.disc_jockey_revive.SongPlayer.PlayMode; import semmiedev.disc_jockey_revive.SongPlayer.PlayMode;
import static net.minecraft.client.toast.TutorialToast.PROGRESS_BAR_WIDTH;
public class DiscJockeyScreen extends Screen { public class DiscJockeyScreen extends Screen {
private static final MutableText private static final MutableText
SELECT_SONG = Text.translatable(Main.MOD_ID+".screen.select_song"), SELECT_SONG = Text.translatable(Main.MOD_ID+".screen.select_song"),
@ -58,15 +61,23 @@ public class DiscJockeyScreen extends Screen {
private ButtonWidget folderUpButton, playModeButton; private ButtonWidget folderUpButton, playModeButton;
public SongFolder currentFolder; public SongFolder currentFolder;
private PlayMode currentPlayMode = PlayMode.STOP_AFTER; private PlayMode currentPlayMode = PlayMode.STOP_AFTER;
private int progressBarWidth = 200; // 进度条宽度
private int progressBarHeight = 5; // 进度条高度
private int progressBarYOffset = 5; // 进度条Y偏移
private ProgressBarRenderer progressBarRenderer;
public DiscJockeyScreen() { public DiscJockeyScreen() {
super(Main.NAME); super(Main.NAME);
this.progressBarRenderer = new ProgressBarRenderer();
} }
@Override @Override
protected void init() { protected void init() {
shouldFilter = true; shouldFilter = true;
songListWidget = new SongListWidget(client, width, height - 64 - 32, 32, 20, this); songListWidget = new SongListWidget(client, width, height - 100, 32, 20, this);
boolean isLargeScreen = width > 900;
int progressBarY = height - 90;
// 恢复播放模式 // 恢复播放模式
currentPlayMode = Main.config.playMode; currentPlayMode = Main.config.playMode;
@ -111,6 +122,20 @@ public class DiscJockeyScreen extends Screen {
}).dimensions(width - 120, 10, 100, 20).build(); }).dimensions(width - 120, 10, 100, 20).build();
addDrawableChild(playModeButton); addDrawableChild(playModeButton);
int buttonY;
if (isLargeScreen){
buttonY = height - 30;
} else {
buttonY = height - 60;
}
int centerX = width / 2;
// 上一首
addDrawableChild(ButtonWidget.builder(Text.literal("◀◀◀"), button -> {
Main.SONG_PLAYER.playPreviousSong();
}).dimensions(centerX - 110, buttonY, 40, 20).build());
// 播放暂停
playButton = ButtonWidget.builder(PLAY, button -> { playButton = ButtonWidget.builder(PLAY, button -> {
if (Main.SONG_PLAYER.running) { if (Main.SONG_PLAYER.running) {
Main.SONG_PLAYER.stop(); Main.SONG_PLAYER.stop();
@ -118,12 +143,18 @@ public class DiscJockeyScreen extends Screen {
SongListWidget.SongEntry entry = songListWidget.getSelectedSongOrNull(); SongListWidget.SongEntry entry = songListWidget.getSelectedSongOrNull();
if (entry != null) { if (entry != null) {
Main.SONG_PLAYER.start(entry.song); Main.SONG_PLAYER.start(entry.song);
client.setScreen(null); // client.setScreen(null);
} }
} }
}).dimensions(width / 2 - 160, height - 61, 100, 20).build(); }).dimensions(centerX - 50, buttonY, 100, 20).build();
addDrawableChild(playButton); addDrawableChild(playButton);
// 下一首
addDrawableChild(ButtonWidget.builder(Text.literal("▶▶▶"), button -> {
Main.SONG_PLAYER.playNextSong();
}).dimensions(centerX + 60, buttonY, 40, 20).build());
// 预览
previewButton = ButtonWidget.builder(PREVIEW, button -> { previewButton = ButtonWidget.builder(PREVIEW, button -> {
if (Main.PREVIEWER.running) { if (Main.PREVIEWER.running) {
Main.PREVIEWER.stop(); Main.PREVIEWER.stop();
@ -131,9 +162,12 @@ public class DiscJockeyScreen extends Screen {
SongListWidget.SongEntry entry = songListWidget.getSelectedSongOrNull(); SongListWidget.SongEntry entry = songListWidget.getSelectedSongOrNull();
if (entry != null) Main.PREVIEWER.start(entry.song); if (entry != null) Main.PREVIEWER.start(entry.song);
} }
}).dimensions(width / 2 - 50, height - 61, 100, 20).build(); }).dimensions(centerX + 110, buttonY, 100, 20).build();
addDrawableChild(previewButton); addDrawableChild(previewButton);
int bottomY = height - 30;
addDrawableChild(ButtonWidget.builder(Text.translatable(Main.MOD_ID+".screen.blocks"), button -> { addDrawableChild(ButtonWidget.builder(Text.translatable(Main.MOD_ID+".screen.blocks"), button -> {
// TODO: 6/2/2022 Add an auto build mode // TODO: 6/2/2022 Add an auto build mode
if (BlocksOverlay.itemStacks == null) { if (BlocksOverlay.itemStacks == null) {
@ -171,7 +205,7 @@ public class DiscJockeyScreen extends Screen {
BlocksOverlay.itemStacks = null; BlocksOverlay.itemStacks = null;
client.setScreen(null); client.setScreen(null);
} }
}).dimensions(width / 2 + 60, height - 61, 100, 20).build()); }).dimensions(width - 110, height - 31, 100, 20).build());
// 打开文件夹 // 打开文件夹
addDrawableChild(ButtonWidget.builder(OPEN_FOLDER, button -> { addDrawableChild(ButtonWidget.builder(OPEN_FOLDER, button -> {
@ -200,16 +234,16 @@ public class DiscJockeyScreen extends Screen {
Text.translatable(Main.MOD_ID+".screen.open_folder_failed") Text.translatable(Main.MOD_ID+".screen.open_folder_failed")
.formatted(Formatting.RED)); .formatted(Formatting.RED));
} }
}).dimensions(width / 2 - 160, height - 31, 100, 20).build()); }).dimensions(10, bottomY, 100, 20).build());
// 重新加载 // 重新加载
addDrawableChild(ButtonWidget.builder(RELOAD, button -> { addDrawableChild(ButtonWidget.builder(RELOAD, button -> {
SongLoader.loadSongs(); SongLoader.loadSongs();
client.setScreen(null); client.setScreen(null);
}).dimensions(width / 2 + 60, height - 31, 100, 20).build()); }).dimensions(120, bottomY, 100, 20).build());
TextFieldWidget searchBar = new TextFieldWidget(textRenderer, width / 2 - 50, height - 31, 100, 20, Text.translatable(Main.MOD_ID+".screen.search")); TextFieldWidget searchBar = new TextFieldWidget(textRenderer, 230, height - 31, 100, 20, Text.translatable(Main.MOD_ID+".screen.search"));
searchBar.setChangedListener(query -> { searchBar.setChangedListener(query -> {
query = query.toLowerCase().replaceAll("\\s", ""); query = query.toLowerCase().replaceAll("\\s", "");
if (this.query.equals(query)) return; if (this.query.equals(query)) return;
@ -218,6 +252,8 @@ public class DiscJockeyScreen extends Screen {
}); });
addDrawableChild(searchBar); addDrawableChild(searchBar);
// Main.LOGGER.info("播放界面初始化完成!");
// TODO: 6/2/2022 Add a reload button // TODO: 6/2/2022 Add a reload button
} }
@ -232,6 +268,25 @@ public class DiscJockeyScreen extends Screen {
String folderName = currentFolder == null ? "/" : currentFolder.name; String folderName = currentFolder == null ? "/" : currentFolder.name;
context.drawTextWithShadow(textRenderer, CURRENT_FOLDER.getString() + ": " + folderName, 35, 15, 0xFFFFFF); context.drawTextWithShadow(textRenderer, CURRENT_FOLDER.getString() + ": " + folderName, 35, 15, 0xFFFFFF);
context.drawTextWithShadow(textRenderer, PLAY_MODE.getString() + ":", width - 220, 15, 0xFFFFFF); context.drawTextWithShadow(textRenderer, PLAY_MODE.getString() + ":", width - 220, 15, 0xFFFFFF);
int screenWidth = context.getScaledWindowWidth();
// 进度条
if (Main.SONG_PLAYER.running && Main.SONG_PLAYER.song != null) {
int progressBarX = 10;
int progressBarY = height - 50; // 按钮上方
int barWidth = screenWidth - 20;
progressBarRenderer.renderProgressBar(
context,
progressBarX,
progressBarY,
barWidth,
5,
Main.SONG_PLAYER.getProgress(),
Main.SONG_PLAYER.getFormattedTime()
);
}
} }
@Override @Override
@ -354,7 +409,7 @@ public class DiscJockeyScreen extends Screen {
SongLoader.SONGS.add(song); SongLoader.SONGS.add(song);
} }
} catch (IOException exception) { } catch (IOException exception) {
Main.LOGGER.warn("Failed to copy song file from {} to {}", path, Main.songsFolder.toPath(), exception); Main.LOGGER.warn("无法将歌曲文件从 {} 复制到 {} ", path, Main.songsFolder.toPath(), exception);
} }
}); });

View File

@ -61,5 +61,7 @@
"disc_jockey_revive.screen.open_folder": "Open Folder", "disc_jockey_revive.screen.open_folder": "Open Folder",
"disc_jockey_revive.screen.open_folder_failed": "Failed to open folder", "disc_jockey_revive.screen.open_folder_failed": "Failed to open folder",
"disc_jockey_revive.screen.reload": "Reload Songs", "disc_jockey_revive.screen.reload": "Reload Songs",
"disc_jockey_revive.screen.reloading": "Reloading songs..." "disc_jockey_revive.screen.reloading": "Reloading songs...",
"text.autoconfig.disc_jockey_revive.option.showHudProgressBar": "Show HUD progress bar",
"text.autoconfig.disc_jockey_revive.option.showHudProgressBar.@Tooltip": "Show song playback progress bar in game。"
} }

View File

@ -60,5 +60,7 @@
"disc_jockey_revive.screen.mode_stop": "播完停止","disc_jockey_revive.screen.open_folder": "打开文件夹", "disc_jockey_revive.screen.mode_stop": "播完停止","disc_jockey_revive.screen.open_folder": "打开文件夹",
"disc_jockey_revive.screen.open_folder_failed": "无法打开文件夹", "disc_jockey_revive.screen.open_folder_failed": "无法打开文件夹",
"disc_jockey_revive.screen.reload": "重新加载", "disc_jockey_revive.screen.reload": "重新加载",
"disc_jockey_revive.screen.reloading": "正在重新加载..." "disc_jockey_revive.screen.reloading": "正在重新加载...",
"text.autoconfig.disc_jockey_revive.option.showHudProgressBar": "显示HUD进度条",
"text.autoconfig.disc_jockey_revive.option.showHudProgressBar.@Tooltip": "在游戏界面显示歌曲播放进度条"
} }