├── .gitattributes ├── .github └── workflows │ └── gradle-publish-1.20.yml ├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libs └── jflac-codec-1.5.3-SNAPSHOT.jar └── src └── main ├── java └── com │ └── github │ └── tartaricacid │ └── netmusic │ ├── NetMusic.java │ ├── api │ ├── EncryptUtils.java │ ├── ExtraMusicList.java │ ├── NetEaseMusic.java │ ├── NetWorker.java │ ├── WebApi.java │ ├── pojo │ │ ├── NetEaseMusicList.java │ │ └── NetEaseMusicSong.java │ └── search │ │ ├── NeteaseMusicSearch.java │ │ └── SearchResponse.java │ ├── block │ ├── BlockCDBurner.java │ ├── BlockComputer.java │ └── BlockMusicPlayer.java │ ├── client │ ├── audio │ │ ├── FlacAudioFileReader.java │ │ ├── FlacFormatConversionProvider.java │ │ ├── MpegAudioFileReader.java │ │ ├── MpegFormatConversionProvider.java │ │ ├── MusicPlayManager.java │ │ ├── NetMusicAudioStream.java │ │ └── NetMusicSound.java │ ├── config │ │ └── MusicListManage.java │ ├── event │ │ └── ClientEvent.java │ ├── gui │ │ ├── CDBurnerMenuScreen.java │ │ └── ComputerMenuScreen.java │ ├── init │ │ ├── InitContainerGui.java │ │ └── InitModel.java │ ├── model │ │ └── ModelMusicPlayer.java │ └── renderer │ │ ├── MusicPlayerItemRenderer.java │ │ └── MusicPlayerRenderer.java │ ├── command │ └── NetMusicCommand.java │ ├── compat │ └── tlm │ │ ├── MaidPlugin.java │ │ ├── ai │ │ └── PlaySoundFunction.java │ │ ├── backpack │ │ ├── MusicPlayerBackpack.java │ │ └── data │ │ │ └── MusicPlayerBackpackData.java │ │ ├── client │ │ ├── audio │ │ │ └── MaidNetMusicSound.java │ │ ├── gui │ │ │ └── MusicPlayerBackpackContainerScreen.java │ │ └── model │ │ │ └── MusicPlayerBackpackModel.java │ │ ├── init │ │ ├── CompatRegistry.java │ │ ├── ContainerInit.java │ │ ├── ContainerScreenInit.java │ │ ├── ModelInit.java │ │ └── NetworkInit.java │ │ ├── inventory │ │ └── MusicPlayerBackpackContainer.java │ │ └── message │ │ ├── MaidMusicToClientMessage.java │ │ └── MaidStopMusicMessage.java │ ├── config │ └── GeneralConfig.java │ ├── init │ ├── CommandRegistry.java │ ├── CommonRegistry.java │ ├── InitBlocks.java │ ├── InitContainer.java │ ├── InitItems.java │ └── InitSounds.java │ ├── inventory │ ├── CDBurnerMenu.java │ ├── ComputerMenu.java │ └── MusicPlayerInv.java │ ├── item │ ├── ItemMusicCD.java │ └── ItemMusicPlayer.java │ ├── network │ ├── NetworkHandler.java │ └── message │ │ ├── GetMusicListMessage.java │ │ ├── MusicToClientMessage.java │ │ └── SetMusicIDMessage.java │ └── tileentity │ └── TileEntityMusicPlayer.java └── resources ├── META-INF ├── accesstransformer.cfg ├── mods.toml └── services │ ├── javax.sound.sampled.spi.AudioFileReader │ └── javax.sound.sampled.spi.FormatConversionProvider ├── assets ├── minecraft │ └── atlases │ │ └── blocks.json └── netmusic │ ├── blockstates │ ├── cd_burner.json │ ├── computer.json │ └── music_player.json │ ├── lang │ ├── en_us.json │ └── zh_cn.json │ ├── models │ ├── block │ │ ├── cd_burner.json │ │ ├── computer.json │ │ └── music_player.json │ └── item │ │ ├── cd_burner.json │ │ ├── computer.json │ │ ├── music_cd.json │ │ ├── music_player.json │ │ └── music_player_backpack.json │ ├── music.json │ ├── sounds.json │ └── textures │ ├── block │ ├── cd_burner.png │ ├── computer.png │ └── music_player.png │ ├── entity │ └── music_player_backpack.png │ ├── gui │ ├── cd_burner.png │ ├── computer.png │ └── maid_music_player.png │ ├── item │ ├── music_cd.png │ └── music_player_backpack.png │ └── slot │ └── music_cd_slot.png ├── data └── netmusic │ ├── loot_tables │ └── blocks │ │ ├── cd_burner.json │ │ ├── computer.json │ │ └── music_player.json │ └── recipes │ ├── cd_burner.json │ ├── computer.json │ ├── music_cd.json │ ├── music_player.json │ └── tlm │ └── music_player_backpack.json └── pack.mcmeta /.gitattributes: -------------------------------------------------------------------------------- 1 | # Disable autocrlf on generated files, they always generate with LF 2 | # Add any extra files or paths here to make git stop saying they 3 | # are changed when only line endings change. 4 | src/generated/**/.cache/cache text eol=lf 5 | src/generated/**/*.json text eol=lf 6 | -------------------------------------------------------------------------------- /.github/workflows/gradle-publish-1.20.yml: -------------------------------------------------------------------------------- 1 | name: Gradle Package 1.20 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - "1.20" 8 | paths: 9 | - 'src/main/**' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | with: 19 | ref: "1.20" 20 | 21 | - name: Set up JDK 17 22 | uses: actions/setup-java@v4 23 | with: 24 | java-version: '17' 25 | distribution: 'microsoft' 26 | server-id: github 27 | settings-path: ${{ github.workspace }} 28 | 29 | - name: Cache Gradle packages 30 | uses: actions/cache@v4 31 | with: 32 | path: | 33 | ~/.gradle/caches 34 | ~/.gradle/wrapper 35 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 36 | restore-keys: ${{ runner.os }}-gradle- 37 | 38 | - name: Build with Gradle 39 | run: | 40 | chmod +x ./gradlew 41 | ./gradlew build 42 | 43 | - name: Create timestamp 44 | id: create_timestamp 45 | run: echo "timestamp=$(date '+%Y-%m-%d-%H-%M-%S')" >> $GITHUB_OUTPUT 46 | shell: bash 47 | 48 | - name: Send file to Release 49 | uses: softprops/action-gh-release@v2 50 | with: 51 | tag_name: snapshot-${{ steps.create_timestamp.outputs.timestamp }} 52 | body: ${{ github.event.head_commit.message }} 53 | draft: false 54 | prerelease: true 55 | files: ./build/libs/*.jar 56 | 57 | - name: Cleanup Gradle Cache 58 | run: | 59 | rm -f ~/.gradle/caches/modules-2/modules-2.lock 60 | rm -f ~/.gradle/caches/modules-2/gc.properties -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # eclipse 2 | bin 3 | *.launch 4 | .settings 5 | .metadata 6 | .classpath 7 | .project 8 | 9 | # idea 10 | out 11 | *.ipr 12 | *.iws 13 | *.iml 14 | .idea 15 | 16 | # gradle 17 | build 18 | .gradle 19 | 20 | # other 21 | eclipse 22 | run 23 | logs 24 | 25 | # Files from Forge MDK 26 | forge*changelog.txt 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Net Music Mod 网络音乐机 2 | 3 | ## Support 支持情况 4 | ### Update 更新 5 | - 1.20.1 Forge 6 | - 1.21.1 NeoForge 7 | - 1.20.1 Fabric 8 | - 1.21.1 Fabric 9 | 10 | ### Stop Support 停止更新 11 | - 1.19.2 Forge 12 | - 1.18.2 Forge 13 | - 1.16.5 Forge 14 | - 1.12.2 Forge 15 | 16 | ## Acknowledgments 鸣谢 17 | Thanks to IMG for helping to make the Fabric update 18 | 感谢 IMG 帮忙制作的 Fabric 更新 -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | maven { url = 'https://maven.minecraftforge.net' } 4 | maven { url = 'https://maven.parchmentmc.org' } 5 | mavenCentral() 6 | gradlePluginPortal() 7 | } 8 | dependencies { 9 | classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true 10 | classpath 'org.parchmentmc:librarian:1.+' 11 | classpath 'gradle.plugin.com.github.johnrengelman:shadow:7.1.2' 12 | } 13 | } 14 | apply plugin: 'net.minecraftforge.gradle' 15 | apply plugin: 'org.parchmentmc.librarian.forgegradle' 16 | apply plugin: 'eclipse' 17 | apply plugin: 'com.github.johnrengelman.shadow' 18 | apply plugin: 'java' 19 | 20 | version = '1.1.6' 21 | group = 'com.github.tartaricacid' 22 | archivesBaseName = 'netmusic-forge-1.20.1' 23 | 24 | java.toolchain.languageVersion = JavaLanguageVersion.of(17) 25 | 26 | minecraft { 27 | mappings channel: 'parchment', version: '2023.08.20-1.20.1' 28 | accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') 29 | runs { 30 | client { 31 | jvmArgs "-XX:+AllowEnhancedClassRedefinition" 32 | workingDirectory project.file('run/client_a') 33 | property 'forge.logging.markers', 'REGISTRIES' 34 | property 'forge.logging.console.level', 'debug' 35 | property 'fml.earlyprogresswindow', 'false' 36 | property 'mixin.env.remapRefMap', 'true' 37 | property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" 38 | mods { 39 | netmusic { 40 | source sourceSets.main 41 | } 42 | } 43 | } 44 | 45 | client2 { 46 | parent minecraft.runs.client 47 | workingDirectory project.file('run/client_b') 48 | args '--username', 'tartaric_acid' 49 | mods { 50 | netmusic { 51 | source sourceSets.main 52 | } 53 | } 54 | } 55 | 56 | server { 57 | jvmArgs "-XX:+AllowEnhancedClassRedefinition" 58 | workingDirectory project.file('run') 59 | property 'forge.logging.markers', 'REGISTRIES' 60 | property 'forge.logging.console.level', 'debug' 61 | property 'forge.enabledGameTestNamespaces', 'netmusic' 62 | property 'mixin.env.remapRefMap', 'true' 63 | property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" 64 | mods { 65 | netmusic { 66 | source sourceSets.main 67 | } 68 | } 69 | } 70 | } 71 | } 72 | 73 | sourceSets.main.resources { srcDir 'src/generated/resources' } 74 | 75 | repositories { 76 | flatDir { 77 | dir "libs" 78 | } 79 | maven { 80 | // location of the maven that hosts JEI files since January 2023 81 | // Patchouli 82 | name = "Jared's maven" 83 | url = "https://maven.blamejared.com/" 84 | content { 85 | includeGroup "mezz.jei" 86 | includeGroup "vazkii.patchouli" 87 | } 88 | } 89 | maven { 90 | // location of a maven mirror for JEI files, as a fallback 91 | name = "ModMaven" 92 | url = "https://modmaven.dev" 93 | content { 94 | includeGroup "mezz.jei" 95 | includeGroup "vazkii.patchouli" 96 | } 97 | } 98 | maven { 99 | name = "Curse Maven" 100 | url = "https://www.cursemaven.com" 101 | content { 102 | includeGroup "curse.maven" 103 | } 104 | } 105 | } 106 | 107 | dependencies { 108 | minecraft 'net.minecraftforge:forge:1.20.1-47.1.0' 109 | minecraftLibrary 'com.googlecode.soundlibs:mp3spi:1.9.5.4' 110 | minecraftLibrary 'libs:jflac-codec-1.5.3:SNAPSHOT' 111 | 112 | // compile against the JEI API but do not include it at runtime 113 | compileOnly(fg.deobf("mezz.jei:jei-1.20.1-common-api:15.0.0.12")) 114 | compileOnly(fg.deobf("mezz.jei:jei-1.20.1-forge-api:15.0.0.12")) 115 | // at runtime, use the full JEI jar for Forge 116 | runtimeOnly(fg.deobf("mezz.jei:jei-1.20.1-forge:15.0.0.12")) 117 | // Sound Physics Remastered 118 | runtimeOnly(fg.deobf("curse.maven:sound-physics-remastered-535489:5525256")) 119 | 120 | implementation fg.deobf('curse.maven:touhou-little-maid-355044:6589004-sources-6589098') 121 | 122 | runtimeOnly "curse.maven:kotlin-for-forge-351264:4983659" 123 | runtimeOnly fg.deobf("curse.maven:libipn-679177:4870127") 124 | implementation fg.deobf("curse.maven:inventory-profiles-next-495267:4813458") 125 | } 126 | 127 | tasks.withType(JavaCompile).configureEach { 128 | options.encoding = 'UTF-8' 129 | } 130 | 131 | shadowJar { 132 | dependencies { 133 | include(dependency("com.googlecode.soundlibs:mp3spi:1.9.5.4")) 134 | include(dependency("com.googlecode.soundlibs:jlayer:1.0.1.4")) 135 | include(dependency("com.googlecode.soundlibs:tritonus-share:0.3.7.4")) 136 | include(dependency("libs:jflac-codec-1.5.3:SNAPSHOT")) 137 | } 138 | 139 | relocate 'javazoom.jl', 'com.github.tartaricacid.netmusic.soundlibs.javazoom.jl' 140 | relocate 'javazoom.spi', 'com.github.tartaricacid.netmusic.soundlibs.javazoom.spi' 141 | relocate 'org.tritonus', 'com.github.tartaricacid.netmusic.soundlibs.org.tritonus' 142 | relocate 'org.jflac', 'com.github.tartaricacid.netmusic.soundlibs.org.jflac' 143 | 144 | mergeServiceFiles() 145 | } 146 | 147 | reobf { 148 | shadowJar {} 149 | } 150 | 151 | task shadowJarPublish(type: Jar) { 152 | dependsOn reobfShadowJar 153 | archiveClassifier = "all" 154 | from zipTree("${project.projectDir}/build/reobfShadowJar/output.jar") 155 | } 156 | 157 | assemble.dependsOn shadowJar 158 | tasks.build.dependsOn shadowJarPublish 159 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Sets default memory used for gradle commands. Can be overridden by user or command line properties. 2 | # This is required to provide enough memory for the Minecraft decompilation process. 3 | org.gradle.jvmargs=-Xmx3G 4 | org.gradle.daemon=false -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TartaricAcid/NetMusic/468fc9929b37ca82cfdf11bea4aafbb64668114b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /libs/jflac-codec-1.5.3-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TartaricAcid/NetMusic/468fc9929b37ca82cfdf11bea4aafbb64668114b/libs/jflac-codec-1.5.3-SNAPSHOT.jar -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/NetMusic.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic; 2 | 3 | import com.github.tartaricacid.netmusic.api.NetEaseMusic; 4 | import com.github.tartaricacid.netmusic.api.WebApi; 5 | import com.github.tartaricacid.netmusic.config.GeneralConfig; 6 | import com.github.tartaricacid.netmusic.init.InitBlocks; 7 | import com.github.tartaricacid.netmusic.init.InitContainer; 8 | import com.github.tartaricacid.netmusic.init.InitItems; 9 | import com.github.tartaricacid.netmusic.init.InitSounds; 10 | import net.minecraftforge.fml.ModLoadingContext; 11 | import net.minecraftforge.fml.common.Mod; 12 | import net.minecraftforge.fml.config.ModConfig; 13 | import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; 14 | import org.apache.logging.log4j.LogManager; 15 | import org.apache.logging.log4j.Logger; 16 | 17 | @Mod(NetMusic.MOD_ID) 18 | public class NetMusic { 19 | public static final String MOD_ID = "netmusic"; 20 | public static final Logger LOGGER = LogManager.getLogger(MOD_ID); 21 | public static WebApi NET_EASE_WEB_API; 22 | 23 | public NetMusic() { 24 | NET_EASE_WEB_API = new NetEaseMusic().getApi(); 25 | InitBlocks.BLOCKS.register(FMLJavaModLoadingContext.get().getModEventBus()); 26 | InitBlocks.TILE_ENTITIES.register(FMLJavaModLoadingContext.get().getModEventBus()); 27 | InitItems.ITEMS.register(FMLJavaModLoadingContext.get().getModEventBus()); 28 | InitItems.TABS.register(FMLJavaModLoadingContext.get().getModEventBus()); 29 | InitSounds.SOUND_EVENTS.register(FMLJavaModLoadingContext.get().getModEventBus()); 30 | InitContainer.CONTAINER_TYPE.register(FMLJavaModLoadingContext.get().getModEventBus()); 31 | ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, GeneralConfig.init()); 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/api/EncryptUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.api; 2 | 3 | import org.apache.commons.codec.binary.Base64; 4 | 5 | import javax.crypto.Cipher; 6 | import javax.crypto.spec.IvParameterSpec; 7 | import javax.crypto.spec.SecretKeySpec; 8 | import java.math.BigInteger; 9 | import java.net.URLEncoder; 10 | import java.nio.charset.StandardCharsets; 11 | import java.util.Random; 12 | 13 | /** 14 | * @author 内个球 15 | */ 16 | public class EncryptUtils { 17 | public static String encryptedParam(String text) throws Exception { 18 | if (text == null) { 19 | return "params=null&encSecKey=null"; 20 | } 21 | String modulus = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7" + 22 | "b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280" + 23 | "104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932" + 24 | "575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b" + 25 | "3ece0462db0a22b8e7"; 26 | String nonce = "0CoJUm6Qyw8W8jud"; 27 | String pubKey = "010001"; 28 | String secKey = getRandomString(); 29 | String encText = aesEncrypt(aesEncrypt(text, nonce), secKey); 30 | String encSecKey = rsaEncrypt(secKey, pubKey, modulus); 31 | return "params=" + URLEncoder.encode(encText, "UTF-8") + "&encSecKey=" + URLEncoder.encode(encSecKey, "UTF-8"); 32 | } 33 | 34 | private static String aesEncrypt(String text, String key) throws Exception { 35 | IvParameterSpec ivParameterSpec = new IvParameterSpec("0102030405060708".getBytes(StandardCharsets.UTF_8)); 36 | SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"); 37 | Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 38 | cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec); 39 | byte[] encrypted = cipher.doFinal(text.getBytes()); 40 | return Base64.encodeBase64String(encrypted); 41 | } 42 | 43 | private static String rsaEncrypt(String text, String pubKey, String modulus) { 44 | text = new StringBuilder(text).reverse().toString(); 45 | BigInteger val = new BigInteger(1, text.getBytes()); 46 | BigInteger exp = new BigInteger(pubKey, 16); 47 | BigInteger mod = new BigInteger(modulus, 16); 48 | StringBuilder hexString = new StringBuilder(val.modPow(exp, mod).toString(16)); 49 | if (hexString.length() >= 256) { 50 | return hexString.substring(hexString.length() - 256); 51 | } else { 52 | while (hexString.length() < 256) { 53 | hexString.insert(0, "0"); 54 | } 55 | return hexString.toString(); 56 | } 57 | } 58 | 59 | private static String getRandomString() { 60 | StringBuilder stringBuffer = new StringBuilder(); 61 | Random r = new Random(); 62 | for (int i = 0; i < 16; i++) { 63 | stringBuffer.append(Integer.toHexString(r.nextInt(16))); 64 | } 65 | return stringBuffer.toString(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/api/ExtraMusicList.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.api; 2 | 3 | import com.github.tartaricacid.netmusic.api.pojo.NetEaseMusicList; 4 | import com.google.common.collect.Lists; 5 | import com.google.gson.annotations.SerializedName; 6 | 7 | import java.util.List; 8 | 9 | public class ExtraMusicList { 10 | @SerializedName("code") 11 | private int code; 12 | 13 | @SerializedName("songs") 14 | private List tracks = Lists.newArrayList(); 15 | 16 | public int getCode() { 17 | return code; 18 | } 19 | 20 | public List getTracks() { 21 | return tracks; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/api/NetEaseMusic.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.api; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import java.util.HashMap; 6 | 7 | /** 8 | * @author 内个球 9 | */ 10 | public class NetEaseMusic { 11 | private final HashMap requestPropertyData = new HashMap<>(); 12 | 13 | public NetEaseMusic() { 14 | init(); 15 | } 16 | 17 | public NetEaseMusic(String cookie) { 18 | init(); 19 | requestPropertyData.put("Cookie", cookie); 20 | } 21 | 22 | public static String getOrigin() { 23 | return "http://music.163.com"; 24 | } 25 | 26 | public static String getReferer() { 27 | return "http://music.163.com/"; 28 | } 29 | 30 | public static String getUserAgent() { 31 | return StringUtils.joinWith("\u0020", 32 | "Mozilla/5.0 (Windows NT 6.1; Win64; x64)", 33 | "AppleWebKit/537.36 (KHTML, like Gecko)", 34 | "Chrome/81.0.4044.138", 35 | "Safari/537.36"); 36 | } 37 | 38 | private void init() { 39 | requestPropertyData.put("Host", "music.163.com"); 40 | requestPropertyData.put("Origin", getOrigin()); 41 | requestPropertyData.put("Referer", getReferer()); 42 | requestPropertyData.put("Content-Type", "application/x-www-form-urlencoded"); 43 | requestPropertyData.put("User-Agent", getUserAgent()); 44 | } 45 | 46 | public WebApi getApi() { 47 | return new WebApi(requestPropertyData); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/api/NetWorker.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.api; 2 | 3 | import com.github.tartaricacid.netmusic.config.GeneralConfig; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | import javax.annotation.Nullable; 7 | import java.io.BufferedReader; 8 | import java.io.IOException; 9 | import java.io.InputStreamReader; 10 | import java.io.PrintWriter; 11 | import java.net.InetSocketAddress; 12 | import java.net.Proxy; 13 | import java.net.URL; 14 | import java.net.URLConnection; 15 | import java.nio.charset.StandardCharsets; 16 | import java.util.Collection; 17 | import java.util.Map; 18 | 19 | /** 20 | * @author 内个球 21 | */ 22 | public class NetWorker { 23 | public static String get(String url, Map requestPropertyData) throws IOException { 24 | StringBuilder result = new StringBuilder(); 25 | 26 | URL urlConnect = new URL(url); 27 | URLConnection connection = urlConnect.openConnection(getProxyFromConfig()); 28 | 29 | Collection keys = requestPropertyData.keySet(); 30 | for (String key : keys) { 31 | String val = requestPropertyData.get(key); 32 | connection.setRequestProperty(key, val); 33 | } 34 | connection.setConnectTimeout(12000); 35 | connection.setDoInput(true); 36 | 37 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)); 38 | String line; 39 | while ((line = bufferedReader.readLine()) != null) { 40 | result.append(line); 41 | } 42 | 43 | bufferedReader.close(); 44 | 45 | return result.toString(); 46 | } 47 | 48 | @Nullable 49 | public static String getRedirectUrl(String url, Map requestPropertyData) throws IOException { 50 | URL urlConnect = new URL(url); 51 | URLConnection connection = urlConnect.openConnection(getProxyFromConfig()); 52 | Collection keys = requestPropertyData.keySet(); 53 | for (String key : keys) { 54 | String val = requestPropertyData.get(key); 55 | connection.setRequestProperty(key, val); 56 | } 57 | connection.setConnectTimeout(3_000); 58 | connection.setReadTimeout(5_000); 59 | return connection.getHeaderField("Location"); 60 | } 61 | 62 | public static String post(String url, String param, Map requestPropertyData) throws IOException { 63 | StringBuilder result = new StringBuilder(); 64 | BufferedReader bufferedReader; 65 | PrintWriter printWriter; 66 | 67 | URL urlConnect = new URL(url); 68 | URLConnection connection = urlConnect.openConnection(getProxyFromConfig()); 69 | 70 | Collection keys = requestPropertyData.keySet(); 71 | for (String key : keys) { 72 | String val = requestPropertyData.get(key); 73 | connection.setRequestProperty(key, val); 74 | } 75 | connection.setConnectTimeout(12000); 76 | connection.setDoOutput(true); 77 | connection.setDoInput(true); 78 | 79 | printWriter = new PrintWriter(connection.getOutputStream()); 80 | printWriter.print(param); 81 | printWriter.flush(); 82 | 83 | bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)); 84 | String line; 85 | while ((line = bufferedReader.readLine()) != null) { 86 | result.append(line); 87 | } 88 | 89 | bufferedReader.close(); 90 | printWriter.close(); 91 | 92 | return result.toString(); 93 | } 94 | 95 | public static Proxy getProxyFromConfig() { 96 | Proxy.Type proxyType = GeneralConfig.PROXY_TYPE.get(); 97 | String proxyAddress = GeneralConfig.PROXY_ADDRESS.get(); 98 | if (proxyType == Proxy.Type.DIRECT || StringUtils.isBlank(proxyAddress)) { 99 | return Proxy.NO_PROXY; 100 | } 101 | String[] split = proxyAddress.split(":", 2); 102 | if (split.length != 2) { 103 | return Proxy.NO_PROXY; 104 | } 105 | return new Proxy(proxyType, new InetSocketAddress(split[0], Integer.parseInt(split[1]))); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/api/WebApi.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.api; 2 | 3 | import com.google.common.net.UrlEscapers; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | import javax.annotation.Nullable; 7 | import java.io.IOException; 8 | import java.net.URLEncoder; 9 | import java.util.Arrays; 10 | import java.util.HashMap; 11 | 12 | /** 13 | * @author 内个球 14 | */ 15 | public final class WebApi { 16 | public static final int TYPE_SONG = 1; 17 | public static final int TYPE_ALBUM = 10; 18 | public static final int TYPE_SINGER = 100; 19 | public static final int TYPE_PLAY_LIST = 1000; 20 | public static final int TYPE_USER = 1002; 21 | public static final int TYPE_RADIO = 1009; 22 | 23 | private final HashMap requestPropertyData; 24 | 25 | public WebApi(HashMap requestPropertyData) { 26 | this.requestPropertyData = requestPropertyData; 27 | } 28 | 29 | public static String getSearchUrl(String searchText, int type, int limit) { 30 | String escape = UrlEscapers.urlPathSegmentEscaper().escape(searchText); 31 | return "https://music.163.com/api/search/get/web?s=%s&type=%d&limit=%d".formatted(escape, type, limit); 32 | } 33 | 34 | /** 35 | * 这个 URL 目前已经出问题了,无法使用 36 | */ 37 | @Deprecated 38 | public String search(String key, long size, long page, int type) throws Exception { 39 | String url = "http://music.163.com/weapi/cloudsearch/get/web?csrf_token="; 40 | String param = "{\"s\":\"" + key + "\",\"type\":" + type + ",\"offset\":" + (page - 1) * size + ",\"limit\":" + size + ",\"total\":true,\"csrf_token\":\"\"}"; 41 | String encrypt = EncryptUtils.encryptedParam(param); 42 | return NetWorker.post(url, encrypt, requestPropertyData); 43 | } 44 | 45 | public String album(long albumId) throws Exception { 46 | String url = "http://music.163.com/weapi/v1/album/" + albumId + "?id=" + albumId + "&offset=0&total=true&limit=12"; 47 | String param = "{\"album_id\":" + albumId + ",\"csrf_token\":\"\"}"; 48 | String encrypt = EncryptUtils.encryptedParam(param); 49 | return NetWorker.post(url, encrypt, requestPropertyData); 50 | } 51 | 52 | public String song(long songId) throws IOException { 53 | String url = "http://music.163.com/api/song/detail/?id=" + songId + "&ids=%5B" + songId + "%5D"; 54 | return NetWorker.get(url, requestPropertyData); 55 | } 56 | 57 | public String songs(long... songIds) throws IOException { 58 | String ids = StringUtils.deleteWhitespace(Arrays.toString(songIds)); 59 | String url = "http://music.163.com/api/song/detail/?ids=" + URLEncoder.encode(ids, "utf-8"); 60 | return NetWorker.get(url, requestPropertyData); 61 | } 62 | 63 | public String lyric(long songId) throws IOException { 64 | String url = "http://music.163.com/api/song/lyric/?id=" + songId + "&lv=-1&kv=-1&tv=-1"; 65 | return NetWorker.get(url, requestPropertyData); 66 | } 67 | 68 | public String mp3(long quality, long... songIds) throws Exception { 69 | String url = "http://music.163.com/weapi/song/enhance/player/url?csrf_token="; 70 | String param = "{\"ids\":" + Arrays.toString(songIds) + ",\"br\":" + quality + ",\"csrf_token\":\"\"}"; 71 | String encrypt = EncryptUtils.encryptedParam(param); 72 | return NetWorker.post(url, encrypt, requestPropertyData); 73 | } 74 | 75 | public String songComments(long songId, long size, long page) throws Exception { 76 | String url = "http://music.163.com/weapi/v1/resource/comments/R_SO_4_" + songId + "?csrf_token="; 77 | String param = "{\"id\":" + songId + ",\"offset\":" + (page - 1) * size + ",\"limit\":" + size + ",\"total\":true ,\"csrf_token\":\"\"}"; 78 | String encrypt = EncryptUtils.encryptedParam(param); 79 | return NetWorker.post(url, encrypt, requestPropertyData); 80 | } 81 | 82 | public String listComments(long listId, long size, long page) throws Exception { 83 | String url = "http://music.163.com/weapi/v1/resource/comments/A_PL_0_" + listId + "?csrf_token="; 84 | String param = "{\"id\":" + listId + ",\"offset\":" + (page - 1) * size + ",\"limit\":" + size + ",\"total\":true ,\"csrf_token\":\"\"}"; 85 | String encrypt = EncryptUtils.encryptedParam(param); 86 | return NetWorker.post(url, encrypt, requestPropertyData); 87 | } 88 | 89 | public String list(long listId) throws Exception { 90 | String url = "http://music.163.com/weapi/v3/playlist/detail?csrf_token="; 91 | String param = "{\"id\":" + listId + ",\"csrf_token\":\"\"}"; 92 | String encrypt = EncryptUtils.encryptedParam(param); 93 | return NetWorker.post(url, encrypt, requestPropertyData); 94 | } 95 | 96 | public String userAllList(long userId, long size, long page) throws Exception { 97 | String url = "http://music.163.com/api/user/playlist/?uid=" + userId + "&offset=0&total=true&limit=1000"; 98 | return NetWorker.get(url, requestPropertyData); 99 | } 100 | 101 | @Nullable 102 | public String getRedirectMusicUrl(long musicId) throws Exception { 103 | String url = String.format("https://music.163.com/song/media/outer/url?id=%d.mp3", musicId); 104 | return NetWorker.getRedirectUrl(url, requestPropertyData); 105 | } 106 | 107 | public HashMap getRequestPropertyData() { 108 | return requestPropertyData; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/api/pojo/NetEaseMusicList.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.api.pojo; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.gson.annotations.SerializedName; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | public class NetEaseMusicList { 11 | @SerializedName(value = "result", alternate = "playlist") 12 | private PlayList playList; 13 | 14 | @SerializedName("code") 15 | private int code; 16 | 17 | public PlayList getPlayList() { 18 | return playList; 19 | } 20 | 21 | public int getCode() { 22 | return code; 23 | } 24 | 25 | public static class Track { 26 | @SerializedName("id") 27 | private long id; 28 | 29 | @SerializedName("name") 30 | private String name; 31 | 32 | @SerializedName(value = "artists", alternate = "ar") 33 | private List artists; 34 | 35 | @SerializedName(value = "album", alternate = "al") 36 | private Album album; 37 | 38 | @SerializedName(value = "duration", alternate = "dt") 39 | private int duration; 40 | 41 | @SerializedName(value = "transNames", alternate = "tns") 42 | private List transNames; 43 | 44 | @SerializedName("fee") 45 | private int fee; 46 | 47 | public long getId() { 48 | return id; 49 | } 50 | 51 | public String getName() { 52 | return name; 53 | } 54 | 55 | public List getArtists() { 56 | if (artists == null || artists.isEmpty()) { 57 | return Collections.emptyList(); 58 | } 59 | List artistNames = Lists.newArrayList(); 60 | artists.forEach(artist -> artistNames.add(artist.name)); 61 | return artistNames; 62 | } 63 | 64 | public Album getAlbum() { 65 | return album; 66 | } 67 | 68 | public int getDuration() { 69 | return duration; 70 | } 71 | 72 | public String getTransName() { 73 | if (transNames == null || transNames.isEmpty()) { 74 | return StringUtils.EMPTY; 75 | } 76 | return transNames.get(0); 77 | } 78 | 79 | public boolean needVip() { 80 | return fee == 1; 81 | } 82 | } 83 | 84 | private static class Creator { 85 | @SerializedName("nickname") 86 | private String nickname; 87 | } 88 | 89 | private static class Artist { 90 | @SerializedName("name") 91 | private String name; 92 | } 93 | 94 | private static class Album { 95 | @SerializedName("name") 96 | private String name; 97 | } 98 | 99 | public class PlayList { 100 | @SerializedName("name") 101 | private String name; 102 | 103 | @SerializedName("trackCount") 104 | private int trackCount; 105 | 106 | @SerializedName("playCount") 107 | private int playCount; 108 | 109 | @SerializedName("creator") 110 | private Creator creator; 111 | 112 | @SerializedName("createTime") 113 | private long createTime; 114 | 115 | @SerializedName("subscribedCount") 116 | private int subscribedCount; 117 | 118 | @SerializedName("shareCount") 119 | private int shareCount; 120 | 121 | @SerializedName("tags") 122 | private List tags = Lists.newArrayList(); 123 | 124 | @SerializedName("description") 125 | private String description = ""; 126 | 127 | @SerializedName("tracks") 128 | private List tracks; 129 | 130 | @SerializedName("trackIds") 131 | private List trackIds; 132 | 133 | public String getName() { 134 | return name; 135 | } 136 | 137 | public int getTrackCount() { 138 | return trackCount; 139 | } 140 | 141 | public int getPlayCount() { 142 | return playCount; 143 | } 144 | 145 | public int getSubscribedCount() { 146 | return subscribedCount; 147 | } 148 | 149 | public int getShareCount() { 150 | return shareCount; 151 | } 152 | 153 | public String getDescription() { 154 | return description; 155 | } 156 | 157 | public List getTracks() { 158 | return tracks; 159 | } 160 | 161 | public List getTrackIds() { 162 | return trackIds; 163 | } 164 | 165 | public class TrackId { 166 | @SerializedName("id") 167 | private long id; 168 | 169 | public long getId() { 170 | return id; 171 | } 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/api/pojo/NetEaseMusicSong.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.api.pojo; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.gson.annotations.SerializedName; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import javax.annotation.Nullable; 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | public class NetEaseMusicSong { 12 | @SerializedName("code") 13 | private int code; 14 | 15 | @SerializedName("songs") 16 | private List song; 17 | 18 | public static class Song { 19 | @SerializedName("id") 20 | private long id; 21 | 22 | @SerializedName("name") 23 | private String name; 24 | 25 | @SerializedName(value = "transNames", alternate = "tns") 26 | private List transNames; 27 | 28 | @SerializedName(value = "duration", alternate = "dt") 29 | private int duration; 30 | 31 | @SerializedName("fee") 32 | private int fee; 33 | 34 | @SerializedName(value = "artists", alternate = "ar") 35 | private List artists; 36 | 37 | public long getId() { 38 | return id; 39 | } 40 | 41 | public String getName() { 42 | return name; 43 | } 44 | 45 | public int getDuration() { 46 | return duration; 47 | } 48 | 49 | public String getTransName() { 50 | if (transNames == null || transNames.isEmpty()) { 51 | return StringUtils.EMPTY; 52 | } 53 | return transNames.get(0); 54 | } 55 | 56 | public boolean needVip() { 57 | return fee == 1; 58 | } 59 | 60 | public List getArtists() { 61 | if (artists == null || artists.isEmpty()) { 62 | return Collections.emptyList(); 63 | } 64 | List artistNames = Lists.newArrayList(); 65 | artists.forEach(artist -> artistNames.add(artist.name)); 66 | return artistNames; 67 | } 68 | } 69 | 70 | private static class Artist { 71 | @SerializedName("name") 72 | private String name; 73 | } 74 | 75 | @Nullable 76 | public Song getSong() { 77 | if (song.isEmpty()) { 78 | return null; 79 | } 80 | return song.get(0); 81 | } 82 | 83 | public int getCode() { 84 | return code; 85 | } 86 | } -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/api/search/NeteaseMusicSearch.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.api.search; 2 | 3 | import com.github.tartaricacid.netmusic.api.NetEaseMusic; 4 | import com.github.tartaricacid.netmusic.api.NetWorker; 5 | import com.github.tartaricacid.netmusic.api.WebApi; 6 | import com.google.common.net.HttpHeaders; 7 | 8 | import java.net.InetSocketAddress; 9 | import java.net.Proxy; 10 | import java.net.ProxySelector; 11 | import java.net.URI; 12 | import java.net.http.HttpClient; 13 | import java.net.http.HttpRequest; 14 | import java.net.http.HttpResponse; 15 | import java.time.Duration; 16 | import java.util.function.BiConsumer; 17 | 18 | public class NeteaseMusicSearch { 19 | public static void searchFirstSong(String searchText, BiConsumer, Throwable> callback) { 20 | Proxy proxy = NetWorker.getProxyFromConfig(); 21 | InetSocketAddress addr = (InetSocketAddress) proxy.address(); 22 | ProxySelector selector = ProxySelector.of(addr); 23 | 24 | URI uri = URI.create(WebApi.getSearchUrl(searchText, WebApi.TYPE_SONG, 1)); 25 | 26 | HttpClient client = HttpClient.newBuilder() 27 | .connectTimeout(Duration.ofSeconds(30)) 28 | .proxy(selector) 29 | .build(); 30 | 31 | HttpRequest request = HttpRequest.newBuilder() 32 | .timeout(Duration.ofSeconds(30)) 33 | .header(HttpHeaders.REFERER, NetEaseMusic.getReferer()) 34 | .header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded") 35 | .header(HttpHeaders.USER_AGENT, NetEaseMusic.getUserAgent()) 36 | .uri(uri).GET().build(); 37 | 38 | client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).whenComplete(callback); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/api/search/SearchResponse.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.api.search; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import javax.annotation.Nullable; 6 | import java.util.List; 7 | 8 | public class SearchResponse { 9 | @SerializedName("result") 10 | private Result result; 11 | 12 | @Nullable 13 | public Song getFirstSong() { 14 | if (result != null && result.songs != null && !result.songs.isEmpty()) { 15 | return result.songs.get(0); 16 | } 17 | return null; 18 | } 19 | 20 | public static class Song { 21 | @SerializedName("id") 22 | private long id; 23 | 24 | @SerializedName("name") 25 | private String name; 26 | 27 | @SerializedName("duration") 28 | private long durationMs; 29 | 30 | public String getUrl() { 31 | return String.format("https://music.163.com/song/media/outer/url?id=%d.mp3", id); 32 | } 33 | 34 | public int getTimeSecond() { 35 | return (int) (durationMs / 1000); 36 | } 37 | 38 | public String getName() { 39 | return name; 40 | } 41 | } 42 | 43 | private static class Result { 44 | @SerializedName("songs") 45 | private List songs; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/block/BlockCDBurner.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.block; 2 | 3 | import com.github.tartaricacid.netmusic.inventory.CDBurnerMenu; 4 | import net.minecraft.ChatFormatting; 5 | import net.minecraft.core.BlockPos; 6 | import net.minecraft.core.Direction; 7 | import net.minecraft.network.chat.Component; 8 | import net.minecraft.world.InteractionHand; 9 | import net.minecraft.world.InteractionResult; 10 | import net.minecraft.world.MenuProvider; 11 | import net.minecraft.world.SimpleMenuProvider; 12 | import net.minecraft.world.entity.player.Player; 13 | import net.minecraft.world.item.ItemStack; 14 | import net.minecraft.world.item.TooltipFlag; 15 | import net.minecraft.world.item.context.BlockPlaceContext; 16 | import net.minecraft.world.level.BlockGetter; 17 | import net.minecraft.world.level.Level; 18 | import net.minecraft.world.level.block.Block; 19 | import net.minecraft.world.level.block.HorizontalDirectionalBlock; 20 | import net.minecraft.world.level.block.SoundType; 21 | import net.minecraft.world.level.block.state.BlockBehaviour; 22 | import net.minecraft.world.level.block.state.BlockState; 23 | import net.minecraft.world.level.block.state.StateDefinition; 24 | import net.minecraft.world.phys.BlockHitResult; 25 | import net.minecraft.world.phys.shapes.CollisionContext; 26 | import net.minecraft.world.phys.shapes.VoxelShape; 27 | 28 | import javax.annotation.Nullable; 29 | import java.util.List; 30 | 31 | public class BlockCDBurner extends HorizontalDirectionalBlock { 32 | protected static final VoxelShape BLOCK_AABB = Block.box(0, 0, 0, 16, 8, 16); 33 | 34 | public BlockCDBurner() { 35 | super(BlockBehaviour.Properties.of().sound(SoundType.WOOD).strength(0.5f).noOcclusion()); 36 | this.registerDefaultState(this.stateDefinition.any().setValue(FACING, Direction.NORTH)); 37 | } 38 | 39 | @Override 40 | protected void createBlockStateDefinition(StateDefinition.Builder builder) { 41 | builder.add(FACING); 42 | } 43 | 44 | @Nullable 45 | @Override 46 | public BlockState getStateForPlacement(BlockPlaceContext context) { 47 | Direction direction = context.getHorizontalDirection().getOpposite(); 48 | return this.defaultBlockState().setValue(FACING, direction); 49 | } 50 | 51 | @Override 52 | public VoxelShape getShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context) { 53 | return BLOCK_AABB; 54 | } 55 | 56 | @Override 57 | public InteractionResult use(BlockState blockState, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hitResult) { 58 | if (level.isClientSide) { 59 | return InteractionResult.SUCCESS; 60 | } else { 61 | player.openMenu(blockState.getMenuProvider(level, pos)); 62 | return InteractionResult.CONSUME; 63 | } 64 | } 65 | 66 | @Override 67 | public MenuProvider getMenuProvider(BlockState blockState, Level level, BlockPos blockPos) { 68 | return new SimpleMenuProvider((id, inventory, player) -> new CDBurnerMenu(id, inventory), Component.literal("cd_burner")); 69 | } 70 | 71 | @Override 72 | public void appendHoverText(ItemStack stack, @Nullable BlockGetter level, List tooltip, TooltipFlag flag) { 73 | tooltip.add(Component.translatable("block.netmusic.cd_burner.desc").withStyle(ChatFormatting.GRAY)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/block/BlockComputer.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.block; 2 | 3 | import com.github.tartaricacid.netmusic.inventory.ComputerMenu; 4 | import net.minecraft.ChatFormatting; 5 | import net.minecraft.core.BlockPos; 6 | import net.minecraft.core.Direction; 7 | import net.minecraft.network.chat.Component; 8 | import net.minecraft.world.InteractionHand; 9 | import net.minecraft.world.InteractionResult; 10 | import net.minecraft.world.MenuProvider; 11 | import net.minecraft.world.SimpleMenuProvider; 12 | import net.minecraft.world.entity.player.Player; 13 | import net.minecraft.world.item.ItemStack; 14 | import net.minecraft.world.item.TooltipFlag; 15 | import net.minecraft.world.item.context.BlockPlaceContext; 16 | import net.minecraft.world.level.BlockGetter; 17 | import net.minecraft.world.level.Level; 18 | import net.minecraft.world.level.block.Block; 19 | import net.minecraft.world.level.block.HorizontalDirectionalBlock; 20 | import net.minecraft.world.level.block.SoundType; 21 | import net.minecraft.world.level.block.state.BlockBehaviour; 22 | import net.minecraft.world.level.block.state.BlockState; 23 | import net.minecraft.world.level.block.state.StateDefinition; 24 | import net.minecraft.world.phys.BlockHitResult; 25 | import net.minecraft.world.phys.shapes.BooleanOp; 26 | import net.minecraft.world.phys.shapes.CollisionContext; 27 | import net.minecraft.world.phys.shapes.Shapes; 28 | import net.minecraft.world.phys.shapes.VoxelShape; 29 | 30 | import javax.annotation.Nullable; 31 | import java.util.List; 32 | 33 | public class BlockComputer extends HorizontalDirectionalBlock { 34 | protected static final VoxelShape NORTH_AABB = makeShape(); 35 | protected static final VoxelShape SOUTH_AABB = rotateShape(Direction.SOUTH, Direction.NORTH, NORTH_AABB); 36 | protected static final VoxelShape EAST_AABB = rotateShape(Direction.SOUTH, Direction.EAST, NORTH_AABB); 37 | protected static final VoxelShape WEST_AABB = rotateShape(Direction.NORTH, Direction.EAST, NORTH_AABB); 38 | 39 | public BlockComputer() { 40 | super(BlockBehaviour.Properties.of().sound(SoundType.WOOD).strength(0.5f).noOcclusion()); 41 | this.registerDefaultState(this.stateDefinition.any().setValue(FACING, Direction.NORTH)); 42 | } 43 | 44 | private static VoxelShape makeShape() { 45 | VoxelShape shape = Shapes.empty(); 46 | shape = Shapes.join(shape, Shapes.box(0, 0, 0.40625, 1, 0.3125, 1), BooleanOp.OR); 47 | shape = Shapes.join(shape, Shapes.box(0.1875, 0.3125, 0.40625, 0.8125, 0.375, 0.875), BooleanOp.OR); 48 | shape = Shapes.join(shape, Shapes.box(0.125, 0.375, 0.53125, 0.875, 0.84375, 0.9375), BooleanOp.OR); 49 | shape = Shapes.join(shape, Shapes.box(0.1250625, 0.5608175, 0.47502125, 0.8749375, 0.9356925, 0.88114625), BooleanOp.OR); 50 | shape = Shapes.join(shape, Shapes.box(0.1875, 0.4375, 0.40625, 0.8125, 0.9375, 0.59375), BooleanOp.OR); 51 | shape = Shapes.join(shape, Shapes.box(0.0625, 0.3125, 0.34375, 0.9375, 0.4375, 0.59375), BooleanOp.OR); 52 | shape = Shapes.join(shape, Shapes.box(0.0625, 0.9375, 0.34375, 0.9375, 1.0625, 0.59375), BooleanOp.OR); 53 | shape = Shapes.join(shape, Shapes.box(0.8125, 0.4375, 0.34375, 0.9375, 0.9375, 0.59375), BooleanOp.OR); 54 | shape = Shapes.join(shape, Shapes.box(0.0625, 0.4375, 0.34375, 0.1875, 0.9375, 0.59375), BooleanOp.OR); 55 | shape = Shapes.join(shape, Shapes.box(0, 0.0625, 0.03125, 1, 0.1875, 0.375), BooleanOp.OR); 56 | return shape; 57 | } 58 | 59 | private static VoxelShape rotateShape(Direction from, Direction to, VoxelShape shape) { 60 | VoxelShape[] buffer = new VoxelShape[]{shape, Shapes.empty()}; 61 | int times = (to.ordinal() - from.get2DDataValue() + 4) % 4; 62 | for (int i = 0; i < times; i++) { 63 | buffer[0].forAllBoxes((minX, minY, minZ, maxX, maxY, maxZ) -> buffer[1] = Shapes.or(buffer[1], Shapes.create(1 - maxZ, minY, minX, 1 - minZ, maxY, maxX))); 64 | buffer[0] = buffer[1]; 65 | buffer[1] = Shapes.empty(); 66 | } 67 | return buffer[0]; 68 | } 69 | 70 | @Override 71 | protected void createBlockStateDefinition(StateDefinition.Builder builder) { 72 | builder.add(FACING); 73 | } 74 | 75 | @Nullable 76 | @Override 77 | public BlockState getStateForPlacement(BlockPlaceContext context) { 78 | Direction direction = context.getHorizontalDirection().getOpposite(); 79 | return this.defaultBlockState().setValue(FACING, direction); 80 | } 81 | 82 | @Override 83 | public VoxelShape getShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context) { 84 | Direction direction = state.getValue(FACING); 85 | switch (direction) { 86 | case SOUTH -> { 87 | return SOUTH_AABB; 88 | } 89 | case EAST -> { 90 | return EAST_AABB; 91 | } 92 | case WEST -> { 93 | return WEST_AABB; 94 | } 95 | default -> { 96 | return NORTH_AABB; 97 | } 98 | } 99 | } 100 | 101 | @Override 102 | public InteractionResult use(BlockState blockState, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hitResult) { 103 | if (level.isClientSide) { 104 | return InteractionResult.SUCCESS; 105 | } else { 106 | player.openMenu(blockState.getMenuProvider(level, pos)); 107 | return InteractionResult.CONSUME; 108 | } 109 | } 110 | 111 | @Override 112 | public MenuProvider getMenuProvider(BlockState blockState, Level level, BlockPos blockPos) { 113 | return new SimpleMenuProvider((id, inventory, player) -> new ComputerMenu(id, inventory), Component.literal("computer")); 114 | } 115 | 116 | @Override 117 | public void appendHoverText(ItemStack stack, @Nullable BlockGetter level, List tooltip, TooltipFlag flag) { 118 | tooltip.add(Component.translatable("block.netmusic.computer.web_link.desc").withStyle(ChatFormatting.GRAY)); 119 | tooltip.add(Component.translatable("block.netmusic.computer.local_file.desc").withStyle(ChatFormatting.GRAY)); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/client/audio/FlacFormatConversionProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * libFLAC - Free Lossless Audio Codec library 3 | * Copyright (C) 2001,2002,2003 Josh Coalson 4 | * 5 | * This library is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU Library General Public 7 | * License as published by the Free Software Foundation; either 8 | * version 2 of the License, or (at your option) any later version. 9 | * 10 | * This library is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | * Library General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Library General Public 16 | * License along with this library; if not, write to the 17 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 18 | * Boston, MA 02111-1307, USA. 19 | */ 20 | 21 | package com.github.tartaricacid.netmusic.client.audio; 22 | 23 | import org.jflac.sound.spi.Flac2PcmAudioInputStream; 24 | import org.jflac.sound.spi.FlacEncoding; 25 | 26 | import javax.sound.sampled.AudioFormat; 27 | import javax.sound.sampled.AudioInputStream; 28 | import javax.sound.sampled.AudioFormat.Encoding; 29 | import javax.sound.sampled.spi.FormatConversionProvider; 30 | 31 | /** 32 | * A format conversion provider provides format conversion services from one or 33 | * more input formats to one or more output formats. Converters include codecs, 34 | * which encode and/or decode audio data, as well as transcoders, etc. Format 35 | * converters provide methods for determining what conversions are supported and 36 | * for obtaining an audio stream from which converted data can be read. The 37 | * source format represents the format of the incoming audio data, which will be 38 | * converted. The target format represents the format of the processed, 39 | * converted audio data. This is the format of the data that can be read from 40 | * the stream returned by one of the getAudioInputStream methods. 41 | * 42 | * @author Marc Gimpel, Wimba S.A. (marc@wimba.com) 43 | * @author Florian Bomers 44 | * @version $Revision: 1.1 $ 45 | */ 46 | public class FlacFormatConversionProvider extends FormatConversionProvider { 47 | private static final boolean DEBUG = false; 48 | private static final boolean HAS_ENCODING = false; 49 | 50 | public FlacFormatConversionProvider() { 51 | } 52 | 53 | public AudioFormat.Encoding[] getSourceEncodings() { 54 | AudioFormat.Encoding[] encodings = new AudioFormat.Encoding[]{FlacEncoding.FLAC}; 55 | return encodings; 56 | } 57 | 58 | public AudioFormat.Encoding[] getTargetEncodings() { 59 | AudioFormat.Encoding[] encodings = new AudioFormat.Encoding[]{FlacEncoding.PCM_SIGNED}; 60 | return encodings; 61 | } 62 | 63 | private boolean isBitSizeOK(AudioFormat format, boolean notSpecifiedOK) { 64 | int bitSize = format.getSampleSizeInBits(); 65 | return notSpecifiedOK && bitSize == -1 || bitSize == 8 || bitSize == 16 || bitSize == 24; 66 | } 67 | 68 | private boolean isChannelsOK(AudioFormat format, boolean notSpecifiedOK) { 69 | int channels = format.getChannels(); 70 | return notSpecifiedOK && channels == -1 || channels == 1 || channels == 2; 71 | } 72 | 73 | public AudioFormat.Encoding[] getTargetEncodings(AudioFormat sourceFormat) { 74 | boolean bitSizeOK = this.isBitSizeOK(sourceFormat, true); 75 | boolean channelsOK = this.isChannelsOK(sourceFormat, true); 76 | AudioFormat.Encoding[] encodings; 77 | if (bitSizeOK && channelsOK && sourceFormat.getEncoding().equals(FlacEncoding.FLAC)) { 78 | encodings = new AudioFormat.Encoding[]{Encoding.PCM_SIGNED}; 79 | return encodings; 80 | } else { 81 | encodings = new AudioFormat.Encoding[0]; 82 | return encodings; 83 | } 84 | } 85 | 86 | public AudioFormat[] getTargetFormats(AudioFormat.Encoding targetEncoding, AudioFormat sourceFormat) { 87 | return this.getTargetFormats(targetEncoding, sourceFormat, true); 88 | } 89 | 90 | private AudioFormat[] getTargetFormats(AudioFormat.Encoding targetEncoding, AudioFormat sourceFormat, boolean notSpecifiedOK) { 91 | boolean bitSizeOK = this.isBitSizeOK(sourceFormat, notSpecifiedOK); 92 | boolean channelsOK = this.isChannelsOK(sourceFormat, notSpecifiedOK); 93 | AudioFormat[] formats; 94 | if (bitSizeOK && channelsOK && sourceFormat.getEncoding().equals(FlacEncoding.FLAC) && targetEncoding.equals(Encoding.PCM_SIGNED)) { 95 | formats = new AudioFormat[]{new AudioFormat(sourceFormat.getSampleRate(), sourceFormat.getSampleSizeInBits(), sourceFormat.getChannels(), true, false)}; 96 | return formats; 97 | } else { 98 | formats = new AudioFormat[0]; 99 | return formats; 100 | } 101 | } 102 | 103 | public AudioInputStream getAudioInputStream(AudioFormat.Encoding targetEncoding, AudioInputStream sourceStream) { 104 | AudioFormat[] formats = this.getTargetFormats(targetEncoding, sourceStream.getFormat(), false); 105 | if (formats != null && formats.length > 0) { 106 | return this.getAudioInputStream(formats[0], sourceStream); 107 | } else { 108 | throw new IllegalArgumentException("conversion not supported"); 109 | } 110 | } 111 | 112 | public AudioInputStream getAudioInputStream(AudioFormat targetFormat, AudioInputStream sourceStream) { 113 | AudioFormat sourceFormat = sourceStream.getFormat(); 114 | AudioFormat[] formats = this.getTargetFormats(targetFormat.getEncoding(), sourceFormat, false); 115 | if (formats != null && formats.length > 0) { 116 | if (sourceFormat.equals(targetFormat)) { 117 | return sourceStream; 118 | } else if (sourceFormat.getChannels() == targetFormat.getChannels() && sourceFormat.getSampleSizeInBits() == targetFormat.getSampleSizeInBits() && !targetFormat.isBigEndian() && sourceFormat.getEncoding().equals(FlacEncoding.FLAC) && targetFormat.getEncoding().equals(Encoding.PCM_SIGNED)) { 119 | return new Flac2PcmAudioInputStream(sourceStream, targetFormat, -1L); 120 | } else if (sourceFormat.getChannels() == targetFormat.getChannels() && sourceFormat.getSampleSizeInBits() == targetFormat.getSampleSizeInBits() && sourceFormat.getEncoding().equals(Encoding.PCM_SIGNED) && targetFormat.getEncoding().equals(FlacEncoding.FLAC)) { 121 | throw new IllegalArgumentException("FLAC encoder not yet implemented"); 122 | } else { 123 | throw new IllegalArgumentException("unable to convert " + sourceFormat.toString() + " to " + targetFormat.toString()); 124 | } 125 | } else { 126 | throw new IllegalArgumentException("conversion not supported"); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/client/audio/MpegFormatConversionProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MpegFormatConversionProvider. 3 | * 4 | * JavaZOOM : mp3spi@javazoom.net 5 | * http://www.javazoom.net 6 | * 7 | * --------------------------------------------------------------------------- 8 | * This program is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU Library General Public License as published 10 | * by the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Library General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Library General Public 19 | * License along with this program; if not, write to the Free Software 20 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 21 | * -------------------------------------------------------------------------- 22 | */ 23 | 24 | package com.github.tartaricacid.netmusic.client.audio; 25 | 26 | import javazoom.spi.mpeg.sampled.convert.DecodedMpegAudioInputStream; 27 | import javazoom.spi.mpeg.sampled.file.MpegEncoding; 28 | import org.tritonus.share.TDebug; 29 | import org.tritonus.share.sampled.Encodings; 30 | import org.tritonus.share.sampled.convert.TEncodingFormatConversionProvider; 31 | 32 | import javax.sound.sampled.AudioFormat; 33 | import javax.sound.sampled.AudioInputStream; 34 | import javax.sound.sampled.AudioSystem; 35 | import java.util.Arrays; 36 | 37 | public class MpegFormatConversionProvider extends TEncodingFormatConversionProvider { 38 | private static final AudioFormat.Encoding MPEG1L3 = Encodings.getEncoding("MPEG1L3"); 39 | private static final AudioFormat.Encoding PCM_SIGNED = Encodings.getEncoding("PCM_SIGNED"); 40 | 41 | private static final AudioFormat[] INPUT_FORMATS = 42 | { 43 | // mono 44 | new AudioFormat(MPEG1L3, -1.0F, -1, 1, -1, -1.0F, false), 45 | new AudioFormat(MPEG1L3, -1.0F, -1, 1, -1, -1.0F, true), 46 | // stereo 47 | new AudioFormat(MPEG1L3, -1.0F, -1, 2, -1, -1.0F, false), 48 | new AudioFormat(MPEG1L3, -1.0F, -1, 2, -1, -1.0F, true), 49 | }; 50 | 51 | 52 | private static final AudioFormat[] OUTPUT_FORMATS = 53 | { 54 | // mono, 16 bit signed 55 | new AudioFormat(PCM_SIGNED, -1.0F, 16, 1, 2, -1.0F, false), 56 | new AudioFormat(PCM_SIGNED, -1.0F, 16, 1, 2, -1.0F, true), 57 | // stereo, 16 bit signed 58 | new AudioFormat(PCM_SIGNED, -1.0F, 16, 2, 4, -1.0F, false), 59 | new AudioFormat(PCM_SIGNED, -1.0F, 16, 2, 4, -1.0F, true), 60 | }; 61 | 62 | /** 63 | * Constructor. 64 | */ 65 | public MpegFormatConversionProvider() { 66 | super(Arrays.asList(INPUT_FORMATS), Arrays.asList(OUTPUT_FORMATS)); 67 | if (TDebug.TraceAudioConverter) { 68 | TDebug.out(">MpegFormatConversionProvider()"); 69 | } 70 | } 71 | 72 | public AudioInputStream getAudioInputStream(AudioFormat targetFormat, AudioInputStream audioInputStream) { 73 | if (TDebug.TraceAudioConverter) { 74 | TDebug.out(">MpegFormatConversionProvider.getAudioInputStream(AudioFormat targetFormat, AudioInputStream audioInputStream):"); 75 | } 76 | return new DecodedMpegAudioInputStream(targetFormat, audioInputStream); 77 | } 78 | 79 | /** 80 | * Add conversion support for any MpegEncoding source with FrameRate or FrameSize not empty. 81 | * 82 | * @param targetFormat 83 | * @param sourceFormat 84 | * @return 85 | */ 86 | public boolean isConversionSupported(AudioFormat targetFormat, AudioFormat sourceFormat) { 87 | if (TDebug.TraceAudioConverter) { 88 | TDebug.out(">MpegFormatConversionProvider.isConversionSupported(AudioFormat targetFormat, AudioFormat sourceFormat):"); 89 | TDebug.out("checking if conversion possible"); 90 | TDebug.out("from: " + sourceFormat); 91 | TDebug.out("to: " + targetFormat); 92 | } 93 | 94 | boolean conversion = super.isConversionSupported(targetFormat, sourceFormat); 95 | if (!conversion) { 96 | AudioFormat.Encoding enc = sourceFormat.getEncoding(); 97 | if (enc instanceof MpegEncoding) { 98 | if ((sourceFormat.getFrameRate() != AudioSystem.NOT_SPECIFIED) || (sourceFormat.getFrameSize() != AudioSystem.NOT_SPECIFIED)) { 99 | conversion = true; 100 | } 101 | } 102 | } 103 | return conversion; 104 | } 105 | } 106 | 107 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/client/audio/MusicPlayManager.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.client.audio; 2 | 3 | import com.github.tartaricacid.netmusic.NetMusic; 4 | import com.github.tartaricacid.netmusic.api.NetWorker; 5 | import net.minecraft.ChatFormatting; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.client.player.LocalPlayer; 8 | import net.minecraft.client.resources.sounds.SoundInstance; 9 | import net.minecraft.network.chat.Component; 10 | import net.minecraftforge.api.distmarker.Dist; 11 | import net.minecraftforge.api.distmarker.OnlyIn; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.net.MalformedURLException; 16 | import java.net.URISyntaxException; 17 | import java.net.URL; 18 | import java.util.function.Function; 19 | 20 | @OnlyIn(Dist.CLIENT) 21 | public final class MusicPlayManager { 22 | private static final String ERROR_404 = "http://music.163.com/404"; 23 | private static final String MUSIC_163_URL = "https://music.163.com/"; 24 | private static final String LOCAL_FILE_PROTOCOL = "file"; 25 | 26 | public static void play(String url, String songName, Function sound) { 27 | String rawUrl = url; 28 | if (url.startsWith(MUSIC_163_URL)) { 29 | try { 30 | url = NetWorker.getRedirectUrl(url, NetMusic.NET_EASE_WEB_API.getRequestPropertyData()); 31 | } catch (IOException e) { 32 | e.printStackTrace(); 33 | } 34 | } 35 | if (url != null) { 36 | if (url.equals(ERROR_404)) { 37 | LocalPlayer player = Minecraft.getInstance().player; 38 | if (player != null) { 39 | player.sendSystemMessage(Component.translatable("message.netmusic.music_player.404", rawUrl).withStyle(ChatFormatting.RED)); 40 | } 41 | NetMusic.LOGGER.info("Music not found: {}", rawUrl); 42 | return; 43 | } 44 | playMusic(url, songName, sound); 45 | } 46 | } 47 | 48 | private static void playMusic(String url, String songName, Function sound) { 49 | final URL urlFinal; 50 | try { 51 | urlFinal = new URL(url); 52 | // 如果是本地文件 53 | if (urlFinal.getProtocol().equals(LOCAL_FILE_PROTOCOL)) { 54 | File file = new File(urlFinal.toURI()); 55 | if (!file.exists()) { 56 | NetMusic.LOGGER.info("File not found: {}", url); 57 | return; 58 | } 59 | } 60 | Minecraft.getInstance().submitAsync(() -> { 61 | Minecraft.getInstance().getSoundManager().play(sound.apply(urlFinal)); 62 | Minecraft.getInstance().gui.setNowPlaying(Component.literal(songName)); 63 | }); 64 | } catch (MalformedURLException | URISyntaxException e) { 65 | e.printStackTrace(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/client/audio/NetMusicAudioStream.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.client.audio; 2 | 3 | import com.github.tartaricacid.netmusic.config.GeneralConfig; 4 | import net.minecraft.client.sounds.AudioStream; 5 | import org.lwjgl.BufferUtils; 6 | 7 | import javax.sound.sampled.AudioFormat; 8 | import javax.sound.sampled.AudioInputStream; 9 | import javax.sound.sampled.AudioSystem; 10 | import javax.sound.sampled.UnsupportedAudioFileException; 11 | import java.io.BufferedInputStream; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.net.URL; 15 | import java.nio.ByteBuffer; 16 | 17 | /** 18 | * @author SQwatermark 19 | */ 20 | public class NetMusicAudioStream implements AudioStream { 21 | private final AudioInputStream stream; 22 | private final int frameSize; 23 | private final byte[] frame; 24 | 25 | public NetMusicAudioStream(URL url) throws UnsupportedAudioFileException, IOException { 26 | InputStream inputStream = url.openStream(); 27 | // 有些流不支持 mark/reset, 需要用 BufferedInputStream 包装 28 | BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); 29 | skipID3(bufferedInputStream); 30 | AudioInputStream originalInputStream = AudioSystem.getAudioInputStream(bufferedInputStream); 31 | AudioFormat originalFormat = originalInputStream.getFormat(); 32 | AudioFormat targetFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, originalFormat.getSampleRate(), 16, 33 | originalFormat.getChannels(), originalFormat.getChannels() * 2, originalFormat.getSampleRate(), false); 34 | AudioInputStream targetInputStream = AudioSystem.getAudioInputStream(targetFormat, originalInputStream); 35 | if (GeneralConfig.ENABLE_STEREO.get()) { 36 | targetFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, originalFormat.getSampleRate(), 16, 37 | 1, 2, originalFormat.getSampleRate(), false); 38 | this.stream = AudioSystem.getAudioInputStream(targetFormat, targetInputStream); 39 | } else { 40 | this.stream = targetInputStream; 41 | } 42 | this.frameSize = stream.getFormat().getFrameSize(); 43 | frame = new byte[frameSize]; 44 | } 45 | 46 | @Override 47 | public AudioFormat getFormat() { 48 | return stream.getFormat(); 49 | } 50 | 51 | /** 52 | * 从流中读取音频数据,并返回一个最多包含指定字节数的字节缓冲区。 53 | * 该方法从流中读取音频帧并将其添加到输出缓冲区,直到缓冲区至少 54 | * 包含指定数量的字节或到达流的末尾。 55 | * 56 | * @param size 要读取的最大字节数 57 | * @return 字节缓冲区,最多包含要读取的指定字节数 58 | * @throws IOException 如果在读取音频数据时发生I/O错误 59 | */ 60 | @Override 61 | public ByteBuffer read(int size) throws IOException { 62 | // 创建指定大小的ByteBuffer 63 | ByteBuffer byteBuffer = BufferUtils.createByteBuffer(size); 64 | int bytesRead = 0, count = 0; 65 | // 循环读取数据直到达到指定大小或输入流结束 66 | do { 67 | // 读取下一部分数据 68 | count = this.stream.read(frame); 69 | // 将读取的数据写入ByteBuffer 70 | if (count != -1) { 71 | byteBuffer.put(frame); 72 | } 73 | } while (count != -1 && (bytesRead += frameSize) < size); 74 | // 翻转ByteBuffer,准备进行读取操作 75 | byteBuffer.flip(); 76 | // 返回包含读取数据的ByteBuffer 77 | return byteBuffer; 78 | } 79 | 80 | @Override 81 | public void close() throws IOException { 82 | stream.close(); 83 | } 84 | 85 | /** 86 | * 跳过 ID3 标签 87 | * @param inputStream 输入的音频流 88 | * @throws IOException IO 异常 89 | */ 90 | private static void skipID3(InputStream inputStream) throws IOException { 91 | // 读取 ID3 标签头部 92 | inputStream.mark(10); 93 | byte[] header = new byte[10]; 94 | int read = inputStream.read(header, 0, 10); 95 | if (read < 10) { 96 | inputStream.reset(); 97 | return; 98 | } 99 | 100 | // 检查是否有 ID3 标签 101 | if (header[0] == 'I' && header[1] == 'D' && header[2] == '3') { 102 | // 计算元数据大小 103 | int size = (header[6] << 21) | (header[7] << 14) | (header[8] << 7) | header[9]; 104 | 105 | // 跳过元数据 106 | int skipped = 0; 107 | int skip = 0; 108 | do { 109 | skip = (int) inputStream.skip(size - skipped); 110 | if (skip != 0) { 111 | skipped += skip; 112 | } 113 | } while (skipped < size && skip != 0); 114 | } else { 115 | inputStream.reset(); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/client/audio/NetMusicSound.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.client.audio; 2 | 3 | import com.github.tartaricacid.netmusic.init.InitSounds; 4 | import com.github.tartaricacid.netmusic.tileentity.TileEntityMusicPlayer; 5 | import net.minecraft.Util; 6 | import net.minecraft.client.Minecraft; 7 | import net.minecraft.client.resources.sounds.AbstractTickableSoundInstance; 8 | import net.minecraft.client.resources.sounds.Sound; 9 | import net.minecraft.client.resources.sounds.SoundInstance; 10 | import net.minecraft.client.sounds.AudioStream; 11 | import net.minecraft.client.sounds.SoundBufferLibrary; 12 | import net.minecraft.core.BlockPos; 13 | import net.minecraft.core.particles.ParticleTypes; 14 | import net.minecraft.sounds.SoundSource; 15 | import net.minecraft.world.level.Level; 16 | import net.minecraft.world.level.block.entity.BlockEntity; 17 | 18 | import javax.sound.sampled.UnsupportedAudioFileException; 19 | import java.io.IOException; 20 | import java.net.URL; 21 | import java.util.concurrent.CompletableFuture; 22 | 23 | public class NetMusicSound extends AbstractTickableSoundInstance { 24 | private final URL songUrl; 25 | private final int tickTimes; 26 | private final BlockPos pos; 27 | private int tick; 28 | 29 | public NetMusicSound(BlockPos pos, URL songUrl, int timeSecond) { 30 | super(InitSounds.NET_MUSIC.get(), SoundSource.RECORDS, SoundInstance.createUnseededRandom()); 31 | this.songUrl = songUrl; 32 | this.x = pos.getX() + 0.5f; 33 | this.y = pos.getY() + 0.5f; 34 | this.z = pos.getZ() + 0.5f; 35 | this.tickTimes = timeSecond * 20; 36 | this.volume = 4.0f; 37 | this.tick = 0; 38 | this.pos = pos; 39 | } 40 | 41 | @Override 42 | public void tick() { 43 | Level world = Minecraft.getInstance().level; 44 | if (world == null) { 45 | return; 46 | } 47 | tick++; 48 | if (tick > tickTimes + 50) { 49 | this.stop(); 50 | } else { 51 | if (world.getGameTime() % 8 == 0) { 52 | for (int i = 0; i < 2; i++) { 53 | world.addParticle(ParticleTypes.NOTE, 54 | x - 0.5f + world.random.nextDouble(), 55 | y + world.random.nextDouble() + 1, 56 | z - 0.5f + world.random.nextDouble(), 57 | world.random.nextGaussian(), world.random.nextGaussian(), world.random.nextInt(3)); 58 | } 59 | } 60 | } 61 | 62 | BlockEntity te = world.getBlockEntity(pos); 63 | if (te instanceof TileEntityMusicPlayer) { 64 | TileEntityMusicPlayer musicPlay = (TileEntityMusicPlayer) te; 65 | if (!musicPlay.isPlay()) { 66 | this.stop(); 67 | } 68 | } else { 69 | this.stop(); 70 | } 71 | } 72 | 73 | @Override 74 | public CompletableFuture getStream(SoundBufferLibrary soundBuffers, Sound sound, boolean looping) { 75 | return CompletableFuture.supplyAsync(() -> { 76 | try { 77 | return new NetMusicAudioStream(this.songUrl); 78 | } catch (IOException | UnsupportedAudioFileException e) { 79 | e.printStackTrace(); 80 | } 81 | return null; 82 | }, Util.backgroundExecutor()); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/client/config/MusicListManage.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.client.config; 2 | 3 | import com.github.tartaricacid.netmusic.NetMusic; 4 | import com.github.tartaricacid.netmusic.api.ExtraMusicList; 5 | import com.github.tartaricacid.netmusic.api.pojo.NetEaseMusicList; 6 | import com.github.tartaricacid.netmusic.api.pojo.NetEaseMusicSong; 7 | import com.github.tartaricacid.netmusic.item.ItemMusicCD; 8 | import com.google.common.collect.Lists; 9 | import com.google.gson.Gson; 10 | import com.google.gson.GsonBuilder; 11 | import com.google.gson.reflect.TypeToken; 12 | import net.minecraft.client.Minecraft; 13 | import net.minecraft.resources.ResourceLocation; 14 | import net.minecraft.server.packs.resources.Resource; 15 | import org.apache.commons.io.FileUtils; 16 | 17 | import java.io.File; 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.io.InputStreamReader; 21 | import java.nio.charset.StandardCharsets; 22 | import java.nio.file.Files; 23 | import java.nio.file.Path; 24 | import java.nio.file.Paths; 25 | import java.util.List; 26 | import java.util.Optional; 27 | 28 | public class MusicListManage { 29 | private static final int MAX_NUM = 100; 30 | private static final Gson GSON = new Gson(); 31 | private static final Path CONFIG_DIR = Paths.get("config").resolve("net_music"); 32 | private static final Path CONFIG_FILE = CONFIG_DIR.resolve("music.json"); 33 | public static List SONGS = Lists.newArrayList(); 34 | 35 | public static void loadConfigSongs() throws IOException { 36 | if (!Files.isDirectory(CONFIG_DIR)) { 37 | Files.createDirectories(CONFIG_DIR); 38 | } 39 | 40 | File file = CONFIG_FILE.toFile(); 41 | InputStream stream = null; 42 | if (Files.exists(CONFIG_FILE)) { 43 | stream = Files.newInputStream(file.toPath()); 44 | } else { 45 | ResourceLocation res = new ResourceLocation(NetMusic.MOD_ID, "music.json"); 46 | Optional optional = Minecraft.getInstance().getResourceManager().getResource(res); 47 | if (optional.isPresent()) { 48 | stream = optional.get().open(); 49 | } 50 | } 51 | if (stream != null) { 52 | SONGS = GSON.fromJson(new InputStreamReader(stream, StandardCharsets.UTF_8), 53 | new TypeToken>() { 54 | }.getType()); 55 | } 56 | } 57 | 58 | public static ItemMusicCD.SongInfo get163Song(long id) throws Exception { 59 | NetEaseMusicSong pojo = GSON.fromJson(NetMusic.NET_EASE_WEB_API.song(id), NetEaseMusicSong.class); 60 | return new ItemMusicCD.SongInfo(pojo); 61 | } 62 | 63 | public static void add163List(long id) throws Exception { 64 | if (!Files.isDirectory(CONFIG_DIR)) { 65 | Files.createDirectories(CONFIG_DIR); 66 | } 67 | 68 | NetEaseMusicList pojo = GSON.fromJson(NetMusic.NET_EASE_WEB_API.list(id), NetEaseMusicList.class); 69 | 70 | int count = pojo.getPlayList().getTracks().size(); 71 | int size = Math.min(pojo.getPlayList().getTrackIds().size(), MAX_NUM); 72 | // 获取额外歌曲 73 | if (count < size) { 74 | long[] ids = new long[size - count]; 75 | for (int i = count; i < size; i++) { 76 | ids[i - count] = pojo.getPlayList().getTrackIds().get(i).getId(); 77 | } 78 | String extraTrackInfo = NetMusic.NET_EASE_WEB_API.songs(ids); 79 | ExtraMusicList extra = GSON.fromJson(extraTrackInfo, ExtraMusicList.class); 80 | pojo.getPlayList().getTracks().addAll(extra.getTracks()); 81 | } 82 | 83 | SONGS.clear(); 84 | for (NetEaseMusicList.Track track : pojo.getPlayList().getTracks()) { 85 | SONGS.add(new ItemMusicCD.SongInfo(track)); 86 | } 87 | 88 | Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); 89 | FileUtils.write(CONFIG_FILE.toFile(), gson.toJson(SONGS), StandardCharsets.UTF_8); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/client/event/ClientEvent.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.client.event; 2 | 3 | 4 | import com.github.tartaricacid.netmusic.NetMusic; 5 | import com.github.tartaricacid.netmusic.client.config.MusicListManage; 6 | import net.minecraftforge.api.distmarker.Dist; 7 | import net.minecraftforge.eventbus.api.SubscribeEvent; 8 | import net.minecraftforge.fml.common.Mod; 9 | import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; 10 | 11 | import java.io.IOException; 12 | 13 | @Mod.EventBusSubscriber(modid = NetMusic.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD) 14 | public class ClientEvent { 15 | @SubscribeEvent 16 | public static void onClientSetup(FMLClientSetupEvent event) { 17 | event.enqueueWork(() -> { 18 | try { 19 | MusicListManage.loadConfigSongs(); 20 | } catch (IOException e) { 21 | e.printStackTrace(); 22 | } 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/client/gui/CDBurnerMenuScreen.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.client.gui; 2 | 3 | import com.github.tartaricacid.netmusic.NetMusic; 4 | import com.github.tartaricacid.netmusic.client.config.MusicListManage; 5 | import com.github.tartaricacid.netmusic.inventory.CDBurnerMenu; 6 | import com.github.tartaricacid.netmusic.item.ItemMusicCD; 7 | import com.github.tartaricacid.netmusic.network.NetworkHandler; 8 | import com.github.tartaricacid.netmusic.network.message.SetMusicIDMessage; 9 | import com.mojang.blaze3d.platform.InputConstants; 10 | import net.minecraft.ChatFormatting; 11 | import net.minecraft.Util; 12 | import net.minecraft.client.Minecraft; 13 | import net.minecraft.client.gui.GuiGraphics; 14 | import net.minecraft.client.gui.components.Button; 15 | import net.minecraft.client.gui.components.Checkbox; 16 | import net.minecraft.client.gui.components.EditBox; 17 | import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; 18 | import net.minecraft.network.chat.Component; 19 | import net.minecraft.resources.ResourceLocation; 20 | import net.minecraft.world.entity.player.Inventory; 21 | import net.minecraft.world.item.ItemStack; 22 | import org.anti_ad.mc.ipn.api.IPNIgnore; 23 | import org.apache.commons.lang3.StringUtils; 24 | 25 | import java.util.regex.Matcher; 26 | import java.util.regex.Pattern; 27 | 28 | @IPNIgnore 29 | public class CDBurnerMenuScreen extends AbstractContainerScreen { 30 | private static final ResourceLocation BG = new ResourceLocation(NetMusic.MOD_ID, "textures/gui/cd_burner.png"); 31 | private static final Pattern ID_REG = Pattern.compile("^\\d{4,}$"); 32 | private static final Pattern URL_1_REG = Pattern.compile("^https://music\\.163\\.com/song\\?id=(\\d+).*$"); 33 | private static final Pattern URL_2_REG = Pattern.compile("^https://music\\.163\\.com/#/song\\?id=(\\d+).*$"); 34 | private EditBox textField; 35 | private Checkbox readOnlyButton; 36 | private Component tips = Component.empty(); 37 | 38 | public CDBurnerMenuScreen(CDBurnerMenu screenContainer, Inventory inv, Component titleIn) { 39 | super(screenContainer, inv, titleIn); 40 | this.imageHeight = 176; 41 | } 42 | 43 | @Override 44 | protected void init() { 45 | super.init(); 46 | 47 | String perText = ""; 48 | boolean focus = false; 49 | if (textField != null) { 50 | perText = textField.getValue(); 51 | focus = textField.isFocused(); 52 | } 53 | textField = new EditBox(getMinecraft().font, leftPos + 12, topPos + 18, 132, 16, Component.literal("Music ID Box")) { 54 | @Override 55 | public void insertText(String text) { 56 | Matcher matcher1 = URL_1_REG.matcher(text); 57 | if (matcher1.find()) { 58 | String group = matcher1.group(1); 59 | super.insertText(group); 60 | return; 61 | } 62 | 63 | Matcher matcher2 = URL_2_REG.matcher(text); 64 | if (matcher2.find()) { 65 | String group = matcher2.group(1); 66 | super.insertText(group); 67 | return; 68 | } 69 | 70 | super.insertText(text); 71 | } 72 | }; 73 | textField.setValue(perText); 74 | textField.setBordered(false); 75 | textField.setMaxLength(19); 76 | textField.setTextColor(0xF3EFE0); 77 | textField.setFocused(focus); 78 | textField.moveCursorToEnd(); 79 | this.addWidget(this.textField); 80 | 81 | this.readOnlyButton = new Checkbox(leftPos + 66, topPos + 34, 80, 20, Component.translatable("gui.netmusic.cd_burner.read_only"), false); 82 | this.addRenderableWidget(this.readOnlyButton); 83 | this.addRenderableWidget(Button.builder(Component.translatable("gui.netmusic.cd_burner.craft"), (b) -> handleCraftButton()) 84 | .pos(leftPos + 7, topPos + 35).size(55, 18).build()); 85 | } 86 | 87 | private void handleCraftButton() { 88 | ItemStack cd = this.getMenu().getInput().getStackInSlot(0); 89 | if (cd.isEmpty()) { 90 | this.tips = Component.translatable("gui.netmusic.cd_burner.cd_is_empty"); 91 | return; 92 | } 93 | ItemMusicCD.SongInfo songInfo = ItemMusicCD.getSongInfo(cd); 94 | if (songInfo != null && songInfo.readOnly) { 95 | this.tips = Component.translatable("gui.netmusic.cd_burner.cd_read_only"); 96 | return; 97 | } 98 | if (Util.isBlank(textField.getValue())) { 99 | this.tips = Component.translatable("gui.netmusic.cd_burner.no_music_id"); 100 | return; 101 | } 102 | if (ID_REG.matcher(textField.getValue()).matches()) { 103 | long id = Long.parseLong(textField.getValue()); 104 | try { 105 | ItemMusicCD.SongInfo song = MusicListManage.get163Song(id); 106 | if (StringUtils.isBlank(song.songUrl) || StringUtils.isBlank(song.songName)) { 107 | this.tips = Component.translatable("gui.netmusic.cd_burner.get_info_error"); 108 | return; 109 | } 110 | song.readOnly = this.readOnlyButton.selected(); 111 | NetworkHandler.CHANNEL.sendToServer(new SetMusicIDMessage(song)); 112 | } catch (Exception e) { 113 | this.tips = Component.translatable("gui.netmusic.cd_burner.get_info_error"); 114 | e.printStackTrace(); 115 | } 116 | } else { 117 | this.tips = Component.translatable("gui.netmusic.cd_burner.music_id_error"); 118 | } 119 | } 120 | 121 | @Override 122 | protected void renderLabels(GuiGraphics graphics, int x, int y) { 123 | } 124 | 125 | @Override 126 | protected void renderBg(GuiGraphics graphics, float partialTicks, int x, int y) { 127 | renderBackground(graphics); 128 | int posX = this.leftPos; 129 | int posY = (this.height - this.imageHeight) / 2; 130 | graphics.blit(BG, posX, posY, 0, 0, this.imageWidth, this.imageHeight); 131 | } 132 | 133 | @Override 134 | public void render(GuiGraphics graphics, int x, int y, float partialTicks) { 135 | super.render(graphics, x, y, partialTicks); 136 | textField.render(graphics, x, y, partialTicks); 137 | if (Util.isBlank(textField.getValue()) && !textField.isFocused()) { 138 | graphics.drawString(font, Component.translatable("gui.netmusic.cd_burner.id.tips").withStyle(ChatFormatting.ITALIC), this.leftPos + 12, this.topPos + 18, ChatFormatting.GRAY.getColor(), false); 139 | } 140 | graphics.drawWordWrap(font, tips, this.leftPos + 8, this.topPos + 57, 135, 0xCF0000); 141 | renderTooltip(graphics, x, y); 142 | } 143 | 144 | @Override 145 | public void resize(Minecraft minecraft, int width, int height) { 146 | String value = this.textField.getValue(); 147 | super.resize(minecraft, width, height); 148 | this.textField.setValue(value); 149 | } 150 | 151 | @Override 152 | protected void containerTick() { 153 | this.textField.tick(); 154 | } 155 | 156 | @Override 157 | public boolean mouseClicked(double mouseX, double mouseY, int button) { 158 | if (this.textField.mouseClicked(mouseX, mouseY, button)) { 159 | this.setFocused(this.textField); 160 | return true; 161 | } 162 | return super.mouseClicked(mouseX, mouseY, button); 163 | } 164 | 165 | @Override 166 | public boolean keyPressed(int keyCode, int scanCode, int modifiers) { 167 | InputConstants.Key mouseKey = InputConstants.getKey(keyCode, scanCode); 168 | // 防止 E 键关闭界面 169 | if (this.getMinecraft().options.keyInventory.isActiveAndMatches(mouseKey) && textField.isFocused()) { 170 | return true; 171 | } 172 | return super.keyPressed(keyCode, scanCode, modifiers); 173 | } 174 | 175 | @Override 176 | protected void insertText(String text, boolean overwrite) { 177 | if (overwrite) { 178 | this.textField.setValue(text); 179 | } else { 180 | this.textField.insertText(text); 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/client/init/InitContainerGui.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.client.init; 2 | 3 | import com.github.tartaricacid.netmusic.client.gui.CDBurnerMenuScreen; 4 | import com.github.tartaricacid.netmusic.client.gui.ComputerMenuScreen; 5 | import com.github.tartaricacid.netmusic.compat.tlm.init.CompatRegistry; 6 | import com.github.tartaricacid.netmusic.inventory.CDBurnerMenu; 7 | import com.github.tartaricacid.netmusic.inventory.ComputerMenu; 8 | import net.minecraft.client.gui.screens.MenuScreens; 9 | import net.minecraftforge.api.distmarker.Dist; 10 | import net.minecraftforge.eventbus.api.SubscribeEvent; 11 | import net.minecraftforge.fml.common.Mod; 12 | import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; 13 | 14 | @Mod.EventBusSubscriber(value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD) 15 | public class InitContainerGui { 16 | @SubscribeEvent 17 | public static void clientSetup(FMLClientSetupEvent evt) { 18 | evt.enqueueWork(() -> MenuScreens.register(CDBurnerMenu.TYPE, CDBurnerMenuScreen::new)); 19 | evt.enqueueWork(() -> MenuScreens.register(ComputerMenu.TYPE, ComputerMenuScreen::new)); 20 | CompatRegistry.initContainerScreen(evt); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/client/init/InitModel.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.client.init; 2 | 3 | 4 | import com.github.tartaricacid.netmusic.client.model.ModelMusicPlayer; 5 | import com.github.tartaricacid.netmusic.client.renderer.MusicPlayerRenderer; 6 | import com.github.tartaricacid.netmusic.tileentity.TileEntityMusicPlayer; 7 | import net.minecraft.client.renderer.blockentity.BlockEntityRenderers; 8 | import net.minecraftforge.api.distmarker.Dist; 9 | import net.minecraftforge.client.event.EntityRenderersEvent; 10 | import net.minecraftforge.eventbus.api.SubscribeEvent; 11 | import net.minecraftforge.fml.common.Mod; 12 | import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; 13 | 14 | @Mod.EventBusSubscriber(value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD) 15 | public class InitModel { 16 | @SubscribeEvent 17 | public static void clientSetup(FMLClientSetupEvent evt) { 18 | BlockEntityRenderers.register(TileEntityMusicPlayer.TYPE, MusicPlayerRenderer::new); 19 | } 20 | 21 | @SubscribeEvent 22 | public static void onRegisterLayers(EntityRenderersEvent.RegisterLayerDefinitions event) { 23 | event.registerLayerDefinition(ModelMusicPlayer.LAYER, ModelMusicPlayer::createBodyLayer); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/client/renderer/MusicPlayerItemRenderer.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.client.renderer; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import net.minecraft.client.model.geom.EntityModelSet; 5 | import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer; 6 | import net.minecraft.client.renderer.MultiBufferSource; 7 | import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher; 8 | import net.minecraft.core.Direction; 9 | import net.minecraft.world.item.ItemDisplayContext; 10 | import net.minecraft.world.item.ItemStack; 11 | 12 | public class MusicPlayerItemRenderer extends BlockEntityWithoutLevelRenderer { 13 | public MusicPlayerItemRenderer(BlockEntityRenderDispatcher dispatcher, EntityModelSet modelSet) { 14 | super(dispatcher, modelSet); 15 | } 16 | 17 | @Override 18 | public void renderByItem(ItemStack stack, ItemDisplayContext transformType, PoseStack poseStack, MultiBufferSource bufferIn, int combinedLightIn, int combinedOverlayIn) { 19 | poseStack.scale(4 / 3.0f, 4 / 3.0f, 4 / 3.0f); 20 | poseStack.translate(0.5 - 0.5 / 0.75, 0, 0.5 - 0.5 / 0.75); 21 | MusicPlayerRenderer.instance.renderMusicPlayer(poseStack, bufferIn, combinedLightIn, Direction.WEST); 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/client/renderer/MusicPlayerRenderer.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.client.renderer; 2 | 3 | import com.github.tartaricacid.netmusic.NetMusic; 4 | import com.github.tartaricacid.netmusic.client.model.ModelMusicPlayer; 5 | import com.github.tartaricacid.netmusic.tileentity.TileEntityMusicPlayer; 6 | import com.mojang.blaze3d.vertex.PoseStack; 7 | import com.mojang.blaze3d.vertex.VertexConsumer; 8 | import com.mojang.math.Axis; 9 | import net.minecraft.client.model.geom.ModelPart; 10 | import net.minecraft.client.renderer.MultiBufferSource; 11 | import net.minecraft.client.renderer.RenderType; 12 | import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; 13 | import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; 14 | import net.minecraft.client.renderer.texture.OverlayTexture; 15 | import net.minecraft.core.Direction; 16 | import net.minecraft.resources.ResourceLocation; 17 | import net.minecraft.world.item.ItemStack; 18 | import net.minecraft.world.level.block.HorizontalDirectionalBlock; 19 | 20 | public class MusicPlayerRenderer implements BlockEntityRenderer { 21 | public static ModelMusicPlayer MODEL; 22 | public static final ResourceLocation TEXTURE = new ResourceLocation(NetMusic.MOD_ID, "textures/block/music_player.png"); 23 | public static MusicPlayerRenderer instance; 24 | 25 | public MusicPlayerRenderer(BlockEntityRendererProvider.Context dispatcher) { 26 | MODEL = new ModelMusicPlayer<>(dispatcher.bakeLayer(ModelMusicPlayer.LAYER)); 27 | instance = this; 28 | } 29 | 30 | @Override 31 | public void render(TileEntityMusicPlayer te, float pPartialTicks, PoseStack matrixStack, MultiBufferSource buffer, int combinedLight, int combinedOverlay) { 32 | Direction facing = te.getBlockState().getValue(HorizontalDirectionalBlock.FACING); 33 | ItemStack cd = te.getPlayerInv().getStackInSlot(0); 34 | ModelPart disc = MODEL.getDiscBone(); 35 | disc.visible = !cd.isEmpty(); 36 | if (!cd.isEmpty() && te.isPlay()) { 37 | disc.yRot = (float) ((2 * Math.PI / 40) * ((System.currentTimeMillis() / 50) % 40)); 38 | } 39 | renderMusicPlayer(matrixStack, buffer, combinedLight, facing); 40 | } 41 | 42 | public void renderMusicPlayer(PoseStack matrixStack, MultiBufferSource buffer, int combinedLight, Direction facing) { 43 | matrixStack.pushPose(); 44 | matrixStack.scale(0.75f, 0.75f, 0.75f); 45 | matrixStack.translate(0.5 / 0.75, 1.5, 0.5 / 0.75); 46 | switch (facing) { 47 | case NORTH: 48 | default: 49 | break; 50 | case SOUTH: 51 | matrixStack.mulPose(Axis.YP.rotationDegrees(180)); 52 | break; 53 | case EAST: 54 | matrixStack.mulPose(Axis.YP.rotationDegrees(270)); 55 | break; 56 | case WEST: 57 | matrixStack.mulPose(Axis.YP.rotationDegrees(90)); 58 | break; 59 | } 60 | matrixStack.mulPose(Axis.ZP.rotationDegrees(180)); 61 | VertexConsumer vertexBuilder = buffer.getBuffer(RenderType.entityTranslucent(TEXTURE)); 62 | MODEL.renderToBuffer(matrixStack, vertexBuilder, combinedLight, OverlayTexture.NO_OVERLAY, 1, 1, 1, 1); 63 | matrixStack.popPose(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/command/NetMusicCommand.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.command; 2 | 3 | import com.github.tartaricacid.netmusic.client.config.MusicListManage; 4 | import com.github.tartaricacid.netmusic.init.InitItems; 5 | import com.github.tartaricacid.netmusic.item.ItemMusicCD; 6 | import com.github.tartaricacid.netmusic.network.NetworkHandler; 7 | import com.github.tartaricacid.netmusic.network.message.GetMusicListMessage; 8 | import com.mojang.brigadier.Command; 9 | import com.mojang.brigadier.arguments.LongArgumentType; 10 | import com.mojang.brigadier.builder.LiteralArgumentBuilder; 11 | import com.mojang.brigadier.builder.RequiredArgumentBuilder; 12 | import com.mojang.brigadier.context.CommandContext; 13 | import net.minecraft.commands.CommandSourceStack; 14 | import net.minecraft.commands.Commands; 15 | import net.minecraft.network.chat.Component; 16 | import net.minecraft.server.level.ServerPlayer; 17 | import net.minecraft.sounds.SoundEvents; 18 | import net.minecraft.sounds.SoundSource; 19 | import net.minecraft.world.entity.item.ItemEntity; 20 | import net.minecraft.world.item.ItemStack; 21 | 22 | public class NetMusicCommand { 23 | private static final String ROOT_NAME = "netmusic"; 24 | private static final String RELOAD_NAME = "reload"; 25 | private static final String GET_163_NAME = "get163"; 26 | private static final String GET_163_CD_NAME = "get163cd"; 27 | private static final String SONG_LIST_ID = "song_list_id"; 28 | private static final String SONG_ID = "song_id"; 29 | 30 | public static LiteralArgumentBuilder get() { 31 | LiteralArgumentBuilder root = Commands.literal(ROOT_NAME) 32 | .requires((source -> source.hasPermission(2))); 33 | LiteralArgumentBuilder get163List = Commands.literal(GET_163_NAME); 34 | LiteralArgumentBuilder get163Song = Commands.literal(GET_163_CD_NAME); 35 | LiteralArgumentBuilder reload = Commands.literal(RELOAD_NAME); 36 | RequiredArgumentBuilder songListId = Commands.argument(SONG_LIST_ID, LongArgumentType.longArg()); 37 | RequiredArgumentBuilder songId = Commands.argument(SONG_ID, LongArgumentType.longArg()); 38 | 39 | root.then(get163List.then(songListId.executes(NetMusicCommand::getSongList))); 40 | root.then(get163Song.then(songId.executes(NetMusicCommand::getSong))); 41 | root.then(reload.executes(NetMusicCommand::reload)); 42 | return root; 43 | } 44 | 45 | private static int getSong(CommandContext context) { 46 | try { 47 | long songId = LongArgumentType.getLong(context, SONG_ID); 48 | ItemMusicCD.SongInfo songInfo = MusicListManage.get163Song(songId); 49 | ItemStack musicDisc = ItemMusicCD.setSongInfo(songInfo, InitItems.MUSIC_CD.get().getDefaultInstance()); 50 | ServerPlayer serverPlayer = context.getSource().getPlayerOrException(); 51 | boolean canPlaceIn = serverPlayer.getInventory().add(musicDisc); 52 | if (canPlaceIn && musicDisc.isEmpty()) { 53 | musicDisc.setCount(1); 54 | ItemEntity dropItem = serverPlayer.drop(musicDisc, false); 55 | if (dropItem != null) { 56 | dropItem.makeFakeItem(); 57 | } 58 | serverPlayer.level().playSound(null, serverPlayer.getX(), serverPlayer.getY(), serverPlayer.getZ(), SoundEvents.ITEM_PICKUP, SoundSource.PLAYERS, 0.2F, ((serverPlayer.getRandom().nextFloat() - serverPlayer.getRandom().nextFloat()) * 0.7F + 1.0F) * 2.0F); 59 | serverPlayer.inventoryMenu.broadcastChanges(); 60 | } else { 61 | ItemEntity dropItem = serverPlayer.drop(musicDisc, false); 62 | if (dropItem != null) { 63 | dropItem.setNoPickUpDelay(); 64 | dropItem.setThrower(serverPlayer.getUUID()); 65 | } 66 | } 67 | context.getSource().sendSuccess(() -> Component.translatable("command.netmusic.music_cd.add163cd.success"), false); 68 | } catch (Exception e) { 69 | e.printStackTrace(); 70 | context.getSource().sendFailure(Component.translatable("command.netmusic.music_cd.add163cd.fail")); 71 | } 72 | return Command.SINGLE_SUCCESS; 73 | } 74 | 75 | private static int getSongList(CommandContext context) { 76 | try { 77 | long listId = LongArgumentType.getLong(context, SONG_LIST_ID); 78 | ServerPlayer serverPlayer = context.getSource().getPlayerOrException(); 79 | NetworkHandler.sendToClientPlayer(new GetMusicListMessage(listId), serverPlayer); 80 | } catch (Exception e) { 81 | e.printStackTrace(); 82 | } 83 | return Command.SINGLE_SUCCESS; 84 | } 85 | 86 | private static int reload(CommandContext context) { 87 | try { 88 | ServerPlayer serverPlayer = context.getSource().getPlayerOrException(); 89 | NetworkHandler.sendToClientPlayer(new GetMusicListMessage(GetMusicListMessage.RELOAD_MESSAGE), serverPlayer); 90 | } catch (Exception e) { 91 | e.printStackTrace(); 92 | } 93 | return Command.SINGLE_SUCCESS; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/compat/tlm/MaidPlugin.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.compat.tlm; 2 | 3 | import com.github.tartaricacid.netmusic.compat.tlm.ai.PlaySoundFunction; 4 | import com.github.tartaricacid.netmusic.compat.tlm.backpack.MusicPlayerBackpack; 5 | import com.github.tartaricacid.touhoulittlemaid.ai.service.function.FunctionCallRegister; 6 | import com.github.tartaricacid.touhoulittlemaid.api.ILittleMaid; 7 | import com.github.tartaricacid.touhoulittlemaid.api.LittleMaidExtension; 8 | import com.github.tartaricacid.touhoulittlemaid.entity.backpack.BackpackManager; 9 | 10 | @LittleMaidExtension 11 | public class MaidPlugin implements ILittleMaid { 12 | @Override 13 | public void addMaidBackpack(BackpackManager manager) { 14 | manager.add(new MusicPlayerBackpack()); 15 | } 16 | 17 | @Override 18 | public void registerAIFunctionCall(FunctionCallRegister register) { 19 | register.register(new PlaySoundFunction()); 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/compat/tlm/ai/PlaySoundFunction.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.compat.tlm.ai; 2 | 3 | import com.github.tartaricacid.netmusic.NetMusic; 4 | import com.github.tartaricacid.netmusic.api.search.NeteaseMusicSearch; 5 | import com.github.tartaricacid.netmusic.api.search.SearchResponse; 6 | import com.github.tartaricacid.netmusic.compat.tlm.backpack.MusicPlayerBackpack; 7 | import com.github.tartaricacid.netmusic.compat.tlm.message.MaidMusicToClientMessage; 8 | import com.github.tartaricacid.netmusic.network.NetworkHandler; 9 | import com.github.tartaricacid.touhoulittlemaid.ai.service.function.IFunctionCall; 10 | import com.github.tartaricacid.touhoulittlemaid.ai.service.function.response.ToolResponse; 11 | import com.github.tartaricacid.touhoulittlemaid.ai.service.function.schema.parameter.ObjectParameter; 12 | import com.github.tartaricacid.touhoulittlemaid.ai.service.function.schema.parameter.Parameter; 13 | import com.github.tartaricacid.touhoulittlemaid.ai.service.function.schema.parameter.StringParameter; 14 | import com.github.tartaricacid.touhoulittlemaid.ai.service.llm.openai.request.ChatCompletion; 15 | import com.github.tartaricacid.touhoulittlemaid.entity.passive.EntityMaid; 16 | import com.google.gson.Gson; 17 | import com.mojang.serialization.Codec; 18 | import com.mojang.serialization.codecs.RecordCodecBuilder; 19 | 20 | import java.net.http.HttpResponse; 21 | import java.util.concurrent.CountDownLatch; 22 | import java.util.concurrent.TimeUnit; 23 | 24 | public class PlaySoundFunction implements IFunctionCall { 25 | public static final String ID = "netmusic:play_sound"; 26 | private static final String DESCRIPTION = """ 27 | When I mention something related to playing music. 28 | Please extract the song keywords from my words and call this function to search and play the music."""; 29 | private static final String SEARCH_TEXT_TITLE = "Song Keywords"; 30 | private static final String SEARCH_TEXT = "search_text"; 31 | private static final String ERROR_TIMEOUT = "Search music timed out"; 32 | private static final String ERROR_INTERRUPTED = "Search music was interrupted"; 33 | private static final String ERROR_NETWORK = "A network error occurred while searching for music"; 34 | private static final String ERROR_MUSIC_NOT_FOUND = "Music not found for the given keywords: "; 35 | private static final String ERROR_PARSE_JSON = "Failed to parse search response"; 36 | private static final String PLAY_SUCCESS = "Successfully started playing music: "; 37 | private static final Gson GSON = new Gson(); 38 | 39 | @Override 40 | public String getId() { 41 | return ID; 42 | } 43 | 44 | @Override 45 | public String getDescription(EntityMaid maid) { 46 | return DESCRIPTION; 47 | } 48 | 49 | @Override 50 | public boolean addToChatCompletion(EntityMaid maid, ChatCompletion chatCompletion) { 51 | return maid.getMaidBackpackType().getId().equals(MusicPlayerBackpack.ID); 52 | } 53 | 54 | @Override 55 | public Parameter addParameters(ObjectParameter root, EntityMaid maid) { 56 | StringParameter searchText = StringParameter.create().setTitle(SEARCH_TEXT_TITLE); 57 | root.addProperties(SEARCH_TEXT, searchText); 58 | return root; 59 | } 60 | 61 | @Override 62 | public Codec codec() { 63 | return RecordCodecBuilder.create(instance -> instance.group( 64 | Codec.STRING.fieldOf(SEARCH_TEXT).forGetter(Result::searchText) 65 | ).apply(instance, Result::new)); 66 | } 67 | 68 | @Override 69 | public ToolResponse onToolCall(Result result, EntityMaid maid) { 70 | CountDownLatch latch = new CountDownLatch(1); 71 | String searchText = result.searchText().trim(); 72 | String[] toolResponseText = new String[1]; 73 | NeteaseMusicSearch.searchFirstSong(searchText, (response, throwable) -> 74 | tryToPlayMusic(maid, response, throwable, toolResponseText, searchText, latch)); 75 | try { 76 | // 最多等待 5 秒 77 | if (!latch.await(5, TimeUnit.SECONDS)) { 78 | return new ToolResponse(ERROR_TIMEOUT); 79 | } 80 | } catch (InterruptedException e) { 81 | Thread.currentThread().interrupt(); 82 | return new ToolResponse(ERROR_INTERRUPTED); 83 | } 84 | return new ToolResponse(toolResponseText[0]); 85 | } 86 | 87 | private void tryToPlayMusic(EntityMaid maid, HttpResponse response, Throwable throwable, 88 | String[] toolResponseText, String searchText, CountDownLatch latch) { 89 | try { 90 | if (throwable != null) { 91 | toolResponseText[0] = ERROR_NETWORK; 92 | NetMusic.LOGGER.error(throwable); 93 | return; 94 | } 95 | if (response.statusCode() != 200) { 96 | toolResponseText[0] = ERROR_NETWORK; 97 | NetMusic.LOGGER.error("Search request failed with status code: {}", response.statusCode()); 98 | return; 99 | } 100 | SearchResponse searchResponse = GSON.fromJson(response.body(), SearchResponse.class); 101 | SearchResponse.Song songResult = searchResponse.getFirstSong(); 102 | if (songResult == null) { 103 | toolResponseText[0] = ERROR_MUSIC_NOT_FOUND + searchText; 104 | return; 105 | } 106 | MaidMusicToClientMessage msg = new MaidMusicToClientMessage(maid.getId(), songResult.getUrl(), 107 | songResult.getTimeSecond(), songResult.getName()); 108 | NetworkHandler.sendToNearby(maid.level(), maid.blockPosition(), msg); 109 | toolResponseText[0] = PLAY_SUCCESS + songResult.getName(); 110 | } catch (Exception e) { 111 | toolResponseText[0] = ERROR_PARSE_JSON; 112 | NetMusic.LOGGER.error("Failed to parse search response", e); 113 | } finally { 114 | latch.countDown(); 115 | } 116 | } 117 | 118 | public record Result(String searchText) { 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/compat/tlm/backpack/MusicPlayerBackpack.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.compat.tlm.backpack; 2 | 3 | import com.github.tartaricacid.netmusic.NetMusic; 4 | import com.github.tartaricacid.netmusic.compat.tlm.backpack.data.MusicPlayerBackpackData; 5 | import com.github.tartaricacid.netmusic.compat.tlm.client.model.MusicPlayerBackpackModel; 6 | import com.github.tartaricacid.netmusic.compat.tlm.inventory.MusicPlayerBackpackContainer; 7 | import com.github.tartaricacid.netmusic.init.InitItems; 8 | import com.github.tartaricacid.touhoulittlemaid.api.backpack.IBackpackData; 9 | import com.github.tartaricacid.touhoulittlemaid.api.backpack.IMaidBackpack; 10 | import com.github.tartaricacid.touhoulittlemaid.entity.item.EntityTombstone; 11 | import com.github.tartaricacid.touhoulittlemaid.entity.passive.EntityMaid; 12 | import com.github.tartaricacid.touhoulittlemaid.inventory.container.AbstractMaidContainer; 13 | import com.mojang.blaze3d.vertex.PoseStack; 14 | import net.minecraft.client.model.EntityModel; 15 | import net.minecraft.client.model.geom.EntityModelSet; 16 | import net.minecraft.network.chat.Component; 17 | import net.minecraft.resources.ResourceLocation; 18 | import net.minecraft.world.MenuProvider; 19 | import net.minecraft.world.entity.player.Inventory; 20 | import net.minecraft.world.entity.player.Player; 21 | import net.minecraft.world.item.Item; 22 | import net.minecraft.world.item.ItemStack; 23 | import net.minecraftforge.api.distmarker.Dist; 24 | import net.minecraftforge.api.distmarker.OnlyIn; 25 | import org.jetbrains.annotations.Nullable; 26 | 27 | public class MusicPlayerBackpack extends IMaidBackpack { 28 | public static final ResourceLocation ID = new ResourceLocation(NetMusic.MOD_ID, "music_player_backpack"); 29 | public static final ResourceLocation TEXTURE = new ResourceLocation(NetMusic.MOD_ID, "textures/entity/music_player_backpack.png"); 30 | private static final int MAX_AVAILABLE = 30; 31 | 32 | @Override 33 | public ResourceLocation getId() { 34 | return ID; 35 | } 36 | 37 | @Override 38 | public Item getItem() { 39 | return InitItems.MUSIC_PLAYER_BACKPACK.get(); 40 | } 41 | 42 | @Override 43 | public void onPutOn(ItemStack itemStack, Player player, EntityMaid entityMaid) { 44 | } 45 | 46 | @Override 47 | public void onTakeOff(ItemStack stack, Player player, EntityMaid maid) { 48 | this.dropAllItems(maid); 49 | } 50 | 51 | @Override 52 | public void onSpawnTombstone(EntityMaid entityMaid, EntityTombstone entityTombstone) { 53 | } 54 | 55 | @Override 56 | public MenuProvider getGuiProvider(final int entityId) { 57 | return new MenuProvider() { 58 | @Override 59 | public Component getDisplayName() { 60 | return Component.literal("Maid Music Player Container"); 61 | } 62 | 63 | @Override 64 | public AbstractMaidContainer createMenu(int index, Inventory playerInventory, Player player) { 65 | return new MusicPlayerBackpackContainer(index, playerInventory, entityId); 66 | } 67 | }; 68 | } 69 | 70 | @Override 71 | public boolean hasBackpackData() { 72 | return true; 73 | } 74 | 75 | @Nullable 76 | @Override 77 | public IBackpackData getBackpackData(EntityMaid maid) { 78 | return new MusicPlayerBackpackData(); 79 | } 80 | 81 | @Override 82 | public int getAvailableMaxContainerIndex() { 83 | return MAX_AVAILABLE; 84 | } 85 | 86 | @Override 87 | @OnlyIn(Dist.CLIENT) 88 | public void offsetBackpackItem(PoseStack poseStack) { 89 | poseStack.translate(0.0, 0.625, -0.05); 90 | } 91 | 92 | @Nullable 93 | @Override 94 | @OnlyIn(Dist.CLIENT) 95 | public EntityModel getBackpackModel(EntityModelSet entityModelSet) { 96 | return new MusicPlayerBackpackModel<>(entityModelSet.bakeLayer(MusicPlayerBackpackModel.LAYER)); 97 | } 98 | 99 | @Nullable 100 | @Override 101 | @OnlyIn(Dist.CLIENT) 102 | public ResourceLocation getBackpackTexture() { 103 | return TEXTURE; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/compat/tlm/backpack/data/MusicPlayerBackpackData.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.compat.tlm.backpack.data; 2 | 3 | import com.github.tartaricacid.netmusic.init.InitItems; 4 | import com.github.tartaricacid.netmusic.item.ItemMusicCD; 5 | import com.github.tartaricacid.netmusic.network.NetworkHandler; 6 | import com.github.tartaricacid.netmusic.compat.tlm.message.MaidMusicToClientMessage; 7 | import com.github.tartaricacid.touhoulittlemaid.api.backpack.IBackpackData; 8 | import com.github.tartaricacid.touhoulittlemaid.entity.passive.EntityMaid; 9 | import net.minecraft.nbt.CompoundTag; 10 | import net.minecraft.nbt.Tag; 11 | import net.minecraft.world.inventory.ContainerData; 12 | import net.minecraft.world.item.ItemStack; 13 | import net.minecraftforge.items.wrapper.CombinedInvWrapper; 14 | 15 | public class MusicPlayerBackpackData implements IBackpackData { 16 | private int selectSlotId = 0; 17 | private int playTick = 0; 18 | 19 | private final ContainerData dataAccess = new ContainerData() { 20 | @Override 21 | public int get(int index) { 22 | if (index == 0) { 23 | return MusicPlayerBackpackData.this.selectSlotId; 24 | } 25 | if (index == 1) { 26 | return MusicPlayerBackpackData.this.playTick; 27 | } 28 | return 0; 29 | } 30 | 31 | @Override 32 | public void set(int index, int value) { 33 | if (index == 0) { 34 | MusicPlayerBackpackData.this.selectSlotId = value; 35 | } 36 | if (index == 1) { 37 | MusicPlayerBackpackData.this.playTick = value; 38 | } 39 | } 40 | 41 | @Override 42 | public int getCount() { 43 | return 2; 44 | } 45 | }; 46 | 47 | @Override 48 | public ContainerData getDataAccess() { 49 | return dataAccess; 50 | } 51 | 52 | @Override 53 | public void load(CompoundTag compoundTag, EntityMaid entityMaid) { 54 | if (compoundTag.contains("MusicPlayerSelectSlotId", Tag.TAG_INT)) { 55 | this.selectSlotId = compoundTag.getInt("MusicPlayerSelectSlotId"); 56 | } 57 | } 58 | 59 | @Override 60 | public void save(CompoundTag compoundTag, EntityMaid entityMaid) { 61 | compoundTag.putInt("MusicPlayerSelectSlotId", this.selectSlotId); 62 | } 63 | 64 | @Override 65 | public void serverTick(EntityMaid entityMaid) { 66 | if (this.playTick > 0) { 67 | this.playTick--; 68 | if (playTick == 0) { 69 | playNextSong(entityMaid); 70 | } 71 | } 72 | } 73 | 74 | private void playNextSong(EntityMaid entityMaid) { 75 | CombinedInvWrapper availableInv = entityMaid.getAvailableInv(false); 76 | int startSlot = this.selectSlotId + 6 + 1; 77 | int stopSlot = 6 + 24; 78 | // 先从当前位置 +1 搜索,直到最后 79 | for (int i = startSlot; i < stopSlot; i++) { 80 | if (playMusic(entityMaid, availableInv, i)) { 81 | return; 82 | } 83 | } 84 | // 没有?那就从开头搜索到当前位置 85 | for (int i = 6; i <= startSlot; i++) { 86 | if (playMusic(entityMaid, availableInv, i)) { 87 | return; 88 | } 89 | } 90 | this.selectSlotId = 0; 91 | this.playTick = 0; 92 | } 93 | 94 | private boolean playMusic(EntityMaid entityMaid, CombinedInvWrapper availableInv, int slotId) { 95 | ItemStack stackInSlot = availableInv.getStackInSlot(slotId); 96 | if (stackInSlot.is(InitItems.MUSIC_CD.get())) { 97 | ItemMusicCD.SongInfo info = ItemMusicCD.getSongInfo(stackInSlot); 98 | if (info == null) { 99 | return false; 100 | } 101 | this.selectSlotId = slotId - 6; 102 | this.playTick = info.songTime * 20 + 64; 103 | MaidMusicToClientMessage msg = new MaidMusicToClientMessage(entityMaid.getId(), info.songUrl, info.songTime, info.songName); 104 | NetworkHandler.sendToNearby(entityMaid.level(), entityMaid.blockPosition(), msg); 105 | return true; 106 | } 107 | return false; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/compat/tlm/client/audio/MaidNetMusicSound.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.compat.tlm.client.audio; 2 | 3 | import com.github.tartaricacid.netmusic.client.audio.NetMusicAudioStream; 4 | import com.github.tartaricacid.netmusic.compat.tlm.backpack.MusicPlayerBackpack; 5 | import com.github.tartaricacid.netmusic.init.InitSounds; 6 | import com.github.tartaricacid.touhoulittlemaid.entity.passive.EntityMaid; 7 | import net.minecraft.Util; 8 | import net.minecraft.client.Minecraft; 9 | import net.minecraft.client.resources.sounds.AbstractTickableSoundInstance; 10 | import net.minecraft.client.resources.sounds.Sound; 11 | import net.minecraft.client.resources.sounds.SoundInstance; 12 | import net.minecraft.client.sounds.AudioStream; 13 | import net.minecraft.client.sounds.SoundBufferLibrary; 14 | import net.minecraft.core.particles.ParticleTypes; 15 | import net.minecraft.sounds.SoundSource; 16 | import net.minecraft.world.level.Level; 17 | 18 | import javax.sound.sampled.UnsupportedAudioFileException; 19 | import java.io.IOException; 20 | import java.net.URL; 21 | import java.util.concurrent.CompletableFuture; 22 | 23 | public class MaidNetMusicSound extends AbstractTickableSoundInstance { 24 | private final EntityMaid maid; 25 | private final URL songUrl; 26 | private final int tickTimes; 27 | private int tick; 28 | 29 | public MaidNetMusicSound(EntityMaid maid, URL songUrl, int timeSecond) { 30 | super(InitSounds.NET_MUSIC.get(), SoundSource.RECORDS, SoundInstance.createUnseededRandom()); 31 | this.maid = maid; 32 | this.songUrl = songUrl; 33 | this.x = maid.getX(); 34 | this.y = maid.getY(); 35 | this.z = maid.getZ(); 36 | this.tickTimes = timeSecond * 20; 37 | this.volume = 4.0f; 38 | this.tick = 0; 39 | } 40 | 41 | @Override 42 | public void tick() { 43 | if (this.maid.isRemoved()) { 44 | this.stop(); 45 | } 46 | if (!(maid.getMaidBackpackType() instanceof MusicPlayerBackpack)) { 47 | this.stop(); 48 | } 49 | Level world = Minecraft.getInstance().level; 50 | if (world == null) { 51 | this.stop(); 52 | return; 53 | } 54 | 55 | tick++; 56 | if (tick > tickTimes + 50) { 57 | this.stop(); 58 | } else { 59 | this.x = this.maid.getX(); 60 | this.y = this.maid.getY(); 61 | this.z = this.maid.getZ(); 62 | if (world.getGameTime() % 8 == 0) { 63 | for (int i = 0; i < 2; i++) { 64 | world.addParticle(ParticleTypes.NOTE, 65 | x - 0.5 + world.random.nextDouble(), 66 | y + 1.5 + world.random.nextDouble(), 67 | z - 0.5 + world.random.nextDouble(), 68 | world.random.nextGaussian(), world.random.nextGaussian(), world.random.nextInt(3)); 69 | } 70 | } 71 | } 72 | } 73 | 74 | @Override 75 | public CompletableFuture getStream(SoundBufferLibrary soundBuffers, Sound sound, boolean looping) { 76 | return CompletableFuture.supplyAsync(() -> { 77 | try { 78 | return new NetMusicAudioStream(this.songUrl); 79 | } catch (IOException | UnsupportedAudioFileException e) { 80 | e.printStackTrace(); 81 | } 82 | return null; 83 | }, Util.backgroundExecutor()); 84 | } 85 | 86 | public int getMaidId() { 87 | return this.maid.getId(); 88 | } 89 | 90 | public void setStop() { 91 | this.stop(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/compat/tlm/client/gui/MusicPlayerBackpackContainerScreen.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.compat.tlm.client.gui; 2 | 3 | import com.github.tartaricacid.netmusic.NetMusic; 4 | import com.github.tartaricacid.netmusic.compat.tlm.inventory.MusicPlayerBackpackContainer; 5 | import com.github.tartaricacid.touhoulittlemaid.client.gui.entity.maid.AbstractMaidContainerGui; 6 | import com.github.tartaricacid.touhoulittlemaid.client.gui.entity.maid.backpack.IBackpackContainerScreen; 7 | import com.github.tartaricacid.touhoulittlemaid.entity.passive.EntityMaid; 8 | import com.mojang.blaze3d.systems.RenderSystem; 9 | import net.minecraft.client.gui.GuiGraphics; 10 | import net.minecraft.client.gui.components.Button; 11 | import net.minecraft.client.gui.components.Tooltip; 12 | import net.minecraft.client.renderer.GameRenderer; 13 | import net.minecraft.network.chat.Component; 14 | import net.minecraft.resources.ResourceLocation; 15 | import net.minecraft.world.entity.player.Inventory; 16 | import org.anti_ad.mc.ipn.api.IPNButton; 17 | import org.anti_ad.mc.ipn.api.IPNGuiHint; 18 | import org.anti_ad.mc.ipn.api.IPNPlayerSideOnly; 19 | 20 | @IPNPlayerSideOnly 21 | @IPNGuiHint(button = IPNButton.SORT, horizontalOffset = -36, bottom = -12) 22 | @IPNGuiHint(button = IPNButton.SORT_COLUMNS, horizontalOffset = -24, bottom = -24) 23 | @IPNGuiHint(button = IPNButton.SORT_ROWS, horizontalOffset = -12, bottom = -36) 24 | @IPNGuiHint(button = IPNButton.SHOW_EDITOR, horizontalOffset = -5) 25 | @IPNGuiHint(button = IPNButton.SETTINGS, horizontalOffset = -5) 26 | public class MusicPlayerBackpackContainerScreen extends AbstractMaidContainerGui implements IBackpackContainerScreen { 27 | private static final ResourceLocation BACKPACK = new ResourceLocation(NetMusic.MOD_ID, "textures/gui/maid_music_player.png"); 28 | private final EntityMaid maid; 29 | 30 | public MusicPlayerBackpackContainerScreen(MusicPlayerBackpackContainer container, Inventory inv, Component titleIn) { 31 | super(container, inv, titleIn); 32 | this.imageHeight = 256; 33 | this.imageWidth = 256; 34 | this.maid = menu.getMaid(); 35 | } 36 | 37 | @Override 38 | protected void init() { 39 | super.init(); 40 | this.addRenderableWidget(Button.builder(Component.literal("<"), b -> clickButton(0)) 41 | .size(15, 20).pos(this.leftPos + 142, this.topPos + 135) 42 | .tooltip(Tooltip.create(Component.translatable("gui.netmusic.maid.music_player_backpack.previous"))) 43 | .build()); 44 | this.addRenderableWidget(Button.builder(Component.literal(">"), b -> clickButton(1)) 45 | .size(15, 20).pos(this.leftPos + 235, this.topPos + 135) 46 | .tooltip(Tooltip.create(Component.translatable("gui.netmusic.maid.music_player_backpack.next"))) 47 | .build()); 48 | 49 | this.addRenderableWidget(Button.builder(Component.translatable("gui.netmusic.maid.music_player_backpack.stop"), b -> this.clickButton(2)) 50 | .size(36, 20).pos(this.leftPos + 159, this.topPos + 135).build()); 51 | this.addRenderableWidget(Button.builder(Component.translatable("gui.netmusic.maid.music_player_backpack.play"), b -> this.clickButton(3)) 52 | .size(36, 20).pos(this.leftPos + 197, this.topPos + 135).build()); 53 | } 54 | 55 | private void clickButton(int id) { 56 | if (this.getMinecraft().gameMode != null) { 57 | this.getMinecraft().gameMode.handleInventoryButtonClick(this.menu.containerId, id); 58 | } 59 | } 60 | 61 | @Override 62 | protected void renderBg(GuiGraphics graphics, float partialTicks, int x, int y) { 63 | super.renderBg(graphics, partialTicks, x, y); 64 | RenderSystem.setShader(GameRenderer::getPositionTexShader); 65 | RenderSystem.setShaderTexture(0, BACKPACK); 66 | graphics.blit(BACKPACK, leftPos + 85, topPos + 36, 0, 0, 165, 128); 67 | int selectSlotId = this.menu.getSelectSlotId(); 68 | int xIndex = selectSlotId % 6; 69 | int yIndex = selectSlotId / 6; 70 | graphics.blit(BACKPACK, leftPos + 142 + 18 * xIndex, topPos + 56 + 18 * yIndex, 165, 0, 18, 18); 71 | } 72 | } -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/compat/tlm/client/model/MusicPlayerBackpackModel.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.compat.tlm.client.model; 2 | 3 | import com.github.tartaricacid.netmusic.NetMusic; 4 | import com.mojang.blaze3d.vertex.PoseStack; 5 | import com.mojang.blaze3d.vertex.VertexConsumer; 6 | import net.minecraft.client.model.EntityModel; 7 | import net.minecraft.client.model.geom.ModelLayerLocation; 8 | import net.minecraft.client.model.geom.ModelPart; 9 | import net.minecraft.client.model.geom.PartPose; 10 | import net.minecraft.client.model.geom.builders.*; 11 | import net.minecraft.resources.ResourceLocation; 12 | import net.minecraft.world.entity.Entity; 13 | 14 | public class MusicPlayerBackpackModel extends EntityModel { 15 | public static final ModelLayerLocation LAYER = new ModelLayerLocation(new ResourceLocation(NetMusic.MOD_ID, "main"), "music_player_backpack"); 16 | private final ModelPart main; 17 | 18 | public MusicPlayerBackpackModel(ModelPart root) { 19 | this.main = root.getChild("main"); 20 | } 21 | 22 | public static LayerDefinition createBodyLayer() { 23 | MeshDefinition meshdefinition = new MeshDefinition(); 24 | PartDefinition partdefinition = meshdefinition.getRoot(); 25 | 26 | PartDefinition main = partdefinition.addOrReplaceChild("main", CubeListBuilder.create(), PartPose.offset(0.0F, 13.25F, 1.0F)); 27 | 28 | PartDefinition bone466 = main.addOrReplaceChild("bone466", CubeListBuilder.create().texOffs(0, 26).addBox(-5.0F, -27.0F, -2.0F, 1.0F, 16.0F, 1.0F, new CubeDeformation(0.0F)) 29 | .texOffs(4, 26).addBox(4.0F, -27.0F, -2.0F, 1.0F, 16.0F, 1.0F, new CubeDeformation(0.0F)) 30 | .texOffs(0, 24).addBox(-6.0F, -20.0F, -2.0F, 12.0F, 1.0F, 1.0F, new CubeDeformation(-0.1F)), PartPose.offset(0.0F, 20.0F, 0.0F)); 31 | 32 | PartDefinition bone36 = bone466.addOrReplaceChild("bone36", CubeListBuilder.create().texOffs(0, 20).addBox(-6.0F, 3.5F, -0.5F, 12.0F, 1.0F, 1.0F, new CubeDeformation(-0.1F)) 33 | .texOffs(0, 16).addBox(-6.0F, 5.5F, -0.5F, 12.0F, 1.0F, 1.0F, new CubeDeformation(-0.1F)), PartPose.offset(0.0F, -28.5F, -1.5F)); 34 | 35 | PartDefinition bone = bone466.addOrReplaceChild("bone", CubeListBuilder.create().texOffs(0, 6).addBox(6.5F, -0.5F, -0.5F, 1.0F, 1.0F, 1.0F, new CubeDeformation(0.15F)) 36 | .texOffs(0, 6).addBox(-0.5F, -0.5F, -0.5F, 1.0F, 1.0F, 1.0F, new CubeDeformation(0.15F)), PartPose.offsetAndRotation(-3.5F, -19.5F, -1.5F, 0.7854F, 0.0F, 0.0F)); 37 | 38 | PartDefinition bone2 = bone466.addOrReplaceChild("bone2", CubeListBuilder.create().texOffs(0, 6).addBox(6.5F, -0.5F, -0.5F, 1.0F, 1.0F, 1.0F, new CubeDeformation(0.15F)) 39 | .texOffs(0, 6).addBox(-0.5F, -0.5F, -0.5F, 1.0F, 1.0F, 1.0F, new CubeDeformation(0.15F)), PartPose.offsetAndRotation(-3.5F, -13.5F, -1.5F, 0.7854F, 0.0F, 0.0F)); 40 | 41 | PartDefinition bone22 = bone466.addOrReplaceChild("bone22", CubeListBuilder.create(), PartPose.offset(4.0F, -18.9017F, -3.8731F)); 42 | 43 | PartDefinition bone23 = bone22.addOrReplaceChild("bone23", CubeListBuilder.create().texOffs(0, 0).mirror().addBox(-1.0F, -1.0F, -2.0F, 1.0F, 1.0F, 4.0F, new CubeDeformation(0.025F)).mirror(false), PartPose.offset(0.0F, 0.0F, 0.0F)); 44 | 45 | PartDefinition bone30 = bone22.addOrReplaceChild("bone30", CubeListBuilder.create().texOffs(0, 8).mirror().addBox(-1.0F, -1.0F, -3.0F, 1.0F, 6.0F, 1.0F, new CubeDeformation(0.0F)).mirror(false), PartPose.offset(0.0F, 0.0F, 0.0F)); 46 | 47 | PartDefinition bone31 = bone22.addOrReplaceChild("bone31", CubeListBuilder.create().texOffs(10, 0).mirror().addBox(-0.5F, -0.5F, -5.0F, 1.0F, 1.0F, 5.0F, new CubeDeformation(0.025F)).mirror(false), PartPose.offsetAndRotation(-0.5F, 5.5F, 2.0F, -0.2443F, 0.0F, 0.0F)); 48 | 49 | PartDefinition bone24 = bone466.addOrReplaceChild("bone24", CubeListBuilder.create(), PartPose.offset(-4.0F, -18.9017F, -3.8731F)); 50 | 51 | PartDefinition bone25 = bone24.addOrReplaceChild("bone25", CubeListBuilder.create().texOffs(0, 0).addBox(0.0F, -1.0F, -2.0F, 1.0F, 1.0F, 4.0F, new CubeDeformation(0.025F)), PartPose.offset(0.0F, 0.0F, 0.0F)); 52 | 53 | PartDefinition bone26 = bone24.addOrReplaceChild("bone26", CubeListBuilder.create().texOffs(0, 8).addBox(0.0F, -1.0F, -3.0F, 1.0F, 6.0F, 1.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 0.0F, 0.0F)); 54 | 55 | PartDefinition bone27 = bone24.addOrReplaceChild("bone27", CubeListBuilder.create().texOffs(10, 0).addBox(-0.5F, -0.5F, -5.0F, 1.0F, 1.0F, 5.0F, new CubeDeformation(0.025F)), PartPose.offsetAndRotation(0.5F, 5.5F, 2.0F, -0.2443F, 0.0F, 0.0F)); 56 | 57 | PartDefinition bone4 = bone466.addOrReplaceChild("bone4", CubeListBuilder.create().texOffs(24, 0).addBox(-5.5F, -2.3361F, -2.487F, 11.0F, 1.0F, 8.0F, new CubeDeformation(-0.25F)) 58 | .texOffs(0, 22).addBox(-6.0F, -1.6861F, -3.237F, 12.0F, 1.0F, 1.0F, new CubeDeformation(-0.1F)) 59 | .texOffs(0, 18).addBox(-6.0F, -1.6861F, 3.263F, 12.0F, 1.0F, 1.0F, new CubeDeformation(-0.1F)) 60 | .texOffs(13, 0).addBox(-5.0F, -1.6861F, -3.737F, 1.0F, 1.0F, 9.0F, new CubeDeformation(-0.1F)) 61 | .texOffs(0, 6).addBox(4.0F, -1.6861F, -3.737F, 1.0F, 1.0F, 9.0F, new CubeDeformation(-0.1F)), PartPose.offset(0.0F, -12.3139F, 1.237F)); 62 | 63 | PartDefinition bone5 = bone4.addOrReplaceChild("bone5", CubeListBuilder.create().texOffs(8, 33).addBox(4.0F, -0.5F, -3.0F, 1.0F, 1.0F, 6.0F, new CubeDeformation(-0.2F)) 64 | .texOffs(8, 26).addBox(-5.0F, -0.5F, -3.0F, 1.0F, 1.0F, 6.0F, new CubeDeformation(-0.2F)), PartPose.offsetAndRotation(0.0F, 0.0F, 0.0F, 0.3927F, 0.0F, 0.0F)); 65 | 66 | PartDefinition bone6 = main.addOrReplaceChild("bone6", CubeListBuilder.create().texOffs(35, 0).addBox(-15.0F, -13.1861F, -13.237F, 30.0F, 32.0F, 27.0F, new CubeDeformation(-10.5F)) 67 | .texOffs(0, 59).addBox(-11.5F, -7.9861F, -6.187F, 24.0F, 24.0F, 18.0F, new CubeDeformation(-8.5F)) 68 | .texOffs(86, 59).addBox(-8.0F, -6.1861F, -2.187F, 13.0F, 13.0F, 10.0F, new CubeDeformation(-4.5F)), PartPose.offset(0.0F, -2.6139F, 2.237F)); 69 | 70 | return LayerDefinition.create(meshdefinition, 256, 256); 71 | } 72 | 73 | @Override 74 | public void setupAnim(T entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) { 75 | } 76 | 77 | @Override 78 | public void renderToBuffer(PoseStack poseStack, VertexConsumer vertexConsumer, int packedLight, int packedOverlay, float red, float green, float blue, float alpha) { 79 | main.render(poseStack, vertexConsumer, packedLight, packedOverlay, red, green, blue, alpha); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/compat/tlm/init/CompatRegistry.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.compat.tlm.init; 2 | 3 | import com.github.tartaricacid.netmusic.init.InitItems; 4 | import net.minecraft.world.item.CreativeModeTab; 5 | import net.minecraft.world.item.ItemStack; 6 | import net.minecraftforge.api.distmarker.Dist; 7 | import net.minecraftforge.api.distmarker.OnlyIn; 8 | import net.minecraftforge.client.event.EntityRenderersEvent; 9 | import net.minecraftforge.eventbus.api.SubscribeEvent; 10 | import net.minecraftforge.fml.ModList; 11 | import net.minecraftforge.fml.common.Mod; 12 | import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; 13 | import net.minecraftforge.network.simple.SimpleChannel; 14 | import net.minecraftforge.registries.RegisterEvent; 15 | 16 | @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD) 17 | public class CompatRegistry { 18 | public static final String TLM = "touhou_little_maid"; 19 | 20 | @SubscribeEvent 21 | public static void initContainer(RegisterEvent event) { 22 | checkModLoad(TLM, () -> ContainerInit.init(event)); 23 | } 24 | 25 | @SubscribeEvent 26 | @OnlyIn(Dist.CLIENT) 27 | public static void onRegisterLayers(EntityRenderersEvent.RegisterLayerDefinitions event) { 28 | checkModLoad(TLM, () -> ModelInit.init(event)); 29 | } 30 | 31 | @OnlyIn(Dist.CLIENT) 32 | public static void initContainerScreen(FMLClientSetupEvent event) { 33 | checkModLoad(TLM, () -> ContainerScreenInit.init(event)); 34 | } 35 | 36 | public static void initCreativeModeTab(CreativeModeTab.Output output) { 37 | checkModLoad(TLM, () -> output.accept(new ItemStack(InitItems.MUSIC_PLAYER_BACKPACK.get()))); 38 | } 39 | 40 | public static void initNetwork(SimpleChannel channel) { 41 | checkModLoad(TLM, () -> NetworkInit.init(channel)); 42 | } 43 | 44 | private static void checkModLoad(String modId, Runnable runnable) { 45 | if (ModList.get().isLoaded(modId)) { 46 | runnable.run(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/compat/tlm/init/ContainerInit.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.compat.tlm.init; 2 | 3 | import com.github.tartaricacid.netmusic.compat.tlm.inventory.MusicPlayerBackpackContainer; 4 | import net.minecraftforge.registries.ForgeRegistries; 5 | import net.minecraftforge.registries.RegisterEvent; 6 | 7 | public class ContainerInit { 8 | public static void init(RegisterEvent event) { 9 | event.register(ForgeRegistries.Keys.MENU_TYPES, helper -> helper.register("maid_music_player_backpack", MusicPlayerBackpackContainer.TYPE)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/compat/tlm/init/ContainerScreenInit.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.compat.tlm.init; 2 | 3 | import com.github.tartaricacid.netmusic.compat.tlm.client.gui.MusicPlayerBackpackContainerScreen; 4 | import com.github.tartaricacid.netmusic.compat.tlm.inventory.MusicPlayerBackpackContainer; 5 | import net.minecraft.client.gui.screens.MenuScreens; 6 | import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; 7 | 8 | public class ContainerScreenInit { 9 | public static void init(FMLClientSetupEvent evt) { 10 | evt.enqueueWork(() -> MenuScreens.register(MusicPlayerBackpackContainer.TYPE, MusicPlayerBackpackContainerScreen::new)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/compat/tlm/init/ModelInit.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.compat.tlm.init; 2 | 3 | import com.github.tartaricacid.netmusic.compat.tlm.client.model.MusicPlayerBackpackModel; 4 | import net.minecraftforge.client.event.EntityRenderersEvent; 5 | 6 | public class ModelInit { 7 | public static void init(EntityRenderersEvent.RegisterLayerDefinitions event) { 8 | event.registerLayerDefinition(MusicPlayerBackpackModel.LAYER, MusicPlayerBackpackModel::createBodyLayer); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/compat/tlm/init/NetworkInit.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.compat.tlm.init; 2 | 3 | import com.github.tartaricacid.netmusic.compat.tlm.message.MaidMusicToClientMessage; 4 | import com.github.tartaricacid.netmusic.compat.tlm.message.MaidStopMusicMessage; 5 | import net.minecraftforge.network.NetworkDirection; 6 | import net.minecraftforge.network.simple.SimpleChannel; 7 | 8 | import java.util.Optional; 9 | 10 | public class NetworkInit { 11 | public static void init(SimpleChannel channel) { 12 | channel.registerMessage(99, MaidMusicToClientMessage.class, MaidMusicToClientMessage::encode, MaidMusicToClientMessage::decode, MaidMusicToClientMessage::handle, 13 | Optional.of(NetworkDirection.PLAY_TO_CLIENT)); 14 | channel.registerMessage(100, MaidStopMusicMessage.class, MaidStopMusicMessage::encode, MaidStopMusicMessage::decode, MaidStopMusicMessage::handle, 15 | Optional.of(NetworkDirection.PLAY_TO_CLIENT)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/compat/tlm/inventory/MusicPlayerBackpackContainer.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.compat.tlm.inventory; 2 | 3 | import com.github.tartaricacid.netmusic.NetMusic; 4 | import com.github.tartaricacid.netmusic.compat.tlm.backpack.data.MusicPlayerBackpackData; 5 | import com.github.tartaricacid.netmusic.compat.tlm.message.MaidMusicToClientMessage; 6 | import com.github.tartaricacid.netmusic.compat.tlm.message.MaidStopMusicMessage; 7 | import com.github.tartaricacid.netmusic.init.InitItems; 8 | import com.github.tartaricacid.netmusic.item.ItemMusicCD; 9 | import com.github.tartaricacid.netmusic.network.NetworkHandler; 10 | import com.github.tartaricacid.touhoulittlemaid.inventory.container.MaidMainContainer; 11 | import com.mojang.datafixers.util.Pair; 12 | import net.minecraft.resources.ResourceLocation; 13 | import net.minecraft.world.entity.player.Inventory; 14 | import net.minecraft.world.entity.player.Player; 15 | import net.minecraft.world.inventory.ContainerData; 16 | import net.minecraft.world.inventory.InventoryMenu; 17 | import net.minecraft.world.inventory.MenuType; 18 | import net.minecraft.world.item.ItemStack; 19 | import net.minecraftforge.api.distmarker.Dist; 20 | import net.minecraftforge.api.distmarker.OnlyIn; 21 | import net.minecraftforge.common.extensions.IForgeMenuType; 22 | import net.minecraftforge.items.SlotItemHandler; 23 | import net.minecraftforge.items.wrapper.CombinedInvWrapper; 24 | import org.jetbrains.annotations.NotNull; 25 | 26 | public class MusicPlayerBackpackContainer extends MaidMainContainer { 27 | public static final MenuType TYPE = IForgeMenuType.create((windowId, inv, data) -> new MusicPlayerBackpackContainer(windowId, inv, data.readInt())); 28 | private static final ResourceLocation EMPTY_CD_SLOT = new ResourceLocation(NetMusic.MOD_ID, "slot/music_cd_slot"); 29 | private final ContainerData data; 30 | 31 | public MusicPlayerBackpackContainer(int id, Inventory inventory, int entityId) { 32 | super(TYPE, id, inventory, entityId); 33 | MusicPlayerBackpackData musicPlayerBackpackData; 34 | if (this.getMaid().getBackpackData() instanceof MusicPlayerBackpackData) { 35 | musicPlayerBackpackData = (MusicPlayerBackpackData) this.getMaid().getBackpackData(); 36 | } else { 37 | musicPlayerBackpackData = new MusicPlayerBackpackData(); 38 | } 39 | this.data = musicPlayerBackpackData.getDataAccess(); 40 | this.addDataSlots(this.data); 41 | } 42 | 43 | @Override 44 | protected void addBackpackInv(Inventory inventory) { 45 | for (int y = 0; y < 4; y++) { 46 | for (int x = 0; x < 6; x++) { 47 | int index = (y + 1) * 6 + x; 48 | addSlot(new SlotItemHandler(maid.getMaidInv(), index, 143 + 18 * x, 57 + 18 * y) { 49 | @Override 50 | public boolean mayPlace(@NotNull ItemStack stack) { 51 | return stack.is(InitItems.MUSIC_CD.get()); 52 | } 53 | 54 | @Override 55 | @OnlyIn(Dist.CLIENT) 56 | public Pair getNoItemIcon() { 57 | return Pair.of(InventoryMenu.BLOCK_ATLAS, EMPTY_CD_SLOT); 58 | } 59 | }); 60 | } 61 | } 62 | } 63 | 64 | @Override 65 | public boolean clickMenuButton(Player player, int id) { 66 | if (id == 0) { 67 | return previousSlot(); 68 | } 69 | if (id == 1) { 70 | return nextSlot(); 71 | } 72 | if (id == 2) { 73 | return stopMusic(); 74 | } 75 | if (id == 3) { 76 | // 先停止播放 77 | this.stopMusic(); 78 | return playMusic(); 79 | } 80 | return false; 81 | } 82 | 83 | private boolean previousSlot() { 84 | this.stopMusic(); 85 | int slotId = this.data.get(0); 86 | slotId = slotId - 1; 87 | if (slotId < 0) { 88 | slotId = 23; 89 | } 90 | this.data.set(0, slotId); 91 | return true; 92 | } 93 | 94 | private boolean nextSlot() { 95 | this.stopMusic(); 96 | int slotId = this.data.get(0); 97 | slotId = slotId + 1; 98 | if (slotId > 23) { 99 | slotId = 0; 100 | } 101 | this.data.set(0, slotId); 102 | return true; 103 | } 104 | 105 | private boolean playMusic() { 106 | if (this.maid == null) { 107 | return false; 108 | } 109 | int slotId = this.getSelectSlotId(); 110 | if (0 <= slotId && slotId < 24) { 111 | CombinedInvWrapper availableInv = this.maid.getAvailableInv(false); 112 | ItemStack stackInSlot = availableInv.getStackInSlot(6 + slotId); 113 | if (stackInSlot.is(InitItems.MUSIC_CD.get())) { 114 | ItemMusicCD.SongInfo info = ItemMusicCD.getSongInfo(stackInSlot); 115 | if (info == null) { 116 | return false; 117 | } 118 | this.setSoundTicks(info.songTime * 20 + 64); 119 | MaidMusicToClientMessage msg = new MaidMusicToClientMessage(this.maid.getId(), info.songUrl, info.songTime, info.songName); 120 | NetworkHandler.sendToNearby(this.maid.level(), this.maid.blockPosition(), msg); 121 | return true; 122 | } 123 | } 124 | return false; 125 | } 126 | 127 | private boolean stopMusic() { 128 | if (this.maid == null) { 129 | return false; 130 | } 131 | this.setSoundTicks(0); 132 | MaidStopMusicMessage stopMsg = new MaidStopMusicMessage(this.maid.getId()); 133 | NetworkHandler.sendToNearby(this.maid.level(), this.maid.blockPosition(), stopMsg); 134 | return true; 135 | } 136 | 137 | public int getSelectSlotId() { 138 | return this.data.get(0); 139 | } 140 | 141 | public void setSoundTicks(int ticks) { 142 | this.data.set(1, ticks); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/compat/tlm/message/MaidMusicToClientMessage.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.compat.tlm.message; 2 | 3 | import com.github.tartaricacid.netmusic.client.audio.MusicPlayManager; 4 | import com.github.tartaricacid.netmusic.compat.tlm.client.audio.MaidNetMusicSound; 5 | import com.github.tartaricacid.touhoulittlemaid.entity.passive.EntityMaid; 6 | import net.minecraft.Util; 7 | import net.minecraft.client.Minecraft; 8 | import net.minecraft.network.FriendlyByteBuf; 9 | import net.minecraft.world.entity.Entity; 10 | import net.minecraftforge.api.distmarker.Dist; 11 | import net.minecraftforge.api.distmarker.OnlyIn; 12 | import net.minecraftforge.network.NetworkEvent; 13 | 14 | import java.util.concurrent.CompletableFuture; 15 | import java.util.function.Supplier; 16 | 17 | public class MaidMusicToClientMessage { 18 | private final int entityId; 19 | private final String url; 20 | private final int timeSecond; 21 | private final String songName; 22 | 23 | public MaidMusicToClientMessage(int entityId, String url, int timeSecond, String songName) { 24 | this.entityId = entityId; 25 | this.url = url; 26 | this.timeSecond = timeSecond; 27 | this.songName = songName; 28 | } 29 | 30 | public static MaidMusicToClientMessage decode(FriendlyByteBuf buf) { 31 | return new MaidMusicToClientMessage(buf.readInt(), buf.readUtf(), buf.readInt(), buf.readUtf()); 32 | } 33 | 34 | public static void encode(MaidMusicToClientMessage message, FriendlyByteBuf buf) { 35 | buf.writeInt(message.entityId); 36 | buf.writeUtf(message.url); 37 | buf.writeInt(message.timeSecond); 38 | buf.writeUtf(message.songName); 39 | } 40 | 41 | public static void handle(MaidMusicToClientMessage message, Supplier contextSupplier) { 42 | NetworkEvent.Context context = contextSupplier.get(); 43 | if (context.getDirection().getReceptionSide().isClient()) { 44 | context.enqueueWork(() -> CompletableFuture.runAsync(() -> onHandle(message), Util.backgroundExecutor())); 45 | } 46 | context.setPacketHandled(true); 47 | } 48 | 49 | @OnlyIn(Dist.CLIENT) 50 | private static void onHandle(MaidMusicToClientMessage message) { 51 | if (Minecraft.getInstance().level == null) { 52 | return; 53 | } 54 | Entity entity = Minecraft.getInstance().level.getEntity(message.entityId); 55 | if (!(entity instanceof EntityMaid maid)) { 56 | return; 57 | } 58 | MusicPlayManager.play(message.url, message.songName, url -> new MaidNetMusicSound(maid, url, message.timeSecond)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/compat/tlm/message/MaidStopMusicMessage.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.compat.tlm.message; 2 | 3 | import com.github.tartaricacid.netmusic.compat.tlm.client.audio.MaidNetMusicSound; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.client.multiplayer.ClientLevel; 6 | import net.minecraft.client.resources.sounds.SoundInstance; 7 | import net.minecraft.client.sounds.ChannelAccess; 8 | import net.minecraft.network.FriendlyByteBuf; 9 | import net.minecraftforge.api.distmarker.Dist; 10 | import net.minecraftforge.api.distmarker.OnlyIn; 11 | import net.minecraftforge.network.NetworkEvent; 12 | 13 | import java.util.Map; 14 | import java.util.function.Supplier; 15 | 16 | public class MaidStopMusicMessage { 17 | private final int entityId; 18 | 19 | public MaidStopMusicMessage(int entityId) { 20 | this.entityId = entityId; 21 | } 22 | 23 | public static MaidStopMusicMessage decode(FriendlyByteBuf buffer) { 24 | return new MaidStopMusicMessage(buffer.readInt()); 25 | } 26 | 27 | public static void encode(MaidStopMusicMessage message, FriendlyByteBuf buf) { 28 | buf.writeInt(message.entityId); 29 | } 30 | 31 | public static void handle(MaidStopMusicMessage message, Supplier contextSupplier) { 32 | NetworkEvent.Context context = contextSupplier.get(); 33 | if (context.getDirection().getReceptionSide().isClient()) { 34 | context.enqueueWork(() -> onHandle(message)); 35 | } 36 | context.setPacketHandled(true); 37 | } 38 | 39 | @OnlyIn(Dist.CLIENT) 40 | private static void onHandle(MaidStopMusicMessage message) { 41 | ClientLevel level = Minecraft.getInstance().level; 42 | if (level == null) { 43 | return; 44 | } 45 | Map sounds = Minecraft.getInstance().getSoundManager().soundEngine.instanceToChannel; 46 | for (SoundInstance instance : sounds.keySet()) { 47 | if (!(instance instanceof MaidNetMusicSound sound)) { 48 | continue; 49 | } 50 | if (sound.getMaidId() == message.entityId) { 51 | sound.setStop(); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/config/GeneralConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.config; 2 | 3 | import net.minecraftforge.common.ForgeConfigSpec; 4 | 5 | import java.net.Proxy; 6 | 7 | public class GeneralConfig { 8 | public static ForgeConfigSpec.BooleanValue ENABLE_STEREO; 9 | public static ForgeConfigSpec.EnumValue PROXY_TYPE; 10 | public static ForgeConfigSpec.ConfigValue PROXY_ADDRESS; 11 | 12 | public static ForgeConfigSpec init() { 13 | ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder(); 14 | builder.push("general"); 15 | 16 | builder.comment("Whether stereo playback is enabled"); 17 | ENABLE_STEREO = builder.define("EnableStereo", true); 18 | 19 | builder.comment("Proxy Type, http and socks are supported"); 20 | PROXY_TYPE = builder.defineEnum("ProxyType", Proxy.Type.DIRECT); 21 | 22 | builder.comment("Proxy Address, such as 127.0.0.1:1080, empty is no proxy"); 23 | PROXY_ADDRESS = builder.define("ProxyAddress", ""); 24 | 25 | builder.pop(); 26 | return builder.build(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/init/CommandRegistry.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.init; 2 | 3 | import com.github.tartaricacid.netmusic.command.NetMusicCommand; 4 | import net.minecraftforge.event.RegisterCommandsEvent; 5 | import net.minecraftforge.eventbus.api.SubscribeEvent; 6 | import net.minecraftforge.fml.common.Mod; 7 | 8 | @Mod.EventBusSubscriber 9 | public class CommandRegistry { 10 | @SubscribeEvent 11 | public static void onServerStaring(RegisterCommandsEvent event) { 12 | event.getDispatcher().register(NetMusicCommand.get()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/init/CommonRegistry.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.init; 2 | 3 | import com.github.tartaricacid.netmusic.network.NetworkHandler; 4 | import net.minecraftforge.eventbus.api.SubscribeEvent; 5 | import net.minecraftforge.fml.common.Mod; 6 | import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; 7 | 8 | @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD) 9 | public final class CommonRegistry { 10 | @SubscribeEvent 11 | public static void onSetupEvent(FMLCommonSetupEvent event) { 12 | event.enqueueWork(NetworkHandler::init); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/init/InitBlocks.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.init; 2 | 3 | import com.github.tartaricacid.netmusic.NetMusic; 4 | import com.github.tartaricacid.netmusic.block.BlockCDBurner; 5 | import com.github.tartaricacid.netmusic.block.BlockComputer; 6 | import com.github.tartaricacid.netmusic.block.BlockMusicPlayer; 7 | import com.github.tartaricacid.netmusic.tileentity.TileEntityMusicPlayer; 8 | import net.minecraft.world.level.block.Block; 9 | import net.minecraft.world.level.block.entity.BlockEntityType; 10 | import net.minecraftforge.registries.DeferredRegister; 11 | import net.minecraftforge.registries.ForgeRegistries; 12 | import net.minecraftforge.registries.RegistryObject; 13 | 14 | public class InitBlocks { 15 | public static final DeferredRegister BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, NetMusic.MOD_ID); 16 | public static final DeferredRegister> TILE_ENTITIES = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, NetMusic.MOD_ID); 17 | 18 | public static RegistryObject MUSIC_PLAYER = BLOCKS.register("music_player", BlockMusicPlayer::new); 19 | public static RegistryObject CD_BURNER = BLOCKS.register("cd_burner", BlockCDBurner::new); 20 | public static RegistryObject COMPUTER = BLOCKS.register("computer", BlockComputer::new); 21 | 22 | public static RegistryObject> MUSIC_PLAYER_TE = TILE_ENTITIES.register("music_player", () -> TileEntityMusicPlayer.TYPE); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/init/InitContainer.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.init; 2 | 3 | import com.github.tartaricacid.netmusic.NetMusic; 4 | import com.github.tartaricacid.netmusic.inventory.CDBurnerMenu; 5 | import com.github.tartaricacid.netmusic.inventory.ComputerMenu; 6 | import net.minecraft.world.inventory.MenuType; 7 | import net.minecraftforge.registries.DeferredRegister; 8 | import net.minecraftforge.registries.ForgeRegistries; 9 | import net.minecraftforge.registries.RegistryObject; 10 | 11 | public class InitContainer { 12 | public static final DeferredRegister> CONTAINER_TYPE = DeferredRegister.create(ForgeRegistries.MENU_TYPES, NetMusic.MOD_ID); 13 | 14 | public static final RegistryObject> CD_BURNER_CONTAINER = CONTAINER_TYPE.register("cd_burner", () -> CDBurnerMenu.TYPE); 15 | public static final RegistryObject> COMPUTER_CONTAINER = CONTAINER_TYPE.register("computer", () -> ComputerMenu.TYPE); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/init/InitItems.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.init; 2 | 3 | import com.github.tartaricacid.netmusic.NetMusic; 4 | import com.github.tartaricacid.netmusic.client.config.MusicListManage; 5 | import com.github.tartaricacid.netmusic.compat.tlm.init.CompatRegistry; 6 | import com.github.tartaricacid.netmusic.item.ItemMusicCD; 7 | import com.github.tartaricacid.netmusic.item.ItemMusicPlayer; 8 | import net.minecraft.core.registries.Registries; 9 | import net.minecraft.network.chat.Component; 10 | import net.minecraft.world.item.BlockItem; 11 | import net.minecraft.world.item.CreativeModeTab; 12 | import net.minecraft.world.item.Item; 13 | import net.minecraft.world.item.ItemStack; 14 | import net.minecraftforge.registries.DeferredRegister; 15 | import net.minecraftforge.registries.ForgeRegistries; 16 | import net.minecraftforge.registries.RegistryObject; 17 | 18 | public class InitItems { 19 | public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, NetMusic.MOD_ID); 20 | public static final DeferredRegister TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, NetMusic.MOD_ID); 21 | 22 | public static RegistryObject MUSIC_CD = ITEMS.register("music_cd", ItemMusicCD::new); 23 | public static RegistryObject MUSIC_PLAYER = ITEMS.register("music_player", ItemMusicPlayer::new); 24 | public static RegistryObject CD_BURNER = ITEMS.register("cd_burner", () -> new BlockItem(InitBlocks.CD_BURNER.get(), new Item.Properties().stacksTo(1))); 25 | public static RegistryObject COMPUTER = ITEMS.register("computer", () -> new BlockItem(InitBlocks.COMPUTER.get(), new Item.Properties().stacksTo(1))); 26 | public static RegistryObject MUSIC_PLAYER_BACKPACK = ITEMS.register("music_player_backpack", () -> new Item(new Item.Properties().stacksTo(1))); 27 | 28 | public static RegistryObject NET_MUSIC_TAB = TABS.register("netmusic", () -> CreativeModeTab.builder().title(Component.translatable("itemGroup.netmusic")) 29 | .icon(() -> new ItemStack(InitBlocks.MUSIC_PLAYER.get())).displayItems((parameters, output) -> { 30 | output.accept(new ItemStack(MUSIC_PLAYER.get())); 31 | output.accept(new ItemStack(InitItems.CD_BURNER.get())); 32 | output.accept(new ItemStack(InitItems.COMPUTER.get())); 33 | CompatRegistry.initCreativeModeTab(output); 34 | output.accept(new ItemStack(InitItems.MUSIC_CD.get())); 35 | for (ItemMusicCD.SongInfo info : MusicListManage.SONGS) { 36 | ItemStack stack = new ItemStack(MUSIC_CD.get()); 37 | ItemMusicCD.setSongInfo(info, stack); 38 | output.accept(stack); 39 | } 40 | } 41 | ).build()); 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/init/InitSounds.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.init; 2 | 3 | import com.github.tartaricacid.netmusic.NetMusic; 4 | import net.minecraft.resources.ResourceLocation; 5 | import net.minecraft.sounds.SoundEvent; 6 | import net.minecraftforge.registries.DeferredRegister; 7 | import net.minecraftforge.registries.ForgeRegistries; 8 | import net.minecraftforge.registries.RegistryObject; 9 | 10 | public class InitSounds { 11 | public static final DeferredRegister SOUND_EVENTS = DeferredRegister.create(ForgeRegistries.SOUND_EVENTS, NetMusic.MOD_ID); 12 | public static RegistryObject NET_MUSIC = SOUND_EVENTS.register("net_music", () -> SoundEvent.createVariableRangeEvent(new ResourceLocation(NetMusic.MOD_ID, "net_music"))); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/inventory/CDBurnerMenu.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.inventory; 2 | 3 | import com.github.tartaricacid.netmusic.init.InitItems; 4 | import com.github.tartaricacid.netmusic.item.ItemMusicCD; 5 | import net.minecraft.world.entity.player.Inventory; 6 | import net.minecraft.world.entity.player.Player; 7 | import net.minecraft.world.inventory.AbstractContainerMenu; 8 | import net.minecraft.world.inventory.MenuType; 9 | import net.minecraft.world.inventory.Slot; 10 | import net.minecraft.world.item.ItemStack; 11 | import net.minecraftforge.common.extensions.IForgeMenuType; 12 | import net.minecraftforge.items.ItemHandlerHelper; 13 | import net.minecraftforge.items.ItemStackHandler; 14 | import net.minecraftforge.items.SlotItemHandler; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | public class CDBurnerMenu extends AbstractContainerMenu { 18 | public static final MenuType TYPE = IForgeMenuType.create((windowId, inv, data) -> new CDBurnerMenu(windowId, inv)); 19 | private final ItemStackHandler input = new ItemStackHandler() { 20 | @Override 21 | public boolean isItemValid(int slot, @NotNull ItemStack stack) { 22 | return stack.getItem() == InitItems.MUSIC_CD.get(); 23 | } 24 | }; 25 | private final ItemStackHandler output = new ItemStackHandler() { 26 | @Override 27 | public boolean isItemValid(int slot, @NotNull ItemStack stack) { 28 | return false; 29 | } 30 | 31 | @Override 32 | protected int getStackLimit(int slot, @NotNull ItemStack stack) { 33 | return 1; 34 | } 35 | }; 36 | private ItemMusicCD.SongInfo songInfo; 37 | 38 | public CDBurnerMenu(int id, Inventory inventory) { 39 | super(TYPE, id); 40 | 41 | this.addSlot(new SlotItemHandler(input, 0, 147, 14)); 42 | this.addSlot(new SlotItemHandler(output, 0, 147, 67)); 43 | 44 | for (int i = 0; i < 9; ++i) { 45 | this.addSlot(new Slot(inventory, i, 8 + i * 18, 152)); 46 | } 47 | 48 | for (int i = 0; i < 3; ++i) { 49 | for (int j = 0; j < 9; ++j) { 50 | this.addSlot(new Slot(inventory, j + i * 9 + 9, 8 + j * 18, 94 + i * 18)); 51 | } 52 | } 53 | } 54 | 55 | @Override 56 | public ItemStack quickMoveStack(Player player, int index) { 57 | ItemStack itemStack = ItemStack.EMPTY; 58 | Slot slot = this.slots.get(index); 59 | if (slot != null && slot.hasItem()) { 60 | ItemStack slotItem = slot.getItem(); 61 | itemStack = slotItem.copy(); 62 | if (index < 2) { 63 | if (!this.moveItemStackTo(slotItem, 2, this.slots.size(), false)) { 64 | return ItemStack.EMPTY; 65 | } 66 | } else if (!this.moveItemStackTo(slotItem, 0, 2, true)) { 67 | return ItemStack.EMPTY; 68 | } 69 | 70 | if (slotItem.isEmpty()) { 71 | slot.setByPlayer(ItemStack.EMPTY); 72 | } else { 73 | slot.setChanged(); 74 | } 75 | } 76 | 77 | return itemStack; 78 | } 79 | 80 | @Override 81 | public boolean stillValid(Player player) { 82 | return true; 83 | } 84 | 85 | @Override 86 | public void removed(Player player) { 87 | super.removed(player); 88 | ItemHandlerHelper.giveItemToPlayer(player, input.getStackInSlot(0)); 89 | ItemHandlerHelper.giveItemToPlayer(player, output.getStackInSlot(0)); 90 | } 91 | 92 | public void setSongInfo(ItemMusicCD.SongInfo setSongInfo) { 93 | this.songInfo = setSongInfo; 94 | if (!this.input.getStackInSlot(0).isEmpty() && this.output.getStackInSlot(0).isEmpty()) { 95 | ItemStack itemStack = this.input.extractItem(0, 1, false); 96 | ItemMusicCD.SongInfo rawSongInfo = ItemMusicCD.getSongInfo(itemStack); 97 | if (rawSongInfo == null || !rawSongInfo.readOnly) { 98 | ItemMusicCD.setSongInfo(this.songInfo, itemStack); 99 | } 100 | this.output.setStackInSlot(0, itemStack); 101 | } 102 | } 103 | 104 | public ItemStackHandler getInput() { 105 | return input; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/inventory/ComputerMenu.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.inventory; 2 | 3 | import com.github.tartaricacid.netmusic.init.InitItems; 4 | import com.github.tartaricacid.netmusic.item.ItemMusicCD; 5 | import net.minecraft.world.entity.player.Inventory; 6 | import net.minecraft.world.entity.player.Player; 7 | import net.minecraft.world.inventory.AbstractContainerMenu; 8 | import net.minecraft.world.inventory.MenuType; 9 | import net.minecraft.world.inventory.Slot; 10 | import net.minecraft.world.item.ItemStack; 11 | import net.minecraftforge.common.extensions.IForgeMenuType; 12 | import net.minecraftforge.items.ItemHandlerHelper; 13 | import net.minecraftforge.items.ItemStackHandler; 14 | import net.minecraftforge.items.SlotItemHandler; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | public class ComputerMenu extends AbstractContainerMenu { 18 | public static final MenuType TYPE = IForgeMenuType.create((windowId, inv, data) -> new ComputerMenu(windowId, inv)); 19 | private final ItemStackHandler input = new ItemStackHandler() { 20 | @Override 21 | public boolean isItemValid(int slot, @NotNull ItemStack stack) { 22 | return stack.getItem() == InitItems.MUSIC_CD.get(); 23 | } 24 | }; 25 | private final ItemStackHandler output = new ItemStackHandler() { 26 | @Override 27 | public boolean isItemValid(int slot, @NotNull ItemStack stack) { 28 | return false; 29 | } 30 | 31 | @Override 32 | protected int getStackLimit(int slot, @NotNull ItemStack stack) { 33 | return 1; 34 | } 35 | }; 36 | private ItemMusicCD.SongInfo songInfo; 37 | 38 | public ComputerMenu(int id, Inventory inventory) { 39 | super(TYPE, id); 40 | 41 | this.addSlot(new SlotItemHandler(input, 0, 147, 14)); 42 | this.addSlot(new SlotItemHandler(output, 0, 147, 79)); 43 | 44 | for (int i = 0; i < 9; ++i) { 45 | this.addSlot(new Slot(inventory, i, 8 + i * 18, 192)); 46 | } 47 | 48 | for (int i = 0; i < 3; ++i) { 49 | for (int j = 0; j < 9; ++j) { 50 | this.addSlot(new Slot(inventory, j + i * 9 + 9, 8 + j * 18, 134 + i * 18)); 51 | } 52 | } 53 | } 54 | 55 | @Override 56 | public ItemStack quickMoveStack(Player player, int index) { 57 | ItemStack itemStack = ItemStack.EMPTY; 58 | Slot slot = this.slots.get(index); 59 | if (slot != null && slot.hasItem()) { 60 | ItemStack slotItem = slot.getItem(); 61 | itemStack = slotItem.copy(); 62 | if (index < 2) { 63 | if (!this.moveItemStackTo(slotItem, 2, this.slots.size(), false)) { 64 | return ItemStack.EMPTY; 65 | } 66 | } else if (!this.moveItemStackTo(slotItem, 0, 2, true)) { 67 | return ItemStack.EMPTY; 68 | } 69 | 70 | if (slotItem.isEmpty()) { 71 | slot.setByPlayer(ItemStack.EMPTY); 72 | } else { 73 | slot.setChanged(); 74 | } 75 | } 76 | 77 | return itemStack; 78 | } 79 | 80 | @Override 81 | public boolean stillValid(Player player) { 82 | return true; 83 | } 84 | 85 | @Override 86 | public void removed(Player player) { 87 | super.removed(player); 88 | ItemHandlerHelper.giveItemToPlayer(player, input.getStackInSlot(0)); 89 | ItemHandlerHelper.giveItemToPlayer(player, output.getStackInSlot(0)); 90 | } 91 | 92 | public void setSongInfo(ItemMusicCD.SongInfo setSongInfo) { 93 | this.songInfo = setSongInfo; 94 | if (!this.input.getStackInSlot(0).isEmpty() && this.output.getStackInSlot(0).isEmpty()) { 95 | ItemStack itemStack = this.input.extractItem(0, 1, false); 96 | ItemMusicCD.SongInfo rawSongInfo = ItemMusicCD.getSongInfo(itemStack); 97 | if (rawSongInfo == null || !rawSongInfo.readOnly) { 98 | ItemMusicCD.setSongInfo(this.songInfo, itemStack); 99 | } 100 | this.output.setStackInSlot(0, itemStack); 101 | } 102 | } 103 | 104 | public ItemStackHandler getInput() { 105 | return input; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/inventory/MusicPlayerInv.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.inventory; 2 | 3 | import com.github.tartaricacid.netmusic.init.InitItems; 4 | import com.github.tartaricacid.netmusic.item.ItemMusicCD; 5 | import com.github.tartaricacid.netmusic.tileentity.TileEntityMusicPlayer; 6 | import net.minecraft.world.item.ItemStack; 7 | import net.minecraftforge.items.ItemStackHandler; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import javax.annotation.Nonnull; 11 | 12 | public class MusicPlayerInv extends ItemStackHandler { 13 | private final TileEntityMusicPlayer te; 14 | 15 | public MusicPlayerInv(TileEntityMusicPlayer te) { 16 | super(); 17 | this.te = te; 18 | } 19 | 20 | @Override 21 | public boolean isItemValid(int slot, @Nonnull ItemStack stack) { 22 | return stack.getItem() == InitItems.MUSIC_CD.get(); 23 | } 24 | 25 | @Override 26 | protected int getStackLimit(int slot, @NotNull ItemStack stack) { 27 | return 1; 28 | } 29 | 30 | @Override 31 | protected void onContentsChanged(int slot) { 32 | ItemStack stackInSlot = getStackInSlot(slot); 33 | if (stackInSlot.isEmpty()) { 34 | te.setPlay(false); 35 | te.setCurrentTime(0); 36 | } 37 | te.markDirty(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/item/ItemMusicPlayer.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.item; 2 | 3 | import com.github.tartaricacid.netmusic.client.renderer.MusicPlayerItemRenderer; 4 | import com.github.tartaricacid.netmusic.init.InitBlocks; 5 | import net.minecraft.client.Minecraft; 6 | import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer; 7 | import net.minecraft.world.item.BlockItem; 8 | import net.minecraft.world.item.Item; 9 | import net.minecraftforge.client.extensions.common.IClientItemExtensions; 10 | 11 | import java.util.function.Consumer; 12 | 13 | public class ItemMusicPlayer extends BlockItem { 14 | public ItemMusicPlayer() { 15 | super(InitBlocks.MUSIC_PLAYER.get(), (new Item.Properties())); 16 | } 17 | 18 | @Override 19 | public void initializeClient(Consumer consumer) { 20 | consumer.accept(new IClientItemExtensions() { 21 | @Override 22 | public BlockEntityWithoutLevelRenderer getCustomRenderer() { 23 | Minecraft minecraft = Minecraft.getInstance(); 24 | return new MusicPlayerItemRenderer(minecraft.getBlockEntityRenderDispatcher(), minecraft.getEntityModels()); 25 | } 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/network/NetworkHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.network; 2 | 3 | import com.github.tartaricacid.netmusic.NetMusic; 4 | import com.github.tartaricacid.netmusic.compat.tlm.init.CompatRegistry; 5 | import com.github.tartaricacid.netmusic.network.message.GetMusicListMessage; 6 | import com.github.tartaricacid.netmusic.network.message.MusicToClientMessage; 7 | import com.github.tartaricacid.netmusic.network.message.SetMusicIDMessage; 8 | import net.minecraft.core.BlockPos; 9 | import net.minecraft.resources.ResourceLocation; 10 | import net.minecraft.server.level.ServerLevel; 11 | import net.minecraft.server.level.ServerPlayer; 12 | import net.minecraft.world.level.ChunkPos; 13 | import net.minecraft.world.level.Level; 14 | import net.minecraftforge.network.NetworkDirection; 15 | import net.minecraftforge.network.NetworkRegistry; 16 | import net.minecraftforge.network.PacketDistributor; 17 | import net.minecraftforge.network.simple.SimpleChannel; 18 | 19 | import java.util.Optional; 20 | 21 | public class NetworkHandler { 22 | private static final String VERSION = "1.0.0"; 23 | 24 | public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel(new ResourceLocation(NetMusic.MOD_ID, "network"), 25 | () -> VERSION, it -> it.equals(VERSION), it -> it.equals(VERSION)); 26 | 27 | public static void init() { 28 | CHANNEL.registerMessage(0, MusicToClientMessage.class, MusicToClientMessage::encode, MusicToClientMessage::decode, MusicToClientMessage::handle, 29 | Optional.of(NetworkDirection.PLAY_TO_CLIENT)); 30 | CHANNEL.registerMessage(1, GetMusicListMessage.class, GetMusicListMessage::encode, GetMusicListMessage::decode, GetMusicListMessage::handle, 31 | Optional.of(NetworkDirection.PLAY_TO_CLIENT)); 32 | CHANNEL.registerMessage(2, SetMusicIDMessage.class, SetMusicIDMessage::encode, SetMusicIDMessage::decode, SetMusicIDMessage::handle, 33 | Optional.of(NetworkDirection.PLAY_TO_SERVER)); 34 | CompatRegistry.initNetwork(CHANNEL); 35 | } 36 | 37 | public static void sendToNearby(Level world, BlockPos pos, Object toSend) { 38 | if (world instanceof ServerLevel) { 39 | ServerLevel ws = (ServerLevel) world; 40 | 41 | ws.getChunkSource().chunkMap.getPlayers(new ChunkPos(pos), false).stream() 42 | .filter(p -> p.distanceToSqr(pos.getX(), pos.getY(), pos.getZ()) < 96 * 96) 43 | .forEach(p -> CHANNEL.send(PacketDistributor.PLAYER.with(() -> p), toSend)); 44 | } 45 | } 46 | 47 | public static void sendToClientPlayer(Object message, ServerPlayer player) { 48 | CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), message); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/network/message/GetMusicListMessage.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.network.message; 2 | 3 | import com.github.tartaricacid.netmusic.client.config.MusicListManage; 4 | import net.minecraft.ChatFormatting; 5 | import net.minecraft.client.Minecraft; 6 | import net.minecraft.client.player.LocalPlayer; 7 | import net.minecraft.network.FriendlyByteBuf; 8 | import net.minecraft.network.chat.Component; 9 | import net.minecraftforge.network.NetworkEvent; 10 | 11 | import java.util.function.Supplier; 12 | 13 | public class GetMusicListMessage { 14 | public static final long RELOAD_MESSAGE = -1; 15 | private final long musicListId; 16 | 17 | public GetMusicListMessage(long musicListId) { 18 | this.musicListId = musicListId; 19 | } 20 | 21 | public static GetMusicListMessage decode(FriendlyByteBuf buf) { 22 | return new GetMusicListMessage(buf.readLong()); 23 | } 24 | 25 | public static void encode(GetMusicListMessage message, FriendlyByteBuf buf) { 26 | buf.writeLong(message.musicListId); 27 | } 28 | 29 | public static void handle(GetMusicListMessage message, Supplier contextSupplier) { 30 | NetworkEvent.Context context = contextSupplier.get(); 31 | if (context.getDirection().getReceptionSide().isClient()) { 32 | context.enqueueWork(() -> { 33 | LocalPlayer player = Minecraft.getInstance().player; 34 | try { 35 | if (message.musicListId == RELOAD_MESSAGE) { 36 | MusicListManage.loadConfigSongs(); 37 | if (player != null) { 38 | player.sendSystemMessage(Component.translatable("command.netmusic.music_cd.reload.success")); 39 | } 40 | } else { 41 | MusicListManage.add163List(message.musicListId); 42 | if (player != null) { 43 | player.sendSystemMessage(Component.translatable("command.netmusic.music_cd.add163.success")); 44 | } 45 | } 46 | } catch (Exception e) { 47 | if (player != null) { 48 | player.sendSystemMessage(Component.translatable("command.netmusic.music_cd.add163.fail").withStyle(ChatFormatting.RED)); 49 | } 50 | e.printStackTrace(); 51 | } 52 | }); 53 | } 54 | context.setPacketHandled(true); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/network/message/MusicToClientMessage.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.network.message; 2 | 3 | import com.github.tartaricacid.netmusic.client.audio.MusicPlayManager; 4 | import com.github.tartaricacid.netmusic.client.audio.NetMusicSound; 5 | import net.minecraft.Util; 6 | import net.minecraft.core.BlockPos; 7 | import net.minecraft.network.FriendlyByteBuf; 8 | import net.minecraftforge.api.distmarker.Dist; 9 | import net.minecraftforge.api.distmarker.OnlyIn; 10 | import net.minecraftforge.network.NetworkEvent; 11 | 12 | import java.util.concurrent.CompletableFuture; 13 | import java.util.function.Supplier; 14 | 15 | public class MusicToClientMessage { 16 | private final BlockPos pos; 17 | private final String url; 18 | private final int timeSecond; 19 | private final String songName; 20 | 21 | public MusicToClientMessage(BlockPos pos, String url, int timeSecond, String songName) { 22 | this.pos = pos; 23 | this.url = url; 24 | this.timeSecond = timeSecond; 25 | this.songName = songName; 26 | } 27 | 28 | public static MusicToClientMessage decode(FriendlyByteBuf buf) { 29 | return new MusicToClientMessage(BlockPos.of(buf.readLong()), buf.readUtf(), buf.readInt(), buf.readUtf()); 30 | } 31 | 32 | public static void encode(MusicToClientMessage message, FriendlyByteBuf buf) { 33 | buf.writeLong(message.pos.asLong()); 34 | buf.writeUtf(message.url); 35 | buf.writeInt(message.timeSecond); 36 | buf.writeUtf(message.songName); 37 | } 38 | 39 | public static void handle(MusicToClientMessage message, Supplier contextSupplier) { 40 | NetworkEvent.Context context = contextSupplier.get(); 41 | if (context.getDirection().getReceptionSide().isClient()) { 42 | context.enqueueWork(() -> CompletableFuture.runAsync(() -> onHandle(message), Util.backgroundExecutor())); 43 | } 44 | context.setPacketHandled(true); 45 | } 46 | 47 | @OnlyIn(Dist.CLIENT) 48 | private static void onHandle(MusicToClientMessage message) { 49 | MusicPlayManager.play(message.url, message.songName, url -> new NetMusicSound(message.pos, url, message.timeSecond)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/network/message/SetMusicIDMessage.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.network.message; 2 | 3 | import com.github.tartaricacid.netmusic.inventory.CDBurnerMenu; 4 | import com.github.tartaricacid.netmusic.inventory.ComputerMenu; 5 | import com.github.tartaricacid.netmusic.item.ItemMusicCD; 6 | import net.minecraft.nbt.CompoundTag; 7 | import net.minecraft.network.FriendlyByteBuf; 8 | import net.minecraft.server.level.ServerPlayer; 9 | import net.minecraftforge.network.NetworkEvent; 10 | 11 | import java.util.function.Supplier; 12 | 13 | public class SetMusicIDMessage { 14 | private final ItemMusicCD.SongInfo song; 15 | 16 | public SetMusicIDMessage(ItemMusicCD.SongInfo song) { 17 | this.song = song; 18 | } 19 | 20 | public static SetMusicIDMessage decode(FriendlyByteBuf buf) { 21 | CompoundTag tag = buf.readNbt(); 22 | ItemMusicCD.SongInfo songData = ItemMusicCD.SongInfo.deserializeNBT(tag); 23 | return new SetMusicIDMessage(songData); 24 | } 25 | 26 | public static void encode(SetMusicIDMessage message, FriendlyByteBuf buf) { 27 | CompoundTag tag = new CompoundTag(); 28 | ItemMusicCD.SongInfo.serializeNBT(message.song, tag); 29 | buf.writeNbt(tag); 30 | } 31 | 32 | public static void handle(SetMusicIDMessage message, Supplier contextSupplier) { 33 | NetworkEvent.Context context = contextSupplier.get(); 34 | if (context.getDirection().getReceptionSide().isServer()) { 35 | context.enqueueWork(() -> { 36 | ServerPlayer sender = context.getSender(); 37 | if (sender == null) { 38 | return; 39 | } 40 | if (sender.containerMenu instanceof CDBurnerMenu menu) { 41 | menu.setSongInfo(message.song); 42 | return; 43 | } 44 | if (sender.containerMenu instanceof ComputerMenu menu) { 45 | menu.setSongInfo(message.song); 46 | } 47 | }); 48 | } 49 | context.setPacketHandled(true); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/github/tartaricacid/netmusic/tileentity/TileEntityMusicPlayer.java: -------------------------------------------------------------------------------- 1 | package com.github.tartaricacid.netmusic.tileentity; 2 | 3 | import com.github.tartaricacid.netmusic.block.BlockMusicPlayer; 4 | import com.github.tartaricacid.netmusic.init.InitBlocks; 5 | import com.github.tartaricacid.netmusic.inventory.MusicPlayerInv; 6 | import com.github.tartaricacid.netmusic.item.ItemMusicCD; 7 | import com.github.tartaricacid.netmusic.network.NetworkHandler; 8 | import com.github.tartaricacid.netmusic.network.message.MusicToClientMessage; 9 | import net.minecraft.core.BlockPos; 10 | import net.minecraft.core.Direction; 11 | import net.minecraft.nbt.CompoundTag; 12 | import net.minecraft.network.protocol.Packet; 13 | import net.minecraft.network.protocol.game.ClientGamePacketListener; 14 | import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; 15 | import net.minecraft.world.item.ItemStack; 16 | import net.minecraft.world.level.Level; 17 | import net.minecraft.world.level.block.Block; 18 | import net.minecraft.world.level.block.entity.BlockEntity; 19 | import net.minecraft.world.level.block.entity.BlockEntityType; 20 | import net.minecraft.world.level.block.state.BlockState; 21 | import net.minecraftforge.common.capabilities.Capability; 22 | import net.minecraftforge.common.capabilities.ForgeCapabilities; 23 | import net.minecraftforge.common.util.LazyOptional; 24 | import net.minecraftforge.items.IItemHandler; 25 | import net.minecraftforge.items.ItemStackHandler; 26 | import org.jetbrains.annotations.NotNull; 27 | 28 | import javax.annotation.Nullable; 29 | 30 | import static com.github.tartaricacid.netmusic.block.BlockMusicPlayer.CYCLE_DISABLE; 31 | 32 | public class TileEntityMusicPlayer extends BlockEntity { 33 | public static final BlockEntityType TYPE = BlockEntityType.Builder.of(TileEntityMusicPlayer::new, InitBlocks.MUSIC_PLAYER.get()).build(null); 34 | private static final String CD_ITEM_TAG = "ItemStackCD"; 35 | private static final String IS_PLAY_TAG = "IsPlay"; 36 | private static final String CURRENT_TIME_TAG = "CurrentTime"; 37 | private static final String SIGNAL_TAG = "RedStoneSignal"; 38 | private final ItemStackHandler playerInv = new MusicPlayerInv(this); 39 | private LazyOptional playerInvHandler; 40 | private boolean isPlay = false; 41 | private int currentTime; 42 | private boolean hasSignal = false; 43 | 44 | public TileEntityMusicPlayer(BlockPos blockPos, BlockState blockState) { 45 | super(TYPE, blockPos, blockState); 46 | } 47 | 48 | @Override 49 | public void saveAdditional(CompoundTag compound) { 50 | getPersistentData().put(CD_ITEM_TAG, playerInv.serializeNBT()); 51 | getPersistentData().putBoolean(IS_PLAY_TAG, isPlay); 52 | getPersistentData().putInt(CURRENT_TIME_TAG, currentTime); 53 | getPersistentData().putBoolean(SIGNAL_TAG, hasSignal); 54 | super.saveAdditional(compound); 55 | } 56 | 57 | @Override 58 | public void load(CompoundTag nbt) { 59 | super.load(nbt); 60 | playerInv.deserializeNBT(getPersistentData().getCompound(CD_ITEM_TAG)); 61 | isPlay = getPersistentData().getBoolean(IS_PLAY_TAG); 62 | currentTime = getPersistentData().getInt(CURRENT_TIME_TAG); 63 | hasSignal = getPersistentData().getBoolean(SIGNAL_TAG); 64 | } 65 | 66 | @Override 67 | public CompoundTag getUpdateTag() { 68 | return this.saveWithoutMetadata(); 69 | } 70 | 71 | @Nullable 72 | @Override 73 | public Packet getUpdatePacket() { 74 | return ClientboundBlockEntityDataPacket.create(this); 75 | } 76 | 77 | public ItemStackHandler getPlayerInv() { 78 | return playerInv; 79 | } 80 | 81 | @Override 82 | public @NotNull LazyOptional getCapability(@NotNull Capability cap, @Nullable Direction side) { 83 | if (!this.remove && cap == ForgeCapabilities.ITEM_HANDLER) { 84 | if (this.playerInvHandler == null) { 85 | this.playerInvHandler = LazyOptional.of(this::createHandler); 86 | } 87 | return this.playerInvHandler.cast(); 88 | } 89 | return super.getCapability(cap, side); 90 | } 91 | 92 | @Override 93 | public void setBlockState(BlockState blockState) { 94 | super.setBlockState(blockState); 95 | if (this.playerInvHandler != null) { 96 | LazyOptional oldHandler = this.playerInvHandler; 97 | this.playerInvHandler = null; 98 | oldHandler.invalidate(); 99 | } 100 | } 101 | 102 | private IItemHandler createHandler() { 103 | BlockState state = this.getBlockState(); 104 | if (state.getBlock() instanceof BlockMusicPlayer) { 105 | return this.playerInv; 106 | } 107 | return null; 108 | } 109 | 110 | public boolean isPlay() { 111 | return isPlay; 112 | } 113 | 114 | public void setPlay(boolean play) { 115 | isPlay = play; 116 | } 117 | 118 | public void setPlayToClient(ItemMusicCD.SongInfo info) { 119 | this.setCurrentTime(info.songTime * 20 + 64); 120 | this.isPlay = true; 121 | if (level != null && !level.isClientSide) { 122 | MusicToClientMessage msg = new MusicToClientMessage(worldPosition, info.songUrl, info.songTime, info.songName); 123 | NetworkHandler.sendToNearby(level, worldPosition, msg); 124 | } 125 | } 126 | 127 | public void markDirty() { 128 | this.setChanged(); 129 | if (level != null) { 130 | BlockState state = level.getBlockState(worldPosition); 131 | level.sendBlockUpdated(worldPosition, state, state, Block.UPDATE_ALL); 132 | } 133 | } 134 | 135 | @Override 136 | public void invalidateCaps() { 137 | super.invalidateCaps(); 138 | if (playerInvHandler != null) { 139 | playerInvHandler.invalidate(); 140 | playerInvHandler = null; 141 | } 142 | } 143 | 144 | public void setCurrentTime(int time) { 145 | this.currentTime = time; 146 | } 147 | 148 | public int getCurrentTime() { 149 | return currentTime; 150 | } 151 | 152 | public boolean hasSignal() { 153 | return hasSignal; 154 | } 155 | 156 | public void setSignal(boolean signal) { 157 | this.hasSignal = signal; 158 | } 159 | 160 | public void tickTime() { 161 | if (currentTime > 0) { 162 | currentTime--; 163 | } 164 | } 165 | 166 | public static void tick(Level level, BlockPos blockPos, BlockState blockState, TileEntityMusicPlayer te) { 167 | te.tickTime(); 168 | if (0 < te.getCurrentTime() && te.getCurrentTime() < 16 && te.getCurrentTime() % 5 == 0) { 169 | if (blockState.getValue(CYCLE_DISABLE)) { 170 | te.setPlay(false); 171 | te.markDirty(); 172 | } else { 173 | ItemStack stackInSlot = te.getPlayerInv().getStackInSlot(0); 174 | if (stackInSlot.isEmpty()) { 175 | return; 176 | } 177 | ItemMusicCD.SongInfo songInfo = ItemMusicCD.getSongInfo(stackInSlot); 178 | if (songInfo != null) { 179 | te.setPlayToClient(songInfo); 180 | } 181 | } 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/accesstransformer.cfg: -------------------------------------------------------------------------------- 1 | public net.minecraft.client.sounds.SoundManager f_120349_ # soundEngine 2 | public net.minecraft.client.sounds.SoundEngine f_120226_ # instanceToChannel -------------------------------------------------------------------------------- /src/main/resources/META-INF/mods.toml: -------------------------------------------------------------------------------- 1 | modLoader = "javafml" 2 | loaderVersion = "[46,)" 3 | license = "MIT" 4 | [[mods]] 5 | modId = "netmusic" 6 | version = "${file.jarVersion}" 7 | displayName = "Net Music Mod" 8 | authors = "TartaricAcid, 郝南村, 琥珀酸, 帕金伊, SQwatermark" 9 | description = "A music player mod" 10 | 11 | [[dependencies.netmusic]] 12 | modId = "forge" 13 | mandatory = true 14 | versionRange = "[46,)" 15 | ordering = "NONE" 16 | side = "BOTH" 17 | 18 | [[dependencies.netmusic]] 19 | modId = "minecraft" 20 | mandatory = true 21 | versionRange = "[1.20,1.20.2)" 22 | ordering = "NONE" 23 | side = "BOTH" 24 | 25 | [[dependencies.netmusic]] 26 | modId = "touhou_little_maid" 27 | mandatory = false 28 | versionRange = "[1.3.0,)" 29 | ordering = "AFTER" 30 | side = "BOTH" 31 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/javax.sound.sampled.spi.AudioFileReader: -------------------------------------------------------------------------------- 1 | # Providers of Speex sound file-reading services 2 | com.github.tartaricacid.netmusic.client.audio.MpegAudioFileReader 3 | com.github.tartaricacid.netmusic.client.audio.FlacAudioFileReader -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/javax.sound.sampled.spi.FormatConversionProvider: -------------------------------------------------------------------------------- 1 | # for the javalayer mp3 decoder 2 | com.github.tartaricacid.netmusic.client.audio.MpegFormatConversionProvider 3 | com.github.tartaricacid.netmusic.client.audio.FlacFormatConversionProvider -------------------------------------------------------------------------------- /src/main/resources/assets/minecraft/atlases/blocks.json: -------------------------------------------------------------------------------- 1 | { 2 | "sources": [ 3 | { 4 | "type": "single", 5 | "resource": "netmusic:slot/music_cd_slot" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/blockstates/cd_burner.json: -------------------------------------------------------------------------------- 1 | { 2 | "variants": { 3 | "facing=north": { 4 | "model": "netmusic:block/cd_burner" 5 | }, 6 | "facing=south": { 7 | "model": "netmusic:block/cd_burner", 8 | "y": 180 9 | }, 10 | "facing=west": { 11 | "model": "netmusic:block/cd_burner", 12 | "y": 270 13 | }, 14 | "facing=east": { 15 | "model": "netmusic:block/cd_burner", 16 | "y": 90 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/blockstates/computer.json: -------------------------------------------------------------------------------- 1 | { 2 | "variants": { 3 | "facing=north": { 4 | "model": "netmusic:block/computer" 5 | }, 6 | "facing=south": { 7 | "model": "netmusic:block/computer", 8 | "y": 180 9 | }, 10 | "facing=west": { 11 | "model": "netmusic:block/computer", 12 | "y": 270 13 | }, 14 | "facing=east": { 15 | "model": "netmusic:block/computer", 16 | "y": 90 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/blockstates/music_player.json: -------------------------------------------------------------------------------- 1 | { 2 | "variants": { 3 | "facing=north": { 4 | "model": "netmusic:block/music_player" 5 | }, 6 | "facing=south": { 7 | "model": "netmusic:block/music_player", 8 | "y": 180 9 | }, 10 | "facing=west": { 11 | "model": "netmusic:block/music_player", 12 | "y": 270 13 | }, 14 | "facing=east": { 15 | "model": "netmusic:block/music_player", 16 | "y": 90 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "itemGroup.netmusic": "Net Music", 3 | "item.netmusic.music_cd": "Music CD", 4 | "item.netmusic.music_player_backpack": "Music Player Backpack", 5 | "tooltips.netmusic.cd.trans_name": "Name", 6 | "tooltips.netmusic.cd.artists": "Artists", 7 | "tooltips.netmusic.cd.time": "Time", 8 | "tooltips.netmusic.cd.time.format": "%smin%ss", 9 | "tooltips.netmusic.cd.empty": "Empty", 10 | "tooltips.netmusic.cd.read_only": "[Read Only]", 11 | "block.netmusic.music_player": "Music Player", 12 | "block.netmusic.cd_burner": "CD Burner", 13 | "block.netmusic.cd_burner.desc": "Only NetEase Cloud Music links can be written (Only available in Chinese mainland)", 14 | "block.netmusic.computer": "Computer", 15 | "block.netmusic.computer.web_link.desc": "You can use any network link, Only supports mp3 and flac formats", 16 | "block.netmusic.computer.local_file.desc": "You can also use the local file path, but then only you can hear the music", 17 | "message.netmusic.music_player.need_vip": "This song is a VIP song and cannot be played", 18 | "message.netmusic.music_player.404": "Unable to find song: %s", 19 | "subtitle.netmusic.block.music_player": "Music Player Sound", 20 | "command.netmusic.music_cd.reload.success": "All CD is reloaded!", 21 | "command.netmusic.music_cd.add163.success": "Add NetEase music list success!", 22 | "command.netmusic.music_cd.add163.fail": "Add NetEase music list fail! Please check if the playlist ID is mistyped?", 23 | "command.netmusic.music_cd.add163cd.success": "Add NetEase music success!", 24 | "command.netmusic.music_cd.add163cd.fail": "Add NetEase music fail! Please check if the music ID is mistyped?", 25 | "gui.netmusic.cd_burner.craft": "Craft CD", 26 | "gui.netmusic.cd_burner.read_only": "Read Only", 27 | "gui.netmusic.cd_burner.id.tips": "Enter the song ID", 28 | "gui.netmusic.cd_burner.no_music_id": "The song ID cannot be empty, please enter the song ID as a number!", 29 | "gui.netmusic.cd_burner.music_id_error": "The song ID format is incorrect, please enter the song ID as a number!", 30 | "gui.netmusic.cd_burner.get_info_error": "The song information cannot be obtained, please check the song ID or network", 31 | "gui.netmusic.cd_burner.cd_read_only": "The CD is read only!", 32 | "gui.netmusic.cd_burner.cd_is_empty": "Please put the CD in the input slot!", 33 | "gui.netmusic.maid.music_player_backpack.previous": "Previous Music", 34 | "gui.netmusic.maid.music_player_backpack.next": "Next Music", 35 | "gui.netmusic.maid.music_player_backpack.play": "Play", 36 | "gui.netmusic.maid.music_player_backpack.stop": "Stop", 37 | "gui.netmusic.computer.url.tips": "Enter the song url", 38 | "gui.netmusic.computer.url.empty": "The song URL cannot be empty", 39 | "gui.netmusic.computer.url.error": "The song URL format is incorrect, please enter a web link or local file path", 40 | "gui.netmusic.computer.url.local_file_error": "The local file does not exist", 41 | "gui.netmusic.computer.name.tips": "Enter the song name", 42 | "gui.netmusic.computer.name.empty": "The song name cannot be empty", 43 | "gui.netmusic.computer.time.tips": "Time (s)", 44 | "gui.netmusic.computer.time.empty": "The song time cannot be empty", 45 | "gui.netmusic.computer.time.not_number": "The song time format is incorrect, please enter the song time as a number!", 46 | "jei.netmusic.altar_craft.item_craft.result": "Craft Item" 47 | } -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/lang/zh_cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "block.netmusic.cd_burner": "唱片刻录机", 3 | "block.netmusic.cd_burner.desc": "只可以写入网易云音乐链接(仅可在中国大陆可用)", 4 | "block.netmusic.computer": "电脑", 5 | "block.netmusic.computer.local_file.desc": "你还可以使用本地文件路径,但这样就只有你自己能听到音乐", 6 | "block.netmusic.computer.web_link.desc": "你可以输入任意网络链接,只支持 mp3 和 flac 格式", 7 | "block.netmusic.music_player": "唱片机", 8 | "command.netmusic.music_cd.add163.fail": "添加网易云歌单失败!请检查歌单 ID 是否错误?", 9 | "command.netmusic.music_cd.add163.success": "添加网易云歌单成功!", 10 | "command.netmusic.music_cd.add163cd.fail": "添加网易云单曲失败!请检查歌曲 ID 是否错误?", 11 | "command.netmusic.music_cd.add163cd.success": "添加网易云单曲成功!", 12 | "command.netmusic.music_cd.reload.success": "所有的唱片重载了!", 13 | "gui.netmusic.cd_burner.cd_is_empty": "请在输入格放入唱片", 14 | "gui.netmusic.cd_burner.cd_read_only": "这个唱片是只读的!", 15 | "gui.netmusic.cd_burner.craft": "制作唱片", 16 | "gui.netmusic.cd_burner.get_info_error": "无法获取歌曲信息,请检查歌曲ID或者网络!", 17 | "gui.netmusic.cd_burner.id.tips": "输入歌曲 ID", 18 | "gui.netmusic.cd_burner.music_id_error": "歌曲ID必须为数字,请输入数字形式的歌曲ID!", 19 | "gui.netmusic.cd_burner.no_music_id": "歌曲ID不能为空,请输入数字形式的歌曲ID!", 20 | "gui.netmusic.cd_burner.read_only": "只读", 21 | "gui.netmusic.computer.name.empty": "歌曲名不能为空", 22 | "gui.netmusic.computer.name.tips": "输入歌曲名", 23 | "gui.netmusic.computer.time.empty": "歌曲时长不能为空", 24 | "gui.netmusic.computer.time.not_number": "歌曲时长格式不正确,请输入数字!", 25 | "gui.netmusic.computer.time.tips": "时长(秒)", 26 | "gui.netmusic.computer.url.empty": "歌曲URL不能为空", 27 | "gui.netmusic.computer.url.error": "歌曲URL格式不正确,请输入网络链接或者本地文件路径", 28 | "gui.netmusic.computer.url.local_file_error": "本地文件不存在", 29 | "gui.netmusic.computer.url.tips": "输入歌曲 URL", 30 | "gui.netmusic.maid.music_player_backpack.next": "下一首", 31 | "gui.netmusic.maid.music_player_backpack.play": "播放", 32 | "gui.netmusic.maid.music_player_backpack.previous": "上一首", 33 | "gui.netmusic.maid.music_player_backpack.stop": "停止", 34 | "item.netmusic.music_cd": "音乐唱片", 35 | "item.netmusic.music_player_backpack": "音响背包", 36 | "itemGroup.netmusic": "网络音乐机", 37 | "message.netmusic.music_player.need_vip": "这首歌曲是 VIP 歌曲,无法播放", 38 | "message.netmusic.music_player.404": "无法找到链接为 %s 的歌曲", 39 | "subtitle.netmusic.block.music_player": "唱片机声", 40 | "tooltips.netmusic.cd.artists": "歌手", 41 | "tooltips.netmusic.cd.empty": "空", 42 | "tooltips.netmusic.cd.read_only": "[只读]", 43 | "tooltips.netmusic.cd.time": "时长", 44 | "tooltips.netmusic.cd.time.format": "%s分%s秒", 45 | "tooltips.netmusic.cd.trans_name": "歌名", 46 | "jei.netmusic.altar_craft.item_craft.result": "合成物品" 47 | } -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/models/block/computer.json: -------------------------------------------------------------------------------- 1 | { 2 | "credit": "Made with Blockbench", 3 | "textures": { 4 | "0": "netmusic:block/computer", 5 | "particle": "netmusic:block/computer" 6 | }, 7 | "elements": [ 8 | { 9 | "from": [0, 0, 6.5], 10 | "to": [16, 5, 16], 11 | "rotation": {"angle": 0, "axis": "y", "origin": [0, 0.6, 0]}, 12 | "faces": { 13 | "north": {"uv": [7, 3, 11, 4.25], "texture": "#0"}, 14 | "east": {"uv": [5.25, 10.5, 7.75, 11.75], "texture": "#0"}, 15 | "south": {"uv": [8, 0, 12, 1.25], "texture": "#0"}, 16 | "west": {"uv": [10.75, 5.75, 13.25, 7], "texture": "#0"}, 17 | "up": {"uv": [4, 2.5, 0, 0], "texture": "#0"}, 18 | "down": {"uv": [4, 2.5, 0, 5], "texture": "#0"} 19 | } 20 | }, 21 | { 22 | "from": [3, 5, 6.5], 23 | "to": [13, 6, 14], 24 | "rotation": {"angle": 0, "axis": "y", "origin": [0, 0.6, -1]}, 25 | "faces": { 26 | "north": {"uv": [8, 2.75, 10.5, 3], "texture": "#0"}, 27 | "east": {"uv": [7, 4.75, 9, 5], "texture": "#0"}, 28 | "south": {"uv": [10.75, 7.5, 13.25, 7.75], "texture": "#0"}, 29 | "west": {"uv": [12, 1, 14, 1.25], "texture": "#0"}, 30 | "up": {"uv": [2.5, 9, 0, 7], "texture": "#0"}, 31 | "down": {"uv": [5, 8.5, 2.5, 10.5], "texture": "#0"} 32 | } 33 | }, 34 | { 35 | "from": [2, 6, 8.5], 36 | "to": [14, 13.5, 15], 37 | "rotation": {"angle": 0, "axis": "y", "origin": [0, 0.6, -1]}, 38 | "faces": { 39 | "north": {"uv": [4, 3, 7, 5], "texture": "#0"}, 40 | "east": {"uv": [0, 9, 1.75, 11], "texture": "#0"}, 41 | "south": {"uv": [0, 5, 3, 7], "texture": "#0"}, 42 | "west": {"uv": [9, 5.75, 10.75, 7.75], "texture": "#0"}, 43 | "up": {"uv": [6, 6.75, 3, 5], "texture": "#0"}, 44 | "down": {"uv": [9, 5, 6, 6.75], "texture": "#0"} 45 | } 46 | }, 47 | { 48 | "from": [2.001, 8.97308, 7.60034], 49 | "to": [13.999, 14.97108, 14.09834], 50 | "rotation": {"angle": 22.5, "axis": "x", "origin": [8, 11.97208, 10.84934]}, 51 | "faces": { 52 | "north": {"uv": [8, 1.25, 11, 2.75], "texture": "#0"}, 53 | "east": {"uv": [7.75, 10.5, 9.5, 12], "texture": "#0"}, 54 | "south": {"uv": [9, 4.25, 12, 5.75], "texture": "#0"}, 55 | "west": {"uv": [9.5, 10.75, 11.25, 12.25], "texture": "#0"}, 56 | "up": {"uv": [6, 8.5, 3, 6.75], "texture": "#0"}, 57 | "down": {"uv": [9, 6.75, 6, 8.5], "texture": "#0"} 58 | } 59 | }, 60 | { 61 | "from": [3, 7, 6.5], 62 | "to": [13, 15, 9.5], 63 | "rotation": {"angle": 0, "axis": "y", "origin": [0, 0.6, 0]}, 64 | "faces": { 65 | "north": {"uv": [5, 8.5, 7.5, 10.5], "texture": "#0"}, 66 | "east": {"uv": [7.75, 12, 8.5, 14], "texture": "#0"}, 67 | "south": {"uv": [7.5, 8.5, 10, 10.5], "texture": "#0"}, 68 | "west": {"uv": [8.5, 12, 9.25, 14], "texture": "#0"}, 69 | "up": {"uv": [13.75, 11.5, 11.25, 10.75], "texture": "#0"}, 70 | "down": {"uv": [3.5, 11.5, 1, 12.25], "texture": "#0"} 71 | } 72 | }, 73 | { 74 | "from": [1, 5, 5.5], 75 | "to": [15, 7, 9.5], 76 | "rotation": {"angle": 0, "axis": "y", "origin": [0, 0.6, 0]}, 77 | "faces": { 78 | "north": {"uv": [11, 3.75, 14.5, 4.25], "texture": "#0"}, 79 | "east": {"uv": [9, 7.75, 10, 8.25], "texture": "#0"}, 80 | "south": {"uv": [11.25, 11.5, 14.75, 12], "texture": "#0"}, 81 | "west": {"uv": [12, 4.25, 13, 4.75], "texture": "#0"}, 82 | "up": {"uv": [13.5, 8.75, 10, 7.75], "texture": "#0"}, 83 | "down": {"uv": [13.5, 8.75, 10, 9.75], "texture": "#0"} 84 | } 85 | }, 86 | { 87 | "from": [1, 15, 5.5], 88 | "to": [15, 17, 9.5], 89 | "rotation": {"angle": 0, "axis": "y", "origin": [0, 0.6, 0]}, 90 | "faces": { 91 | "north": {"uv": [3.5, 11.75, 7, 12.25], "texture": "#0"}, 92 | "east": {"uv": [12, 4.75, 13, 5.25], "texture": "#0"}, 93 | "south": {"uv": [12, 0, 15.5, 0.5], "texture": "#0"}, 94 | "west": {"uv": [12, 5.25, 13, 5.75], "texture": "#0"}, 95 | "up": {"uv": [13.5, 10.75, 10, 9.75], "texture": "#0"}, 96 | "down": {"uv": [5.25, 10.5, 1.75, 11.5], "texture": "#0"} 97 | } 98 | }, 99 | { 100 | "from": [13, 7, 5.5], 101 | "to": [15, 15, 9.5], 102 | "rotation": {"angle": 0, "axis": "y", "origin": [0, 0.6, 0]}, 103 | "faces": { 104 | "north": {"uv": [5, 12.25, 5.5, 14.25], "texture": "#0"}, 105 | "east": {"uv": [3, 12.25, 4, 14.25], "texture": "#0"}, 106 | "south": {"uv": [5.5, 12.25, 6, 14.25], "texture": "#0"}, 107 | "west": {"uv": [4, 12.25, 5, 14.25], "texture": "#0"}, 108 | "up": {"uv": [6.5, 13.25, 6, 12.25], "texture": "#0"}, 109 | "down": {"uv": [7, 12.25, 6.5, 13.25], "texture": "#0"} 110 | } 111 | }, 112 | { 113 | "from": [1, 7, 5.5], 114 | "to": [3, 15, 9.5], 115 | "rotation": {"angle": 0, "axis": "y", "origin": [16, 0.6, 0]}, 116 | "faces": { 117 | "north": {"uv": [1, 12.25, 1.5, 14.25], "texture": "#0"}, 118 | "east": {"uv": [0, 11, 1, 13], "texture": "#0"}, 119 | "south": {"uv": [1.5, 12.25, 2, 14.25], "texture": "#0"}, 120 | "west": {"uv": [11, 1.75, 12, 3.75], "texture": "#0"}, 121 | "up": {"uv": [3, 8, 2.5, 7], "texture": "#0"}, 122 | "down": {"uv": [2.25, 9, 1.75, 10], "texture": "#0"} 123 | } 124 | }, 125 | { 126 | "from": [0, 1, 0.5], 127 | "to": [16, 3, 6], 128 | "rotation": {"angle": -22.5, "axis": "x", "origin": [8, 1.5, 3.25]}, 129 | "faces": { 130 | "north": {"uv": [10.75, 7, 14.75, 7.5], "texture": "#0"}, 131 | "east": {"uv": [7, 4.25, 8.5, 4.75], "texture": "#0"}, 132 | "south": {"uv": [11, 1.25, 15, 1.75], "texture": "#0"}, 133 | "west": {"uv": [12, 0.5, 13.5, 1], "texture": "#0"}, 134 | "up": {"uv": [8, 1.5, 4, 0], "texture": "#0"}, 135 | "down": {"uv": [8, 1.5, 4, 3], "texture": "#0"} 136 | } 137 | } 138 | ], 139 | "display": { 140 | "thirdperson_righthand": { 141 | "rotation": [45, 0, 0], 142 | "translation": [0, 0.5, -0.5], 143 | "scale": [0.375, 0.375, 0.375] 144 | }, 145 | "thirdperson_lefthand": { 146 | "rotation": [45, 0, 0], 147 | "translation": [0, 0.5, -0.5], 148 | "scale": [0.375, 0.375, 0.375] 149 | }, 150 | "firstperson_righthand": { 151 | "rotation": [0, 135, 0], 152 | "translation": [1.75, 1.75, 1.75], 153 | "scale": [0.35, 0.35, 0.35] 154 | }, 155 | "firstperson_lefthand": { 156 | "rotation": [0, 135, 0], 157 | "translation": [1.75, 1.75, 1.75], 158 | "scale": [0.35, 0.35, 0.35] 159 | }, 160 | "ground": { 161 | "translation": [0, 3, 0], 162 | "scale": [0.5, 0.5, 0.5] 163 | }, 164 | "gui": { 165 | "rotation": [10, 145, 0], 166 | "translation": [0, -0.75, 0], 167 | "scale": [0.6, 0.6, 0.6] 168 | }, 169 | "head": { 170 | "translation": [0, 12.5, 0] 171 | }, 172 | "fixed": { 173 | "translation": [0, -1, -1.75], 174 | "scale": [0.75, 0.75, 0.75] 175 | } 176 | } 177 | } -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/models/block/music_player.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": "builtin/entity", 3 | "textures": { 4 | "particle": "netmusic:block/music_player" 5 | } 6 | } -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/models/item/cd_burner.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": "netmusic:block/cd_burner" 3 | } -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/models/item/computer.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": "netmusic:block/computer" 3 | } -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/models/item/music_cd.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": "item/generated", 3 | "textures": { 4 | "layer0": "netmusic:item/music_cd" 5 | } 6 | } -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/models/item/music_player.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": "builtin/entity", 3 | "display": { 4 | "gui": { 5 | "rotation": [22.5, 22.5, 0], 6 | "translation": [0, -2.5, 0], 7 | "scale": [0.5, 0.5, 0.5] 8 | }, 9 | "ground": { 10 | "rotation": [0, 0, 0], 11 | "translation": [-2.75, 2, -1.75], 12 | "scale": [0.375, 0.375, 0.375] 13 | }, 14 | "head": { 15 | "rotation": [0, 180, 0], 16 | "translation": [2.75, 8, 1.75], 17 | "scale": [0.5, 0.5, 0.5] 18 | }, 19 | "thirdperson_righthand": { 20 | "rotation": [45, -115, 0], 21 | "translation": [0, -0.5, -0.5], 22 | "scale": [0.375, 0.375, 0.375] 23 | }, 24 | "firstperson_righthand": { 25 | "rotation": [0, -45, 30], 26 | "translation": [1.75, 1.75, 1.75], 27 | "scale": [0.25, 0.25, 0.25] 28 | }, 29 | "fixed": { 30 | "translation": [2, -2.25, 0], 31 | "rotation": [0, 180, 0], 32 | "scale": [0.4, 0.4, 0.4] 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/models/item/music_player_backpack.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": "item/generated", 3 | "textures": { 4 | "layer0": "netmusic:item/music_player_backpack" 5 | } 6 | } -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/music.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "url": "https://music.163.com/song/media/outer/url?id=510265273.mp3", 4 | "name": "Ceddin Deden", 5 | "time_second": 130, 6 | "trans_name": "祖父,先祖", 7 | "vip": false, 8 | "read_only": true, 9 | "artists": [ 10 | "Mansur Yeşildağ" 11 | ] 12 | }, 13 | { 14 | "url": "https://music.163.com/song/media/outer/url?id=420664.mp3", 15 | "name": "神々Cガール", 16 | "time_second": 214, 17 | "trans_name": "神是中学生", 18 | "vip": false, 19 | "artists": [ 20 | "池頼広" 21 | ] 22 | }, 23 | { 24 | "url": "https://music.163.com/song/media/outer/url?id=41645488.mp3", 25 | "name": "ちいさな冒険者", 26 | "time_second": 198, 27 | "trans_name": "小小冒险者", 28 | "vip": false, 29 | "artists": [ 30 | "雨宮天", 31 | "高橋李依", 32 | "茅野愛衣" 33 | ] 34 | }, 35 | { 36 | "url": "https://music.163.com/song/media/outer/url?id=464740274.mp3", 37 | "name": "Londonderry Air (arr. F. Kreisler)", 38 | "time_second": 221, 39 | "trans_name": "伦敦德里小调(F·克莱斯勒改编)", 40 | "vip": false, 41 | "artists": [ 42 | "Fritz Kreisler", 43 | "Franz Rupp" 44 | ] 45 | }, 46 | { 47 | "url": "https://music.163.com/song/media/outer/url?id=420693.mp3", 48 | "name": "あの夏の坂道", 49 | "time_second": 116, 50 | "trans_name": "那年夏天的坂道", 51 | "vip": false, 52 | "artists": [ 53 | "池頼広" 54 | ] 55 | }, 56 | { 57 | "url": "https://music.163.com/song/media/outer/url?id=28566360.mp3", 58 | "name": "Que Sera Sera", 59 | "time_second": 206, 60 | "trans_name": "", 61 | "vip": false, 62 | "artists": [ 63 | "手嶌葵" 64 | ] 65 | }, 66 | { 67 | "url": "https://music.163.com/song/media/outer/url?id=473403148.mp3", 68 | "name": "It's a HORI style!", 69 | "time_second": 269, 70 | "trans_name": "", 71 | "vip": false, 72 | "artists": [ 73 | "Famishin", 74 | "Angel Note" 75 | ] 76 | }, 77 | { 78 | "url": "https://music.163.com/song/media/outer/url?id=27514630.mp3", 79 | "name": "夜明け", 80 | "time_second": 125, 81 | "trans_name": "", 82 | "vip": false, 83 | "artists": [ 84 | "菊谷知樹" 85 | ] 86 | }, 87 | { 88 | "url": "https://music.163.com/song/media/outer/url?id=740611.mp3", 89 | "name": "明日の朝には", 90 | "time_second": 182, 91 | "trans_name": "明日清晨", 92 | "vip": false, 93 | "artists": [ 94 | "ハンバート ハンバート" 95 | ] 96 | }, 97 | { 98 | "url": "https://music.163.com/song/media/outer/url?id=487598857.mp3", 99 | "name": "きみの窓から", 100 | "time_second": 232, 101 | "trans_name": "", 102 | "vip": false, 103 | "artists": [ 104 | "桑田貴子" 105 | ] 106 | }, 107 | { 108 | "url": "https://music.163.com/song/media/outer/url?id=5104264.mp3", 109 | "name": "Only Sixteen", 110 | "time_second": 132, 111 | "trans_name": "", 112 | "vip": false, 113 | "artists": [ 114 | "Craig Douglas" 115 | ] 116 | }, 117 | { 118 | "url": "https://music.163.com/song/media/outer/url?id=37132083.mp3", 119 | "name": "ホシボシノマホウ", 120 | "time_second": 151, 121 | "trans_name": "", 122 | "vip": false, 123 | "artists": [ 124 | "スタジオネネム" 125 | ] 126 | }, 127 | { 128 | "url": "https://music.163.com/song/media/outer/url?id=1397003602.mp3", 129 | "name": "南屏晚钟 (吉他弹唱)", 130 | "time_second": 217, 131 | "trans_name": "", 132 | "vip": false, 133 | "artists": [ 134 | "猫二" 135 | ] 136 | }, 137 | { 138 | "url": "https://music.163.com/song/media/outer/url?id=537690736.mp3", 139 | "name": "500 Miles", 140 | "time_second": 193, 141 | "trans_name": "", 142 | "vip": false, 143 | "artists": [ 144 | "The Journeymen" 145 | ] 146 | }, 147 | { 148 | "url": "https://music.163.com/song/media/outer/url?id=776502.mp3", 149 | "name": "年中无休のいたずらフェアリー", 150 | "time_second": 206, 151 | "trans_name": "", 152 | "vip": false, 153 | "artists": [ 154 | "Lunatic Locus" 155 | ] 156 | } 157 | ] -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/sounds.json: -------------------------------------------------------------------------------- 1 | { 2 | "net_music": { 3 | "category": "record", 4 | "subtitle": "subtitle.netmusic.block.music_player", 5 | "sounds": [ 6 | { 7 | "name": "random/orb", 8 | "stream": true 9 | } 10 | ] 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/textures/block/cd_burner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TartaricAcid/NetMusic/468fc9929b37ca82cfdf11bea4aafbb64668114b/src/main/resources/assets/netmusic/textures/block/cd_burner.png -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/textures/block/computer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TartaricAcid/NetMusic/468fc9929b37ca82cfdf11bea4aafbb64668114b/src/main/resources/assets/netmusic/textures/block/computer.png -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/textures/block/music_player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TartaricAcid/NetMusic/468fc9929b37ca82cfdf11bea4aafbb64668114b/src/main/resources/assets/netmusic/textures/block/music_player.png -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/textures/entity/music_player_backpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TartaricAcid/NetMusic/468fc9929b37ca82cfdf11bea4aafbb64668114b/src/main/resources/assets/netmusic/textures/entity/music_player_backpack.png -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/textures/gui/cd_burner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TartaricAcid/NetMusic/468fc9929b37ca82cfdf11bea4aafbb64668114b/src/main/resources/assets/netmusic/textures/gui/cd_burner.png -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/textures/gui/computer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TartaricAcid/NetMusic/468fc9929b37ca82cfdf11bea4aafbb64668114b/src/main/resources/assets/netmusic/textures/gui/computer.png -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/textures/gui/maid_music_player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TartaricAcid/NetMusic/468fc9929b37ca82cfdf11bea4aafbb64668114b/src/main/resources/assets/netmusic/textures/gui/maid_music_player.png -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/textures/item/music_cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TartaricAcid/NetMusic/468fc9929b37ca82cfdf11bea4aafbb64668114b/src/main/resources/assets/netmusic/textures/item/music_cd.png -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/textures/item/music_player_backpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TartaricAcid/NetMusic/468fc9929b37ca82cfdf11bea4aafbb64668114b/src/main/resources/assets/netmusic/textures/item/music_player_backpack.png -------------------------------------------------------------------------------- /src/main/resources/assets/netmusic/textures/slot/music_cd_slot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TartaricAcid/NetMusic/468fc9929b37ca82cfdf11bea4aafbb64668114b/src/main/resources/assets/netmusic/textures/slot/music_cd_slot.png -------------------------------------------------------------------------------- /src/main/resources/data/netmusic/loot_tables/blocks/cd_burner.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "minecraft:block", 3 | "pools": [ 4 | { 5 | "rolls": 1, 6 | "entries": [ 7 | { 8 | "type": "minecraft:item", 9 | "name": "netmusic:cd_burner" 10 | } 11 | ], 12 | "conditions": [ 13 | { 14 | "condition": "minecraft:survives_explosion" 15 | } 16 | ] 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /src/main/resources/data/netmusic/loot_tables/blocks/computer.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "minecraft:block", 3 | "pools": [ 4 | { 5 | "rolls": 1, 6 | "entries": [ 7 | { 8 | "type": "minecraft:item", 9 | "name": "netmusic:computer" 10 | } 11 | ], 12 | "conditions": [ 13 | { 14 | "condition": "minecraft:survives_explosion" 15 | } 16 | ] 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /src/main/resources/data/netmusic/loot_tables/blocks/music_player.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "minecraft:block", 3 | "pools": [ 4 | { 5 | "rolls": 1, 6 | "entries": [ 7 | { 8 | "type": "minecraft:item", 9 | "name": "netmusic:music_player" 10 | } 11 | ], 12 | "conditions": [ 13 | { 14 | "condition": "minecraft:survives_explosion" 15 | } 16 | ] 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /src/main/resources/data/netmusic/recipes/cd_burner.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "minecraft:crafting_shapeless", 3 | "result": { 4 | "item": "netmusic:cd_burner" 5 | }, 6 | "ingredients": [ 7 | { 8 | "item": "minecraft:jukebox" 9 | }, 10 | { 11 | "tag": "forge:ingots/iron" 12 | }, 13 | { 14 | "tag": "forge:dusts/redstone" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /src/main/resources/data/netmusic/recipes/computer.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "minecraft:crafting_shapeless", 3 | "result": { 4 | "item": "netmusic:computer" 5 | }, 6 | "ingredients": [ 7 | { 8 | "item": "minecraft:jukebox" 9 | }, 10 | { 11 | "tag": "forge:ingots/gold" 12 | }, 13 | { 14 | "tag": "forge:dusts/redstone" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /src/main/resources/data/netmusic/recipes/music_cd.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "minecraft:crafting_shapeless", 3 | "result": { 4 | "item": "netmusic:music_cd" 5 | }, 6 | "ingredients": [ 7 | { 8 | "tag": "forge:dyes/gray" 9 | }, 10 | { 11 | "tag": "forge:dyes/brown" 12 | }, 13 | { 14 | "item": "minecraft:clay_ball" 15 | }, 16 | { 17 | "item": "minecraft:clay_ball" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /src/main/resources/data/netmusic/recipes/music_player.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "minecraft:crafting_shaped", 3 | "result": { 4 | "item": "netmusic:music_player" 5 | }, 6 | "pattern": [ 7 | "OBO", 8 | "ODO", 9 | "OOO" 10 | ], 11 | "key": { 12 | "B": { 13 | "item": "minecraft:book" 14 | }, 15 | "O": { 16 | "tag": "minecraft:planks" 17 | }, 18 | "D": { 19 | "tag": "forge:gems/diamond" 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/resources/data/netmusic/recipes/tlm/music_player_backpack.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "touhou_little_maid:altar_crafting", 3 | "output": { 4 | "type": "minecraft:item", 5 | "nbt": { 6 | "Item": { 7 | "id": "netmusic:music_player_backpack", 8 | "Count": 1 9 | } 10 | } 11 | }, 12 | "power": 0.2, 13 | "ingredients": [ 14 | { 15 | "item": "touhou_little_maid:maid_backpack_middle" 16 | }, 17 | { 18 | "item": "netmusic:music_player" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "net music resources", 4 | "pack_format": 15 5 | } 6 | } 7 | --------------------------------------------------------------------------------