Compare commits

..

13 Commits

Author SHA1 Message Date
81908d4548 a 2025-08-27 17:07:16 +08:00
2b390f8908 8 2025-07-21 16:36:48 +08:00
645bec3987 8 2025-07-21 14:57:02 +08:00
BRanulf
3eabe96e6f weeeeee 2025-07-17 09:40:22 +08:00
BRanulf
7bed25b441 eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee 2025-07-16 12:59:07 +08:00
BRanulf
2334a12f19 更新项目结构 2025-07-16 00:18:11 +08:00
BRanulf
2b922647ab huh? 2025-07-11 22:02:58 +08:00
BRanulf
ad971a375b huh? 2025-07-11 21:43:42 +08:00
BRanulf
f60886c6d9 我又是个傻子 2025-07-11 21:21:46 +08:00
BRanulf
d3d4a5b29f 我是傻子 2025-07-11 21:13:58 +08:00
BRanulf
75ffb8b54b wow 2025-07-11 21:06:38 +08:00
BRanulf
ad44ffec5b yeah~ 2025-07-08 21:45:41 +08:00
BRanulf
b928cb9956 yee~ 2025-07-08 20:58:15 +08:00
42 changed files with 767 additions and 252 deletions

View File

@ -1,12 +1,75 @@
# 源README
Download the mod at [Modrinth](https://modrinth.com/mod/disc-jockey) or [CurseForge](https://www.curseforge.com/minecraft/mc-mods/disc-jockey)
# 该版本附加
## 关于此项目
此 Mod 是基于 [Disc-Jockey](https://github.com/SemmieDev/Disc-Jockey)(由 [SemmieDev](https://github.com/SemmieDev/) 开发)的修改版本,适配了 Minecraft 1.21.4。
原始代码版权归原作者所有,修改部分版权归 BRanulf 所有。
___
# Disc Jockey Revive
同时,该二改版本我本人并没有上传至任何其他网站(包括 Modrinth 、CurseForge 、Github 等),请勿用于商业用途
一个 Minecraft Fabric 模组,旨在为音符盒音乐爱好者提供强大的播放和即兴演奏功能
[唯一下载地址](https://git.branulf.top/Branulf/DIsc_Jockey_revive/releases)
<!-- ## 模组特性
~~这边建议去给原作者点个Star~~
### 🎵 歌曲播放
- **播放 `.nbs` 歌曲文件**:支持标准的 Note Block Studio (`.nbs`) 歌曲文件(暂不支持带变速的)。
- **文件夹管理**:支持歌曲文件夹,方便组织和浏览您的音乐库。
- **多种播放模式**
- **单曲循环**:重复播放当前歌曲。
- **列表循环**:按顺序循环播放当前文件夹或列表中的歌曲。
- **随机播放**:随机播放当前文件夹或列表中的歌曲。
- **播完停止**:歌曲播放完毕后自动停止。
- **播放进度条**:在 HUD 上显示歌曲播放进度和时间。
- **实时调速**在播放界面通过滑块实时调整歌曲播放速度0.25x - 4.0x)。
- **播放失败处理**:当附近音符盒配置不正确时,可选择停止播放或自动播放下一首。
- **音符盒材料清单**:显示播放当前歌曲所需的所有音符盒材料及数量。
- **自动调音**:模组会自动识别并调音附近的音符盒以匹配歌曲音高。
- **全向音符盒音效 (客户端)**
- **常规模式**:在播放歌曲或预览时,使音符盒音效全向传播,提供更沉浸的听觉体验。
- **强制模式**:强制所有音符盒音效(包括手动触发的)始终全向传播。
### 🎹 即兴演奏 (Live DJ)
- **独立界面**:通过专用快捷键打开一个独立界面,用于即兴演奏。
- **键盘弹奏**:利用键盘按键直接弹奏音符盒,就像在 FL Studio 中一样。
- **自定义键位映射**
- 独立的键位映射配置界面,允许您为每个键盘按键设置特定的音符(乐器和音高)。
- 默认提供类似于 FL Studio 的键盘布局预设。
- **即兴演奏调音**:在即兴演奏界面中,可以一键对周围的音符盒进行调音,以确保弹奏的音符准确无误。
- **独立于歌曲播放**:即兴演奏功能与歌曲播放功能完全独立,互不干扰。
### ⚙️ 配置与调试
- **Mod Menu 集成**:通过 Mod Menu 轻松访问模组配置。
- **快捷键**
- 打开歌曲选择界面 (默认 `J`)。
- 打开即兴演奏界面 (默认 `UNKNOW未指定`)。
- **调试模式**:可配置的调试模式,开启后会在日志中输出详细的调试信息,方便排查问题。
## 如何使用
1. **安装**:将 `Disc Jockey Revive` 模组文件和 Fabric API 放入您的 Minecraft `mods` 文件夹。
2. **添加歌曲**
* 在游戏中打开歌曲选择界面 (默认 `J` 键)。
* 点击“打开文件夹”按钮,将 `.nbs` 歌曲文件放入弹出的 `disc_jockey/songs` 文件夹中。
* 您也可以直接将 `.nbs` 文件拖放到歌曲选择界面中。
* 点击“重新加载”按钮以加载新添加的歌曲。
3. **播放歌曲**
* 在歌曲选择界面中选择一首歌曲。
* 点击“播放”按钮。模组将自动寻找并调音附近的音符盒。
* 确保您处于生存模式或创造模式,并且附近有足够的音符盒。
* 您可以通过滑块调整播放速度,并通过播放模式按钮切换循环方式。
4. **即兴演奏**
* 在游戏中打开即兴演奏界面 (默认 `UNKNOW` 键)。
* 点击“开始调音”按钮,模组将自动调音附近的音符盒。
* 调音完成后,您可以使用键盘按键直接弹奏音符盒。
* 点击“编辑键位映射”按钮可以自定义每个按键对应的音符。 -->
## 注意事项
* **服务器兼容性**:此模组通过发送玩家交互数据包来控制音符盒。在某些服务器上,这可能被反作弊插件误判为作弊行为。**强烈建议在使用前联系服务器管理员。**
* **音符盒数量**:为了获得最佳播放效果,请确保您周围有足够数量且种类齐全的音符盒。
* **非官方版本**:此版本为非官方修订版,仅供学习和参考。请支持原版模组作者。
## 贡献与支持
如果您有任何问题、建议或发现 Bug请联系[BRanulf](https://space.bilibili.com/479966868)(仅限于改二改版本,原版本请前往[原作者GitHub](https://github.com/SemmieDev/Disc-Jockey))。
不回复是正常现象因为我也只是随便做做并没有打算正经地维护真要搞的化我估计会去GitHub创建分支而不是只在自己仓库中修改。
---

View File

@ -1,89 +1,51 @@
plugins {
id 'fabric-loom' version '1.9.2'
id 'maven-publish'
id 'fabric-loom' version '1.10.5' apply false
// id 'maven-publish' apply false
id 'com.replaymod.preprocess' version '9d21b334a7'
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
}
version = project.mod_version
group = project.maven_group
repositories {
maven { url "https://maven.shedaniel.me/" }
maven {url "https://maven.terraformersmc.com/"}
preprocess {
strictExtraMappings.set(false)
def mc12104 = createNode("1.21.4", 1_21_04, "")
def mc12105 = createNode("1.21.5", 1_21_05, "")
def mc12106 = createNode("1.21.6", 1_21_06, "")
def mc12107 = createNode("1.21.7", 1_21_07, "")
def mc12108 = createNode("1.21.8", 1_21_08, "")
mc12108.link(mc12107, file("versions/mapping_12108_12107.txt"))
mc12107.link(mc12106, file("versions/mapping_12107_12106.txt"))
mc12106.link(mc12105, file("versions/mapping_12106_12105.txt"))
mc12105.link(mc12104, file("versions/mapping_12105_12104.txt"))
}
dependencies {
// To change the versions see the gradle.properties file
// implementation files("libs/modmenu-13.0.3.jar")
// implementation files("libs/modmenu-13.0.3.pom")
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
// Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
include modApi("me.shedaniel.cloth:cloth-config-fabric:17.0.142") {
exclude(group: "net.fabricmc.fabric-api")
tasks.register('buildAndGather') {
subprojects {
dependsOn project.tasks.named('build').get()
}
doFirst {
println 'Gathering builds'
def buildLibs = {
p -> p.buildDir.toPath().resolve('libs')
}
delete fileTree(buildLibs(rootProject)) {
include '*'
}
subprojects {
copy {
from(buildLibs(project)) {
include '*.jar'
exclude '*-dev.jar', '*-sources.jar', '*-shadow.jar'
}
into buildLibs(rootProject)
duplicatesStrategy DuplicatesStrategy.INCLUDE
}
}
modImplementation "me.shedaniel.cloth:cloth-config-fabric:17.0.142"
// 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 {
inputs.property "version", project.version
filteringCharset "UTF-8"
filesMatching("fabric.mod.json") {
expand "version": project.version
}
}
def targetJavaVersion = 21
tasks.withType(JavaCompile).configureEach {
// ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
// If Javadoc is generated, this must be specified in that task too.
it.options.encoding = "UTF-8"
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
it.options.release = targetJavaVersion
}
}
java {
def javaVersion = JavaVersion.toVersion(targetJavaVersion)
if (JavaVersion.current() < javaVersion) {
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
}
archivesBaseName = project.archives_base_name
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this line, sources will not be generated.
withSourcesJar()
}
jar {
from("LICENSE") {
rename { "${it}_${project.archivesBaseName}" }
}
}
// configure the maven publication
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories {
// Add repositories to publish to here.
// Notice: This block does NOT have the same function as the block in the top level.
// The repositories here will be used for publishing your artifact, not for
// retrieving dependencies.
}
}

174
common.gradle Normal file
View File

@ -0,0 +1,174 @@
apply plugin: 'maven-publish'
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'fabric-loom'
apply plugin: 'com.replaymod.preprocess'
int mcVersion = project.mcVersion
repositories {
maven {
url 'https://maven.fabricmc.net/' // loom
}
maven {
url 'https://masa.dy.fi/maven' // masa便
}
maven {
url 'https://maven.fallenbreath.me/releases' // preprocessor
}
maven {
url 'https://jitpack.io' // preprocessor
}
maven {
url 'https://www.cursemaven.com' // curseforge便
}
maven {
url "https://maven.shedaniel.me/" // cloth config
}
maven {
url "https://maven.terraformersmc.com/" // modmenu
}
}
dependencies {
minecraft "com.mojang:minecraft:${project.minecraft_version}" //
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" //
modImplementation "net.fabricmc:fabric-loader:${rootProject.loader_version}" //
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}"
include modApi("me.shedaniel.cloth:cloth-config-fabric:${project.cloth_config_version}") {
exclude(group: "net.fabricmc.fabric-api")
}
modImplementation "me.shedaniel.cloth:cloth-config-fabric:${project.cloth_config_version}"
// modCompileOnly files("${rootProject.projectDir}/libs/modmenu-13.0.3.jar")
modImplementation("com.terraformersmc:modmenu:${project.modmenu_version}")
// modImplementation files("${rootProject.projectDir}/libs/LibGui-12.0.1+1.21.2.jar")
}
String MIXIN_CONFIG_PATH = 'disc_jockey.mixins.json' // mixin
JavaVersion JAVA_COMPATIBILITY // java自动
if (mcVersion >= 12100) {
JAVA_COMPATIBILITY = JavaVersion.VERSION_21
} else if (mcVersion >= 11800) {
JAVA_COMPATIBILITY = JavaVersion.VERSION_17
} else if (mcVersion >= 11700) {
JAVA_COMPATIBILITY = JavaVersion.VERSION_16
} else {
JAVA_COMPATIBILITY = JavaVersion.VERSION_1_8
}
JavaVersion MIXIN_COMPATIBILITY_LEVEL = JAVA_COMPATIBILITY
loom {
def commonVmArgs = ['-Dmixin.debug.export=true']
runConfigs.configureEach {
runDir '../../run'
vmArgs commonVmArgs
ideConfigGenerated true
}
runs {
def auditVmArgs = [*commonVmArgs, '-Ddisc_jockey_revive.mixin_audit=true']
serverMixinAudit {
server()
vmArgs auditVmArgs
ideConfigGenerated false
}
clientMixinAudit {
client()
vmArgs auditVmArgs
ideConfigGenerated false
}
}
}
//
shadowJar {
configurations = [project.configurations.shadow]
exclude("META-INF")
archiveClassifier.set('shadow')
}
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
tasks.withType(ShadowJar).configureEach {
enableRelocation true
relocationPrefix 'semmiedev.disc_jockey_revive.libs'
}
remapJar {
dependsOn(shadowJar)
mustRunAfter(shadowJar)
inputFile = shadowJar.archiveFile
}
String modVersionSuffix = ''
if (System.getenv("BUILD_RELEASE") != "true") {
String buildNumber = System.getenv("BUILD_ID")
modVersionSuffix += buildNumber != null ? ('+build.' + buildNumber) : '' //
}
String fullModVersion = rootProject.mod_version + modVersionSuffix
String fullProjectVersion
group = rootProject.maven_group
if (System.getenv("JITPACK") == "true") {
base.archivesName = rootProject.archives_base_name + '-' + project.minecraft_version
fullProjectVersion = 'v' + rootProject.mod_version + modVersionSuffix
} else {
base.archivesName = rootProject.archives_base_name
fullProjectVersion = 'v' + rootProject.mod_version + '-' + project.minecraft_version + modVersionSuffix
}
version = fullProjectVersion
processResources {
inputs.property "id", rootProject.mod_id //
inputs.property "name", rootProject.mod_name //
inputs.property "version", fullModVersion
filesMatching("fabric.mod.json") {
def valueMap = [
"id" : rootProject.mod_id,
"name" : rootProject.mod_name,
"version" : fullModVersion,
"minecraft_version": project.minecraft_dependency, //
"loader_version" : rootProject.loader_version //
]
expand valueMap
}
filesMatching(MIXIN_CONFIG_PATH) {
filter { s -> s.replace('{{COMPATIBILITY_LEVEL}}', "JAVA_${MIXIN_COMPATIBILITY_LEVEL.ordinal() + 1}") }
}
}
tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
options.compilerArgs << "-Xlint:deprecation" << "-Xlint:unchecked"
if (JAVA_COMPATIBILITY <= JavaVersion.VERSION_1_8) {
options.compilerArgs << '-Xlint:-options'
}
}
java {
sourceCompatibility = JAVA_COMPATIBILITY
targetCompatibility = JAVA_COMPATIBILITY
withSourcesJar()
}
jar {
from(rootProject.file('LICENSE')) {
rename { "${it}_${rootProject.archives_base_name}" }
}
}
//publishing {
// publications {
// mavenJava(MavenPublication) {
// from components.java
// }
// }
// repositories {
// }
//}

View File

@ -1,12 +1,14 @@
# Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx1G
org.gradle.jvmargs=-Xmx2G
# Fabric Properties
# check these on https://modmuss50.me/fabric.html
minecraft_version=1.21.4
yarn_mappings=1.21.4+build.8
loader_version=0.16.10
mod_name=Disc Jockey Revive
mod_id=disc_jockey_revive
# Mod Properties
mod_version=1.14.514.037
mod_version=1.14.514.050
maven_group=semmiedev
archives_base_name=disc_jockey_revive
# Dependencies

View File

@ -1,78 +0,0 @@
<?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>

View File

@ -1,8 +0,0 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.terraformersmc</groupId>
<artifactId>modmenu</artifactId>
<version>13.0.3</version>
</project>

View File

@ -1,9 +1,35 @@
import groovy.json.JsonSlurper
pluginManagement {
repositories {
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
maven {
name = 'Jitpack'
url = 'https://jitpack.io' // preprocessor
}
mavenCentral()
gradlePluginPortal()
}
resolutionStrategy {
eachPlugin {
switch (requested.id.id) {
case "com.replaymod.preprocess": {
useModule("com.github.Fallen-Breath:preprocessor:${requested.version}")
break
}
}
}
}
}
def settings = new JsonSlurper().parseText(file('settings.json').text)
for (String version : settings.versions) {
include(":$version")
def proj = project(":$version")
proj.projectDir = file("versions/$version")
proj.buildFileName = "../../common.gradle"
}

9
settings.json Normal file
View File

@ -0,0 +1,9 @@
{
"versions": [
"1.21.4",
"1.21.5",
"1.21.6",
"1.21.7",
"1.21.8"
]
}

View File

@ -1,25 +1,42 @@
package semmiedev.disc_jockey_revive;
import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import org.apache.logging.log4j.Logger;
import java.util.Arrays;
// 这玩意一般不用我自己用的但是不想删
public class DebugLogger {
public static void log(String message) {
if (Main.config != null && Main.config.debugModeEnabled) {
Main.LOGGER.info("[DiscJockeyRevive-调试] " + message);
sendMessage(message);
}
}
public static void log(String format, Object... arguments) {
if (Main.config != null && Main.config.debugModeEnabled) {
Main.LOGGER.info("[DiscJockeyRevive-调试] " + format, arguments);
sendMessage(format + ": " + Arrays.toString(arguments));
}
}
public static void log(String message, Throwable t) {
if (Main.config != null && Main.config.debugModeEnabled) {
Main.LOGGER.error("[DiscJockeyRevive-调试] " + message, t);
sendMessage(message + ": " + t.getMessage());
}
}
public static void sendMessage(String message) {
MinecraftClient client = MinecraftClient.getInstance();
if (client != null && client.inGameHud != null && client.inGameHud.getChatHud() != null) {
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.of("[DiscJockeyRevive-调试]"+message.formatted(Formatting.GRAY)));
} else {
// Main.LOGGER.error("[DiscJockeyRevive-调试] 聊天窗口未初始化,{}",message);
}
}
}

View File

@ -48,6 +48,7 @@ public class KeyMappingManager {
}
public void loadMappings() {
DebugLogger.log("KeyMappingManager: 加载键位预设");
if (!MAPPINGS_FILE.exists()) {
Main.LOGGER.info("未找到键映射文件,正在创建默认的类似于 FL Studio 的映射。");
mappings.clear();
@ -91,6 +92,8 @@ public class KeyMappingManager {
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));
DebugLogger.log("KeyMappingManager: 默认预设创建完成");
saveMappings();
return;
@ -118,6 +121,7 @@ public class KeyMappingManager {
public void saveMappings() {
MAPPINGS_FILE.getParentFile().mkdirs();
DebugLogger.log("KeyMappingManager: 开始保存按键映射");
try (FileWriter writer = new FileWriter(MAPPINGS_FILE)) {
HashMap<String, NoteData> dataToSave = new HashMap<>();
for (Map.Entry<InputUtil.Key, Note> entry : mappings.entrySet()) {
@ -136,10 +140,12 @@ public class KeyMappingManager {
public void setMapping(InputUtil.Key key, Note note) {
mappings.put(key, note);
DebugLogger.log("KeyMappingManager: 已设置按键映射:"+key.getTranslationKey()+""+getNoteDisplayName(note));
}
public void removeMapping(InputUtil.Key key) {
mappings.remove(key);
DebugLogger.log("KeyMappingManager: 已移除键位预设:"+key.getTranslationKey());
}
public Map<InputUtil.Key, Note> getMappings() {
@ -149,7 +155,7 @@ public class KeyMappingManager {
public static String getNoteDisplayName(Note note) {
if (note == null) return "未设置";
String instrumentTranslationKey = "block.minecraft.note_block.instrument." + note.instrument().asString();
String instrumentTranslationKey = " " + note.instrument().asString();
String instrumentName = Text.translatable(instrumentTranslationKey).getString();
int pitch = note.note();

View File

@ -51,6 +51,7 @@ public class LiveDjPlayer implements ClientTickEvents.StartWorldTick {
// 调音
public synchronized void startTuning() {
DebugLogger.log("LiveDjPlayer: 开始调音。");
if (tuned && noteBlocks != null) {
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".player.retuning").formatted(Formatting.YELLOW));
} else {
@ -75,6 +76,7 @@ public class LiveDjPlayer implements ClientTickEvents.StartWorldTick {
// 停止调音
public synchronized void stopTuning() {
tuningRequested = false;
DebugLogger.log("LiveDjPlayer: 停止调音");
}
// 是否正在调音
@ -93,6 +95,7 @@ public class LiveDjPlayer implements ClientTickEvents.StartWorldTick {
// 播放
public boolean playNoteBlock(Note note) {
DebugLogger.log("LiveDjPlayer: 触发播放");
MinecraftClient client = MinecraftClient.getInstance();
ClientWorld world = client.world;
ClientPlayerEntity player = client.player;
@ -120,6 +123,7 @@ public class LiveDjPlayer implements ClientTickEvents.StartWorldTick {
// 获取可交互的
private boolean sendNotePacket(BlockPos blockPos) {
DebugLogger.log("LiveDjPlayer: 发送播放数据包。");
MinecraftClient client = MinecraftClient.getInstance();
ClientPlayerEntity player = client.player;
long now = System.currentTimeMillis();
@ -451,6 +455,7 @@ public class LiveDjPlayer implements ClientTickEvents.StartWorldTick {
}
private HashMap<Byte, BlockPos> getNotesMapForInstrument(NoteBlockInstrument instrument) {
DebugLogger.log("LiveDjPlayer: 尝试获取音符。");
if (noteBlocks == null) {
noteBlocks = new HashMap<>();
}

View File

@ -14,9 +14,13 @@ import net.minecraft.client.MinecraftClient;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.util.InputUtil;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.sound.SoundEvent;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.glfw.GLFW;
@ -31,7 +35,7 @@ import java.util.ArrayList;
public class Main implements ClientModInitializer {
public static final String MOD_ID = "disc_jockey_revive";
public static final MutableText NAME = Text.literal("Disc Jockey");
public static final Logger LOGGER = LogManager.getLogger("Disc Jockey");
public static final Logger LOGGER = LogManager.getLogger("Disc Jockey Revive");
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();
@ -45,6 +49,7 @@ public class Main implements ClientModInitializer {
@Override
public void onInitializeClient() {
DebugLogger.log("Main: 正在进行初始化。");
configHolder = AutoConfig.register(ModConfig.class, JanksonConfigSerializer::new);
config = configHolder.getConfig();
@ -68,10 +73,12 @@ public class Main implements ClientModInitializer {
PREVIEWER.stop();
SONG_PLAYER.stop();
LIVE_DJ_PLAYER.stopTuning();
DebugLogger.log("Main: 退出世界,停止。");
}
prevWorld = client.world;
if (openScreenKeyBind.wasPressed()) {
DebugLogger.log("Main: 打开主界面:"+openScreenKeyBind);
if (SongLoader.loadingSongs) {
client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".still_loading").formatted(Formatting.RED));
SongLoader.showToast = true;
@ -80,14 +87,17 @@ public class Main implements ClientModInitializer {
}
}
if (openLiveDjScreenKeyBind.wasPressed()) {
if (SongLoader.loadingSongs) {
client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".still_loading").formatted(Formatting.RED));
SongLoader.showToast = true;
} else {
DebugLogger.log("Main: 打开DJ界面"+openLiveDjScreenKeyBind);
// if (SongLoader.loadingSongs) {
// client.inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".still_loading").formatted(Formatting.RED));
// SongLoader.showToast = true;
// } else {
// client.setScreen(new LiveDjScreen());
// }
// 好像这个界面并不需要什么歌曲加载
client.setScreen(new LiveDjScreen());
}
}
}
});
ClientTickEvents.START_WORLD_TICK.register(world -> {
@ -102,8 +112,11 @@ public class Main implements ClientModInitializer {
PREVIEWER.stop();
SONG_PLAYER.stop();
LIVE_DJ_PLAYER.stopTuning();
DebugLogger.log("Main: 断开连接,停止。");
});
HudRenderCallback.EVENT.register(new PlaybackProgressOverlay());
DebugLogger.log("Main: 出屎化完成。");
}
}

View File

@ -12,6 +12,9 @@ public class ModConfig implements ConfigData {
@ConfigEntry.Gui.Tooltip(count = 2) public boolean disableAsyncPlayback;
@ConfigEntry.Gui.Tooltip(count = 2) public boolean omnidirectionalNoteBlockSounds = true;
@ConfigEntry.Gui.Tooltip(count = 1)
public boolean forceOmnidirectionalNoteBlockSounds = false;
@ConfigEntry.Gui.Excluded
public String currentFolderPath = "";
@ -37,6 +40,11 @@ public class ModConfig implements ConfigData {
}
}
public enum PlaybackFailureAction {
STOP,
NEXT_SONG
}
@ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON)
@ConfigEntry.Gui.Tooltip(count = 4)
public ExpectedServerVersion expectedServerVersion = ExpectedServerVersion.All;
@ -50,6 +58,14 @@ public class ModConfig implements ConfigData {
@ConfigEntry.Gui.Tooltip(count = 1)
public boolean showHudProgressBar = true;
@ConfigEntry.Gui.Tooltip(count = 1)
@ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON)
public PlaybackFailureAction playbackFailureAction = PlaybackFailureAction.STOP;
@ConfigEntry.Gui.Tooltip(count = 1)
public boolean debugModeEnabled = false;
@ConfigEntry.Gui.Tooltip(count = 1)
public boolean debugModeChatMessages = false;
}

View File

@ -31,6 +31,14 @@ public record Note(NoteBlockInstrument instrument, byte note) {
NoteBlockInstrument.BANJO,
NoteBlockInstrument.PLING,
NoteBlockInstrument.ZOMBIE,
NoteBlockInstrument.SKELETON,
NoteBlockInstrument.CREEPER,
NoteBlockInstrument.DRAGON,
NoteBlockInstrument.WITHER_SKELETON,
NoteBlockInstrument.PIGLIN,
NoteBlockInstrument.CUSTOM_HEAD
};
static {
@ -50,5 +58,13 @@ public record Note(NoteBlockInstrument instrument, byte note) {
INSTRUMENT_BLOCKS.put(NoteBlockInstrument.BIT, Blocks.EMERALD_BLOCK);
INSTRUMENT_BLOCKS.put(NoteBlockInstrument.BANJO, Blocks.HAY_BLOCK);
INSTRUMENT_BLOCKS.put(NoteBlockInstrument.PLING, Blocks.GLOWSTONE);
INSTRUMENT_BLOCKS.put(NoteBlockInstrument.ZOMBIE, Blocks.ZOMBIE_HEAD);
INSTRUMENT_BLOCKS.put(NoteBlockInstrument.SKELETON, Blocks.SKELETON_SKULL);
INSTRUMENT_BLOCKS.put(NoteBlockInstrument.CREEPER, Blocks.CREEPER_HEAD);
INSTRUMENT_BLOCKS.put(NoteBlockInstrument.DRAGON, Blocks.DRAGON_HEAD);
INSTRUMENT_BLOCKS.put(NoteBlockInstrument.WITHER_SKELETON, Blocks.WITHER_SKELETON_SKULL);
INSTRUMENT_BLOCKS.put(NoteBlockInstrument.PIGLIN, Blocks.PIGLIN_HEAD);
INSTRUMENT_BLOCKS.put(NoteBlockInstrument.CUSTOM_HEAD, Blocks.PLAYER_HEAD);
}
}

View File

@ -11,12 +11,13 @@ public class Previewer implements ClientTickEvents.StartWorldTick {
private int i;
private float tick;
private Song song;
public Song song;
public void start(Song song) {
this.song = song;
Main.TICK_LISTENERS.add(this);
running = true;
DebugLogger.log("Previewer: 开始播放。");
}
public void stop() {
@ -24,6 +25,30 @@ public class Previewer implements ClientTickEvents.StartWorldTick {
running = false;
i = 0;
tick = 0;
DebugLogger.log("Previewer: 停止播放。");
}
// 获取当前播放进度 (0.0 - 1.0)
public synchronized float getProgress() {
if (song == null || !running) return 0;
return Math.min(1.0f, (float) (tick / song.length));
}
// 获取格式化时间字符串
public synchronized String getFormattedTime() {
if (song == null) return "00:00 / 00:00";
double totalSeconds = song.getLengthInSeconds();
double currentSeconds = song.ticksToMilliseconds(tick) / 1000.0;
return formatTime((int) currentSeconds) + " / " + formatTime((int) totalSeconds);
}
// 格式化时间为 mm:ss
private String formatTime(int seconds) {
int min = seconds / 60;
int sec = seconds % 60;
return String.format("%02d:%02d", min, sec);
}
@Override

View File

@ -3,6 +3,7 @@ package semmiedev.disc_jockey_revive;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.toast.SystemToast;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import semmiedev.disc_jockey_revive.gui.SongListWidget;
import java.io.File;
@ -34,6 +35,7 @@ public class SongLoader {
}
public static void loadSongs() {
DebugLogger.log("SongLoader: 加载歌曲");
if (loadingSongs) return;
new Thread(() -> {
loadingSongs = true;
@ -48,13 +50,17 @@ public class SongLoader {
for (Song song : SONGS) SONG_SUGGESTIONS.add(song.displayName);
Main.config.favorites.removeIf(favorite -> SongLoader.SONGS.stream().map(song -> song.fileName).noneMatch(favorite::equals));
if (showToast && MinecraftClient.getInstance().textRenderer != null) SystemToast.add(MinecraftClient.getInstance().getToastManager(), SystemToast.Type.PACK_LOAD_FAILURE, Main.NAME, Text.translatable(Main.MOD_ID+".loading_done"));
if (showToast && MinecraftClient.getInstance().textRenderer != null) {
SystemToast.add(MinecraftClient.getInstance().getToastManager(), SystemToast.Type.PACK_LOAD_FAILURE, Main.NAME, Text.translatable(Main.MOD_ID+".loading_done"));
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".loading_done").formatted(Formatting.AQUA));
}
showToast = true;
loadingSongs = false;
}).start();
}
private static void loadFolder(File folder, SongFolder parentFolder) {
DebugLogger.log("SongLoader: 加载文件夹");
if (!folder.isDirectory()) return;
SongFolder songFolder = new SongFolder(folder.getName(), folder.getPath());
@ -86,6 +92,7 @@ public class SongLoader {
}
public static Song loadSong(File file) throws IOException {
DebugLogger.log("SongLoader: 加载歌曲:"+file.getName());
if (file.isFile()) {
BinaryReader reader = new BinaryReader(Files.newInputStream(file.toPath()));
Song song = new Song();

View File

@ -78,6 +78,7 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
this.playbackThread = new Thread(() -> {
Thread ownThread = this.playbackThread;
DebugLogger.log("SongPlayer: 开始播放线程。");
while(ownThread == this.playbackThread) {
try {
// Accuracy doesn't really matter at this precision imo
@ -102,11 +103,17 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
private boolean isRandomPlaying = false;
private int randomIndex = -1;
public synchronized void setSpeed(float speed) {
this.speed = MathHelper.clamp(speed, 0.25f, 4.0f);
}
public synchronized void stopPlaybackThread() {
this.playbackThread = null; // Should stop on its own then
DebugLogger.log("SongPlayer: 停止播放线程。");
}
public synchronized void start(Song song) {
DebugLogger.log("SongPlayer: 开始播放。");
if (!Main.config.hideWarning && !warned) {
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.translatable("disc_jockey_revive.warning").formatted(Formatting.BOLD, Formatting.RED));
warned = true;
@ -140,7 +147,25 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
}
}
private synchronized void handlePlaybackFailure() {
DebugLogger.log("处理播放失败,配置选项: {}", Main.config.playbackFailureAction.name());
switch (Main.config.playbackFailureAction) {
case NEXT_SONG: // 播放下一首
stop();
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.translatable(Main.MOD_ID+".playback_failure.next_message").formatted(Formatting.GRAY));
playNextSong();
break;
case STOP: // 停止播放原本
default:
stop();
break;
}
}
public synchronized void stop() {
DebugLogger.log("SongPlayer: 停止播放。");
//MinecraftClient.getInstance().send(() -> Main.TICK_LISTENERS.remove(this));
stopPlaybackThread();
running = false;
@ -260,6 +285,7 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
public synchronized void setPlayMode(PlayMode mode) {
this.playMode = mode;
this.loopSong = mode == PlayMode.SINGLE_LOOP;
DebugLogger.log("SongPlayer: 设置播放模式:"+ mode);
}
// 获取当前播放进度 (0.0 - 1.0)
@ -294,6 +320,7 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
int prevIndex = (currentIndex - 1 + SongLoader.currentFolder.songs.size()) % SongLoader.currentFolder.songs.size();
start(SongLoader.currentFolder.songs.get(prevIndex));
DebugLogger.log("SongPlayer: 切上一首。");
}
// 播放下一首
@ -309,6 +336,7 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
int nextIndex = (currentIndex + 1) % SongLoader.currentFolder.songs.size();
start(SongLoader.currentFolder.songs.get(nextIndex));
DebugLogger.log("SongPlayer: 切下一首。");
}
}
@ -426,6 +454,7 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
if (!missingNotes.isEmpty()) {
ChatHud chatHud = MinecraftClient.getInstance().inGameHud.getChatHud();
chatHud.addMessage(Text.translatable(Main.MOD_ID+".player.invalid_note_blocks").formatted(Formatting.RED));
DebugLogger.log("播放失败:缺少音符盒");
HashMap<Block, Integer> missing = new HashMap<>();
for (Note note : missingNotes) {
@ -440,6 +469,8 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick {
missingInstrumentBlocks = missing;
missing.forEach((block, integer) -> chatHud.addMessage(Text.literal(block.getName().getString()+" × "+integer).formatted(Formatting.RED)));
stop();
handlePlaybackFailure();
return;
}
} else if (!tuned) {
//tuned = true;

View File

@ -0,0 +1,16 @@
package semmiedev.disc_jockey_revive.gui;
import net.minecraft.client.gui.widget.SliderWidget;
import net.minecraft.text.Text;
//
public abstract class CustomSliderWidget extends SliderWidget {
public CustomSliderWidget(int x, int y, int width, int height, Text text, double value) {
super(x, y, width, height, text, value);
updateMessage();
}
public void updateMessagePublic() {
updateMessage();
}
}

View File

@ -89,9 +89,9 @@ public class KeyMappingListWidget extends EntryListWidget<KeyMappingListWidget.K
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);
context.drawTextWithShadow(client.textRenderer, keyText, x + 5, textY, 0xFFFFFFFF);
String noteDisplayName = KeyMappingManager.getNoteDisplayName(note);
context.drawTextWithShadow(client.textRenderer, noteDisplayName, x + 100, textY, 0xAAAAAA);
context.drawTextWithShadow(client.textRenderer, noteDisplayName, x + 100, textY, 0xFFAAAAAA);
int buttonWidth = 50;
int buttonHeight = 18;
int buttonY = y + (entryHeight - buttonHeight) / 2;

View File

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

View File

@ -70,7 +70,7 @@ public class SongListWidget extends EntryListWidget<SongListWidget.Entry> {
client.textRenderer,
emoji,
x + 4, y + 6,
favorite ? 0xFFD700 : 0x808080
favorite ? 0xFFFFD700 : 0xFF808080
);
// 歌曲名称靠左显示从收藏图标右侧开始
@ -81,7 +81,7 @@ public class SongListWidget extends EntryListWidget<SongListWidget.Entry> {
client.textRenderer,
displayText,
textX, y + 6,
selected ? 0xFFFFFF : 0x808080
selected ? 0xFFFFFFFF : 0xFF808080
);
}
@ -159,7 +159,7 @@ public class SongListWidget extends EntryListWidget<SongListWidget.Entry> {
client.textRenderer,
displayText,
x + 6, y + 6,
selected ? 0xFFFFFF : 0x808080
selected ? 0xFFFFFFFF : 0xFF808080
);
}

View File

@ -29,7 +29,7 @@ public class PlaybackProgressOverlay implements HudRenderCallback {
int screenHeight = context.getScaledWindowHeight();
int barX = screenWidth / 2 - PROGRESS_BAR_WIDTH / 2;
int barY = screenHeight - 55;
int barY = screenHeight - 55; // 差不多比护甲值高一点
progressBarRenderer.renderProgressBar(
context,
@ -38,7 +38,27 @@ public class PlaybackProgressOverlay implements HudRenderCallback {
PROGRESS_BAR_WIDTH,
5,
Main.SONG_PLAYER.getProgress(),
Main.SONG_PLAYER.getFormattedTime()
Main.SONG_PLAYER.getFormattedTime(),
0x8000FF00
);
}
if (Main.PREVIEWER.running && Main.PREVIEWER.song != null && isInGame) {
int screenWidth = context.getScaledWindowWidth();
int screenHeight = context.getScaledWindowHeight();
int barX = screenWidth / 2 - PROGRESS_BAR_WIDTH / 2;
int barY = screenHeight - 42; // 差不多比生命值高一点
progressBarRenderer.renderProgressBar(
context,
barX,
barY,
PROGRESS_BAR_WIDTH,
5,
Main.PREVIEWER.getProgress(),
Main.PREVIEWER.getFormattedTime(),
0x80FF0000
);
}

View File

@ -1,18 +1,18 @@
package semmiedev.disc_jockey_revive.gui.screen;
import me.shedaniel.autoconfig.AutoConfig;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.ConfirmScreen;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.SliderWidget;
import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.item.ItemStack;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import semmiedev.disc_jockey_revive.Main;
import semmiedev.disc_jockey_revive.Note;
import semmiedev.disc_jockey_revive.Song;
import semmiedev.disc_jockey_revive.SongLoader;
import semmiedev.disc_jockey_revive.*;
import semmiedev.disc_jockey_revive.gui.CustomSliderWidget;
import semmiedev.disc_jockey_revive.gui.ProgressBarRenderer;
import semmiedev.disc_jockey_revive.gui.SongListWidget;
import semmiedev.disc_jockey_revive.gui.hud.BlocksOverlay;
@ -31,21 +31,21 @@ import semmiedev.disc_jockey_revive.SongPlayer.PlayMode;
import static net.minecraft.client.toast.TutorialToast.PROGRESS_BAR_WIDTH;
public class DiscJockeyScreen extends Screen {
private static final MutableText
public static final MutableText
SELECT_SONG = Text.translatable(Main.MOD_ID+".screen.select_song"),
PLAY = Text.translatable(Main.MOD_ID+".screen.play"),
PLAY_STOP = Text.translatable(Main.MOD_ID+".screen.play.stop"),
PLAY_STOP = Text.translatable(Main.MOD_ID+".screen.play.stop").formatted(Formatting.YELLOW),
PREVIEW = Text.translatable(Main.MOD_ID+".screen.preview"),
PREVIEW_STOP = Text.translatable(Main.MOD_ID+".screen.preview.stop"),
PREVIEW_STOP = Text.translatable(Main.MOD_ID+".screen.preview.stop").formatted(Formatting.YELLOW),
DROP_HINT = Text.translatable(Main.MOD_ID+".screen.drop_hint").formatted(Formatting.GRAY)
;
private SongListWidget songListWidget;
private ButtonWidget playButton, previewButton;
public SongListWidget songListWidget;
public ButtonWidget playButton, previewButton;
public boolean shouldFilter;
private String query = "";
public String query = "";
private static final MutableText
public static final MutableText
FOLDER_UP = Text.literal(""),
CURRENT_FOLDER = Text.translatable(Main.MOD_ID+".screen.current_folder"),
PLAY_MODE = Text.translatable(Main.MOD_ID+".screen.play_mode"),
@ -54,16 +54,20 @@ public class DiscJockeyScreen extends Screen {
MODE_RANDOM = Text.translatable(Main.MOD_ID+".screen.mode_random"),
MODE_STOP = Text.translatable(Main.MOD_ID+".screen.mode_stop");
private static final MutableText
public static final MutableText
OPEN_FOLDER = Text.translatable(Main.MOD_ID+".screen.open_folder"),
RELOAD = Text.translatable(Main.MOD_ID+".screen.reload"),
LIVE_DJ = Text.translatable(Main.MOD_ID+".screen.live_dj").formatted(Formatting.GOLD);
private ButtonWidget folderUpButton, playModeButton;
public ButtonWidget folderUpButton, playModeButton;
public SongFolder currentFolder;
private PlayMode currentPlayMode = PlayMode.STOP_AFTER;
public PlayMode currentPlayMode = PlayMode.STOP_AFTER;
private ProgressBarRenderer progressBarRenderer;
public ProgressBarRenderer progressBarRenderer;
public CustomSliderWidget speedSlider;
public ButtonWidget configButton;
public DiscJockeyScreen() {
super(Main.NAME);
@ -72,6 +76,7 @@ public class DiscJockeyScreen extends Screen {
@Override
protected void init() {
DebugLogger.log("DiscJockeyScreen: 开始初始化界面");
shouldFilter = true;
songListWidget = new SongListWidget(client, width, height - 100, 32, 20, this);
boolean isLargeScreen = width > 900;
@ -99,6 +104,7 @@ public class DiscJockeyScreen extends Screen {
}
}
// 返回
folderUpButton = ButtonWidget.builder(FOLDER_UP, button -> {
if (currentFolder != null) {
currentFolder = null;
@ -108,6 +114,7 @@ public class DiscJockeyScreen extends Screen {
}).dimensions(10, 10, 20, 20).build();
addDrawableChild(folderUpButton);
// 播放模式
playModeButton = ButtonWidget.builder(getPlayModeText(), button -> {
switch (currentPlayMode) {
case SINGLE_LOOP -> currentPlayMode = PlayMode.LIST_LOOP;
@ -120,6 +127,40 @@ public class DiscJockeyScreen extends Screen {
}).dimensions(width - 120, 10, 100, 20).build();
addDrawableChild(playModeButton);
// 我展示速度
int sliderWidth = 120;
int sliderX = width - 120 - sliderWidth - 10;
int sliderY = 10;
speedSlider = new CustomSliderWidget(sliderX, sliderY, sliderWidth, 20,
Text.translatable(Main.MOD_ID + ".screen.speed"),
(Main.SONG_PLAYER.speed - 0.25f) / (4.0f - 0.25f)
) {
@Override
protected void updateMessage() {
float currentSpeed = (float) (0.25f + this.value * (4.0f - 0.25f));
this.setMessage(Text.translatable(Main.MOD_ID + ".screen.speed_value",
String.format("%.2f", currentSpeed)));
}
@Override
protected void applyValue() {
float newSpeed = (float) (0.25f + this.value * (4.0f - 0.25f));
Main.SONG_PLAYER.speed = newSpeed;
DebugLogger.log("播放速度设置为: {:.2f}x", newSpeed);
}
}; if (isLargeScreen) addDrawableChild(speedSlider);
// 配置按钮
int configButtonSize = 20;
int configButtonX = width - 120 - sliderWidth - configButtonSize - 10 - 10;
int configButtonY = 10;
if (!isLargeScreen) configButtonX = width - 120 - configButtonSize - 10;
configButton = ButtonWidget.builder(Text.literal(""), button -> {
Screen configScreen = AutoConfig.getConfigScreen(ModConfig.class, this).get();
client.setScreen(configScreen);
}).dimensions(configButtonX, configButtonY, configButtonSize, configButtonSize).build();
addDrawableChild(configButton);
int buttonY;
if (isLargeScreen){
buttonY = height - 30;
@ -129,7 +170,7 @@ public class DiscJockeyScreen extends Screen {
int centerX = width / 2;
// 上一首
addDrawableChild(ButtonWidget.builder(Text.literal("◀◀"), button -> {
addDrawableChild(ButtonWidget.builder(Text.literal("|◀◀"), button -> {
Main.SONG_PLAYER.playPreviousSong();
}).dimensions(centerX - 100, buttonY, 40, 20).build());
@ -148,7 +189,7 @@ public class DiscJockeyScreen extends Screen {
addDrawableChild(playButton);
// 下一首
addDrawableChild(ButtonWidget.builder(Text.literal("▶▶"), button -> {
addDrawableChild(ButtonWidget.builder(Text.literal("▶▶|"), button -> {
Main.SONG_PLAYER.playNextSong();
}).dimensions(centerX + 60, buttonY, 40, 20).build());
@ -274,20 +315,21 @@ public class DiscJockeyScreen extends Screen {
public void render(DrawContext context, int mouseX, int mouseY, float 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);
context.drawCenteredTextWithShadow(textRenderer, DROP_HINT, width / 2, 5, 0xFFFFFFFF);
context.drawCenteredTextWithShadow(textRenderer, SELECT_SONG, width / 2, 20, 0xFFFFFFFF);
// 显示当前文件夹和播放模式
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);
context.drawTextWithShadow(textRenderer, CURRENT_FOLDER.getString() + ": " + folderName, 35, 15, 0xFFFFFFFF);
// context.drawTextWithShadow(textRenderer, PLAY_MODE.getString() + ":", width - 220, 15, 0xFFFFFF);
int screenWidth = context.getScaledWindowWidth();
// 进度条
// 播放进度条
// TODO 关于可拖动什么的以后再加这里放个TODO防止以后忘了
if (Main.SONG_PLAYER.running && Main.SONG_PLAYER.song != null) {
int progressBarX = 10;
int progressBarY = height - 50; // 按钮上方
int progressBarY = height - 52; // 按钮上方
int barWidth = screenWidth - 20;
progressBarRenderer.renderProgressBar(
@ -297,9 +339,35 @@ public class DiscJockeyScreen extends Screen {
barWidth,
5,
Main.SONG_PLAYER.getProgress(),
Main.SONG_PLAYER.getFormattedTime()
Main.SONG_PLAYER.getFormattedTime(),
0x8000FF00
);
}
// 预览进度条
if (Main.PREVIEWER.running && Main.PREVIEWER.song != null) {
int progressBarX = 10;
int progressBarY = height - 37; // 比上一个低一点
int barWidth = screenWidth - 20;
progressBarRenderer.renderProgressBar(
context,
progressBarX,
progressBarY,
barWidth,
5,
Main.PREVIEWER.getProgress(),
Main.PREVIEWER.getFormattedTime(),
0x80FF0000
);
}
if (configButton != null && configButton.isMouseOver(mouseX, mouseY)) {
context.drawTooltip(textRenderer, Text.translatable(Main.MOD_ID + ".screen.open_config"), mouseX, mouseY);
}
// DebugLogger.log("DiscJockeyScreen: 初始化界面完成");
}
@Override

View File

@ -6,6 +6,7 @@ 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.DebugLogger;
import semmiedev.disc_jockey_revive.Main;
import semmiedev.disc_jockey_revive.Note;
import semmiedev.disc_jockey_revive.gui.KeyMappingListWidget;
@ -34,6 +35,8 @@ public class EditKeyMappingsScreen extends Screen {
protected void init() {
super.init();
DebugLogger.log("EditKeyMappingsScreen: 开始初始化界面");
int listTop = 40;
int listBottom = this.height - 50;
int listHeight = listBottom - listTop;
@ -56,6 +59,8 @@ public class EditKeyMappingsScreen extends Screen {
this.client.setScreen(this.parent);
}).dimensions(buttonX, buttonY, buttonWidth, buttonHeight).build();
addDrawableChild(doneButton);
DebugLogger.log("EditKeyMappingsScreen: 界面初始化完成");
}
private void refreshMappingList() {
@ -90,6 +95,7 @@ public class EditKeyMappingsScreen extends Screen {
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
DebugLogger.log("EditKeyMappingsScreen: 按下按键:"+keyCode);
if (waitingForKeyPress) {
if (keyCode == GLFW.GLFW_KEY_ESCAPE) {
@ -129,11 +135,11 @@ public class EditKeyMappingsScreen extends Screen {
@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, TITLE, this.width / 2, 10, 0xFFFFFFFF);
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);
context.drawCenteredTextWithShadow(textRenderer, PRESS_KEY_INSTRUCTION, this.width / 2, this.height / 2 - 10, 0xFFFFFFFF);
}
mappingListWidget.render(context, mouseX, mouseY, delta);
}

View File

@ -38,6 +38,8 @@ public class LiveDjScreen extends Screen {
protected void init() {
super.init();
DebugLogger.log("LiveDjScreen: 开始初始化界面");
int centerX = this.width / 2;
int buttonWidth = 150;
int buttonHeight = 20;
@ -50,13 +52,14 @@ public class LiveDjScreen extends Screen {
Main.LIVE_DJ_PLAYER.startTuning();
}).dimensions(centerX + margin, buttonY, buttonWidth, buttonHeight).build();
addDrawableChild(startTuningButton);
DebugLogger.log("LiveDjScreen: 界面初始化完成");
}
@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);
context.drawCenteredTextWithShadow(textRenderer, TITLE, this.width / 2, 10, 0xFFFFFFFF);
context.drawCenteredTextWithShadow(textRenderer, INSTRUCTIONS, this.width / 2, 30, 0xFFFFFFFF);
Text tuningStatusText;
if (Main.LIVE_DJ_PLAYER.getNoteBlocks() == null) {
@ -109,7 +112,7 @@ public class LiveDjScreen extends Screen {
startTuningButton.active = false;
startTuningButton.visible = false;
}
context.drawCenteredTextWithShadow(textRenderer, tuningStatusText, this.width / 2, 50, 0xFFFFFF);
context.drawCenteredTextWithShadow(textRenderer, tuningStatusText, this.width / 2, 50, 0xFFFFFFFF);
if (!Main.LIVE_DJ_PLAYER.missingInstrumentBlocks.isEmpty()) {
@ -125,6 +128,7 @@ public class LiveDjScreen extends Screen {
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
DebugLogger.log("LiveDjScreen: 按下按键"+keyCode);
InputUtil.Key key = InputUtil.fromKeyCode(keyCode, scanCode);
if (keyCode == GLFW.GLFW_KEY_ESCAPE) {
this.close();

View File

@ -15,6 +15,7 @@ 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.DebugLogger;
import semmiedev.disc_jockey_revive.Main;
import semmiedev.disc_jockey_revive.Note;
import semmiedev.disc_jockey_revive.KeyMappingManager;
@ -46,12 +47,14 @@ public class SelectNoteScreen extends Screen {
protected void init() {
super.init();
DebugLogger.log("SelectNoteScreen: 开始初始化界面");
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()))
CyclingButtonWidget<NoteBlockInstrument> instrumentButton = CyclingButtonWidget.builder((NoteBlockInstrument instrument) -> Text.translatable("" + instrument.asString()))
.values(NoteBlockInstrument.values())
.initially(selectedInstrument)
@ -82,6 +85,8 @@ public class SelectNoteScreen extends Screen {
this.client.setScreen(this.parent);
parent.stopWaitingForKeyPress();
}).dimensions(cancelButtonX, buttonY, buttonWidth, widgetHeight).build());
DebugLogger.log("SelectNoteScreen: 界面初始化完成");
}
private class CustomPitchSlider extends SliderWidget {
public CustomPitchSlider(int x, int y, int width, int height, Text text, double value) {
@ -142,7 +147,7 @@ public class SelectNoteScreen extends Screen {
super.render(context, mouseX, mouseY, delta);
context.drawCenteredTextWithShadow(textRenderer, TITLE, this.width / 2, 10, 0xFFFFFF);
context.drawCenteredTextWithShadow(textRenderer, TITLE, this.width / 2, 10, 0xFFFFFFFF);
context.drawCenteredTextWithShadow(textRenderer, Text.translatable(Main.MOD_ID + ".screen.select_note.mapping_key", Text.translatable(keyToMap.getTranslationKey())), this.width / 2, 30, 0xFFFFFF);
}
@ -154,6 +159,7 @@ public class SelectNoteScreen extends Screen {
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
DebugLogger.log("SelectNoteScreen: 按下按键"+keyCode);
if (keyCode == GLFW.GLFW_KEY_ESCAPE) {
this.client.setScreen(this.parent);

View File

@ -32,11 +32,28 @@ public class ClientWorldMixin {
boolean useDistance, long seed,
CallbackInfo ci
) {
if (
((Main.config.omnidirectionalNoteBlockSounds && Main.SONG_PLAYER.running) || Main.PREVIEWER.running) &&
event.id().getPath().startsWith("block.note_block")
) {
boolean isNoteBlockSound = event.id().getPath().startsWith("block.note_block");
if (Main.config.forceOmnidirectionalNoteBlockSounds && isNoteBlockSound) {
ci.cancel();
playOmnidirectionalSound(event, category, volume, pitch, seed);
return;
}
if (Main.config.omnidirectionalNoteBlockSounds &&
(Main.SONG_PLAYER.running || Main.PREVIEWER.running) &&
isNoteBlockSound) {
ci.cancel();
playOmnidirectionalSound(event, category, volume, pitch, seed);
}
}
private void playOmnidirectionalSound(
SoundEvent event,
SoundCategory category,
float volume, float pitch,
long seed
) {
client.getSoundManager().play(
new PositionedSoundInstance(
event.id(),
@ -52,5 +69,4 @@ public class ClientWorldMixin {
)
);
}
}
}

View File

@ -93,5 +93,23 @@
"disc_jockey_revive.player.invalid_state_tuning": "Cannot tune: Invalid game state or mode.",
"disc_jockey_revive.player.tuned": "Tuning completed。",
"text.autoconfig.disc_jockey_revive.option.debugModeEnabled": "Enable Debug Mode",
"text.autoconfig.disc_jockey_revive.option.debugModeEnabled.@Tooltip": "Enables verbose logging for debugging purposes. \nKeep off unless troubleshooting. \nOf course, many debug-related parts have already been removed."
"text.autoconfig.disc_jockey_revive.option.debugModeEnabled.@Tooltip": "Enable detailed logging for debugging.\nKeep it off unless troubleshooting.\nOf course, many debug parts have been removed.\nEnabling this may cause the log file to be too large.\nHonestly, it's not really helpful for you.",
"disc_jockey_revive.screen.speed": "Speed",
"disc_jockey_revive.screen.speed_value": "Speed: %.2fx",
"text.autoconfig.disc_jockey_revive.option.playbackFailureAction": "On Playback Failure",
"text.autoconfig.disc_jockey_revive.option.playbackFailureAction.@Tooltip": "Action when playback fails due to missing note blocks\nOptions: Stop (default) or Play Next Song",
"disc_jockey_revive.playback_failure.stop": "Stop playback",
"disc_jockey_revive.playback_failure.next": "Play next song",
"disc_jockey_revive.playback_failure.next_message": "Playback failure handling: Play next song.",
"command.failure_action.set": "Playback failure action set to: %s",
"text.autoconfig.disc_jockey_revive.option.forceOmnidirectionalNoteBlockSounds": "Force Omnidirectional Note Blocks",
"text.autoconfig.disc_jockey_revive.option.forceOmnidirectionalNoteBlockSounds.@Tooltip": "Forces all note block sounds to be omnidirectional at all times.\nOverrides the regular omnidirectional setting.",
"disc_jockey_revive.screen.open_config": "Open Mod Settings",
"text.autoconfig.disc_jockey_revive.option.debugModeChatMessages": "Debug mode chat messages",
"text.autoconfig.disc_jockey_revive.option.debugModeChatMessages.@Tooltip": "Enable detailed logging output to chat\nPlease enable debug mode first"
}

View File

@ -54,10 +54,10 @@
"text.autoconfig.disc_jockey_revive.option.delayPlaybackStartBySecs.@Tooltip": "调音完成后延迟指定秒数再开始播放(如 0.5 表示延迟半秒)。",
"disc_jockey_revive.screen.current_folder": "当前文件夹",
"disc_jockey_revive.screen.play_mode": "播放模式",
"disc_jockey_revive.screen.mode_single": "单曲循环",
"disc_jockey_revive.screen.mode_list": "列表循环",
"disc_jockey_revive.screen.mode_random": "随机播放",
"disc_jockey_revive.screen.mode_stop": "播完停止",
"disc_jockey_revive.screen.mode_single": "🔂单曲循环",
"disc_jockey_revive.screen.mode_list": "🔁列表循环",
"disc_jockey_revive.screen.mode_random": "🔀随机播放",
"disc_jockey_revive.screen.mode_stop": "播完停止",
"disc_jockey_revive.screen.open_folder": "打开文件夹",
"disc_jockey_revive.screen.open_folder_failed": "无法打开文件夹",
"disc_jockey_revive.screen.reload": "重新加载",
@ -93,5 +93,23 @@
"disc_jockey_revive.player.invalid_state_tuning": "无法调音:游戏状态或模式无效。",
"disc_jockey_revive.player.tuned": "调音完成。",
"text.autoconfig.disc_jockey_revive.option.debugModeEnabled": "启用调试模式",
"text.autoconfig.disc_jockey_revive.option.debugModeEnabled.@Tooltip": "启用详细日志输出以进行调试。\n除非排查问题否则请保持关闭。\n当然很多调试用的部分已经移除。"
"text.autoconfig.disc_jockey_revive.option.debugModeEnabled.@Tooltip": "启用详细日志输出以进行调试。\n除非排查问题否则请保持关闭。\n当然很多调试用的部分已经移除。\n开启这个可能会导致日志文件占用过大\n老实说其实这对你并没有什么帮助",
"disc_jockey_revive.screen.speed": "速度",
"disc_jockey_revive.screen.speed_value": "速度: %.2fx",
"text.autoconfig.disc_jockey_revive.option.playbackFailureAction": "播放失败处理",
"text.autoconfig.disc_jockey_revive.option.playbackFailureAction.@Tooltip": "当因缺少音符盒导致播放失败时的处理方式\n选项: 停止播放(默认) 或 播放下一首",
"disc_jockey_revive.playback_failure.stop": "停止播放",
"disc_jockey_revive.playback_failure.next": "播放下一首",
"disc_jockey_revive.playback_failure.next_message": "播放失败处理:播放下一首",
"command.failure_action.set": "播放失败处理方式已设置为: %s",
"text.autoconfig.disc_jockey_revive.option.forceOmnidirectionalNoteBlockSounds": "强制全向音符盒音效",
"text.autoconfig.disc_jockey_revive.option.forceOmnidirectionalNoteBlockSounds.@Tooltip": "强制所有音符盒音效始终保持全向传播\n会覆盖常规的全向音符盒设置",
"disc_jockey_revive.screen.open_config": "打开模组设置",
"text.autoconfig.disc_jockey_revive.option.debugModeChatMessages": "调试模式聊天框消息",
"text.autoconfig.disc_jockey_revive.option.debugModeChatMessages.@Tooltip": "启用将详细日志输出到聊天框\n请先启用调试模式"
}

View File

@ -2,7 +2,7 @@
"required": true,
"minVersion": "0.8",
"package": "semmiedev.disc_jockey_revive.mixin",
"compatibilityLevel": "JAVA_21",
"compatibilityLevel": "{{COMPATIBILITY_LEVEL}}",
"mixins": [
"ClientWorldMixin"
],

View File

@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "disc_jockey_revive",
"version": "${version}",
"name": "Disc Jockey Revive",
"name": "${name}",
"description": "在游戏中播放音符盒(打碟机)",
"authors": [
"SemmieDev",
@ -10,7 +10,7 @@
"BRanulf(仅限该版本,请支持上面两个原作者)"
],
"contact": {
"homepage": "https://git.branulf.top/Branulf/DIsc_Jockey_revive",
"homepage": "https://git.branulf.top/Branulf",
"sources": "https://git.branulf.top/Branulf/DIsc_Jockey_revive"
},
"license": "MIT",
@ -28,8 +28,9 @@
"disc_jockey.mixins.json"
],
"depends": {
"fabricloader": ">=${loader_version}",
"fabric": "*",
"minecraft": "~1.21.4",
"minecraft": "${minecraft_version}",
"java": ">=21",
"cloth-config": "*"
}

View File

@ -0,0 +1,11 @@
minecraft_version=1.21.4
yarn_mappings=1.21.4+build.8
minecraft_dependency=1.21.4
game_versions=1.21.4
fabric_api_version=0.119.2+1.21.4
modmenu_version=13.0.3
cloth_config_version=17.0.144

View File

@ -0,0 +1,11 @@
minecraft_version=1.21.5
yarn_mappings=1.21.5+build.1
minecraft_dependency=1.21.5
game_versions=1.21.5
fabric_api_version=0.119.5+1.21.5
modmenu_version=14.0.0-rc.2
cloth_config_version=18.0.145

View File

@ -0,0 +1,11 @@
minecraft_version=1.21.6
yarn_mappings=1.21.6+build.1
minecraft_dependency=1.21.6
game_versions=1.21.6
fabric_api_version=0.127.0+1.21.6
modmenu_version=15.0.0-beta.3
cloth_config_version=19.0.147

View File

@ -0,0 +1,11 @@
minecraft_version=1.21.7
yarn_mappings=1.21.7+build.7
minecraft_dependency=1.21.7
game_versions=1.21.7
fabric_api_version=0.128.1+1.21.7
modmenu_version=15.0.0-beta.3
cloth_config_version=19.0.147

View File

@ -0,0 +1,11 @@
minecraft_version=1.21.8
yarn_mappings=1.21.8+build.1
minecraft_dependency=1.21.8
game_versions=1.21.8
fabric_api_version=0.129.0+1.21.8
modmenu_version=15.0.0-beta.3
cloth_config_version=19.0.147

1
versions/mainProject Normal file
View File

@ -0,0 +1 @@
1.21.4

View File

View File

View File

View File