diff --git a/gradle.properties b/gradle.properties index 23c4965..f0c19d0 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.028 +mod_version=1.14.514.032 maven_group=semmiedev archives_base_name=disc_jockey_revive # Dependencies diff --git a/src/main/java/semmiedev/disc_jockey_revive/KeyMappingManager.java b/src/main/java/semmiedev/disc_jockey_revive/KeyMappingManager.java deleted file mode 100644 index 60e9c64..0000000 --- a/src/main/java/semmiedev/disc_jockey_revive/KeyMappingManager.java +++ /dev/null @@ -1,172 +0,0 @@ -package semmiedev.disc_jockey_revive; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.util.InputUtil; -import net.minecraft.block.enums.NoteBlockInstrument; -import net.minecraft.text.Text; - -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.lang.reflect.Type; -import java.util.HashMap; -import java.util.Map; - -public class KeyMappingManager { - private static final File MAPPINGS_FILE = new File(FabricLoader.getInstance().getConfigDir().toFile(), "disc_jockey" + "/key_mappings.json"); - private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); - private static final Type MAPPING_TYPE = new TypeToken>() {}.getType(); - - private Map mappings = new HashMap<>(); - - private static class NoteData { - String instrument; - byte note; - - public NoteData(Note note) { - this.instrument = note.instrument().name(); - this.note = note.note(); - } - - public Note toNote() { - try { - NoteBlockInstrument instrumentEnum = NoteBlockInstrument.valueOf(instrument); - return new Note(instrumentEnum, note); - } catch (IllegalArgumentException e) { - Main.LOGGER.error("键位映射中出现未知乐器 '{}' ", instrument); - return null; - } - } - } - - public KeyMappingManager() { - loadMappings(); - } - - public void loadMappings() { - if (!MAPPINGS_FILE.exists()) { - Main.LOGGER.info("未找到键映射文件,正在创建默认的类似于 FL Studio 的映射。"); - mappings.clear(); - - NoteBlockInstrument dirt = NoteBlockInstrument.HARP; - NoteBlockInstrument wood = NoteBlockInstrument.BASS; - NoteBlockInstrument gold = NoteBlockInstrument.BELL; - - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_Z, 0), new Note(wood, (byte) 6)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_S, 0), new Note(wood, (byte) 7)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_X, 0), new Note(wood, (byte) 8)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_D, 0), new Note(wood, (byte) 9)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_C, 0), new Note(wood, (byte) 10)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_V, 0), new Note(wood, (byte) 11)); - - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_G, 0), new Note(dirt, (byte) 0)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_B, 0), new Note(dirt, (byte) 1)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_H, 0), new Note(dirt, (byte) 2)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_N, 0), new Note(dirt, (byte) 3)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_J, 0), new Note(dirt, (byte) 4)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_M, 0), new Note(dirt, (byte) 5)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_COMMA, 0), new Note(dirt, (byte) 6)); - - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_Q, 0), new Note(dirt, (byte) 6)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_2, 0), new Note(dirt, (byte) 7)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_W, 0), new Note(dirt, (byte) 8)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_3, 0), new Note(dirt, (byte) 9)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_E, 0), new Note(dirt, (byte) 10)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_R, 0), new Note(dirt, (byte) 11)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_5, 0), new Note(dirt, (byte) 12)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_T, 0), new Note(dirt, (byte) 13)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_6, 0), new Note(dirt, (byte) 14)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_Y, 0), new Note(dirt, (byte) 15)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_7, 0), new Note(dirt, (byte) 16)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_U, 0), new Note(dirt, (byte) 17)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_I, 0), new Note(dirt, (byte) 18)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_9, 0), new Note(dirt, (byte) 19)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_O, 0), new Note(dirt, (byte) 20)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_0, 0), new Note(dirt, (byte) 21)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_P, 0), new Note(dirt, (byte) 22)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_LEFT_BRACKET, 0), new Note(dirt, (byte) 23)); - mappings.put(InputUtil.fromKeyCode(InputUtil.GLFW_KEY_EQUAL, 0), new Note(dirt, (byte) 24)); - - - saveMappings(); - return; - } - - try (FileReader reader = new FileReader(MAPPINGS_FILE)) { - HashMap loadedData = GSON.fromJson(reader, MAPPING_TYPE); - mappings.clear(); - if (loadedData != null) { - for (Map.Entry entry : loadedData.entrySet()) { - InputUtil.Key key = InputUtil.fromTranslationKey(entry.getKey()); - Note note = entry.getValue().toNote(); - if (key != InputUtil.UNKNOWN_KEY && note != null) { - mappings.put(key, note); - } - } - } - Main.LOGGER.info("已加载按键映射。"); - } catch (IOException e) { - Main.LOGGER.error("加载按键映射失败。", e); - } - } - - - - public void saveMappings() { - MAPPINGS_FILE.getParentFile().mkdirs(); - try (FileWriter writer = new FileWriter(MAPPINGS_FILE)) { - HashMap dataToSave = new HashMap<>(); - for (Map.Entry entry : mappings.entrySet()) { - dataToSave.put(entry.getKey().getTranslationKey(), new NoteData(entry.getValue())); - } - GSON.toJson(dataToSave, writer); - Main.LOGGER.info("已保存按键映射。"); - } catch (IOException e) { - Main.LOGGER.error("保存按键映射失败。", e); - } - } - - public Note getNoteForKey(InputUtil.Key key) { - return mappings.get(key); - } - - public void setMapping(InputUtil.Key key, Note note) { - mappings.put(key, note); - } - - public void removeMapping(InputUtil.Key key) { - mappings.remove(key); - } - - public Map getMappings() { - return new HashMap<>(mappings); - } - - public static String getNoteDisplayName(Note note) { - if (note == null) return "未设置"; - - String instrumentTranslationKey = "block.minecraft.note_block.instrument." + note.instrument().asString(); - String instrumentName = Text.translatable(instrumentTranslationKey).getString(); - - int pitch = note.note(); - String[] noteNames = {"F#", "G", "G#", "A", "A#", "B", "C", "C#", "D", "D#", "E", "F"}; - - int octave; - if (pitch >= 19) { - octave = 1; - } else if (pitch >= 7) { - octave = 0; - } else { - octave = -1; - } - - int noteIndexInArray = (pitch - 7 + 12 * 2) % 12; - String noteName = noteNames[noteIndexInArray]; - - return instrumentName + " " + noteName + "(" + octave + ")"; - } -} diff --git a/src/main/java/semmiedev/disc_jockey_revive/Main.java b/src/main/java/semmiedev/disc_jockey_revive/Main.java index 27956ee..9fcbd6e 100644 --- a/src/main/java/semmiedev/disc_jockey_revive/Main.java +++ b/src/main/java/semmiedev/disc_jockey_revive/Main.java @@ -5,7 +5,6 @@ import me.shedaniel.autoconfig.ConfigHolder; import me.shedaniel.autoconfig.serializer.JanksonConfigSerializer; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents; @@ -23,7 +22,6 @@ import org.apache.logging.log4j.Logger; import org.lwjgl.glfw.GLFW; import semmiedev.disc_jockey_revive.gui.hud.BlocksOverlay; import semmiedev.disc_jockey_revive.gui.screen.DiscJockeyScreen; -import semmiedev.disc_jockey_revive.gui.screen.LiveDjScreen; import java.io.File; import java.util.ArrayList; @@ -35,15 +33,11 @@ public class Main implements ClientModInitializer { public static final ArrayList TICK_LISTENERS = new ArrayList<>(); public static final Previewer PREVIEWER = new Previewer(); public static final SongPlayer SONG_PLAYER = new SongPlayer(); - public static KeyMappingManager keyMappingManager; public static File songsFolder; public static ModConfig config; public static ConfigHolder configHolder; - private static KeyBinding openLiveDjScreenKeyBind; - - @Override public void onInitializeClient() { configHolder = AutoConfig.register(ModConfig.class, JanksonConfigSerializer::new); @@ -52,14 +46,10 @@ public class Main implements ClientModInitializer { songsFolder = new File(FabricLoader.getInstance().getConfigDir()+File.separator+"disc_jockey"+File.separator+"songs"); if (!songsFolder.isDirectory()) songsFolder.mkdirs(); - keyMappingManager = new KeyMappingManager(); - 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)); - openLiveDjScreenKeyBind = KeyBindingHelper.registerKeyBinding(new KeyBinding(MOD_ID+".key_bind.open_live_dj_screen", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_UNKNOWN, "key.category."+MOD_ID)); - ClientTickEvents.START_CLIENT_TICK.register(new ClientTickEvents.StartTick() { private ClientWorld prevWorld; @@ -79,22 +69,11 @@ public class Main implements ClientModInitializer { client.setScreen(new DiscJockeyScreen()); } } - - if (openLiveDjScreenKeyBind.wasPressed()) { - if (client.currentScreen == null || client.currentScreen instanceof DiscJockeyScreen) { - client.setScreen(new LiveDjScreen()); - } - } } }); ClientTickEvents.START_WORLD_TICK.register(world -> { - ArrayList listenersCopy = new ArrayList<>(TICK_LISTENERS); - for (ClientTickEvents.StartWorldTick listener : listenersCopy) { - if (TICK_LISTENERS.contains(listener)) { - listener.onStartTick(world); - } - } + for (ClientTickEvents.StartWorldTick listener : TICK_LISTENERS) listener.onStartTick(world); }); ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> { @@ -107,9 +86,5 @@ public class Main implements ClientModInitializer { }); HudRenderCallback.EVENT.register(BlocksOverlay::render); - - ClientLifecycleEvents.CLIENT_STOPPING.register(client -> { - keyMappingManager.saveMappings(); - }); } } diff --git a/src/main/java/semmiedev/disc_jockey_revive/SongLoader.java b/src/main/java/semmiedev/disc_jockey_revive/SongLoader.java index 087073f..f7aff09 100644 --- a/src/main/java/semmiedev/disc_jockey_revive/SongLoader.java +++ b/src/main/java/semmiedev/disc_jockey_revive/SongLoader.java @@ -79,7 +79,7 @@ public class SongLoader { song.folder = songFolder; } } catch (Exception exception) { - Main.LOGGER.error("无法读取或解析歌曲 {}", file.getName(), exception); + Main.LOGGER.error("Unable to read or parse song {}", file.getName(), exception); } } } diff --git a/src/main/java/semmiedev/disc_jockey_revive/SongPlayer.java b/src/main/java/semmiedev/disc_jockey_revive/SongPlayer.java index 2f80b89..abb4ac0 100644 --- a/src/main/java/semmiedev/disc_jockey_revive/SongPlayer.java +++ b/src/main/java/semmiedev/disc_jockey_revive/SongPlayer.java @@ -33,9 +33,9 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick { public boolean running; public Song song; - public int index; - public double tick; - public HashMap> noteBlocks = null; + private int index; + private double tick; // Aka song position + private HashMap> noteBlocks = null; public boolean tuned; private long lastPlaybackTickAt = -1L; @@ -63,10 +63,7 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick { private HashMap> notePredictions = new HashMap<>(); public boolean didSongReachEnd = false; public boolean loopSong = false; - private long pausePlaybackUntil = -1L; - - private boolean manualTuningRequested = false; - + private long pausePlaybackUntil = -1L; // Set after tuning, if configured public SongPlayer() { Main.TICK_LISTENERS.add(this); @@ -88,7 +85,7 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick { }catch (Exception ex) { ex.printStackTrace(); } - MinecraftClient.getInstance().executeSync(this::tickPlayback); + tickPlayback(); } }); this.playbackThread.start(); @@ -101,7 +98,7 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick { STOP_AFTER // 播完就停 } - public PlayMode playMode = PlayMode.STOP_AFTER; + private PlayMode playMode = PlayMode.STOP_AFTER; private boolean isRandomPlaying = false; private int randomIndex = -1; @@ -117,16 +114,17 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick { } if (running) stop(); this.song = song; - //Main.LOGGER.info("Song length: " + song.length + " and tempo " + song.tempo); - //Main.TICK_LISTENERS.add(this); this.loopSong = playMode == PlayMode.SINGLE_LOOP; if(this.playbackThread == null) startPlaybackThread(); running = true; - index = 0; - tick = 0; - startTuning(); - lastPlaybackTickAt = System.currentTimeMillis(); + last100MsSpanAt = System.currentTimeMillis(); + last100MsSpanEstimatedPackets = 0; + reducePacketsUntil = -1L; + stopPacketsUntil = -1L; + lastLookSentAt = -1L; + lastSwingSentAt = -1L; + missingInstrumentBlocks.clear(); didSongReachEnd = false; isRandomPlaying = playMode == PlayMode.RANDOM; if (isRandomPlaying) { @@ -152,184 +150,108 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick { notePredictions.clear(); tuned = false; tuneInitialUntunedBlocks = -1; - manualTuningRequested = false; - lastPlaybackTickAt = -1L; - // last100MsSpanAt = -1L; - // last100MsSpanEstimatedPackets = 0; - // reducePacketsUntil = -1L; - // stopPacketsUntil = -1L; - // lastLookSentAt = -1L; - // lastSwingSentAt = -1L; + last100MsSpanAt = -1L; + last100MsSpanEstimatedPackets = 0; + reducePacketsUntil = -1L; + stopPacketsUntil = -1L; + lastLookSentAt = -1L; + lastSwingSentAt = -1L; didSongReachEnd = false; // Change after running stop() if actually ended cleanly } - public synchronized void startTuning() { - if (tuned && noteBlocks != null) { - MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".player.retuning").formatted(Formatting.YELLOW)); - } else { - MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".player.tuning_started").formatted(Formatting.YELLOW)); - } - noteBlocks = null; - notePredictions.clear(); - tuned = false; - tuneInitialUntunedBlocks = -1; - manualTuningRequested = true; - } - - public boolean isTuningEnabled() { - return running || manualTuningRequested; - } - - - public boolean playNoteBlock(Note note) { - MinecraftClient client = MinecraftClient.getInstance(); - ClientWorld world = client.world; - ClientPlayerEntity player = client.player; - GameMode gameMode = client.interactionManager == null ? null : client.interactionManager.getCurrentGameMode(); - - if (world == null || player == null || gameMode == null || !gameMode.isSurvivalLike()) { - return false; - } - - if (noteBlocks == null || !tuned) { - return false; - } - - @Nullable BlockPos blockPos = noteBlocks.get(note.instrument()).get(note.note()); - if(blockPos == null) { - return false; - } - - if (!canInteractWith(player, blockPos)) { - return false; - } - - return sendNotePacket(blockPos, note); - } - - - private boolean sendNotePacket(BlockPos blockPos, Note note) { - MinecraftClient client = MinecraftClient.getInstance(); - ClientPlayerEntity player = client.player; - long now = System.currentTimeMillis(); - - // Update packet rate tracking - if(last100MsSpanAt != -1L && now - last100MsSpanAt >= 100) { - last100MsSpanEstimatedPackets = 0; - last100MsSpanAt = now; - }else if (last100MsSpanAt == -1L) { - last100MsSpanAt = now; - last100MsSpanEstimatedPackets = 0; - } - - if (stopPacketsUntil != -1L && stopPacketsUntil >= now) { - return false; - } - if (reducePacketsUntil != -1L && reducePacketsUntil >= now && last100MsSpanEstimatedPackets >= last100MsReducePacketsAfter) { - return false; - } - - if(lastInteractAt != -1L) { - availableInteracts += ((System.currentTimeMillis() - lastInteractAt) / (310.0f / 8.0f)); - availableInteracts = Math.min(8f, Math.max(0f, availableInteracts)); - }else { - availableInteracts = 8f; - lastInteractAt = System.currentTimeMillis(); - } - - if (availableInteracts < 1f) { - return false; - } - - - Vec3d unit = Vec3d.ofCenter(blockPos, 0.5).subtract(player.getEyePos()).normalize(); - boolean packetsSent = false; - - if((lastLookSentAt == -1L || now - lastLookSentAt >= 50) && last100MsSpanEstimatedPackets < last100MsReducePacketsAfter) { - client.getNetworkHandler().sendPacket(new PlayerMoveC2SPacket.LookAndOnGround(MathHelper.wrapDegrees((float) (MathHelper.atan2(unit.z, unit.x) * 57.2957763671875) - 90.0f), MathHelper.wrapDegrees((float) (-(MathHelper.atan2(unit.y, Math.sqrt(unit.x * unit.x + unit.z * unit.z)) * 57.2957763671875))), true, false)); - last100MsSpanEstimatedPackets++; - lastLookSentAt = now; - packetsSent = true; - } else if (last100MsSpanEstimatedPackets >= last100MsReducePacketsAfter) { - reducePacketsUntil = Math.max(reducePacketsUntil, now + 500); - } - - - if(last100MsSpanEstimatedPackets < last100MsStopPacketsAfter) { - client.player.networkHandler.sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.START_DESTROY_BLOCK, blockPos, Direction.UP, 0)); - last100MsSpanEstimatedPackets++; - client.player.networkHandler.sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.ABORT_DESTROY_BLOCK, blockPos, Direction.UP, 0)); - last100MsSpanEstimatedPackets++; - lastInteractAt = now; - availableInteracts -= 1f; - packetsSent = true; - } else { - Main.LOGGER.info("短时间内暂停所有数据包!"); - stopPacketsUntil = Math.max(stopPacketsUntil, now + 250); - reducePacketsUntil = Math.max(reducePacketsUntil, now + 10000); - } - - - if((lastSwingSentAt == -1L || now - lastSwingSentAt >= 50) && last100MsSpanEstimatedPackets < last100MsReducePacketsAfter) { - client.executeSync(() -> client.player.swingHand(Hand.MAIN_HAND)); - lastSwingSentAt = now; - last100MsSpanEstimatedPackets++; - packetsSent = true; - } else if (last100MsSpanEstimatedPackets >= last100MsReducePacketsAfter) { - reducePacketsUntil = Math.max(reducePacketsUntil, now + 500); - } - - return packetsSent; - } - - public synchronized void tickPlayback() { - if (!running || song == null) { + if (!running) { lastPlaybackTickAt = -1L; - // last100MsSpanAt = -1L; + last100MsSpanAt = -1L; return; } - - if (pausePlaybackUntil != -1L) { - if (System.currentTimeMillis() <= pausePlaybackUntil) { - return; - } else { - tick = 0; - index = 0; - pausePlaybackUntil = -1L; - lastPlaybackTickAt = System.currentTimeMillis(); - } + long previousPlaybackTickAt = lastPlaybackTickAt; + lastPlaybackTickAt = System.currentTimeMillis(); + if(last100MsSpanAt != -1L && System.currentTimeMillis() - last100MsSpanAt >= 100) { + last100MsSpanEstimatedPackets = 0; + last100MsSpanAt = System.currentTimeMillis(); + }else if (last100MsSpanAt == -1L) { + last100MsSpanAt = System.currentTimeMillis(); + last100MsSpanEstimatedPackets = 0; } - - long currentNow = System.currentTimeMillis(); - long elapsedMs = lastPlaybackTickAt != -1L ? currentNow - lastPlaybackTickAt : (16); - tick += song.millisecondsToTicks(elapsedMs) * speed; - lastPlaybackTickAt = currentNow; - - if(noteBlocks != null && tuned) { - while (index < song.notes.length) { + if(pausePlaybackUntil != -1L && System.currentTimeMillis() <= pausePlaybackUntil) return; + while (running) { + MinecraftClient client = MinecraftClient.getInstance(); + GameMode gameMode = client.interactionManager == null ? null : client.interactionManager.getCurrentGameMode(); + // In the best case, gameMode would only be queried in sync Ticks, no here + if (gameMode == null || !gameMode.isSurvivalLike()) { + client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".player.invalid_game_mode", gameMode == null ? "unknown" : gameMode.getTranslatableName()).formatted(Formatting.RED)); + stop(); + return; + } + long note = song.notes[index]; - - if ((double)(short)note <= tick) { - Note currentNote = new Note(Note.INSTRUMENTS[(byte)(note >> Note.INSTRUMENT_SHIFT)], (byte)(note >> Note.NOTE_SHIFT)); - - playNoteBlock(currentNote); + final long now = System.currentTimeMillis(); + if ((short)note <= Math.round(tick)) { + @Nullable BlockPos blockPos = noteBlocks.get(Note.INSTRUMENTS[(byte)(note >> Note.INSTRUMENT_SHIFT)]).get((byte)(note >> Note.NOTE_SHIFT)); + if(blockPos == null) { + // Instrument got likely mapped to "nothing". Skip it + index++; + continue; + } + if (!canInteractWith(client.player, blockPos)) { + stop(); + client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".player.to_far").formatted(Formatting.RED)); + return; + } + Vec3d unit = Vec3d.ofCenter(blockPos, 0.5).subtract(client.player.getEyePos()).normalize(); + if((lastLookSentAt == -1L || now - lastLookSentAt >= 50) && last100MsSpanEstimatedPackets < last100MsReducePacketsAfter && (reducePacketsUntil == -1L || reducePacketsUntil < now)) { + client.getNetworkHandler().sendPacket(new PlayerMoveC2SPacket.LookAndOnGround(MathHelper.wrapDegrees((float) (MathHelper.atan2(unit.z, unit.x) * 57.2957763671875) - 90.0f), MathHelper.wrapDegrees((float) (-(MathHelper.atan2(unit.y, Math.sqrt(unit.x * unit.x + unit.z * unit.z)) * 57.2957763671875))), true, false)); + last100MsSpanEstimatedPackets++; + lastLookSentAt = now; + }else if(last100MsSpanEstimatedPackets >= last100MsReducePacketsAfter){ + reducePacketsUntil = Math.max(reducePacketsUntil, now + 500); + } + if(last100MsSpanEstimatedPackets < last100MsStopPacketsAfter && (stopPacketsUntil == -1L || stopPacketsUntil < now)) { + // TODO: 5/30/2022 Check if the block needs tuning + //client.interactionManager.attackBlock(blockPos, Direction.UP); + client.player.networkHandler.sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.START_DESTROY_BLOCK, blockPos, Direction.UP, 0)); + last100MsSpanEstimatedPackets++; + }else if(last100MsSpanEstimatedPackets >= last100MsStopPacketsAfter) { + Main.LOGGER.info("Stopping all packets for a bit!"); + stopPacketsUntil = Math.max(stopPacketsUntil, now + 250); + reducePacketsUntil = Math.max(reducePacketsUntil, now + 10000); + } + if(last100MsSpanEstimatedPackets < last100MsReducePacketsAfter && (reducePacketsUntil == -1L || reducePacketsUntil < now)) { + client.player.networkHandler.sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.ABORT_DESTROY_BLOCK, blockPos, Direction.UP, 0)); + last100MsSpanEstimatedPackets++; + }else if(last100MsSpanEstimatedPackets >= last100MsReducePacketsAfter){ + reducePacketsUntil = Math.max(reducePacketsUntil, now + 500); + } + if((lastSwingSentAt == -1L || now - lastSwingSentAt >= 50) &&last100MsSpanEstimatedPackets < last100MsReducePacketsAfter && (reducePacketsUntil == -1L || reducePacketsUntil < now)) { + client.executeSync(() -> client.player.swingHand(Hand.MAIN_HAND)); + lastSwingSentAt = now; + last100MsSpanEstimatedPackets++; + }else if(last100MsSpanEstimatedPackets >= last100MsReducePacketsAfter){ + reducePacketsUntil = Math.max(reducePacketsUntil, now + 500); + } index++; + if (index >= song.notes.length) { + stop(); + didSongReachEnd = true; + if (playMode == PlayMode.SINGLE_LOOP) { + start(song); + } else if (playMode == PlayMode.LIST_LOOP || playMode == PlayMode.RANDOM) { + playNextSong(); + } + break; + } } else { break; } } - if (index >= song.notes.length) { - stop(); - didSongReachEnd = true; - if (playMode == PlayMode.SINGLE_LOOP) { - } else if (playMode == PlayMode.LIST_LOOP || playMode == PlayMode.RANDOM) { - playNextSong(); - } + if(running) { // Might not be running anymore (prevent small offset on song, even if that is not played anymore) + long elapsedMs = previousPlaybackTickAt != -1L && lastPlaybackTickAt != -1L ? lastPlaybackTickAt - previousPlaybackTickAt : (16); // Assume 16ms if unknown + tick += song.millisecondsToTicks(elapsedMs) * speed; } } } @@ -338,12 +260,7 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick { if (SongLoader.currentFolder == null || SongLoader.currentFolder.songs.isEmpty()) return; int currentIndex = SongLoader.currentFolder.songs.indexOf(song); - if (currentIndex == -1) { - if (!SongLoader.currentFolder.songs.isEmpty()) { - start(SongLoader.currentFolder.songs.get(0)); - } - return; - } + if (currentIndex == -1) return; if (playMode == PlayMode.RANDOM) { int newIndex; @@ -362,30 +279,14 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick { this.loopSong = mode == PlayMode.SINGLE_LOOP; } - // 我都不知道这是啥时候的,我也不删 + // this is the original author‘s 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 // 11/1/2023 Playback now done in separate thread. Not ideal but better especially when FPS are low. @Override public void onStartTick(ClientWorld world) { MinecraftClient client = MinecraftClient.getInstance(); if(world == null || client.world == null || client.player == null) return; - long now = System.currentTimeMillis(); - if(last100MsSpanAt != -1L && now - last100MsSpanAt >= 100) { - last100MsSpanEstimatedPackets = 0; - last100MsSpanAt = now; - }else if (last100MsSpanAt == -1L) { - last100MsSpanAt = now; - last100MsSpanEstimatedPackets = 0; - } - - if(lastInteractAt != -1L) { - availableInteracts += ((System.currentTimeMillis() - lastInteractAt) / (310.0f / 8.0f)); - availableInteracts = Math.min(8f, Math.max(0f, availableInteracts)); - }else { - availableInteracts = 8f; - lastInteractAt = System.currentTimeMillis(); - } - + if(song == null || !running) return; // Clear outdated note predictions ArrayList outdatedPredictions = new ArrayList<>(); @@ -395,157 +296,120 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick { } for(BlockPos outdatedPrediction : outdatedPredictions) notePredictions.remove(outdatedPrediction); - if ((noteBlocks == null || !tuned) && (running || manualTuningRequested)) { - ClientPlayerEntity player = client.getInstance().player; - GameMode gameMode = client.interactionManager == null ? null : client.interactionManager.getCurrentGameMode(); - if (player == null || gameMode == null || !gameMode.isSurvivalLike()) { - noteBlocks = null; - notePredictions.clear(); - tuned = false; - tuneInitialUntunedBlocks = -1; - manualTuningRequested = false; - client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".player.invalid_state_tuning").formatted(Formatting.RED)); - return; + if (noteBlocks == null) { + noteBlocks = new HashMap<>(); + + ClientPlayerEntity player = client.player; + + // Create list of available noteblock positions per used instrument + HashMap> noteblocksForInstrument = new HashMap<>(); + for(NoteBlockInstrument instrument : NoteBlockInstrument.values()) + noteblocksForInstrument.put(instrument, new ArrayList<>()); + final Vec3d playerEyePos = player.getEyePos(); + + final int maxOffset; // Rough estimates, of which blocks could be in reach + if(Main.config.expectedServerVersion == ModConfig.ExpectedServerVersion.v1_20_4_Or_Earlier) { + maxOffset = 7; + }else if(Main.config.expectedServerVersion == ModConfig.ExpectedServerVersion.v1_20_5_Or_Later) { + maxOffset = (int) Math.ceil(player.getBlockInteractionRange() + 1.0 + 1.0); + }else if(Main.config.expectedServerVersion == ModConfig.ExpectedServerVersion.All) { + maxOffset = Math.min(7, (int) Math.ceil(player.getBlockInteractionRange() + 1.0 + 1.0)); + }else { + throw new NotImplementedException("ExpectedServerVersion Value not implemented: " + Main.config.expectedServerVersion.name()); + } + final ArrayList orderedOffsets = new ArrayList<>(); + for(int offset = 0; offset <= maxOffset; offset++) { + orderedOffsets.add(offset); + if(offset != 0) orderedOffsets.add(offset * -1); } - if (noteBlocks == null) { - noteBlocks = new HashMap<>(); + for(NoteBlockInstrument instrument : noteblocksForInstrument.keySet().toArray(new NoteBlockInstrument[0])) { + for (int y : orderedOffsets) { + for (int x : orderedOffsets) { + for (int z : orderedOffsets) { + Vec3d vec3d = playerEyePos.add(x, y, z); + BlockPos blockPos = new BlockPos(MathHelper.floor(vec3d.x), MathHelper.floor(vec3d.y), MathHelper.floor(vec3d.z)); + if (!canInteractWith(player, blockPos)) + continue; + BlockState blockState = world.getBlockState(blockPos); + if (!blockState.isOf(Blocks.NOTE_BLOCK) || !world.isAir(blockPos.up())) + continue; - HashMap> noteblocksForInstrument = new HashMap<>(); - for(NoteBlockInstrument instrument : NoteBlockInstrument.values()) - noteblocksForInstrument.put(instrument, new ArrayList<>()); - final Vec3d playerEyePos = player.getEyePos(); - - final int maxOffset; - if(Main.config.expectedServerVersion == ModConfig.ExpectedServerVersion.v1_20_4_Or_Earlier) { - maxOffset = 7; - }else if(Main.config.expectedServerVersion == ModConfig.ExpectedServerVersion.v1_20_5_Or_Later) { - maxOffset = (int) Math.ceil(player.getBlockInteractionRange() + 1.0 + 1.0); - }else if(Main.config.expectedServerVersion == ModConfig.ExpectedServerVersion.All) { - maxOffset = Math.min(7, (int) Math.ceil(player.getBlockInteractionRange() + 1.0 + 1.0)); - }else { - throw new NotImplementedException("ExpectedServerVersion Value not implemented: " + Main.config.expectedServerVersion.name()); - } - final ArrayList orderedOffsets = new ArrayList<>(); - for(int offset = 0; offset <= maxOffset; offset++) { - orderedOffsets.add(offset); - if(offset != 0) orderedOffsets.add(offset * -1); - } - - for(NoteBlockInstrument instrument : noteblocksForInstrument.keySet().toArray(new NoteBlockInstrument[0])) { - for (int y : orderedOffsets) { - for (int x : orderedOffsets) { - for (int z : orderedOffsets) { - Vec3d vec3d = playerEyePos.add(x, y, z); - BlockPos blockPos = new BlockPos(MathHelper.floor(vec3d.x), MathHelper.floor(vec3d.y), MathHelper.floor(vec3d.z)); - if (!canInteractWith(player, blockPos)) - continue; - BlockState blockState = world.getBlockState(blockPos); - if (!blockState.isOf(Blocks.NOTE_BLOCK) || !world.isAir(blockPos.up())) - continue; - - if (blockState.get(Properties.INSTRUMENT) == instrument) - noteblocksForInstrument.get(instrument).add(blockPos); - } + if (blockState.get(Properties.INSTRUMENT) == instrument) + noteblocksForInstrument.get(instrument).add(blockPos); } } } + } - if(!instrumentMap.isEmpty()) { - HashMap> newNoteblocksForInstrument = new HashMap<>(); - for(NoteBlockInstrument orig : noteblocksForInstrument.keySet()) { - NoteBlockInstrument mappedInstrument = instrumentMap.getOrDefault(orig, orig); - if(mappedInstrument == null) { - newNoteblocksForInstrument.put(orig, null); - continue; - } - - newNoteblocksForInstrument.put(orig, noteblocksForInstrument.getOrDefault(instrumentMap.getOrDefault(orig, orig), new ArrayList<>())); - } - noteblocksForInstrument = newNoteblocksForInstrument; - } - - ArrayList capturedNotes = new ArrayList<>(); - - ArrayList neededNotes = new ArrayList<>(); - if (song != null) { - neededNotes.addAll(song.uniqueNotes); - } - if (Main.keyMappingManager != null) { - for (Note mappedNote : Main.keyMappingManager.getMappings().values()) { - if (mappedNote != null && !neededNotes.contains(mappedNote)) { - neededNotes.add(mappedNote); - } - } - } - - - for(Note note : neededNotes) { - ArrayList availableBlocks = noteblocksForInstrument.get(note.instrument()); - if(availableBlocks == null) { - getNotes(note.instrument()).put(note.note(), null); + // Remap instruments for funzies + if(!instrumentMap.isEmpty()) { + HashMap> newNoteblocksForInstrument = new HashMap<>(); + for(NoteBlockInstrument orig : noteblocksForInstrument.keySet()) { + NoteBlockInstrument mappedInstrument = instrumentMap.getOrDefault(orig, orig); + if(mappedInstrument == null) { + // Instrument got likely mapped to "nothing" + newNoteblocksForInstrument.put(orig, null); continue; } - BlockPos bestBlockPos = null; - int bestBlockTuningSteps = Integer.MAX_VALUE; - for(BlockPos blockPos : availableBlocks) { - boolean alreadyAssigned = false; - if (noteBlocks != null) { - for (HashMap instrumentNotes : noteBlocks.values()) { - if (instrumentNotes != null && instrumentNotes.containsValue(blockPos)) { - alreadyAssigned = true; - break; - } - } - } - if (alreadyAssigned) continue; - - int wantedNote = note.note(); - BlockState blockState = world.getBlockState(blockPos); - if (!blockState.contains(Properties.NOTE)) continue; - int currentNote = blockState.get(Properties.NOTE); - - int tuningSteps = wantedNote >= currentNote ? wantedNote - currentNote : (25 - currentNote) + wantedNote; - - if(tuningSteps < bestBlockTuningSteps) { - bestBlockPos = blockPos; - bestBlockTuningSteps = tuningSteps; - } - } - - if(bestBlockPos != null) { - capturedNotes.add(note); - availableBlocks.remove(bestBlockPos); - getNotes(note.instrument()).put(note.note(), bestBlockPos); - } + newNoteblocksForInstrument.put(orig, noteblocksForInstrument.getOrDefault(instrumentMap.getOrDefault(orig, orig), new ArrayList<>())); } - - ArrayList missingNotes = new ArrayList<>(neededNotes); - missingNotes.removeAll(capturedNotes); - if (!missingNotes.isEmpty()) { - ChatHud chatHud = MinecraftClient.getInstance().inGameHud.getChatHud(); - chatHud.addMessage(Text.translatable(Main.MOD_ID+".player.invalid_note_blocks").formatted(Formatting.RED)); - - HashMap missing = new HashMap<>(); - for (Note note : missingNotes) { - NoteBlockInstrument mappedInstrument = instrumentMap.getOrDefault(note.instrument(), note.instrument()); - if(mappedInstrument == null) continue; - Block block = Note.INSTRUMENT_BLOCKS.get(mappedInstrument); - Integer got = missing.get(block); - if (got == null) got = 0; - missing.put(block, got + 1); - } - - missingInstrumentBlocks = missing; - missing.forEach((block, integer) -> chatHud.addMessage(Text.literal(block.getName().getString()+" × "+integer).formatted(Formatting.RED))); - - } else { - missingInstrumentBlocks.clear(); - } - + noteblocksForInstrument = newNoteblocksForInstrument; } + // Find fitting noteblocks with the least amount of adjustments required (to reduce tuning time) + ArrayList capturedNotes = new ArrayList<>(); + for(Note note : song.uniqueNotes) { + ArrayList availableBlocks = noteblocksForInstrument.get(note.instrument()); + if(availableBlocks == null) { + // Note was mapped to "nothing". Pretend it got captured, but just ignore it + capturedNotes.add(note); + getNotes(note.instrument()).put(note.note(), null); + continue; + } + BlockPos bestBlockPos = null; + int bestBlockTuningSteps = Integer.MAX_VALUE; + for(BlockPos blockPos : availableBlocks) { + int wantedNote = note.note(); + int currentNote = client.world.getBlockState(blockPos).get(Properties.NOTE); + int tuningSteps = wantedNote >= currentNote ? wantedNote - currentNote : (25 - currentNote) + wantedNote; + + if(tuningSteps < bestBlockTuningSteps) { + bestBlockPos = blockPos; + bestBlockTuningSteps = tuningSteps; + } + } + + if(bestBlockPos != null) { + capturedNotes.add(note); + availableBlocks.remove(bestBlockPos); + getNotes(note.instrument()).put(note.note(), bestBlockPos); + } // else will be a missing note + } + + ArrayList missingNotes = new ArrayList<>(song.uniqueNotes); + missingNotes.removeAll(capturedNotes); + if (!missingNotes.isEmpty()) { + ChatHud chatHud = MinecraftClient.getInstance().inGameHud.getChatHud(); + chatHud.addMessage(Text.translatable(Main.MOD_ID+".player.invalid_note_blocks").formatted(Formatting.RED)); + + HashMap missing = new HashMap<>(); + for (Note note : missingNotes) { + NoteBlockInstrument mappedInstrument = instrumentMap.getOrDefault(note.instrument(), note.instrument()); + if(mappedInstrument == null) continue; // Ignore if mapped to nothing + Block block = Note.INSTRUMENT_BLOCKS.get(mappedInstrument); + Integer got = missing.get(block); + if (got == null) got = 0; + missing.put(block, got + 1); + } + + missingInstrumentBlocks = missing; + missing.forEach((block, integer) -> chatHud.addMessage(Text.literal(block.getName().getString()+" × "+integer).formatted(Formatting.RED))); + stop(); + } + } else if (!tuned) { + //tuned = true; int ping = 0; { @@ -554,119 +418,112 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick { ping = playerListEntry.getLatency(); } + if(lastInteractAt != -1L) { + // Paper allows 8 interacts per 300 ms (actually 9 it turns out, but lets keep it a bit lower anyway) + availableInteracts += ((System.currentTimeMillis() - lastInteractAt) / (310.0f / 8.0f)); + availableInteracts = Math.min(8f, Math.max(0f, availableInteracts)); + }else { + availableInteracts = 8f; + lastInteractAt = System.currentTimeMillis(); + } + int fullyTunedBlocks = 0; HashMap untunedNotes = new HashMap<>(); + for (Note note : song.uniqueNotes) { + if(noteBlocks == null || noteBlocks.get(note.instrument()) == null) + continue; + BlockPos blockPos = noteBlocks.get(note.instrument()).get(note.note()); + if(blockPos == null) continue; + BlockState blockState = world.getBlockState(blockPos); + int assumedNote = notePredictions.containsKey(blockPos) ? notePredictions.get(blockPos).getLeft() : blockState.get(Properties.NOTE); - if (noteBlocks != null) { - for (HashMap instrumentNotes : noteBlocks.values()) { - if (instrumentNotes == null) continue; - for (Map.Entry entry : instrumentNotes.entrySet()) { - BlockPos blockPos = entry.getValue(); - Byte wantedNote = entry.getKey(); - - if (blockPos == null) continue; - - BlockState blockState = world.getBlockState(blockPos); - if (!blockState.contains(Properties.NOTE)) { - Main.LOGGER.warn("注意 {} 处音符盒在调整过程中改变了状态", blockPos); - noteBlocks = null; - tuned = false; - manualTuningRequested = false; + if (blockState.contains(Properties.NOTE)) { + if(assumedNote == note.note() && blockState.get(Properties.NOTE) == note.note()) + fullyTunedBlocks++; + if (assumedNote != note.note()) { + if (!canInteractWith(client.player, blockPos)) { + stop(); + client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".player.to_far").formatted(Formatting.RED)); return; } - - int assumedNote = notePredictions.containsKey(blockPos) ? notePredictions.get(blockPos).getLeft() : blockState.get(Properties.NOTE); // blockState.get(Properties.NOTE) returns Integer - - byte wantedNotePrimitive = wantedNote.byteValue(); - - if(assumedNote == wantedNotePrimitive && blockState.get(Properties.NOTE).intValue() == wantedNotePrimitive) { - fullyTunedBlocks++; - } else if (assumedNote != wantedNotePrimitive) { - untunedNotes.put(blockPos, blockState.get(Properties.NOTE).intValue()); - } + untunedNotes.put(blockPos, blockState.get(Properties.NOTE)); } + } else { + noteBlocks = null; + break; } } + if(tuneInitialUntunedBlocks == -1 || tuneInitialUntunedBlocks < untunedNotes.size()) + tuneInitialUntunedBlocks = untunedNotes.size(); int existingUniqueNotesCount = 0; - if (noteBlocks != null) { - for(HashMap instrumentNotes : noteBlocks.values()) { - if (instrumentNotes != null) { - for (BlockPos pos : instrumentNotes.values()) { - if (pos != null) { - existingUniqueNotesCount++; - } - } - } - } + for(Note n : song.uniqueNotes) { + if(noteBlocks.get(n.instrument()).get(n.note()) != null) + existingUniqueNotesCount++; } - if(untunedNotes.isEmpty() && fullyTunedBlocks == existingUniqueNotesCount) { + // Wait roundrip + 100ms before considering tuned after changing notes (in case the server rejects an interact) if(lastInteractAt == -1 || System.currentTimeMillis() - lastInteractAt >= ping * 2 + 100) { tuned = true; - if (running && tick == 0 && index == 0) { - pausePlaybackUntil = System.currentTimeMillis() + (long) (Math.abs(Main.config.delayPlaybackStartBySecs) * 1000); - } else { - pausePlaybackUntil = -1L; - } + pausePlaybackUntil = System.currentTimeMillis() + (long) (Math.abs(Main.config.delayPlaybackStartBySecs) * 1000); tuneInitialUntunedBlocks = -1; - manualTuningRequested = false; - client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".player.tuned").formatted(Formatting.GREEN)); - } - } else { - tuned = false; - if(tuneInitialUntunedBlocks == -1 || tuneInitialUntunedBlocks < untunedNotes.size()) - tuneInitialUntunedBlocks = untunedNotes.size(); - if (availableInteracts >= 1f && !untunedNotes.isEmpty()) { - BlockPos blockPosToTune = untunedNotes.keySet().iterator().next(); - int currentNote = untunedNotes.get(blockPosToTune); - Byte targetNote = null; - if (noteBlocks != null) { - for (HashMap instrumentNotes : noteBlocks.values()) { - if (instrumentNotes != null) { - for (Map.Entry entry : instrumentNotes.entrySet()) { - if (blockPosToTune.equals(entry.getValue())) { - targetNote = entry.getKey(); - break; - } - } - } - if (targetNote != null) break; - } - } - - if (targetNote != null) { - byte targetNotePrimitive = targetNote.byteValue(); - int tuningSteps = targetNotePrimitive >= currentNote ? targetNotePrimitive - currentNote : (25 - currentNote) + targetNotePrimitive; - - if (tuningSteps > 0) { - int predictedNote = (currentNote + 1) % 25; - notePredictions.put(blockPosToTune, new Pair<>(predictedNote, System.currentTimeMillis() + ping * 2 + 100)); - client.interactionManager.interactBlock(client.player, Hand.MAIN_HAND, new BlockHitResult(Vec3d.of(blockPosToTune), Direction.UP, blockPosToTune, false)); - lastInteractAt = System.currentTimeMillis(); - availableInteracts -= 1f; - - client.player.swingHand(Hand.MAIN_HAND); - - int totalUntuned = untunedNotes.size(); - int tunedCount = existingUniqueNotesCount - totalUntuned; - - } else { - - untunedNotes.remove(blockPosToTune); - } - } else { - - untunedNotes.remove(blockPosToTune); // Remove to avoid infinite loop - } - } else if (!untunedNotes.isEmpty()) { - + // Tuning finished } } - } - if((playbackThread == null || !playbackThread.isAlive()) && running && Main.config.disableAsyncPlayback) { + + BlockPos lastBlockPos = null; + int lastTunedNote = Integer.MIN_VALUE; + float roughTuneProgress = 1 - (untunedNotes.size() / Math.max(tuneInitialUntunedBlocks + 0f, 1f)); + while(availableInteracts >= 1f && untunedNotes.size() > 0) { + BlockPos blockPos = null; + int searches = 0; + while(blockPos == null) { + searches++; + // Find higher note + for (Map.Entry entry : untunedNotes.entrySet()) { + if (entry.getValue() > lastTunedNote) { + blockPos = entry.getKey(); + break; + } + } + // Find higher note or equal + if (blockPos == null) { + for (Map.Entry entry : untunedNotes.entrySet()) { + if (entry.getValue() >= lastTunedNote) { + blockPos = entry.getKey(); + break; + } + } + } + // Not found. Reset last note + if(blockPos == null) + lastTunedNote = Integer.MIN_VALUE; + if(blockPos == null && searches > 1) { + // Something went wrong. Take any note (one should at least exist here) + blockPos = untunedNotes.keySet().toArray(new BlockPos[0])[0]; + break; + } + } + if(blockPos == null) return; // Something went very, very wrong! + + lastTunedNote = untunedNotes.get(blockPos); + untunedNotes.remove(blockPos); + int assumedNote = notePredictions.containsKey(blockPos) ? notePredictions.get(blockPos).getLeft() : client.world.getBlockState(blockPos).get(Properties.NOTE); + notePredictions.put(blockPos, new Pair<>((assumedNote + 1) % 25, System.currentTimeMillis() + ping * 2 + 100)); + client.interactionManager.interactBlock(client.player, Hand.MAIN_HAND, new BlockHitResult(Vec3d.of(blockPos), Direction.UP, blockPos, false)); + lastInteractAt = System.currentTimeMillis(); + availableInteracts -= 1f; + lastBlockPos = blockPos; + } + if(lastBlockPos != null) { + // Turn head into spinning with time and lookup up further the further tuning is progressed + //client.getNetworkHandler().sendPacket(new PlayerMoveC2SPacket.LookAndOnGround(((float) (System.currentTimeMillis() % 2000)) * (360f/2000f), (1 - roughTuneProgress) * 180 - 90, true)); + client.player.swingHand(Hand.MAIN_HAND); + } + }else if((playbackThread == null || !playbackThread.isAlive()) && running && Main.config.disableAsyncPlayback) { + // Sync playback (off by default). Replacement for playback thread try { tickPlayback(); }catch (Exception ex) { @@ -677,16 +534,13 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick { } private HashMap getNotes(NoteBlockInstrument instrument) { - if (noteBlocks == null) { - noteBlocks = new HashMap<>(); - } return noteBlocks.computeIfAbsent(instrument, k -> new HashMap<>()); } // Before 1.20.5, the server limits interacts to 6 Blocks from Player Eye to Block Center // With 1.20.5 and later, the server does a more complex check, to the closest point of a full block hitbox // (max distance is BlockInteractRange + 1.0). - public boolean canInteractWith(ClientPlayerEntity player, BlockPos blockPos) { + private boolean canInteractWith(ClientPlayerEntity player, BlockPos blockPos) { final Vec3d eyePos = player.getEyePos(); if(Main.config.expectedServerVersion == ModConfig.ExpectedServerVersion.v1_20_4_Or_Earlier) { return eyePos.squaredDistanceTo(blockPos.toCenterPos()) <= 6.0 * 6.0; diff --git a/src/main/java/semmiedev/disc_jockey_revive/gui/KeyMappingListWidget.java b/src/main/java/semmiedev/disc_jockey_revive/gui/KeyMappingListWidget.java deleted file mode 100644 index 1323062..0000000 --- a/src/main/java/semmiedev/disc_jockey_revive/gui/KeyMappingListWidget.java +++ /dev/null @@ -1,136 +0,0 @@ -package semmiedev.disc_jockey_revive.gui; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; -import net.minecraft.client.gui.screen.narration.NarrationPart; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.EntryListWidget; -import net.minecraft.client.util.InputUtil; -import net.minecraft.text.Text; -import semmiedev.disc_jockey_revive.KeyMappingManager; -import semmiedev.disc_jockey_revive.Main; -import semmiedev.disc_jockey_revive.Note; -import semmiedev.disc_jockey_revive.gui.screen.EditKeyMappingsScreen; - -import java.util.Comparator; -import java.util.Map; -import java.util.Objects; - -public class KeyMappingListWidget extends EntryListWidget { - - private final EditKeyMappingsScreen parentScreen; - private boolean buttonsActive = true; - - public KeyMappingListWidget(MinecraftClient client, int width, int height, int top, int itemHeight, EditKeyMappingsScreen parentScreen) { - super(client, width, height, top, itemHeight); - this.parentScreen = parentScreen; - this.centerListVertically = false; - } - public void setMappings(Map mappings) { - this.clearEntries(); - for (Map.Entry entry : mappings.entrySet()) { - - this.addEntry(new KeyMappingEntry(entry.getKey(), entry.getValue(), this.parentScreen)); - } - - this.children().sort(Comparator.comparing(entry -> entry.getKey().getTranslationKey())); - - setButtonsActive(this.buttonsActive); - } - public void setButtonsActive(boolean active) { - this.buttonsActive = active; - - for (KeyMappingEntry entry : children()) { - entry.setButtonsActive(active); - } - } - @Override - public int getRowWidth() { - return this.width - 40; - } - - @Override - protected int getScrollbarX() { - return this.width - 10; - } - - @Override - protected void appendClickableNarrations(NarrationMessageBuilder builder) { - } - - public class KeyMappingEntry extends EntryListWidget.Entry { - private final InputUtil.Key key; - private final Note note; - private final EditKeyMappingsScreen screen; - - private ButtonWidget changeButton; - private ButtonWidget removeButton; - - public KeyMappingEntry(InputUtil.Key key, Note note, EditKeyMappingsScreen screen) { - this.key = key; - this.note = note; - this.screen = screen; - } - - public InputUtil.Key getKey() { - return key; - } - - public Note getNote() { - return note; - } - - public void setButtonsActive(boolean active) { - if (changeButton != null) changeButton.active = active; - if (removeButton != null) removeButton.active = active; - } - @Override - public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { - MinecraftClient client = MinecraftClient.getInstance(); - int textY = y + (entryHeight - client.textRenderer.fontHeight) / 2; - Text keyText = Text.translatable(key.getTranslationKey()); - context.drawTextWithShadow(client.textRenderer, keyText, x + 5, textY, 0xFFFFFF); - String noteDisplayName = KeyMappingManager.getNoteDisplayName(note); - context.drawTextWithShadow(client.textRenderer, noteDisplayName, x + 100, textY, 0xAAAAAA); - int buttonWidth = 50; - int buttonHeight = 18; - int buttonY = y + (entryHeight - buttonHeight) / 2; - int buttonX = x + entryWidth - buttonWidth - 5; - changeButton = ButtonWidget.builder(Text.translatable(Main.MOD_ID + ".screen.edit_mappings.change"), button -> { - screen.startWaitingForKeyPress(this); - }).dimensions(buttonX - buttonWidth - 5, buttonY, buttonWidth, buttonHeight).build(); - changeButton.render(context, mouseX, mouseY, tickDelta); - changeButton.active = buttonsActive; - removeButton = ButtonWidget.builder(Text.translatable(Main.MOD_ID + ".screen.edit_mappings.remove"), button -> { - screen.removeMapping(key); - }).dimensions(buttonX, buttonY, buttonWidth, buttonHeight).build(); - removeButton.render(context, mouseX, mouseY, tickDelta); - removeButton.active = buttonsActive; - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - - if (changeButton.mouseClicked(mouseX, mouseY, button)) { - return true; - } - if (removeButton.mouseClicked(mouseX, mouseY, button)) { - return true; - } - return false; - } - - @Override - public boolean isMouseOver(double mouseX, double mouseY) { - return super.isMouseOver(mouseX, mouseY) || - (changeButton != null && changeButton.isMouseOver(mouseX, mouseY)) || - (removeButton != null && removeButton.isMouseOver(mouseX, mouseY)); - } - public void appendClickableNarrations(NarrationMessageBuilder builder) { - builder.put(NarrationPart.TITLE, Text.translatable(key.getTranslationKey()).append(" -> ").append(KeyMappingManager.getNoteDisplayName(note))); - if (changeButton != null) changeButton.appendNarrations(builder); - if (removeButton != null) removeButton.appendNarrations(builder); - } - } -} 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 eca3e73..33a4127 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,14 +9,12 @@ import net.minecraft.item.ItemStack; import net.minecraft.text.MutableText; import net.minecraft.text.Text; import net.minecraft.util.Formatting; -import org.lwjgl.glfw.GLFW; 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.gui.SongListWidget; import semmiedev.disc_jockey_revive.gui.hud.BlocksOverlay; -import semmiedev.disc_jockey_revive.gui.widget.ProgressBarWidget; import java.io.File; import java.io.IOException; @@ -28,7 +26,6 @@ import java.util.stream.Collectors; import semmiedev.disc_jockey_revive.SongLoader.SongFolder; import semmiedev.disc_jockey_revive.SongPlayer.PlayMode; -import org.jetbrains.annotations.Nullable; public class DiscJockeyScreen extends Screen { private static final MutableText @@ -38,14 +35,12 @@ public class DiscJockeyScreen extends Screen { 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) - ; + ; private SongListWidget songListWidget; - private ButtonWidget playButton, previewButton, prevButton, nextButton; + private ButtonWidget playButton, previewButton; public boolean shouldFilter; private String query = ""; - private ProgressBarWidget progressBar; - private TextFieldWidget searchBar; private static final MutableText FOLDER_UP = Text.literal("↑"), @@ -71,137 +66,39 @@ public class DiscJockeyScreen extends Screen { @Override protected void init() { shouldFilter = true; + songListWidget = new SongListWidget(client, width, height - 64 - 32, 32, 20, this); + // 恢复播放模式 currentPlayMode = Main.config.playMode; + // 恢复文件夹状态 if (!Main.config.currentFolderPath.isEmpty()) { currentFolder = findFolderByPath(Main.config.currentFolderPath); SongLoader.currentFolder = currentFolder; } - - int listTop = 40; - int listBottom = height - 100; - int listHeight = listBottom - listTop; - songListWidget = new SongListWidget(client, width, height - 100, 40, 20, this); addDrawableChild(songListWidget); + for (int i = 0; i < SongLoader.SONGS.size(); i++) { + Song song = SongLoader.SONGS.get(i); + song.entry.songListWidget = songListWidget; + if (song.entry.selected) songListWidget.setSelected(song.entry); - - int playbackButtonHeight = 20; - int playbackButtonY = listBottom +50; - int centerX = width / 2; - - // 上一首 - prevButton = ButtonWidget.builder(Text.literal("⏮"), button -> playPreviousSong()) - .dimensions(centerX - 80, playbackButtonY, 40, playbackButtonHeight).build(); - addDrawableChild(prevButton); - - // 播放 - playButton = ButtonWidget.builder(PLAY, button -> { - if (Main.SONG_PLAYER.running) { - Main.SONG_PLAYER.stop(); - } else { - SongListWidget.SongEntry entry = songListWidget.getSelectedSongOrNull(); - if (entry != null) { - Main.SONG_PLAYER.start(entry.song); - } + // 添加文件夹条目 + if (song.folder != null && !songListWidget.children().contains(song.folder.entry)) { + song.folder.entry = new SongListWidget.FolderEntry(song.folder, songListWidget); + songListWidget.children().add(song.folder.entry); } - }).dimensions(centerX - 30, playbackButtonY, 60, playbackButtonHeight).build(); - addDrawableChild(playButton); - - // 下一首 - nextButton = ButtonWidget.builder(Text.literal("⏭"), button -> playNextSong()) - .dimensions(centerX + 40, playbackButtonY, 40, playbackButtonHeight).build(); - addDrawableChild(nextButton); - - // 预览 - previewButton = ButtonWidget.builder(PREVIEW, button -> { - if (Main.PREVIEWER.running) { - Main.PREVIEWER.stop(); - } else { - SongListWidget.SongEntry entry = songListWidget.getSelectedSongOrNull(); - if (entry != null) Main.PREVIEWER.start(entry.song); - } - }).dimensions(centerX + 90, playbackButtonY, 60, playbackButtonHeight).build(); - addDrawableChild(previewButton); - - // 进度条 - int progressBarHeight = 10; - int progressBarY = playbackButtonY + playbackButtonHeight + 5; - progressBar = new ProgressBarWidget(centerX - 100, progressBarY, 200, progressBarHeight, - Text.translatable(Main.MOD_ID + ".progress"), 0, 100) { - @Override - protected void onProgressChanged(double progress) { - if (Main.SONG_PLAYER.running && Main.SONG_PLAYER.song != null) { - double targetTick = progress * Main.SONG_PLAYER.song.length / 100.0; - Main.SONG_PLAYER.tick = targetTick; - updateNoteIndexFromTick(); - } - } - }; - addDrawableChild(progressBar); - - // 搜索框 - int searchBarWidth = 150; - int searchBarHeight = 20; - int searchBarX = 10; - int searchBarY = height - searchBarHeight - 10; - searchBar = new TextFieldWidget(textRenderer, searchBarX, searchBarY, searchBarWidth, searchBarHeight, - Text.translatable(Main.MOD_ID+".screen.search")); - searchBar.setChangedListener(query -> { - query = query.toLowerCase().replaceAll("\\s", ""); - if (this.query.equals(query)) return; - this.query = query; - shouldFilter = true; - }); - addDrawableChild(searchBar); - int otherButtonWidth = 100; - int otherButtonHeight = 20; - int otherButtonMargin = 10; - - // Blocks - int blocksButtonX = width - otherButtonWidth - otherButtonMargin; - int blocksButtonY = height - otherButtonHeight - otherButtonMargin - otherButtonHeight - otherButtonMargin; - addDrawableChild(ButtonWidget.builder(Text.translatable(Main.MOD_ID+".screen.blocks"), button -> { - if (BlocksOverlay.itemStacks == null) { - SongListWidget.SongEntry entry = songListWidget.getSelectedSongOrNull(); - if (entry != null) { - client.setScreen(null); - updateBlocksOverlay(entry.song); - } - } else { - BlocksOverlay.itemStacks = null; - client.setScreen(null); - } - }).dimensions(blocksButtonX, blocksButtonY, otherButtonWidth, otherButtonHeight).build()); - - // 打开文件夹 - int openFolderButtonX = width - otherButtonWidth - otherButtonMargin; - int openFolderButtonY = height - otherButtonHeight - otherButtonMargin - otherButtonHeight - otherButtonMargin + otherButtonHeight + otherButtonMargin; // 在 Blocks 按钮下方一行 - addDrawableChild(ButtonWidget.builder(OPEN_FOLDER, button -> openSongsFolder()) - .dimensions(openFolderButtonX, openFolderButtonY, otherButtonWidth, otherButtonHeight).build()); - - // 重新加载 - int reloadButtonX = width - otherButtonWidth - otherButtonMargin - otherButtonWidth - otherButtonMargin; - int reloadButtonY = openFolderButtonY; - addDrawableChild(ButtonWidget.builder(RELOAD, button -> { - SongLoader.loadSongs(); - client.setScreen(null); - }).dimensions(reloadButtonX, reloadButtonY, otherButtonWidth, otherButtonHeight).build()); - - + } folderUpButton = ButtonWidget.builder(FOLDER_UP, button -> { if (currentFolder != null) { - SongFolder parent = findParentFolder(currentFolder); - currentFolder = parent; - SongLoader.currentFolder = parent; + currentFolder = null; + SongLoader.currentFolder = null; shouldFilter = true; } }).dimensions(10, 10, 20, 20).build(); addDrawableChild(folderUpButton); - playModeButton = ButtonWidget.builder(getPlayModeText(), button -> { switch (currentPlayMode) { case SINGLE_LOOP -> currentPlayMode = PlayMode.LIST_LOOP; @@ -214,200 +111,131 @@ public class DiscJockeyScreen extends Screen { }).dimensions(width - 120, 10, 100, 20).build(); addDrawableChild(playModeButton); - for (int i = 0; i < SongLoader.SONGS.size(); i++) { - Song song = SongLoader.SONGS.get(i); - song.entry.songListWidget = songListWidget; - if (song.entry.selected) songListWidget.setSelected(song.entry); + playButton = ButtonWidget.builder(PLAY, button -> { + if (Main.SONG_PLAYER.running) { + Main.SONG_PLAYER.stop(); + } else { + SongListWidget.SongEntry entry = songListWidget.getSelectedSongOrNull(); + if (entry != null) { + Main.SONG_PLAYER.start(entry.song); + client.setScreen(null); + } + } + }).dimensions(width / 2 - 160, height - 61, 100, 20).build(); + addDrawableChild(playButton); - if (song.folder != null) { - boolean folderEntryExists = SongLoader.FOLDERS.stream() - .anyMatch(f -> f == song.folder || findFolderByPathInSubfolders(f, song.folder.path) != null); - if (!folderEntryExists) { - } else { - if (song.folder.entry == null) { - song.folder.entry = new SongListWidget.FolderEntry(song.folder, songListWidget); + previewButton = ButtonWidget.builder(PREVIEW, button -> { + if (Main.PREVIEWER.running) { + Main.PREVIEWER.stop(); + } else { + SongListWidget.SongEntry entry = songListWidget.getSelectedSongOrNull(); + if (entry != null) Main.PREVIEWER.start(entry.song); + } + }).dimensions(width / 2 - 50, height - 61, 100, 20).build(); + addDrawableChild(previewButton); + + addDrawableChild(ButtonWidget.builder(Text.translatable(Main.MOD_ID+".screen.blocks"), button -> { + // TODO: 6/2/2022 Add an auto build mode + if (BlocksOverlay.itemStacks == null) { + SongListWidget.SongEntry entry = songListWidget.getSelectedSongOrNull(); + if (entry != null) { + client.setScreen(null); + + BlocksOverlay.itemStacks = new ItemStack[0]; + BlocksOverlay.amounts = new int[0]; + BlocksOverlay.amountOfNoteBlocks = entry.song.uniqueNotes.size(); + + for (Note note : entry.song.uniqueNotes) { + ItemStack itemStack = Note.INSTRUMENT_BLOCKS.get(note.instrument()).asItem().getDefaultStack(); + int index = -1; + + for (int i = 0; i < BlocksOverlay.itemStacks.length; i++) { + if (BlocksOverlay.itemStacks[i].getItem() == itemStack.getItem()) { + index = i; + break; + } + } + + if (index == -1) { + BlocksOverlay.itemStacks = Arrays.copyOf(BlocksOverlay.itemStacks, BlocksOverlay.itemStacks.length + 1); + BlocksOverlay.amounts = Arrays.copyOf(BlocksOverlay.amounts, BlocksOverlay.amounts.length + 1); + + BlocksOverlay.itemStacks[BlocksOverlay.itemStacks.length - 1] = itemStack; + BlocksOverlay.amounts[BlocksOverlay.amounts.length - 1] = 1; + } else { + BlocksOverlay.amounts[index] = BlocksOverlay.amounts[index] + 1; + } } } - } - } - } - - // 播放进度 - private void updateNoteIndexFromTick() { - if (Main.SONG_PLAYER.song == null) return; - - double targetTick = Main.SONG_PLAYER.tick; - int newIndex = 0; - for (int i = 0; i < Main.SONG_PLAYER.song.notes.length; i++) { - long note = Main.SONG_PLAYER.song.notes[i]; - if ((short)note > targetTick) { - newIndex = i; - break; - } - newIndex = i; - } - Main.SONG_PLAYER.index = newIndex; - } - - private void updateBlocksOverlay(Song song) { - BlocksOverlay.itemStacks = new ItemStack[0]; - BlocksOverlay.amounts = new int[0]; - BlocksOverlay.amountOfNoteBlocks = song.uniqueNotes.size(); - - for (Note note : song.uniqueNotes) { - ItemStack itemStack = Note.INSTRUMENT_BLOCKS.get(note.instrument()).asItem().getDefaultStack(); - int index = -1; - - for (int i = 0; i < BlocksOverlay.itemStacks.length; i++) { - if (BlocksOverlay.itemStacks[i].getItem() == itemStack.getItem()) { - index = i; - break; - } - } - - if (index == -1) { - BlocksOverlay.itemStacks = Arrays.copyOf(BlocksOverlay.itemStacks, BlocksOverlay.itemStacks.length + 1); - BlocksOverlay.amounts = Arrays.copyOf(BlocksOverlay.amounts, BlocksOverlay.amounts.length + 1); - - BlocksOverlay.itemStacks[BlocksOverlay.itemStacks.length - 1] = itemStack; - BlocksOverlay.amounts[BlocksOverlay.amounts.length - 1] = 1; } else { - BlocksOverlay.amounts[index] = BlocksOverlay.amounts[index] + 1; + BlocksOverlay.itemStacks = null; + client.setScreen(null); } - } - } + }).dimensions(width / 2 + 60, height - 61, 100, 20).build()); - // 打开文件夹 - private void openSongsFolder() { - try { - String folderPath = currentFolder != null ? - currentFolder.path : Main.songsFolder.getAbsolutePath(); + // 打开文件夹 + addDrawableChild(ButtonWidget.builder(OPEN_FOLDER, button -> { + try { + String folderPath = currentFolder != null ? + currentFolder.path : + Main.songsFolder.getAbsolutePath(); // 使用绝对路径 - File target = new File(folderPath); - if (!target.exists()) { + File target = new File(folderPath); + if (!target.exists()) { + client.inGameHud.getChatHud().addMessage( + Text.translatable(Main.MOD_ID+".screen.folder_not_exist", folderPath) + .formatted(Formatting.RED)); + return; + } + + // Windows 专用命令,其他的不会 + if (System.getProperty("os.name").toLowerCase().contains("win")) { + new ProcessBuilder("explorer.exe", "/select,", target.getAbsolutePath()).start(); + } else { + java.awt.Desktop.getDesktop().open(target); + } + } catch (Exception e) { + Main.LOGGER.error("打开文件夹失败", e); client.inGameHud.getChatHud().addMessage( - Text.translatable(Main.MOD_ID+".screen.folder_not_exist", folderPath) + Text.translatable(Main.MOD_ID+".screen.open_folder_failed") .formatted(Formatting.RED)); - return; } + }).dimensions(width / 2 - 160, height - 31, 100, 20).build()); - if (System.getProperty("os.name").toLowerCase().contains("win")) { - new ProcessBuilder("explorer.exe", "/select,", target.getAbsolutePath()).start(); - } else { - java.awt.Desktop.getDesktop().open(target); - } - } catch (Exception e) { - Main.LOGGER.error("打开文件夹失败", e); - client.inGameHud.getChatHud().addMessage( - Text.translatable(Main.MOD_ID+".screen.open_folder_failed") - .formatted(Formatting.RED)); - } + + // 重新加载 + addDrawableChild(ButtonWidget.builder(RELOAD, button -> { + SongLoader.loadSongs(); + client.setScreen(null); + }).dimensions(width / 2 + 60, height - 31, 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", ""); + if (this.query.equals(query)) return; + this.query = query; + shouldFilter = true; + }); + addDrawableChild(searchBar); + + // TODO: 6/2/2022 Add a reload button } - // 预览 - private void playPreviousSong() { - if (SongLoader.currentFolder == null || SongLoader.currentFolder.songs.isEmpty()) return; - - int currentIndex = SongLoader.currentFolder.songs.indexOf(Main.SONG_PLAYER.song); - if (currentIndex == -1) { - if (!SongLoader.currentFolder.songs.isEmpty()) { - startSong(SongLoader.currentFolder.songs.get(0)); - } - return; - } - - int prevIndex = (currentIndex - 1 + SongLoader.currentFolder.songs.size()) % SongLoader.currentFolder.songs.size(); - startSong(SongLoader.currentFolder.songs.get(prevIndex)); - } - - // 下一首 - private void playNextSong() { - if (SongLoader.currentFolder == null || SongLoader.currentFolder.songs.isEmpty()) return; - - if (Main.SONG_PLAYER.playMode == PlayMode.RANDOM) { - int newIndex; - int currentIndex = SongLoader.currentFolder.songs.indexOf(Main.SONG_PLAYER.song); - do { - newIndex = (int)(Math.random() * SongLoader.currentFolder.songs.size()); - } while (newIndex == currentIndex && - SongLoader.currentFolder.songs.size() > 1); - startSong(SongLoader.currentFolder.songs.get(newIndex)); - } else { - int currentIndex = SongLoader.currentFolder.songs.indexOf(Main.SONG_PLAYER.song); - if (currentIndex == -1) { - if (!SongLoader.currentFolder.songs.isEmpty()) { - startSong(SongLoader.currentFolder.songs.get(0)); - } - return; - } - - int nextIndex = (currentIndex + 1) % SongLoader.currentFolder.songs.size(); - startSong(SongLoader.currentFolder.songs.get(nextIndex)); - } - } - - private void startSong(Song song) { - SongListWidget.SongEntry entry = findEntryForSong(song); - if (entry != null) { - songListWidget.setSelected(entry); - Main.SONG_PLAYER.start(song); - } - } - - private SongListWidget.SongEntry findEntryForSong(Song song) { - for (SongListWidget.Entry entry : songListWidget.children()) { - if (entry instanceof SongListWidget.SongEntry songEntry && songEntry.song == song) { - return songEntry; - } - } - return null; - } - - @Override public void render(DrawContext context, int mouseX, int mouseY, float delta) { - renderBackground(context, mouseX, mouseY, delta); super.render(context, mouseX, mouseY, delta); - // 标题 context.drawCenteredTextWithShadow(textRenderer, DROP_HINT, width / 2, 5, 0xFFFFFF); context.drawCenteredTextWithShadow(textRenderer, SELECT_SONG, width / 2, 20, 0xFFFFFF); - // 文件夹 播放模式 + // 显示当前文件夹和播放模式 String folderName = currentFolder == null ? "/" : currentFolder.name; context.drawTextWithShadow(textRenderer, CURRENT_FOLDER.getString() + ": " + folderName, 35, 15, 0xFFFFFF); context.drawTextWithShadow(textRenderer, PLAY_MODE.getString() + ":", width - 220, 15, 0xFFFFFF); - - // 进度条 - if (Main.SONG_PLAYER.running && Main.SONG_PLAYER.song != null) { - double progress = (Main.SONG_PLAYER.tick / Main.SONG_PLAYER.song.length) * 100; - if (!progressBar.isDragging()) { - progressBar.setProgress(progress); - } - String timeText = formatTime(Main.SONG_PLAYER.getSongElapsedSeconds()) + " / " + - formatTime(Main.SONG_PLAYER.song.getLengthInSeconds()); - int timeTextY = progressBar.getY() + progressBar.getHeight() + 2; - context.drawTextWithShadow(textRenderer, timeText, width / 2 - textRenderer.getWidth(timeText) / 2, timeTextY, 0xFFFFFF); - } else { - progressBar.setProgress(0); - String timeText = formatTime(0) + " / " + formatTime(0); - int timeTextY = progressBar.getY() + progressBar.getHeight() + 2; - context.drawTextWithShadow(textRenderer, timeText, width / 2 - textRenderer.getWidth(timeText) / 2, timeTextY, 0xFFFFFF); - } - } - - // 时间格式hua - private String formatTime(double seconds) { - int totalSeconds = (int) seconds; - int minutes = totalSeconds / 60; - int secs = totalSeconds % 60; - return String.format("%02d:%02d", minutes, secs); } @Override public void tick() { - super.tick(); - previewButton.setMessage(Main.PREVIEWER.running ? PREVIEW_STOP : PREVIEW); playButton.setMessage(Main.SONG_PLAYER.running ? PLAY_STOP : PLAY); @@ -417,7 +245,7 @@ public class DiscJockeyScreen extends Screen { boolean empty = query.isEmpty(); boolean isInSongsOrSubfolder = currentFolder == null || - (Main.songsFolder != null && currentFolder.path.startsWith(Main.songsFolder.getPath())); + currentFolder.path.startsWith(Main.songsFolder.getPath()); if (currentFolder == null) { for (SongFolder folder : SongLoader.FOLDERS) { @@ -429,10 +257,12 @@ public class DiscJockeyScreen extends Screen { } } } else { + // 返回上级 SongListWidget.FolderEntry parentEntry = new SongListWidget.FolderEntry(null, songListWidget); parentEntry.displayName = ".."; songListWidget.children().add(parentEntry); + // 子文件夹 for (SongFolder subFolder : currentFolder.subFolders) { if (empty || subFolder.name.toLowerCase().contains(query)) { if (subFolder.entry == null) { @@ -443,7 +273,9 @@ public class DiscJockeyScreen extends Screen { } } + // 只有在songs目录或其子目录中才显示歌曲(原作者的💩跑我这了) if (isInSongsOrSubfolder) { + // 歌曲条目 List songsToShow = currentFolder == null ? SongLoader.SONGS.stream() .filter(song -> song.folder == null) @@ -452,14 +284,14 @@ public class DiscJockeyScreen extends Screen { .filter(song -> song.folder == currentFolder) .collect(Collectors.toList()); - // 已收藏 + // 已收藏歌曲 for (Song song : songsToShow) { if (song.entry.favorite && (empty || song.searchableFileName.contains(query) || song.searchableName.contains(query))) { songListWidget.children().add(song.entry); } } - // 未收藏 + // 未收藏歌曲 for (Song song : songsToShow) { if (!song.entry.favorite && (empty || song.searchableFileName.contains(query) || song.searchableName.contains(query))) { songListWidget.children().add(song.entry); @@ -469,7 +301,40 @@ public class DiscJockeyScreen extends Screen { } } - // 拖文件 + + + public SongFolder findParentFolder(SongFolder current) { + if (current == null) return null; + + if (SongLoader.FOLDERS.contains(current)) { + return null; + } + + for (SongFolder folder : SongLoader.FOLDERS) { + if (folder.subFolders.contains(current)) { + return folder; + } + SongFolder found = findParentInSubfolders(folder, current); + if (found != null) { + return found; + } + } + return null; + } + + private SongFolder findParentInSubfolders(SongFolder parent, SongFolder target) { + for (SongFolder subFolder : parent.subFolders) { + if (subFolder == target) { + return parent; + } + SongFolder found = findParentInSubfolders(subFolder, target); + if (found != null) { + return found; + } + } + return null; + } + @Override public void onFilesDropped(List paths) { String string = paths.stream().map(Path::getFileName).map(Path::toString).collect(Collectors.joining(", ")); @@ -485,14 +350,15 @@ public class DiscJockeyScreen extends Screen { Song song = SongLoader.loadSong(file); if (song != null) { - File targetFolder = currentFolder != null ? new File(currentFolder.path) : Main.songsFolder; - Files.copy(path, targetFolder.toPath().resolve(file.getName())); - SongLoader.loadSongs(); + Files.copy(path, Main.songsFolder.toPath().resolve(file.getName())); + SongLoader.SONGS.add(song); } } catch (IOException exception) { - Main.LOGGER.warn("无法将歌曲文件从 {} 复制到 {} ", path, Main.songsFolder.toPath(), exception); + Main.LOGGER.warn("Failed to copy song file from {} to {}", path, Main.songsFolder.toPath(), exception); } }); + + SongLoader.sort(); } client.setScreen(this); }, Text.translatable(Main.MOD_ID+".screen.drop_confirm"), Text.literal(string))); @@ -506,11 +372,27 @@ public class DiscJockeyScreen extends Screen { @Override public void close() { super.close(); + // 保存当前文件夹 Main.config.currentFolderPath = currentFolder != null ? currentFolder.path : ""; + // 保存播放模式 Main.config.playMode = currentPlayMode; + // 异步保存配置 new Thread(() -> Main.configHolder.save()).start(); } + private SongFolder findInSubFolders(SongFolder parent, SongFolder target) { + for (SongFolder subFolder : parent.subFolders) { + if (subFolder == target) { + return parent; + } + SongFolder found = findInSubFolders(subFolder, target); + if (found != null) { + return found; + } + } + return null; + } + private SongFolder findFolderByPath(String path) { for (SongFolder folder : SongLoader.FOLDERS) { if (folder.path.equals(path)) { @@ -527,7 +409,6 @@ public class DiscJockeyScreen extends Screen { return null; } - @Nullable private SongFolder findFolderByPathInSubfolders(SongFolder parent, String targetPath) { for (SongFolder subFolder : parent.subFolders) { if (subFolder.path.equals(targetPath)) { @@ -541,38 +422,6 @@ public class DiscJockeyScreen extends Screen { return null; } - @Nullable - public SongFolder findParentFolder(@Nullable SongFolder targetFolder) { - if (targetFolder == null) { - return null; - } - for (SongFolder folder : SongLoader.FOLDERS) { - if (folder.subFolders.contains(targetFolder)) { - return folder; - } - SongFolder parentInSub = findParentFolderInSubfoldersForParent(folder, targetFolder); - if (parentInSub != null) { - return parentInSub; - } - } - return null; - } - - @Nullable - private SongFolder findParentFolderInSubfoldersForParent(SongFolder current, SongFolder targetFolder) { - for (SongFolder subFolder : current.subFolders) { - if (subFolder.subFolders.contains(targetFolder)) { - return subFolder; - } - SongFolder parentInSub = findParentFolderInSubfoldersForParent(subFolder, targetFolder); - if (parentInSub != null) { - return parentInSub; - } - } - return null; - } - - private Text getPlayModeText() { return switch (currentPlayMode) { case SINGLE_LOOP -> MODE_SINGLE; diff --git a/src/main/java/semmiedev/disc_jockey_revive/gui/screen/EditKeyMappingsScreen.java b/src/main/java/semmiedev/disc_jockey_revive/gui/screen/EditKeyMappingsScreen.java deleted file mode 100644 index ae6e656..0000000 --- a/src/main/java/semmiedev/disc_jockey_revive/gui/screen/EditKeyMappingsScreen.java +++ /dev/null @@ -1,150 +0,0 @@ -package semmiedev.disc_jockey_revive.gui.screen; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.Drawable; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.util.InputUtil; -import net.minecraft.text.Text; -import org.lwjgl.glfw.GLFW; -import semmiedev.disc_jockey_revive.Main; -import semmiedev.disc_jockey_revive.Note; -import semmiedev.disc_jockey_revive.gui.KeyMappingListWidget; - -import java.util.Map; - -public class EditKeyMappingsScreen extends Screen { - - private static final Text TITLE = Text.translatable(Main.MOD_ID + ".screen.edit_mappings.title"); - private static final Text ADD_MAPPING_BUTTON_TEXT = Text.translatable(Main.MOD_ID + ".screen.edit_mappings.add_mapping"); - private static final Text DONE_BUTTON_TEXT = Text.translatable("gui.done"); - private static final Text PRESS_KEY_INSTRUCTION = Text.translatable(Main.MOD_ID + ".screen.edit_mappings.press_key"); - - private final Screen parent; - private KeyMappingListWidget mappingListWidget; - private ButtonWidget addMappingButton; - private ButtonWidget doneButton; - - private boolean waitingForKeyPress = false; - private KeyMappingListWidget.KeyMappingEntry entryToEdit = null; - - public EditKeyMappingsScreen(Screen parent) { - super(TITLE); - this.parent = parent; - } - - @Override - protected void init() { - super.init(); - - int listTop = 40; - int listBottom = this.height - 50; - int listHeight = listBottom - listTop; - - mappingListWidget = new KeyMappingListWidget(this.client, this.width, listHeight, listTop, 20, this); - addDrawableChild(mappingListWidget); - refreshMappingList(); - int buttonWidth = 100; - int buttonHeight = 20; - int buttonY = this.height - 30; - int buttonX = this.width / 2 - buttonWidth - 5; - - addMappingButton = ButtonWidget.builder(ADD_MAPPING_BUTTON_TEXT, button -> { - startWaitingForKeyPress(null); - }).dimensions(buttonX, buttonY, buttonWidth, buttonHeight).build(); - addDrawableChild(addMappingButton); - buttonX = this.width / 2 + 5; - doneButton = ButtonWidget.builder(DONE_BUTTON_TEXT, button -> { - Main.keyMappingManager.saveMappings(); - this.client.setScreen(this.parent); - }).dimensions(buttonX, buttonY, buttonWidth, buttonHeight).build(); - addDrawableChild(doneButton); - } - - private void refreshMappingList() { - - mappingListWidget.setMappings(Main.keyMappingManager.getMappings()); - } - public void startWaitingForKeyPress(KeyMappingListWidget.KeyMappingEntry entry) { - this.waitingForKeyPress = true; - this.entryToEdit = entry; - - addMappingButton.active = false; - doneButton.active = false; - mappingListWidget.setButtonsActive(false); - } - public void addNewMapping(InputUtil.Key key, Note note) { - Main.keyMappingManager.setMapping(key, note); - refreshMappingList(); - - } - public void removeMapping(InputUtil.Key key) { - Main.keyMappingManager.removeMapping(key); - refreshMappingList(); - } - public void stopWaitingForKeyPress() { - this.waitingForKeyPress = false; - this.entryToEdit = null; - - addMappingButton.active = true; - doneButton.active = true; - mappingListWidget.setButtonsActive(true); - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (waitingForKeyPress) { - if (keyCode == GLFW.GLFW_KEY_ESCAPE) { - - stopWaitingForKeyPress(); - return true; - } - - InputUtil.Key pressedKey = InputUtil.fromKeyCode(keyCode, scanCode); - - if (entryToEdit != null) { - - Note note = entryToEdit.getNote(); - - Main.keyMappingManager.removeMapping(entryToEdit.getKey()); - - Main.keyMappingManager.setMapping(pressedKey, note); - refreshMappingList(); - stopWaitingForKeyPress(); - } else { - this.client.setScreen(new SelectNoteScreen(this, pressedKey)); - - } - return true; - } - if (mappingListWidget.keyPressed(keyCode, scanCode, modifiers)) { - return true; - } - if (keyCode == GLFW.GLFW_KEY_ESCAPE) { - Main.keyMappingManager.saveMappings(); - this.client.setScreen(this.parent); - return true; - } - - return super.keyPressed(keyCode, scanCode, modifiers); - } - - @Override - public void render(DrawContext context, int mouseX, int mouseY, float delta) { - super.render(context, mouseX, mouseY, delta); - context.drawCenteredTextWithShadow(textRenderer, TITLE, this.width / 2, 10, 0xFFFFFF); - if (waitingForKeyPress) { - - context.fill(0, 0, this.width, this.height, 0x80000000); - context.drawCenteredTextWithShadow(textRenderer, PRESS_KEY_INSTRUCTION, this.width / 2, this.height / 2 - 10, 0xFFFFFF); - } - mappingListWidget.render(context, mouseX, mouseY, delta); - } - - @Override - public boolean shouldPause() { - - return true; - } -} diff --git a/src/main/java/semmiedev/disc_jockey_revive/gui/screen/LiveDjScreen.java b/src/main/java/semmiedev/disc_jockey_revive/gui/screen/LiveDjScreen.java deleted file mode 100644 index a8a9ef7..0000000 --- a/src/main/java/semmiedev/disc_jockey_revive/gui/screen/LiveDjScreen.java +++ /dev/null @@ -1,175 +0,0 @@ -package semmiedev.disc_jockey_revive.gui.screen; - -import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.annotation.Nullable; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.util.InputUtil; -import net.minecraft.sound.SoundCategory; -import net.minecraft.text.Text; -import net.minecraft.util.math.MathHelper; -import net.minecraft.util.math.Vec3d; -import semmiedev.disc_jockey_revive.Main; -import semmiedev.disc_jockey_revive.Note; -import semmiedev.disc_jockey_revive.KeyMappingManager; -import org.lwjgl.glfw.GLFW; - -import java.util.HashMap; -import java.util.Map; - -public class LiveDjScreen extends Screen { - - private static final Text TITLE = Text.translatable(Main.MOD_ID + ".screen.live_dj.title"); - private static final Text INSTRUCTIONS = Text.translatable(Main.MOD_ID + ".screen.live_dj.instructions"); - private static final Text EDIT_MAPPINGS_BUTTON_TEXT = Text.translatable(Main.MOD_ID + ".screen.live_dj.edit_mappings"); - private static final Text START_TUNING_BUTTON_TEXT = Text.translatable(Main.MOD_ID + ".screen.live_dj.start_tuning"); - private static final Text NOT_TUNED_MESSAGE = Text.translatable(Main.MOD_ID + ".player.not_tuned").formatted(net.minecraft.util.Formatting.RED); - private static final Text NOTE_BLOCK_MISSING_MESSAGE = Text.translatable(Main.MOD_ID + ".player.note_block_missing_live").formatted(net.minecraft.util.Formatting.RED); - private static final Text TOO_FAR_MESSAGE = Text.translatable(Main.MOD_ID + ".player.to_far_live").formatted(net.minecraft.util.Formatting.RED); - private static final Text RATE_LIMITED_MESSAGE = Text.translatable(Main.MOD_ID + ".player.rate_limited_live").formatted(net.minecraft.util.Formatting.YELLOW); - private ButtonWidget startTuningButton; - - public LiveDjScreen() { - super(TITLE); - } - - @Override - protected void init() { - super.init(); - - int centerX = this.width / 2; - int buttonWidth = 150; - int buttonHeight = 20; - int buttonY = this.height - 30; - int margin = 5; - addDrawableChild(ButtonWidget.builder(EDIT_MAPPINGS_BUTTON_TEXT, button -> { - MinecraftClient.getInstance().setScreen(new EditKeyMappingsScreen(this)); - }).dimensions(centerX - buttonWidth - margin, buttonY, buttonWidth, buttonHeight).build()); - startTuningButton = ButtonWidget.builder(START_TUNING_BUTTON_TEXT, button -> { - Main.SONG_PLAYER.startTuning(); - }).dimensions(centerX + margin, buttonY, buttonWidth, buttonHeight).build(); - addDrawableChild(startTuningButton); - } - - @Override - public void render(DrawContext context, int mouseX, int mouseY, float delta) { - super.render(context, mouseX, mouseY, delta); - context.drawCenteredTextWithShadow(textRenderer, TITLE, this.width / 2, 10, 0xFFFFFF); - context.drawCenteredTextWithShadow(textRenderer, INSTRUCTIONS, this.width / 2, 30, 0xFFFFFF); - Text tuningStatusText; - if (Main.SONG_PLAYER.noteBlocks == null) { - tuningStatusText = Text.translatable(Main.MOD_ID + ".player.discovering").formatted(net.minecraft.util.Formatting.YELLOW); - startTuningButton.active = true; - startTuningButton.visible = true; - } else if (!Main.SONG_PLAYER.tuned) { - - int totalNeeded = 0; - if (Main.SONG_PLAYER.noteBlocks != null) { - for (HashMap instrumentNotes : Main.SONG_PLAYER.noteBlocks.values()) { - if (instrumentNotes != null) { - for (net.minecraft.util.math.BlockPos pos : instrumentNotes.values()) { - if (pos != null) { - totalNeeded++; - } - } - } - } - } - int tunedCount = 0; - - MinecraftClient client = MinecraftClient.getInstance(); - if (client.world != null && Main.SONG_PLAYER.noteBlocks != null) { - for (HashMap instrumentNotes : Main.SONG_PLAYER.noteBlocks.values()) { - if (instrumentNotes != null) { - for (Map.Entry entry : instrumentNotes.entrySet()) { - net.minecraft.util.math.BlockPos pos = entry.getValue(); - Byte wantedNote = entry.getKey(); - if (pos != null) { - - if (client.world.getBlockState(pos).contains(net.minecraft.state.property.Properties.NOTE)) { - int currentNote = client.world.getBlockState(pos).get(net.minecraft.state.property.Properties.NOTE); - if (currentNote == wantedNote.byteValue()) { - tunedCount++; - } - } - } - } - } - } - } - if (totalNeeded > 0) { - tuningStatusText = Text.translatable(Main.MOD_ID + ".player.tuning_progress", tunedCount, totalNeeded).formatted(net.minecraft.util.Formatting.YELLOW); - } else { - - tuningStatusText = Text.translatable(Main.MOD_ID + ".player.finding_blocks").formatted(net.minecraft.util.Formatting.YELLOW); - } - startTuningButton.active = true; - startTuningButton.visible = true; - - } else { - tuningStatusText = Text.translatable(Main.MOD_ID + ".player.tuned").formatted(net.minecraft.util.Formatting.GREEN); - startTuningButton.active = false; - startTuningButton.visible = false; - } - context.drawCenteredTextWithShadow(textRenderer, tuningStatusText, this.width / 2, 50, 0xFFFFFF); - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - - InputUtil.Key key = InputUtil.fromKeyCode(keyCode, scanCode); - if (keyCode == GLFW.GLFW_KEY_ESCAPE) { - this.close(); - return true; - } - Note note = Main.keyMappingManager.getNoteForKey(key); - - if (note != null) { - - if (!Main.SONG_PLAYER.tuned) { - MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(NOT_TUNED_MESSAGE); - return true; - } - boolean played = Main.SONG_PLAYER.playNoteBlock(note); - if (!played) { - @Nullable net.minecraft.util.math.BlockPos blockPos = null; - if (Main.SONG_PLAYER.noteBlocks != null && Main.SONG_PLAYER.noteBlocks.containsKey(note.instrument())) { - blockPos = Main.SONG_PLAYER.noteBlocks.get(note.instrument()).get(note.note()); - } - - if (blockPos == null) { - MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(NOTE_BLOCK_MISSING_MESSAGE.copy().append(" (" + KeyMappingManager.getNoteDisplayName(note) + ")")); - } else if (!Main.SONG_PLAYER.canInteractWith(MinecraftClient.getInstance().player, blockPos)) { - MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(TOO_FAR_MESSAGE.copy().append(" (" + KeyMappingManager.getNoteDisplayName(note) + ")")); - } else { - - MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(RATE_LIMITED_MESSAGE); - } - } - - return true; - } - return super.keyPressed(keyCode, scanCode, modifiers); - } - - @Override - public void tick() { - super.tick(); - - if (startTuningButton != null) { - startTuningButton.visible = !Main.SONG_PLAYER.tuned; - startTuningButton.active = !Main.SONG_PLAYER.isTuningEnabled(); - } - } - @Override - public boolean shouldPause() { - return false; - } - - @Override - public void close() { - - super.close(); - } -} diff --git a/src/main/java/semmiedev/disc_jockey_revive/gui/screen/SelectNoteScreen.java b/src/main/java/semmiedev/disc_jockey_revive/gui/screen/SelectNoteScreen.java deleted file mode 100644 index eedfc81..0000000 --- a/src/main/java/semmiedev/disc_jockey_revive/gui/screen/SelectNoteScreen.java +++ /dev/null @@ -1,173 +0,0 @@ -package semmiedev.disc_jockey_revive.gui.screen; - -import net.minecraft.block.enums.NoteBlockInstrument; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.CyclingButtonWidget; -import net.minecraft.client.gui.widget.SliderWidget; -import net.minecraft.client.util.InputUtil; -import net.minecraft.sound.SoundCategory; -import net.minecraft.sound.SoundEvent; -import net.minecraft.text.Text; -import net.minecraft.util.Identifier; -import net.minecraft.util.math.MathHelper; -import net.minecraft.util.math.Vec3d; -import org.lwjgl.glfw.GLFW; -import semmiedev.disc_jockey_revive.Main; -import semmiedev.disc_jockey_revive.Note; -import semmiedev.disc_jockey_revive.KeyMappingManager; - -public class SelectNoteScreen extends Screen { - - private static final Text TITLE = Text.translatable(Main.MOD_ID + ".screen.select_note.title"); - private static final Text INSTRUMENT_TEXT = Text.translatable(Main.MOD_ID + ".screen.select_note.instrument"); - private static final Text PITCH_TEXT = Text.translatable(Main.MOD_ID + ".screen.select_note.pitch"); - private static final Text DONE_BUTTON_TEXT = Text.translatable("gui.done"); - private static final Text CANCEL_BUTTON_TEXT = Text.translatable("gui.cancel"); - private static final Text PREVIEW_BUTTON_TEXT = Text.translatable(Main.MOD_ID + ".screen.select_note.preview"); - private final EditKeyMappingsScreen parent; - private final InputUtil.Key keyToMap; - - private NoteBlockInstrument selectedInstrument = NoteBlockInstrument.HARP; - private int selectedPitch = 12; - private CustomPitchSlider pitchSlider; - - private ButtonWidget previewButton; - - public SelectNoteScreen(EditKeyMappingsScreen parent, InputUtil.Key keyToMap) { - super(TITLE); - this.parent = parent; - this.keyToMap = keyToMap; - } - - @Override - protected void init() { - super.init(); - - int centerX = this.width / 2; - int startY = this.height / 2 - 50; - int widgetWidth = 200; - int widgetHeight = 20; - int margin = 5; - CyclingButtonWidget instrumentButton = CyclingButtonWidget.builder((NoteBlockInstrument instrument) -> Text.translatable("block.minecraft.note_block.instrument." + instrument.asString())) - .values(NoteBlockInstrument.values()) - .initially(selectedInstrument) - - .build(centerX - widgetWidth / 2, startY, widgetWidth, widgetHeight, INSTRUMENT_TEXT, (button, instrument) -> { - this.selectedInstrument = instrument; - updatePitchSliderDisplay(); - updatePreviewButton(); - }); - addDrawableChild(instrumentButton); - pitchSlider = new CustomPitchSlider(centerX - widgetWidth / 2, startY + widgetHeight + margin, widgetWidth, widgetHeight, PITCH_TEXT, (selectedPitch / 24.0)); - updatePitchSliderDisplay(); - addDrawableChild(pitchSlider); - previewButton = ButtonWidget.builder(PREVIEW_BUTTON_TEXT, button -> { - playPreviewNote(); - }).dimensions(centerX - widgetWidth / 2, startY + (widgetHeight + margin) * 2, widgetWidth, widgetHeight).build(); - addDrawableChild(previewButton); - int buttonWidth = 100; - int buttonY = this.height - 30; - int doneButtonX = centerX - buttonWidth - margin; - addDrawableChild(ButtonWidget.builder(DONE_BUTTON_TEXT, button -> { - Note selectedNote = new Note(selectedInstrument, (byte) selectedPitch); - parent.addNewMapping(keyToMap, selectedNote); - this.client.setScreen(this.parent); - parent.stopWaitingForKeyPress(); - }).dimensions(doneButtonX, buttonY, buttonWidth, widgetHeight).build()); - int cancelButtonX = centerX + margin; - addDrawableChild(ButtonWidget.builder(CANCEL_BUTTON_TEXT, button -> { - this.client.setScreen(this.parent); - parent.stopWaitingForKeyPress(); - }).dimensions(cancelButtonX, buttonY, buttonWidth, widgetHeight).build()); - } - private class CustomPitchSlider extends SliderWidget { - public CustomPitchSlider(int x, int y, int width, int height, Text text, double value) { - super(x, y, width, height, text, value); - } - - @Override - protected void updateMessage() { - - this.setMessage(PITCH_TEXT.copy().append(": " + KeyMappingManager.getNoteDisplayName(new Note(selectedInstrument, (byte) selectedPitch)))); - } - - @Override - protected void applyValue() { - selectedPitch = (int) Math.round(MathHelper.lerp(this.value, 0.0, 24.0)); - updateMessage(); - updatePreviewButton(); - } - public void forceUpdateDisplay() { - this.updateMessage(); - } - } - private void updatePitchSliderDisplay() { - if (pitchSlider != null) { - - pitchSlider.forceUpdateDisplay(); - } - } - - private void updatePreviewButton() { - - } - - private void playPreviewNote() { - MinecraftClient client = MinecraftClient.getInstance(); - if (client.world != null && client.gameRenderer != null) { - Vec3d pos = client.gameRenderer.getCamera().getPos(); - try { - Note note = new Note(selectedInstrument, (byte) selectedPitch); - - if (note.instrument().canBePitched()) { - Identifier soundId = note.instrument().getSound().value().id(); - float pitchMultiplier = (float) Math.pow(2.0, (note.note() - 12) / 12.0); - client.world.playSound(pos.x, pos.y, pos.z, SoundEvent.of(soundId), SoundCategory.RECORDS, 3.0f, pitchMultiplier, false); - } else { - - Identifier soundId = note.instrument().getSound().value().id(); - client.world.playSound(pos.x, pos.y, pos.z, SoundEvent.of(soundId), SoundCategory.RECORDS, 3.0f, 1.0f, false); - } - - } catch (Exception e) { - Main.LOGGER.error("无法播放预览声音。", e); - } - } - } - @Override - public void render(DrawContext context, int mouseX, int mouseY, float delta) { - - super.render(context, mouseX, mouseY, delta); - - context.drawCenteredTextWithShadow(textRenderer, TITLE, this.width / 2, 10, 0xFFFFFF); - - context.drawCenteredTextWithShadow(textRenderer, Text.translatable(Main.MOD_ID + ".screen.select_note.mapping_key", Text.translatable(keyToMap.getTranslationKey())), this.width / 2, 30, 0xFFFFFF); - } - - @Override - public boolean shouldPause() { - return true; - } - - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - - if (keyCode == GLFW.GLFW_KEY_ESCAPE) { - this.client.setScreen(this.parent); - parent.stopWaitingForKeyPress(); - return true; - } - - if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER) { - Note selectedNote = new Note(selectedInstrument, (byte) selectedPitch); - parent.addNewMapping(keyToMap, selectedNote); - this.client.setScreen(this.parent); - parent.stopWaitingForKeyPress(); - return true; - } - return super.keyPressed(keyCode, scanCode, modifiers); - } -} diff --git a/src/main/java/semmiedev/disc_jockey_revive/gui/widget/ProgressBarWidget.java b/src/main/java/semmiedev/disc_jockey_revive/gui/widget/ProgressBarWidget.java deleted file mode 100644 index ac5275b..0000000 --- a/src/main/java/semmiedev/disc_jockey_revive/gui/widget/ProgressBarWidget.java +++ /dev/null @@ -1,91 +0,0 @@ -package semmiedev.disc_jockey_revive.gui.widget; - -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; -import net.minecraft.client.gui.widget.ClickableWidget; -import net.minecraft.text.Text; -import net.minecraft.util.math.MathHelper; - -public class ProgressBarWidget extends ClickableWidget { - private double progress; - private final int minValue; - private final int maxValue; - private boolean dragging; - - public ProgressBarWidget(int x, int y, int width, int height, Text message, int minValue, int maxValue) { - super(x, y, width, height, message); - this.minValue = minValue; - this.maxValue = maxValue; - this.progress = minValue; - } - - @Override - public void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { - // 背景 - context.fill(getX(), getY(), getX() + width, getY() + height, 0xFF555555); - - // 进度条 - int progressWidth = (int)(width * (progress - minValue) / (maxValue - minValue)); - context.fill(getX(), getY(), getX() + progressWidth, getY() + height, 0xFF00FF00); - - // 滑块 - int sliderX = getX() + progressWidth - 3; - context.fill(sliderX, getY() - 2, sliderX + 6, getY() + height + 2, 0xFFFFFFFF); - } - - @Override - public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (button == 0 && isMouseOver(mouseX, mouseY)) { - setProgressFromMouse(mouseX); - dragging = true; - return true; - } - return false; - } - - @Override - public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { - if (dragging) { - setProgressFromMouse(mouseX); - return true; - } - return false; - } - - @Override - public boolean mouseReleased(double mouseX, double mouseY, int button) { - if (button == 0) { - dragging = false; - return true; - } - return false; - } - - private void setProgressFromMouse(double mouseX) { - double relativeX = MathHelper.clamp(mouseX - getX(), 0, width); - double newProgress = (relativeX / width) * (maxValue - minValue) + minValue; - setProgress(newProgress); - } - - public void setProgress(double progress) { - this.progress = MathHelper.clamp(progress, minValue, maxValue); - onProgressChanged(this.progress); - } - - public double getProgress() { - return progress; - } - - public boolean isDragging() { - return this.dragging; - } - - protected void onProgressChanged(double progress) { - - } - - @Override - protected void appendClickableNarrations(NarrationMessageBuilder builder) { - appendDefaultNarrations(builder); - } -} diff --git a/src/main/resources/assets/disc_jockey/icon1.png b/src/main/resources/assets/disc_jockey/icon1.png new file mode 100644 index 0000000..3882463 Binary files /dev/null and b/src/main/resources/assets/disc_jockey/icon1.png differ 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 2539473..c5bb33f 100644 --- a/src/main/resources/assets/disc_jockey/lang/en_us.json +++ b/src/main/resources/assets/disc_jockey/lang/en_us.json @@ -61,54 +61,5 @@ "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...", - - "key.disc_jockey_revive.open_screen": "Open Disc Jockey Screen", - "key.disc_jockey_revive.open_live_dj_screen": "Open Live DJ Screen", - - "disc_jockey_revive.screen.live_dj.title": "Live DJ Mode", - "disc_jockey_revive.screen.live_dj.instructions": "Press mapped keys to play notes. Press ESC to exit. Default key mapping reference FL Studio", - "disc_jockey_revive.screen.live_dj.edit_mappings": "Edit Mappings", - - "disc_jockey_revive.screen.edit_mappings.title": "Edit Key Mappings", - "disc_jockey_revive.screen.edit_mappings.add_mapping": "Add Mapping", - "disc_jockey_revive.screen.edit_mappings.change": "Change Key", - "disc_jockey_revive.screen.edit_mappings.remove": "Remove", - "disc_jockey_revive.screen.edit_mappings.press_key": "Press any key to map...", - - "disc_jockey_revive.screen.select_note.title": "Select Note", - "disc_jockey_revive.screen.select_note.mapping_key": "Mapping Key: %s", - "disc_jockey_revive.screen.select_note.instrument": "Instrument", - "disc_jockey_revive.screen.select_note.pitch": "Pitch", - "disc_jockey_revive.screen.select_note.preview": "Preview Note", - - "block.minecraft.note_block.instrument.harp": "Air", - "block.minecraft.note_block.instrument.basedrum": "Stone", - "block.minecraft.note_block.instrument.snare": "Sand", - "block.minecraft.note_block.instrument.hat": "Glass", - "block.minecraft.note_block.instrument.bass": "Wood", - "block.minecraft.note_block.instrument.flute": "Clay", - "block.minecraft.note_block.instrument.bell": "Gold Block", - "block.minecraft.note_block.instrument.guitar": "Wool", - "block.minecraft.note_block.instrument.chime": "Packed Ice", - "block.minecraft.note_block.instrument.xylophone": "Bone Block", - "block.minecraft.note_block.instrument.iron_xylophone": "Iron Block", - "block.minecraft.note_block.instrument.cow_bell": "Soul Sand", - "block.minecraft.note_block.instrument.didgeridoo": "Pumpkin", - "block.minecraft.note_block.instrument.bit": "Emerald Block", - "block.minecraft.note_block.instrument.banjo": "Hay Bale", - "block.minecraft.note_block.instrument.pling": "Glowstone", - - "disc_jockey_revive.player.not_tuned": "Note blocks are not tuned yet!", - "disc_jockey_revive.player.discovering": "Discovering note blocks...", - "disc_jockey_revive.player.finding_blocks": "Finding note blocks...", - "disc_jockey_revive.player.tuning_progress": "Tuning note blocks: %s/%s", - "disc_jockey_revive.player.tuned": "Note blocks are tuned!", - "disc_jockey_revive.player.note_block_missing_live": "Note block missing for this note!", - "disc_jockey_revive.player.to_far_live": "Too far from note block for this note!", - "disc_jockey_revive.player.rate_limited_live": "Rate limited. Cannot play note right now.", - - "disc_jockey_revive.screen.live_dj.start_tuning": "Start Tuning", - "disc_jockey_revive.player.tuning_started": "Tuning started...", - "disc_jockey_revive.player.retuning": "Retuning note blocks..." + "disc_jockey_revive.screen.reloading": "Reloading songs..." } \ 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 d1cec9f..0a5cfb8 100644 --- a/src/main/resources/assets/disc_jockey/lang/zh_cn.json +++ b/src/main/resources/assets/disc_jockey/lang/zh_cn.json @@ -60,54 +60,5 @@ "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": "正在重新加载...", - - "key.disc_jockey_revive.open_screen": "打开Disc Jockey界面", - "key.disc_jockey_revive.open_live_dj_screen": "打开现场演奏界面", - - "disc_jockey_revive.screen.live_dj.title": "现场演奏模式", - "disc_jockey_revive.screen.live_dj.instructions": "按下已映射的按键来弹奏音符。按 ESC 退出。默认按键映射参考FL Studio", - "disc_jockey_revive.screen.live_dj.edit_mappings": "编辑按键映射", - - "disc_jockey_revive.screen.edit_mappings.title": "编辑按键映射", - "disc_jockey_revive.screen.edit_mappings.add_mapping": "添加映射", - "disc_jockey_revive.screen.edit_mappings.change": "更改按键", - "disc_jockey_revive.screen.edit_mappings.remove": "移除", - "disc_jockey_revive.screen.edit_mappings.press_key": "按下任意按键进行映射...", - - "disc_jockey_revive.screen.select_note.title": "选择音符", - "disc_jockey_revive.screen.select_note.mapping_key": "映射按键:%s", - "disc_jockey_revive.screen.select_note.instrument": "乐器", - "disc_jockey_revive.screen.select_note.pitch": "音高", - "disc_jockey_revive.screen.select_note.preview": "试听音符", - - "block.minecraft.note_block.instrument.harp": "空气", - "block.minecraft.note_block.instrument.basedrum": "石头", - "block.minecraft.note_block.instrument.snare": "沙子", - "block.minecraft.note_block.instrument.hat": "玻璃", - "block.minecraft.note_block.instrument.bass": "木头", - "block.minecraft.note_block.instrument.flute": "粘土", - "block.minecraft.note_block.instrument.bell": "金块", - "block.minecraft.note_block.instrument.guitar": "羊毛", - "block.minecraft.note_block.instrument.chime": "浮冰", - "block.minecraft.note_block.instrument.xylophone": "骨块", - "block.minecraft.note_block.instrument.iron_xylophone": "铁块", - "block.minecraft.note_block.instrument.cow_bell": "灵魂沙", - "block.minecraft.note_block.instrument.didgeridoo": "南瓜", - "block.minecraft.note_block.instrument.bit": "绿宝石", - "block.minecraft.note_block.instrument.banjo": "甘草快", - "block.minecraft.note_block.instrument.pling": "荧石", - - "disc_jockey_revive.player.not_tuned": "音符盒尚未调音!", - "disc_jockey_revive.player.discovering": "正在发现音符盒...", - "disc_jockey_revive.player.finding_blocks": "正在查找音符盒...", - "disc_jockey_revive.player.tuning_progress": "正在调音音符盒:%s/%s", - "disc_jockey_revive.player.tuned": "音符盒已调音!", - "disc_jockey_revive.player.note_block_missing_live": "此音符的音符盒缺失!", - "disc_jockey_revive.player.to_far_live": "离此音符的音符盒太远!", - "disc_jockey_revive.player.rate_limited_live": "速率受限。暂时无法弹奏音符。", - - "disc_jockey_revive.screen.live_dj.start_tuning": "开始调音", - "disc_jockey_revive.player.tuning_started": "调音已开始...", - "disc_jockey_revive.player.retuning": "正在重新调音音符盒..." + "disc_jockey_revive.screen.reloading": "正在重新加载..." } \ No newline at end of file