aaaaaaaaaaa

This commit is contained in:
BRanulf 2025-07-06 22:52:30 +08:00
parent 8cc8f6a427
commit 5d3f71cef5
14 changed files with 1458 additions and 58 deletions

View File

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

View File

@ -0,0 +1,25 @@
package semmiedev.disc_jockey_revive;
import org.apache.logging.log4j.Logger;
// 这玩意一般不用我自己用的但是不想删
public class DebugLogger {
public static void log(String message) {
if (Main.config != null && Main.config.debugModeEnabled) {
Main.LOGGER.info("[DiscJockeyRevive-调试] " + message);
}
}
public static void log(String format, Object... arguments) {
if (Main.config != null && Main.config.debugModeEnabled) {
Main.LOGGER.info("[DiscJockeyRevive-调试] " + format, arguments);
}
}
public static void log(String message, Throwable t) {
if (Main.config != null && Main.config.debugModeEnabled) {
Main.LOGGER.error("[DiscJockeyRevive-调试] " + message, t);
}
}
}

View File

@ -0,0 +1,172 @@
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<HashMap<String, NoteData>>() {}.getType();
private Map<InputUtil.Key, Note> 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<String, NoteData> loadedData = GSON.fromJson(reader, MAPPING_TYPE);
mappings.clear();
if (loadedData != null) {
for (Map.Entry<String, NoteData> 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<String, NoteData> dataToSave = new HashMap<>();
for (Map.Entry<InputUtil.Key, Note> 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<InputUtil.Key, Note> 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 + ")";
}
}

View File

@ -0,0 +1,475 @@
package semmiedev.disc_jockey_revive;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.enums.NoteBlockInstrument;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.hud.ChatHud;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket;
import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket;
import net.minecraft.state.property.Properties;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Hand;
import net.minecraft.util.Pair;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.*;
import net.minecraft.world.GameMode;
import org.apache.commons.lang3.NotImplementedException;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class LiveDjPlayer implements ClientTickEvents.StartWorldTick {
private HashMap<NoteBlockInstrument, HashMap<Byte, BlockPos>> noteBlocks = null;
private boolean tuned = false;
private boolean tuningRequested = false;
private long last100MsSpanAt = -1L;
private int last100MsSpanEstimatedPackets = 0;
final private int last100MsReducePacketsAfter = 300 / 10, last100MsStopPacketsAfter = 450 / 10;
private long reducePacketsUntil = -1L, stopPacketsUntil = -1L;
private long lastLookSentAt = -1L, lastSwingSentAt = -1L;
private long lastInteractAt = -1;
private float availableInteracts = 8;
private int tuneInitialUntunedBlocks = -1;
private HashMap<BlockPos, Pair<Integer, Long>> notePredictions = new HashMap<>();
public HashMap<Block, Integer> missingInstrumentBlocks = new HashMap<>();
public LiveDjPlayer() {
Main.TICK_LISTENERS.add(this);
}
// 调音
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;
tuningRequested = true;
last100MsSpanAt = System.currentTimeMillis();
last100MsSpanEstimatedPackets = 0;
reducePacketsUntil = -1L;
stopPacketsUntil = -1L;
lastLookSentAt = -1L;
lastSwingSentAt = -1L;
lastInteractAt = -1;
availableInteracts = 8;
}
// 停止调音
public synchronized void stopTuning() {
tuningRequested = false;
}
// 是否正在调音
public boolean isTuningActive() {
return tuningRequested;
}
// 是否已调音
public boolean isTuned() {
return tuned;
}
public HashMap<NoteBlockInstrument, HashMap<Byte, BlockPos>> getNoteBlocks() {
return noteBlocks;
}
// 播放
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);
}
// 获取可交互的
private boolean sendNotePacket(BlockPos blockPos) {
MinecraftClient client = MinecraftClient.getInstance();
ClientPlayerEntity player = client.player;
long now = System.currentTimeMillis();
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("LiveDjPlayer: 短暂暂停所有数据包,因为速率受限!");
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;
}
@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();
}
ArrayList<BlockPos> outdatedPredictions = new ArrayList<>();
for(Map.Entry<BlockPos, Pair<Integer, Long>> entry : notePredictions.entrySet()) {
if(entry.getValue().getRight() < System.currentTimeMillis())
outdatedPredictions.add(entry.getKey());
}
for(BlockPos outdatedPrediction : outdatedPredictions) notePredictions.remove(outdatedPrediction);
if (tuningRequested && !tuned) {
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;
tuningRequested = false;
client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".player.invalid_state_tuning").formatted(Formatting.RED));
return;
}
if (noteBlocks == null) {
noteBlocks = new HashMap<>();
HashMap<NoteBlockInstrument, ArrayList<BlockPos>> 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<Integer> 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);
}
}
}
}
ArrayList<Note> neededNotes = new ArrayList<>();
if (Main.keyMappingManager != null) {
for (Note mappedNote : Main.keyMappingManager.getMappings().values()) {
if (mappedNote != null && !neededNotes.contains(mappedNote)) {
neededNotes.add(mappedNote);
}
}
}
ArrayList<Note> capturedNotes = new ArrayList<>();
for(Note note : neededNotes) {
ArrayList<BlockPos> availableBlocks = noteblocksForInstrument.get(note.instrument());
if(availableBlocks == null) {
getNotesMapForInstrument(note.instrument()).put(note.note(), null);
continue;
}
BlockPos bestBlockPos = null;
int bestBlockTuningSteps = Integer.MAX_VALUE;
for(BlockPos blockPos : availableBlocks) {
boolean alreadyAssigned = false;
if (noteBlocks != null) {
for (HashMap<Byte, BlockPos> 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);
getNotesMapForInstrument(note.instrument()).put(note.note(), bestBlockPos);
}
}
ArrayList<Note> 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<Block, Integer> missing = new HashMap<>();
for (Note note : missingNotes) {
Block block = Note.INSTRUMENT_BLOCKS.get(note.instrument());
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();
}
}
int ping = 0;
{
PlayerListEntry playerListEntry;
if (client.getNetworkHandler() != null && (playerListEntry = client.getNetworkHandler().getPlayerListEntry(client.player.getGameProfile().getId())) != null)
ping = playerListEntry.getLatency();
}
int fullyTunedBlocks = 0;
HashMap<BlockPos, Integer> untunedNotes = new HashMap<>();
if (noteBlocks != null) {
for (HashMap<Byte, BlockPos> instrumentNotes : noteBlocks.values()) {
if (instrumentNotes == null) continue;
for (Map.Entry<Byte, BlockPos> 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("LiveDjPlayer: 音符盒在 {} 处在调音过程中改变了状态", blockPos);
noteBlocks = null;
tuned = false;
tuningRequested = false;
return;
}
int assumedNote = notePredictions.containsKey(blockPos) ? notePredictions.get(blockPos).getLeft() : blockState.get(Properties.NOTE);
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());
}
}
}
}
int existingUniqueNotesCount = 0;
if (noteBlocks != null) {
for(HashMap<Byte, BlockPos> instrumentNotes : noteBlocks.values()) {
if (instrumentNotes != null) {
for (BlockPos pos : instrumentNotes.values()) {
if (pos != null) {
existingUniqueNotesCount++;
}
}
}
}
}
if(untunedNotes.isEmpty() && fullyTunedBlocks == existingUniqueNotesCount) {
if(lastInteractAt == -1 || System.currentTimeMillis() - lastInteractAt >= ping * 2 + 100) {
tuned = true;
tuningRequested = false;
tuneInitialUntunedBlocks = -1;
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<Byte, BlockPos> instrumentNotes : noteBlocks.values()) {
if (instrumentNotes != null) {
for (Map.Entry<Byte, BlockPos> 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);
} else {
untunedNotes.remove(blockPosToTune);
}
} else {
untunedNotes.remove(blockPosToTune);
}
}
}
}
}
private HashMap<Byte, BlockPos> getNotesMapForInstrument(NoteBlockInstrument instrument) {
if (noteBlocks == null) {
noteBlocks = new HashMap<>();
}
return noteBlocks.computeIfAbsent(instrument, k -> new HashMap<>());
}
// 检测玩家是否可以与指定方块进行交互
public 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;
}else if(Main.config.expectedServerVersion == ModConfig.ExpectedServerVersion.v1_20_5_Or_Later) {
double blockInteractRange = player.getBlockInteractionRange() + 1.0;
return new Box(blockPos).squaredMagnitude(eyePos) < blockInteractRange * blockInteractRange;
}else if(Main.config.expectedServerVersion == ModConfig.ExpectedServerVersion.All) {
double blockInteractRange = player.getBlockInteractionRange() + 1.0;
return eyePos.squaredDistanceTo(blockPos.toCenterPos()) <= 6.0 * 6.0 && new Box(blockPos).squaredMagnitude(eyePos) < blockInteractRange * blockInteractRange;
}else {
throw new NotImplementedException("ExpectedServerVersion Value not implemented: " + Main.config.expectedServerVersion.name());
}
}
}

View File

@ -23,6 +23,7 @@ import org.lwjgl.glfw.GLFW;
import semmiedev.disc_jockey_revive.gui.hud.BlocksOverlay; import semmiedev.disc_jockey_revive.gui.hud.BlocksOverlay;
import semmiedev.disc_jockey_revive.gui.hud.PlaybackProgressOverlay; import semmiedev.disc_jockey_revive.gui.hud.PlaybackProgressOverlay;
import semmiedev.disc_jockey_revive.gui.screen.DiscJockeyScreen; import semmiedev.disc_jockey_revive.gui.screen.DiscJockeyScreen;
import semmiedev.disc_jockey_revive.gui.screen.LiveDjScreen;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@ -34,10 +35,13 @@ public class Main implements ClientModInitializer {
public static final ArrayList<ClientTickEvents.StartWorldTick> TICK_LISTENERS = new ArrayList<>(); public static final ArrayList<ClientTickEvents.StartWorldTick> TICK_LISTENERS = new ArrayList<>();
public static final Previewer PREVIEWER = new Previewer(); public static final Previewer PREVIEWER = new Previewer();
public static final SongPlayer SONG_PLAYER = new SongPlayer(); public static final SongPlayer SONG_PLAYER = new SongPlayer();
public static final LiveDjPlayer LIVE_DJ_PLAYER = new LiveDjPlayer();
public static File songsFolder; public static File songsFolder;
public static ModConfig config; public static ModConfig config;
public static ConfigHolder<ModConfig> configHolder; public static ConfigHolder<ModConfig> configHolder;
public static KeyMappingManager keyMappingManager;
@Override @Override
public void onInitializeClient() { public void onInitializeClient() {
@ -49,8 +53,12 @@ public class Main implements ClientModInitializer {
SongLoader.loadSongs(); SongLoader.loadSongs();
keyMappingManager = new KeyMappingManager();
KeyBinding openScreenKeyBind = KeyBindingHelper.registerKeyBinding(new KeyBinding(MOD_ID+".key_bind.open_screen", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_J, "key.category."+MOD_ID)); KeyBinding openScreenKeyBind = KeyBindingHelper.registerKeyBinding(new KeyBinding(MOD_ID+".key_bind.open_screen", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_J, "key.category."+MOD_ID));
KeyBinding 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() { ClientTickEvents.START_CLIENT_TICK.register(new ClientTickEvents.StartTick() {
private ClientWorld prevWorld; private ClientWorld prevWorld;
@ -59,6 +67,7 @@ public class Main implements ClientModInitializer {
if (prevWorld != client.world) { if (prevWorld != client.world) {
PREVIEWER.stop(); PREVIEWER.stop();
SONG_PLAYER.stop(); SONG_PLAYER.stop();
LIVE_DJ_PLAYER.stopTuning();
} }
prevWorld = client.world; prevWorld = client.world;
@ -70,6 +79,14 @@ public class Main implements ClientModInitializer {
client.setScreen(new DiscJockeyScreen()); client.setScreen(new DiscJockeyScreen());
} }
} }
if (openLiveDjScreenKeyBind.wasPressed()) {
if (SongLoader.loadingSongs) {
client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".still_loading").formatted(Formatting.RED));
SongLoader.showToast = true;
} else {
client.setScreen(new LiveDjScreen());
}
}
} }
}); });
@ -84,6 +101,7 @@ public class Main implements ClientModInitializer {
ClientLoginConnectionEvents.DISCONNECT.register((handler, client) -> { ClientLoginConnectionEvents.DISCONNECT.register((handler, client) -> {
PREVIEWER.stop(); PREVIEWER.stop();
SONG_PLAYER.stop(); SONG_PLAYER.stop();
LIVE_DJ_PLAYER.stopTuning();
}); });
HudRenderCallback.EVENT.register(new PlaybackProgressOverlay()); HudRenderCallback.EVENT.register(new PlaybackProgressOverlay());

View File

@ -49,4 +49,7 @@ public class ModConfig implements ConfigData {
@ConfigEntry.Gui.Tooltip(count = 1) @ConfigEntry.Gui.Tooltip(count = 1)
public boolean showHudProgressBar = true; public boolean showHudProgressBar = true;
@ConfigEntry.Gui.Tooltip(count = 1)
public boolean debugModeEnabled = false;
} }

View File

@ -0,0 +1,135 @@
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;
public class KeyMappingListWidget extends EntryListWidget<KeyMappingListWidget.KeyMappingEntry> {
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<InputUtil.Key, Note> mappings) {
this.clearEntries();
for (Map.Entry<InputUtil.Key, Note> 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<KeyMappingEntry> {
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);
}
}
}

View File

@ -29,7 +29,7 @@ public class PlaybackProgressOverlay implements HudRenderCallback {
int screenHeight = context.getScaledWindowHeight(); int screenHeight = context.getScaledWindowHeight();
int barX = screenWidth / 2 - PROGRESS_BAR_WIDTH / 2; int barX = screenWidth / 2 - PROGRESS_BAR_WIDTH / 2;
int barY = screenHeight - 50; int barY = screenHeight - 55;
progressBarRenderer.renderProgressBar( progressBarRenderer.renderProgressBar(
context, context,

View File

@ -56,14 +56,12 @@ public class DiscJockeyScreen extends Screen {
private static final MutableText private static final MutableText
OPEN_FOLDER = Text.translatable(Main.MOD_ID+".screen.open_folder"), OPEN_FOLDER = Text.translatable(Main.MOD_ID+".screen.open_folder"),
RELOAD = Text.translatable(Main.MOD_ID+".screen.reload"); RELOAD = Text.translatable(Main.MOD_ID+".screen.reload"),
LIVE_DJ = Text.translatable(Main.MOD_ID+".screen.live_dj").formatted(Formatting.GOLD);
private ButtonWidget folderUpButton, playModeButton; private ButtonWidget folderUpButton, playModeButton;
public SongFolder currentFolder; public SongFolder currentFolder;
private PlayMode currentPlayMode = PlayMode.STOP_AFTER; private PlayMode currentPlayMode = PlayMode.STOP_AFTER;
private int progressBarWidth = 200; // 进度条宽度
private int progressBarHeight = 5; // 进度条高度
private int progressBarYOffset = 5; // 进度条Y偏移
private ProgressBarRenderer progressBarRenderer; private ProgressBarRenderer progressBarRenderer;
@ -126,14 +124,14 @@ public class DiscJockeyScreen extends Screen {
if (isLargeScreen){ if (isLargeScreen){
buttonY = height - 30; buttonY = height - 30;
} else { } else {
buttonY = height - 60; buttonY = height - 30; // awa
} }
int centerX = width / 2; int centerX = width / 2;
// 上一首 // 上一首
addDrawableChild(ButtonWidget.builder(Text.literal("◀◀◀"), button -> { addDrawableChild(ButtonWidget.builder(Text.literal("◀◀◀"), button -> {
Main.SONG_PLAYER.playPreviousSong(); Main.SONG_PLAYER.playPreviousSong();
}).dimensions(centerX - 110, buttonY, 40, 20).build()); }).dimensions(centerX - 100, buttonY, 40, 20).build());
// 播放暂停 // 播放暂停
playButton = ButtonWidget.builder(PLAY, button -> { playButton = ButtonWidget.builder(PLAY, button -> {
@ -168,6 +166,8 @@ public class DiscJockeyScreen extends Screen {
int bottomY = height - 30; int bottomY = height - 30;
// 音符盒
if (isLargeScreen) {
addDrawableChild(ButtonWidget.builder(Text.translatable(Main.MOD_ID + ".screen.blocks"), button -> { addDrawableChild(ButtonWidget.builder(Text.translatable(Main.MOD_ID + ".screen.blocks"), button -> {
// TODO: 6/2/2022 Add an auto build mode // TODO: 6/2/2022 Add an auto build mode
if (BlocksOverlay.itemStacks == null) { if (BlocksOverlay.itemStacks == null) {
@ -206,6 +206,7 @@ public class DiscJockeyScreen extends Screen {
client.setScreen(null); client.setScreen(null);
} }
}).dimensions(width - 110, height - 31, 100, 20).build()); }).dimensions(width - 110, height - 31, 100, 20).build());
}
// 打开文件夹 // 打开文件夹
addDrawableChild(ButtonWidget.builder(OPEN_FOLDER, button -> { addDrawableChild(ButtonWidget.builder(OPEN_FOLDER, button -> {
@ -238,11 +239,22 @@ public class DiscJockeyScreen extends Screen {
// 重新加载 // 重新加载
if (isLargeScreen) {
addDrawableChild(ButtonWidget.builder(RELOAD, button -> { addDrawableChild(ButtonWidget.builder(RELOAD, button -> {
SongLoader.loadSongs(); SongLoader.loadSongs();
client.setScreen(null); client.setScreen(null);
}).dimensions(120, bottomY, 100, 20).build()); }).dimensions(120, bottomY, 100, 20).build());
}
// 即兴演奏
if (isLargeScreen) {
addDrawableChild(ButtonWidget.builder(LIVE_DJ, button -> {
client.setScreen(new LiveDjScreen());
}).dimensions(width - 220, bottomY, 100, 20).build());
}
// 搜索框
if (isLargeScreen) {
TextFieldWidget searchBar = new TextFieldWidget(textRenderer, 230, height - 31, 100, 20, Text.translatable(Main.MOD_ID + ".screen.search")); TextFieldWidget searchBar = new TextFieldWidget(textRenderer, 230, height - 31, 100, 20, Text.translatable(Main.MOD_ID + ".screen.search"));
searchBar.setChangedListener(query -> { searchBar.setChangedListener(query -> {
query = query.toLowerCase().replaceAll("\\s", ""); query = query.toLowerCase().replaceAll("\\s", "");
@ -251,6 +263,7 @@ public class DiscJockeyScreen extends Screen {
shouldFilter = true; shouldFilter = true;
}); });
addDrawableChild(searchBar); addDrawableChild(searchBar);
}
// Main.LOGGER.info("播放界面初始化完成!"); // Main.LOGGER.info("播放界面初始化完成!");
@ -328,7 +341,7 @@ public class DiscJockeyScreen extends Screen {
} }
} }
// 只有在songs目录或其子目录中才显示歌曲(原作者的💩跑我这) // 只有在songs目录或其子目录中才显示歌曲(在搞了在搞)
if (isInSongsOrSubfolder) { if (isInSongsOrSubfolder) {
// 歌曲条目 // 歌曲条目
List<Song> songsToShow = currentFolder == null ? List<Song> songsToShow = currentFolder == null ?

View File

@ -0,0 +1,146 @@
package semmiedev.disc_jockey_revive.gui.screen;
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.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;
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;
}
}

View File

@ -0,0 +1,179 @@
package semmiedev.disc_jockey_revive.gui.screen;
import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.annotation.Nullable;
import net.minecraft.block.Block;
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.util.InputUtil;
import net.minecraft.text.Text;
import semmiedev.disc_jockey_revive.DebugLogger;
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.LIVE_DJ_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.LIVE_DJ_PLAYER.getNoteBlocks() == null) {
tuningStatusText = Text.translatable(Main.MOD_ID + ".player.discovering").formatted(net.minecraft.util.Formatting.YELLOW);
startTuningButton.active = true;
startTuningButton.visible = true;
} else if (!Main.LIVE_DJ_PLAYER.isTuned()) {
int totalNeeded = 0;
if (Main.LIVE_DJ_PLAYER.getNoteBlocks() != null) {
for (HashMap<Byte, net.minecraft.util.math.BlockPos> instrumentNotes : Main.LIVE_DJ_PLAYER.getNoteBlocks().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.LIVE_DJ_PLAYER.getNoteBlocks() != null) {
for (HashMap<Byte, net.minecraft.util.math.BlockPos> instrumentNotes : Main.LIVE_DJ_PLAYER.getNoteBlocks().values()) {
if (instrumentNotes != null) {
for (Map.Entry<Byte, net.minecraft.util.math.BlockPos> 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);
if (!Main.LIVE_DJ_PLAYER.missingInstrumentBlocks.isEmpty()) {
int yOffset = 70;
context.drawCenteredTextWithShadow(textRenderer, Text.translatable(Main.MOD_ID+".player.invalid_note_blocks").formatted(net.minecraft.util.Formatting.RED), this.width / 2, yOffset, 0xFFFFFF);
yOffset += 12;
for (Map.Entry<Block, Integer> entry : Main.LIVE_DJ_PLAYER.missingInstrumentBlocks.entrySet()) {
context.drawCenteredTextWithShadow(textRenderer, Text.literal(entry.getKey().getName().getString()+" × "+entry.getValue()).formatted(net.minecraft.util.Formatting.RED), this.width / 2, yOffset, 0xFFFFFF);
yOffset += 10;
}
}
}
@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.LIVE_DJ_PLAYER.isTuned()) {
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(NOT_TUNED_MESSAGE);
return true;
}
boolean played = Main.LIVE_DJ_PLAYER.playNoteBlock(note);
if (!played) {
@Nullable net.minecraft.util.math.BlockPos blockPos = null;
if (Main.LIVE_DJ_PLAYER.getNoteBlocks() != null && Main.LIVE_DJ_PLAYER.getNoteBlocks().containsKey(note.instrument())) {
blockPos = Main.LIVE_DJ_PLAYER.getNoteBlocks().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.LIVE_DJ_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.LIVE_DJ_PLAYER.isTuned();
startTuningButton.active = !Main.LIVE_DJ_PLAYER.isTuningActive();
}
}
@Override
public boolean shouldPause() {
return false;
}
@Override
public void close() {
super.close();
Main.LIVE_DJ_PLAYER.stopTuning();
}
}

View File

@ -0,0 +1,173 @@
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<NoteBlockInstrument> 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);
}
}

View File

@ -63,5 +63,35 @@
"disc_jockey_revive.screen.reload": "Reload Songs", "disc_jockey_revive.screen.reload": "Reload Songs",
"disc_jockey_revive.screen.reloading": "Reloading songs...", "disc_jockey_revive.screen.reloading": "Reloading songs...",
"text.autoconfig.disc_jockey_revive.option.showHudProgressBar": "Show HUD progress bar", "text.autoconfig.disc_jockey_revive.option.showHudProgressBar": "Show HUD progress bar",
"text.autoconfig.disc_jockey_revive.option.showHudProgressBar.@Tooltip": "Show song playback progress bar in game。" "text.autoconfig.disc_jockey_revive.option.showHudProgressBar.@Tooltip": "Show song playback progress bar in game。",
"disc_jockey_revive.key_bind.open_live_dj_screen": "Open Live DJ Screen",
"disc_jockey_revive.screen.live_dj": "Live DJ",
"disc_jockey_revive.screen.live_dj.title": "Live DJ Performance",
"disc_jockey_revive.screen.live_dj.instructions": "Press mapped keys to play notes. Tune note blocks first!",
"disc_jockey_revive.screen.live_dj.edit_mappings": "Edit Key Mappings",
"disc_jockey_revive.screen.live_dj.start_tuning": "Start Tuning",
"disc_jockey_revive.screen.edit_mappings.title": "Edit Key Mappings",
"disc_jockey_revive.screen.edit_mappings.add_mapping": "Add New 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 a key to map...",
"disc_jockey_revive.screen.select_note.title": "Select Note for Key",
"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",
"disc_jockey_revive.screen.select_note.mapping_key": "Mapping Key: %s",
"disc_jockey_revive.player.not_tuned": "Note blocks are not tuned yet!",
"disc_jockey_revive.player.note_block_missing_live": "Missing note block for this note!",
"disc_jockey_revive.player.to_far_live": "You are too far from the note block!",
"disc_jockey_revive.player.rate_limited_live": "Rate limited by server, try again soon.",
"disc_jockey_revive.player.discovering": "Discovering note blocks...",
"disc_jockey_revive.player.tuning_progress": "Tuning: %s/%s tuned",
"disc_jockey_revive.player.finding_blocks": "Finding note blocks...",
"disc_jockey_revive.player.retuning": "Retuning note blocks...",
"disc_jockey_revive.player.tuning_started": "Tuning started. Please wait...",
"disc_jockey_revive.player.invalid_state_tuning": "Cannot tune: Invalid game state or mode.",
"disc_jockey_revive.player.tuned": "Tuning completed。",
"text.autoconfig.disc_jockey_revive.option.debugModeEnabled": "Enable Debug Mode",
"text.autoconfig.disc_jockey_revive.option.debugModeEnabled.@Tooltip": "Enables verbose logging for debugging purposes. \nKeep off unless troubleshooting. \nOf course, many debug-related parts have already been removed."
} }

View File

@ -10,7 +10,7 @@
"disc_jockey_revive.screen.drop_hint": "将歌曲文件拖入此窗口以添加", "disc_jockey_revive.screen.drop_hint": "将歌曲文件拖入此窗口以添加",
"disc_jockey_revive.screen.drop_confirm": "是否将以下歌曲添加到 Disc Jockey", "disc_jockey_revive.screen.drop_confirm": "是否将以下歌曲添加到 Disc Jockey",
"disc_jockey_revive.player.invalid_note_blocks": "附近的音符盒配置不正确。缺失:", "disc_jockey_revive.player.invalid_note_blocks": "附近的音符盒配置不正确。缺失:",
"disc_jockey_revive.player.invalid_game_mode": "无法在 %s 模式下播放", "disc_jockey_revive.player.invalid_game_mode": "无法在 %s 下播放",
"disc_jockey_revive.player.to_far": "你距离太远了", "disc_jockey_revive.player.to_far": "你距离太远了",
"disc_jockey_revive.still_loading": "歌曲仍在加载中", "disc_jockey_revive.still_loading": "歌曲仍在加载中",
"disc_jockey_revive.reloading": "正在重新加载所有歌曲", "disc_jockey_revive.reloading": "正在重新加载所有歌曲",
@ -57,10 +57,41 @@
"disc_jockey_revive.screen.mode_single": "单曲循环", "disc_jockey_revive.screen.mode_single": "单曲循环",
"disc_jockey_revive.screen.mode_list": "列表循环", "disc_jockey_revive.screen.mode_list": "列表循环",
"disc_jockey_revive.screen.mode_random": "随机播放", "disc_jockey_revive.screen.mode_random": "随机播放",
"disc_jockey_revive.screen.mode_stop": "播完停止","disc_jockey_revive.screen.open_folder": "打开文件夹", "disc_jockey_revive.screen.mode_stop": "播完停止",
"disc_jockey_revive.screen.open_folder": "打开文件夹",
"disc_jockey_revive.screen.open_folder_failed": "无法打开文件夹", "disc_jockey_revive.screen.open_folder_failed": "无法打开文件夹",
"disc_jockey_revive.screen.reload": "重新加载", "disc_jockey_revive.screen.reload": "重新加载",
"disc_jockey_revive.screen.reloading": "正在重新加载...", "disc_jockey_revive.screen.reloading": "正在重新加载...",
"text.autoconfig.disc_jockey_revive.option.showHudProgressBar": "显示HUD进度条", "text.autoconfig.disc_jockey_revive.option.showHudProgressBar": "显示HUD进度条",
"text.autoconfig.disc_jockey_revive.option.showHudProgressBar.@Tooltip": "在游戏界面显示歌曲播放进度条" "text.autoconfig.disc_jockey_revive.option.showHudProgressBar.@Tooltip": "在游戏界面显示歌曲播放进度条",
"disc_jockey_revive.key_bind.open_live_dj_screen": "打开即兴演奏界面",
"disc_jockey_revive.screen.live_dj": "即兴演奏",
"disc_jockey_revive.screen.live_dj.title": "即兴演奏",
"disc_jockey_revive.screen.live_dj.instructions": "按下映射的按键来演奏音符。请先调音音符盒!",
"disc_jockey_revive.screen.live_dj.edit_mappings": "编辑按键映射",
"disc_jockey_revive.screen.live_dj.start_tuning": "开始调音",
"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.instrument": "乐器",
"disc_jockey_revive.screen.select_note.pitch": "音高",
"disc_jockey_revive.screen.select_note.preview": "试听音符",
"disc_jockey_revive.screen.select_note.mapping_key": "映射按键:%s",
"disc_jockey_revive.player.not_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.player.discovering": "正在发现音符盒...",
"disc_jockey_revive.player.tuning_progress": "调音中:%s/%s 已调音",
"disc_jockey_revive.player.finding_blocks": "正在寻找音符盒...",
"disc_jockey_revive.player.retuning": "正在重新调音音符盒...",
"disc_jockey_revive.player.tuning_started": "调音已开始。请稍候...",
"disc_jockey_revive.player.invalid_state_tuning": "无法调音:游戏状态或模式无效。",
"disc_jockey_revive.player.tuned": "调音完成。",
"text.autoconfig.disc_jockey_revive.option.debugModeEnabled": "启用调试模式",
"text.autoconfig.disc_jockey_revive.option.debugModeEnabled.@Tooltip": "启用详细日志输出以进行调试。\n除非排查问题否则请保持关闭。\n当然很多调试用的部分已经移除。"
} }