TaT
This commit is contained in:
parent
9aae601fc8
commit
727da9f523
@ -29,6 +29,7 @@ dependencies {
|
||||
|
||||
// modCompileOnly("com.terraformersmc:modmenu:13.0.3")、
|
||||
modCompileOnly files("libs/modmenu-13.0.3.jar")
|
||||
modImplementation files("libs/LibGui-12.0.1+1.21.2.jar")
|
||||
}
|
||||
|
||||
processResources {
|
||||
|
@ -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.022
|
||||
mod_version=1.14.514.028
|
||||
maven_group=semmiedev
|
||||
archives_base_name=disc_jockey_revive
|
||||
# Dependencies
|
||||
|
78
libs/LibGui-12.0.1+1.21.2.pom
Normal file
78
libs/LibGui-12.0.1+1.21.2.pom
Normal file
@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<!-- This module was also published with a richer model, Gradle metadata, -->
|
||||
<!-- which should be used instead. Do not delete the following line which -->
|
||||
<!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
|
||||
<!-- that they should prefer consuming it instead. -->
|
||||
<!-- do_not_remove: published-with-gradle-metadata -->
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>io.github.cottonmc</groupId>
|
||||
<artifactId>LibGui</artifactId>
|
||||
<version>12.0.1+1.21.2</version>
|
||||
<name>LibGui</name>
|
||||
<description>Minecraft GUI Library</description>
|
||||
<url>https://github.com/CottonMC/LibGui</url>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT</name>
|
||||
<url>https://github.com/CottonMC/LibGui/blob/HEAD/LICENSE</url>
|
||||
</license>
|
||||
</licenses>
|
||||
<developers>
|
||||
<developer>
|
||||
<name>CottonMC</name>
|
||||
<url>https://github.com/CottonMC</url>
|
||||
</developer>
|
||||
</developers>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.github.juuxel</groupId>
|
||||
<artifactId>libninepatch</artifactId>
|
||||
<version>1.2.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.fabricmc.fabric-api</groupId>
|
||||
<artifactId>fabric-api-base</artifactId>
|
||||
<version>0.4.48+c47b9d4373</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.fabricmc.fabric-api</groupId>
|
||||
<artifactId>fabric-networking-api-v1</artifactId>
|
||||
<version>4.3.3+56ec7ac673</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.fabricmc</groupId>
|
||||
<artifactId>fabric-loader</artifactId>
|
||||
<version>0.16.7</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.fabricmc.fabric-api</groupId>
|
||||
<artifactId>fabric-lifecycle-events-v1</artifactId>
|
||||
<version>2.3.22+c47b9d4373</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.fabricmc.fabric-api</groupId>
|
||||
<artifactId>fabric-rendering-v1</artifactId>
|
||||
<version>8.0.5+c47b9d4373</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.fabricmc.fabric-api</groupId>
|
||||
<artifactId>fabric-resource-loader-v0</artifactId>
|
||||
<version>3.0.5+c47b9d4373</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.cottonmc</groupId>
|
||||
<artifactId>Jankson-Fabric</artifactId>
|
||||
<version>9.0.0+j1.2.3</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -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 + ")";
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ 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;
|
||||
@ -22,6 +23,7 @@ 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;
|
||||
@ -33,11 +35,15 @@ public class Main implements ClientModInitializer {
|
||||
public static final ArrayList<ClientTickEvents.StartWorldTick> 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<ModConfig> configHolder;
|
||||
|
||||
private static KeyBinding openLiveDjScreenKeyBind;
|
||||
|
||||
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
configHolder = AutoConfig.register(ModConfig.class, JanksonConfigSerializer::new);
|
||||
@ -46,10 +52,14 @@ 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;
|
||||
|
||||
@ -69,11 +79,22 @@ 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 -> {
|
||||
for (ClientTickEvents.StartWorldTick listener : TICK_LISTENERS) listener.onStartTick(world);
|
||||
ArrayList<ClientTickEvents.StartWorldTick> listenersCopy = new ArrayList<>(TICK_LISTENERS);
|
||||
for (ClientTickEvents.StartWorldTick listener : listenersCopy) {
|
||||
if (TICK_LISTENERS.contains(listener)) {
|
||||
listener.onStartTick(world);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
|
||||
@ -86,5 +107,9 @@ public class Main implements ClientModInitializer {
|
||||
});
|
||||
|
||||
HudRenderCallback.EVENT.register(BlocksOverlay::render);
|
||||
|
||||
ClientLifecycleEvents.CLIENT_STOPPING.register(client -> {
|
||||
keyMappingManager.saveMappings();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ public class SongLoader {
|
||||
song.folder = songFolder;
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
Main.LOGGER.error("Unable to read or parse song {}", file.getName(), exception);
|
||||
Main.LOGGER.error("无法读取或解析歌曲 {}", file.getName(), exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,9 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
|
||||
public boolean running;
|
||||
public Song song;
|
||||
|
||||
private int index;
|
||||
private double tick; // Aka song position
|
||||
private HashMap<NoteBlockInstrument, HashMap<Byte, BlockPos>> noteBlocks = null;
|
||||
public int index;
|
||||
public double tick;
|
||||
public HashMap<NoteBlockInstrument, HashMap<Byte, BlockPos>> noteBlocks = null;
|
||||
public boolean tuned;
|
||||
private long lastPlaybackTickAt = -1L;
|
||||
|
||||
@ -63,7 +63,10 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
|
||||
private HashMap<BlockPos, Pair<Integer, Long>> notePredictions = new HashMap<>();
|
||||
public boolean didSongReachEnd = false;
|
||||
public boolean loopSong = false;
|
||||
private long pausePlaybackUntil = -1L; // Set after tuning, if configured
|
||||
private long pausePlaybackUntil = -1L;
|
||||
|
||||
private boolean manualTuningRequested = false;
|
||||
|
||||
|
||||
public SongPlayer() {
|
||||
Main.TICK_LISTENERS.add(this);
|
||||
@ -85,7 +88,7 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
|
||||
}catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
tickPlayback();
|
||||
MinecraftClient.getInstance().executeSync(this::tickPlayback);
|
||||
}
|
||||
});
|
||||
this.playbackThread.start();
|
||||
@ -98,7 +101,7 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
|
||||
STOP_AFTER // 播完就停
|
||||
}
|
||||
|
||||
private PlayMode playMode = PlayMode.STOP_AFTER;
|
||||
public PlayMode playMode = PlayMode.STOP_AFTER;
|
||||
private boolean isRandomPlaying = false;
|
||||
private int randomIndex = -1;
|
||||
|
||||
@ -114,17 +117,16 @@ 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) {
|
||||
@ -150,108 +152,184 @@ 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 tickPlayback() {
|
||||
if (!running) {
|
||||
lastPlaybackTickAt = -1L;
|
||||
last100MsSpanAt = -1L;
|
||||
return;
|
||||
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));
|
||||
}
|
||||
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;
|
||||
}
|
||||
if(noteBlocks != null && tuned) {
|
||||
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;
|
||||
noteBlocks = null;
|
||||
notePredictions.clear();
|
||||
tuned = false;
|
||||
tuneInitialUntunedBlocks = -1;
|
||||
manualTuningRequested = true;
|
||||
}
|
||||
|
||||
long note = song.notes[index];
|
||||
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));
|
||||
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) {
|
||||
// Instrument got likely mapped to "nothing". Skip it
|
||||
index++;
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
if (!canInteractWith(client.player, blockPos)) {
|
||||
stop();
|
||||
client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".player.to_far").formatted(Formatting.RED));
|
||||
return;
|
||||
|
||||
if (!canInteractWith(player, blockPos)) {
|
||||
return false;
|
||||
}
|
||||
Vec3d unit = Vec3d.ofCenter(blockPos, 0.5).subtract(client.player.getEyePos()).normalize();
|
||||
if((lastLookSentAt == -1L || now - lastLookSentAt >= 50) && last100MsSpanEstimatedPackets < last100MsReducePacketsAfter && (reducePacketsUntil == -1L || reducePacketsUntil < now)) {
|
||||
|
||||
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;
|
||||
}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++;
|
||||
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) {
|
||||
lastPlaybackTickAt = -1L;
|
||||
// last100MsSpanAt = -1L;
|
||||
return;
|
||||
}
|
||||
|
||||
if (pausePlaybackUntil != -1L) {
|
||||
if (System.currentTimeMillis() <= pausePlaybackUntil) {
|
||||
return;
|
||||
} else {
|
||||
tick = 0;
|
||||
index = 0;
|
||||
pausePlaybackUntil = -1L;
|
||||
lastPlaybackTickAt = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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);
|
||||
|
||||
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(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;
|
||||
if (index >= song.notes.length) {
|
||||
stop();
|
||||
didSongReachEnd = true;
|
||||
if (playMode == PlayMode.SINGLE_LOOP) {
|
||||
} else if (playMode == PlayMode.LIST_LOOP || playMode == PlayMode.RANDOM) {
|
||||
playNextSong();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -260,7 +338,12 @@ 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) return;
|
||||
if (currentIndex == -1) {
|
||||
if (!SongLoader.currentFolder.songs.isEmpty()) {
|
||||
start(SongLoader.currentFolder.songs.get(0));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (playMode == PlayMode.RANDOM) {
|
||||
int newIndex;
|
||||
@ -279,14 +362,30 @@ 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;
|
||||
if(song == null || !running) 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();
|
||||
}
|
||||
|
||||
|
||||
// Clear outdated note predictions
|
||||
ArrayList<BlockPos> outdatedPredictions = new ArrayList<>();
|
||||
@ -296,18 +395,28 @@ 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<NoteBlockInstrument, ArrayList<BlockPos>> 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
|
||||
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) {
|
||||
@ -342,13 +451,11 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
|
||||
}
|
||||
}
|
||||
|
||||
// Remap instruments for funzies
|
||||
if(!instrumentMap.isEmpty()) {
|
||||
HashMap<NoteBlockInstrument, ArrayList<BlockPos>> 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;
|
||||
}
|
||||
@ -358,21 +465,47 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
|
||||
noteblocksForInstrument = newNoteblocksForInstrument;
|
||||
}
|
||||
|
||||
// Find fitting noteblocks with the least amount of adjustments required (to reduce tuning time)
|
||||
ArrayList<Note> capturedNotes = new ArrayList<>();
|
||||
for(Note note : song.uniqueNotes) {
|
||||
|
||||
ArrayList<Note> 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<BlockPos> 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) {
|
||||
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();
|
||||
int currentNote = client.world.getBlockState(blockPos).get(Properties.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) {
|
||||
@ -385,10 +518,10 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
|
||||
capturedNotes.add(note);
|
||||
availableBlocks.remove(bestBlockPos);
|
||||
getNotes(note.instrument()).put(note.note(), bestBlockPos);
|
||||
} // else will be a missing note
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<Note> missingNotes = new ArrayList<>(song.uniqueNotes);
|
||||
ArrayList<Note> missingNotes = new ArrayList<>(neededNotes);
|
||||
missingNotes.removeAll(capturedNotes);
|
||||
if (!missingNotes.isEmpty()) {
|
||||
ChatHud chatHud = MinecraftClient.getInstance().inGameHud.getChatHud();
|
||||
@ -397,7 +530,7 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
|
||||
HashMap<Block, Integer> missing = new HashMap<>();
|
||||
for (Note note : missingNotes) {
|
||||
NoteBlockInstrument mappedInstrument = instrumentMap.getOrDefault(note.instrument(), note.instrument());
|
||||
if(mappedInstrument == null) continue; // Ignore if mapped to nothing
|
||||
if(mappedInstrument == null) continue;
|
||||
Block block = Note.INSTRUMENT_BLOCKS.get(mappedInstrument);
|
||||
Integer got = missing.get(block);
|
||||
if (got == null) got = 0;
|
||||
@ -406,10 +539,13 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
|
||||
|
||||
missingInstrumentBlocks = missing;
|
||||
missing.forEach((block, integer) -> chatHud.addMessage(Text.literal(block.getName().getString()+" × "+integer).formatted(Formatting.RED)));
|
||||
stop();
|
||||
|
||||
} else {
|
||||
missingInstrumentBlocks.clear();
|
||||
}
|
||||
} else if (!tuned) {
|
||||
//tuned = true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
int ping = 0;
|
||||
{
|
||||
@ -418,112 +554,119 @@ 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<BlockPos, Integer> 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 (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));
|
||||
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("注意 {} 处音符盒在调整过程中改变了状态", blockPos);
|
||||
noteBlocks = null;
|
||||
tuned = false;
|
||||
manualTuningRequested = false;
|
||||
return;
|
||||
}
|
||||
untunedNotes.put(blockPos, blockState.get(Properties.NOTE));
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
noteBlocks = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(tuneInitialUntunedBlocks == -1 || tuneInitialUntunedBlocks < untunedNotes.size())
|
||||
tuneInitialUntunedBlocks = untunedNotes.size();
|
||||
|
||||
int existingUniqueNotesCount = 0;
|
||||
for(Note n : song.uniqueNotes) {
|
||||
if(noteBlocks.get(n.instrument()).get(n.note()) != null)
|
||||
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) {
|
||||
// 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;
|
||||
}
|
||||
tuneInitialUntunedBlocks = -1;
|
||||
// Tuning finished
|
||||
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<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;
|
||||
}
|
||||
}
|
||||
|
||||
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<BlockPos, Integer> entry : untunedNotes.entrySet()) {
|
||||
if (entry.getValue() > lastTunedNote) {
|
||||
blockPos = entry.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Find higher note or equal
|
||||
if (blockPos == null) {
|
||||
for (Map.Entry<BlockPos, Integer> 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!
|
||||
if (targetNote != null) {
|
||||
byte targetNotePrimitive = targetNote.byteValue();
|
||||
int tuningSteps = targetNotePrimitive >= currentNote ? targetNotePrimitive - currentNote : (25 - currentNote) + targetNotePrimitive;
|
||||
|
||||
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));
|
||||
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;
|
||||
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);
|
||||
|
||||
int totalUntuned = untunedNotes.size();
|
||||
int tunedCount = existingUniqueNotesCount - totalUntuned;
|
||||
|
||||
} else {
|
||||
|
||||
untunedNotes.remove(blockPosToTune);
|
||||
}
|
||||
}else if((playbackThread == null || !playbackThread.isAlive()) && running && Main.config.disableAsyncPlayback) {
|
||||
// Sync playback (off by default). Replacement for playback thread
|
||||
} else {
|
||||
|
||||
untunedNotes.remove(blockPosToTune); // Remove to avoid infinite loop
|
||||
}
|
||||
} else if (!untunedNotes.isEmpty()) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
if((playbackThread == null || !playbackThread.isAlive()) && running && Main.config.disableAsyncPlayback) {
|
||||
try {
|
||||
tickPlayback();
|
||||
}catch (Exception ex) {
|
||||
@ -534,13 +677,16 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
|
||||
}
|
||||
|
||||
private HashMap<Byte, BlockPos> 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).
|
||||
private boolean canInteractWith(ClientPlayerEntity player, BlockPos blockPos) {
|
||||
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;
|
||||
|
@ -0,0 +1,136 @@
|
||||
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<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);
|
||||
}
|
||||
}
|
||||
}
|
@ -9,12 +9,14 @@ 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;
|
||||
@ -26,6 +28,7 @@ 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,9 +41,11 @@ public class DiscJockeyScreen extends Screen {
|
||||
;
|
||||
|
||||
private SongListWidget songListWidget;
|
||||
private ButtonWidget playButton, previewButton;
|
||||
private ButtonWidget playButton, previewButton, prevButton, nextButton;
|
||||
public boolean shouldFilter;
|
||||
private String query = "";
|
||||
private ProgressBarWidget progressBar;
|
||||
private TextFieldWidget searchBar;
|
||||
|
||||
private static final MutableText
|
||||
FOLDER_UP = Text.literal("↑"),
|
||||
@ -66,39 +71,137 @@ 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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 添加文件夹条目
|
||||
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);
|
||||
int listTop = 40;
|
||||
int listBottom = height - 100;
|
||||
int listHeight = listBottom - listTop;
|
||||
songListWidget = new SongListWidget(client, width, height - 100, 40, 20, this);
|
||||
addDrawableChild(songListWidget);
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}).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) {
|
||||
currentFolder = null;
|
||||
SongLoader.currentFolder = null;
|
||||
SongFolder parent = findParentFolder(currentFolder);
|
||||
currentFolder = parent;
|
||||
SongLoader.currentFolder = parent;
|
||||
shouldFilter = true;
|
||||
}
|
||||
}).dimensions(10, 10, 20, 20).build();
|
||||
addDrawableChild(folderUpButton);
|
||||
|
||||
|
||||
playModeButton = ButtonWidget.builder(getPlayModeText(), button -> {
|
||||
switch (currentPlayMode) {
|
||||
case SINGLE_LOOP -> currentPlayMode = PlayMode.LIST_LOOP;
|
||||
@ -111,41 +214,47 @@ public class DiscJockeyScreen extends Screen {
|
||||
}).dimensions(width - 120, 10, 100, 20).build();
|
||||
addDrawableChild(playModeButton);
|
||||
|
||||
playButton = ButtonWidget.builder(PLAY, button -> {
|
||||
if (Main.SONG_PLAYER.running) {
|
||||
Main.SONG_PLAYER.stop();
|
||||
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);
|
||||
|
||||
if (song.folder != null) {
|
||||
boolean folderEntryExists = SongLoader.FOLDERS.stream()
|
||||
.anyMatch(f -> f == song.folder || findFolderByPathInSubfolders(f, song.folder.path) != null);
|
||||
if (!folderEntryExists) {
|
||||
} else {
|
||||
SongListWidget.SongEntry entry = songListWidget.getSelectedSongOrNull();
|
||||
if (entry != null) {
|
||||
Main.SONG_PLAYER.start(entry.song);
|
||||
client.setScreen(null);
|
||||
if (song.folder.entry == null) {
|
||||
song.folder.entry = new SongListWidget.FolderEntry(song.folder, songListWidget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).dimensions(width / 2 - 160, height - 61, 100, 20).build();
|
||||
addDrawableChild(playButton);
|
||||
|
||||
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);
|
||||
// 播放进度
|
||||
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 = entry.song.uniqueNotes.size();
|
||||
BlocksOverlay.amountOfNoteBlocks = song.uniqueNotes.size();
|
||||
|
||||
for (Note note : entry.song.uniqueNotes) {
|
||||
for (Note note : song.uniqueNotes) {
|
||||
ItemStack itemStack = Note.INSTRUMENT_BLOCKS.get(note.instrument()).asItem().getDefaultStack();
|
||||
int index = -1;
|
||||
|
||||
@ -167,18 +276,12 @@ public class DiscJockeyScreen extends Screen {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BlocksOverlay.itemStacks = null;
|
||||
client.setScreen(null);
|
||||
}
|
||||
}).dimensions(width / 2 + 60, height - 61, 100, 20).build());
|
||||
|
||||
// 打开文件夹
|
||||
addDrawableChild(ButtonWidget.builder(OPEN_FOLDER, button -> {
|
||||
private void openSongsFolder() {
|
||||
try {
|
||||
String folderPath = currentFolder != null ?
|
||||
currentFolder.path :
|
||||
Main.songsFolder.getAbsolutePath(); // 使用绝对路径
|
||||
currentFolder.path : Main.songsFolder.getAbsolutePath();
|
||||
|
||||
File target = new File(folderPath);
|
||||
if (!target.exists()) {
|
||||
@ -188,7 +291,6 @@ public class DiscJockeyScreen extends Screen {
|
||||
return;
|
||||
}
|
||||
|
||||
// Windows 专用命令,其他的不会
|
||||
if (System.getProperty("os.name").toLowerCase().contains("win")) {
|
||||
new ProcessBuilder("explorer.exe", "/select,", target.getAbsolutePath()).start();
|
||||
} else {
|
||||
@ -200,42 +302,112 @@ public class DiscJockeyScreen extends Screen {
|
||||
Text.translatable(Main.MOD_ID+".screen.open_folder_failed")
|
||||
.formatted(Formatting.RED));
|
||||
}
|
||||
}).dimensions(width / 2 - 160, height - 31, 100, 20).build());
|
||||
|
||||
|
||||
// 重新加载
|
||||
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);
|
||||
|
||||
@ -245,7 +417,7 @@ public class DiscJockeyScreen extends Screen {
|
||||
boolean empty = query.isEmpty();
|
||||
|
||||
boolean isInSongsOrSubfolder = currentFolder == null ||
|
||||
currentFolder.path.startsWith(Main.songsFolder.getPath());
|
||||
(Main.songsFolder != null && currentFolder.path.startsWith(Main.songsFolder.getPath()));
|
||||
|
||||
if (currentFolder == null) {
|
||||
for (SongFolder folder : SongLoader.FOLDERS) {
|
||||
@ -257,12 +429,10 @@ 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) {
|
||||
@ -273,9 +443,7 @@ public class DiscJockeyScreen extends Screen {
|
||||
}
|
||||
}
|
||||
|
||||
// 只有在songs目录或其子目录中才显示歌曲(原作者的💩跑我这了)
|
||||
if (isInSongsOrSubfolder) {
|
||||
// 歌曲条目
|
||||
List<Song> songsToShow = currentFolder == null ?
|
||||
SongLoader.SONGS.stream()
|
||||
.filter(song -> song.folder == null)
|
||||
@ -284,14 +452,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);
|
||||
@ -301,40 +469,7 @@ 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<Path> paths) {
|
||||
String string = paths.stream().map(Path::getFileName).map(Path::toString).collect(Collectors.joining(", "));
|
||||
@ -350,15 +485,14 @@ public class DiscJockeyScreen extends Screen {
|
||||
|
||||
Song song = SongLoader.loadSong(file);
|
||||
if (song != null) {
|
||||
Files.copy(path, Main.songsFolder.toPath().resolve(file.getName()));
|
||||
SongLoader.SONGS.add(song);
|
||||
File targetFolder = currentFolder != null ? new File(currentFolder.path) : Main.songsFolder;
|
||||
Files.copy(path, targetFolder.toPath().resolve(file.getName()));
|
||||
SongLoader.loadSongs();
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
Main.LOGGER.warn("Failed to copy song file from {} to {}", path, Main.songsFolder.toPath(), exception);
|
||||
Main.LOGGER.warn("无法将歌曲文件从 {} 复制到 {} ", path, Main.songsFolder.toPath(), exception);
|
||||
}
|
||||
});
|
||||
|
||||
SongLoader.sort();
|
||||
}
|
||||
client.setScreen(this);
|
||||
}, Text.translatable(Main.MOD_ID+".screen.drop_confirm"), Text.literal(string)));
|
||||
@ -372,27 +506,11 @@ 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)) {
|
||||
@ -409,6 +527,7 @@ 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)) {
|
||||
@ -422,6 +541,38 @@ 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;
|
||||
|
@ -0,0 +1,150 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,175 @@
|
||||
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<Byte, net.minecraft.util.math.BlockPos> 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<Byte, net.minecraft.util.math.BlockPos> instrumentNotes : Main.SONG_PLAYER.noteBlocks.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);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -61,5 +61,54 @@
|
||||
"disc_jockey_revive.screen.open_folder": "Open Folder",
|
||||
"disc_jockey_revive.screen.open_folder_failed": "Failed to open folder",
|
||||
"disc_jockey_revive.screen.reload": "Reload Songs",
|
||||
"disc_jockey_revive.screen.reloading": "Reloading songs..."
|
||||
"disc_jockey_revive.screen.reloading": "Reloading songs...",
|
||||
|
||||
"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..."
|
||||
}
|
@ -60,5 +60,54 @@
|
||||
"disc_jockey_revive.screen.mode_stop": "播完停止","disc_jockey_revive.screen.open_folder": "打开文件夹",
|
||||
"disc_jockey_revive.screen.open_folder_failed": "无法打开文件夹",
|
||||
"disc_jockey_revive.screen.reload": "重新加载",
|
||||
"disc_jockey_revive.screen.reloading": "正在重新加载..."
|
||||
"disc_jockey_revive.screen.reloading": "正在重新加载...",
|
||||
|
||||
"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": "正在重新调音音符盒..."
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
"schemaVersion": 1,
|
||||
"id": "disc_jockey_revive",
|
||||
"version": "${version}",
|
||||
"name": "Disc Jockey",
|
||||
"name": "Disc Jockey Revive",
|
||||
"description": "在游戏中播放音符盒(打碟机)",
|
||||
"authors": [
|
||||
"SemmieDev",
|
||||
@ -10,11 +10,11 @@
|
||||
"BRanulf(仅限该版本,请支持上面两个原作者)"
|
||||
],
|
||||
"contact": {
|
||||
"homepage": "https://git.branulf.top/Branulf",
|
||||
"homepage": "https://git.branulf.top/Branulf/DIsc_Jockey_revive",
|
||||
"sources": "https://git.branulf.top/Branulf/DIsc_Jockey_revive"
|
||||
},
|
||||
"license": "MIT",
|
||||
"icon": "assets/disc_jockey/icon.png",
|
||||
"icon": "assets/disc_jockey/icon1.png",
|
||||
"environment": "client",
|
||||
"entrypoints": {
|
||||
"client": [
|
||||
|
Loading…
x
Reference in New Issue
Block a user