diff --git a/.github/workflows/build.yml b/.github/workflows/buildAndRelease.yml similarity index 59% rename from .github/workflows/build.yml rename to .github/workflows/buildAndRelease.yml index c05a066..c71ef9e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/buildAndRelease.yml @@ -26,14 +26,44 @@ jobs: - name: Make gradlew executable run: chmod +x ./gradlew + - name: Run Forge data generation + run: | + echo "=== 运行 Forge 数据生成 ===" + ./gradlew runData --no-daemon + + continue-on-error: false + - name: Build with Gradle - run: ./gradlew build + run: ./gradlew build --no-daemon - name: Prepare release files run: | mkdir -p release-files - cp build/libs/*.jar release-files/ 2>/dev/null || true - echo "准备发布的文件:" + + # 收集所有模块的构建产物 + echo "=== 收集 common 模块构建产物 ===" + if [ -d "common/build/libs" ]; then + cp common/build/libs/*.jar release-files/ 2>/dev/null || echo "common 模块没有 jar 文件" + fi + + echo "=== 收集 fabric 模块构建产物 ===" + if [ -d "fabric/build/libs" ]; then + cp fabric/build/libs/*-dev.jar release-files/ 2>/dev/null || true # 排除dev jar + cp fabric/build/libs/*-sources.jar release-files/ 2>/dev/null || true + cp fabric/build/libs/*-javadoc.jar release-files/ 2>/dev/null || true + # 只复制主jar(没有sources/javadoc/dev classifier的jar) + find fabric/build/libs -name "*.jar" ! -name "*-sources.jar" ! -name "*-javadoc.jar" ! -name "*-dev.jar" -exec cp {} release-files/ \; + fi + + echo "=== 收集 forge 模块构建产物 ===" + if [ -d "forge/build/libs" ]; then + cp forge/build/libs/*-sources.jar release-files/ 2>/dev/null || true + cp forge/build/libs/*-javadoc.jar release-files/ 2>/dev/null || true + # 只复制主jar(没有sources/javadoc classifier的jar) + find forge/build/libs -name "*.jar" ! -name "*-sources.jar" ! -name "*-javadoc.jar" -exec cp {} release-files/ \; + fi + + echo "=== 准备发布的文件 ===" ls -la release-files/ - name: Upload release artifacts @@ -60,6 +90,14 @@ jobs: name: release-files path: ./dist + - name: Extract version info + id: version_info + run: | + # 从tag中提取版本号(去掉v前缀) + VERSION="${GITHUB_REF_NAME#v}" + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "minecraft_version=$(grep "^minecraft_version=" gradle.properties | cut -d'=' -f2)" >> $GITHUB_OUTPUT + - name: Generate CZ-compliant changelog id: generate_changelog run: | @@ -177,19 +215,59 @@ jobs: echo "- 发布日期: $(date '+%Y年%m月%d日')" >> $TEMP_FILE echo "- 当前版本: $CURRENT_TAG" >> $TEMP_FILE + echo "- Minecraft版本: ${{ steps.version_info.outputs.minecraft_version }}" >> $TEMP_FILE echo "" >> $TEMP_FILE echo "---" >> $TEMP_FILE echo "" >> $TEMP_FILE + + echo "## 📦 下载文件" >> $TEMP_FILE + echo "" >> $TEMP_FILE + echo "本次发布包含以下平台的构建文件:" >> $TEMP_FILE + echo "" >> $TEMP_FILE + + # 列出所有发布的文件 + echo "### Fabric 版本" >> $TEMP_FILE + echo "\`\`\`" >> $TEMP_FILE + ls -1 dist/*fabric*.jar 2>/dev/null | grep -v "sources\|javadoc" | xargs -n1 basename || echo "无 Fabric 主文件" >> $TEMP_FILE + echo "\`\`\`" >> $TEMP_FILE + echo "" >> $TEMP_FILE + + echo "### Forge 版本" >> $TEMP_FILE + echo "\`\`\`" >> $TEMP_FILE + ls -1 dist/*forge*.jar 2>/dev/null | grep -v "sources\|javadoc" | xargs -n1 basename || echo "无 Forge 主文件" >> $TEMP_FILE + echo "\`\`\`" >> $TEMP_FILE + echo "" >> $TEMP_FILE + + echo "### 通用模块" >> $TEMP_FILE + echo "\`\`\`" >> $TEMP_FILE + ls -1 dist/*common*.jar 2>/dev/null | grep -v "sources\|javadoc" | xargs -n1 basename || echo "无通用模块" >> $TEMP_FILE + echo "\`\`\`" >> $TEMP_FILE + echo "" >> $TEMP_FILE + + echo "### 源码和文档" >> $TEMP_FILE + echo "\`\`\`" >> $TEMP_FILE + ls -1 dist/*-sources.jar 2>/dev/null | xargs -n1 basename || echo "无源码文件" >> $TEMP_FILE + ls -1 dist/*-javadoc.jar 2>/dev/null | xargs -n1 basename || echo "无文档文件" >> $TEMP_FILE + echo "\`\`\`" >> $TEMP_FILE + echo "" >> $TEMP_FILE + echo "### 📜 详细提交历史" >> $TEMP_FILE echo "" >> $TEMP_FILE + echo "
" >> $TEMP_FILE + echo "点击展开查看完整提交历史" >> $TEMP_FILE + echo "" >> $TEMP_FILE + echo "\`\`\`" >> $TEMP_FILE - # 显示所有提交的详细列表,包含scope信息 + # 显示所有提交的详细列表 if [ -z "$PREV_TAG" ]; then - git log --pretty=format:"- **%h** %s - %an (%ad)" --date=short --reverse | head -100 >> $TEMP_FILE + git log --pretty=format:"%h %s - %an (%ad)" --date=short --reverse >> $TEMP_FILE else - git log --pretty=format:"- **%h** %s - %an (%ad)" --date=short $PREV_TAG..HEAD | head -100 >> $TEMP_FILE + git log --pretty=format:"%h %s - %an (%ad)" --date=short $PREV_TAG..HEAD >> $TEMP_FILE fi + echo "\`\`\`" >> $TEMP_FILE + echo "
" >> $TEMP_FILE + # 将文件内容输出到变量 CHANGELOG_CONTENT=$(cat $TEMP_FILE) echo "changelog<> $GITHUB_OUTPUT @@ -199,9 +277,13 @@ jobs: - name: Create Release uses: ncipollo/release-action@v1 with: - artifacts: "dist/*.jar" + artifacts: | + dist/*.jar tag: ${{ github.ref_name }} - name: "版本 ${{ github.ref_name }}" + name: "${{ steps.version_info.outputs.minecraft_version }} - ${{ github.ref_name }}" body: ${{ steps.generate_changelog.outputs.changelog }} draft: false - prerelease: false \ No newline at end of file + prerelease: false + token: ${{ secrets.GITHUB_TOKEN }} + allowUpdates: true + removeArtifacts: true \ No newline at end of file diff --git a/TEMPLATE_LICENSE.txt b/TEMPLATE_LICENSE.txt deleted file mode 100644 index b64bc64..0000000 --- a/TEMPLATE_LICENSE.txt +++ /dev/null @@ -1,24 +0,0 @@ -MIT License - -Copyright (c) 2023 NeoForged project - -This license applies to the template files as supplied by github.com/NeoForged/MDK - - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/build.gradle b/build.gradle index 9c528d7..541422d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,535 +1,4 @@ -//file:noinspection GroovyAssignabilityCheck plugins { - id 'java' - id 'idea' - id 'java-library' - id 'maven-publish' - id 'com.github.johnrengelman.shadow' version '8.1.1' - id 'net.neoforged.moddev.legacyforge' version '2.0.103' - id 'com.dorongold.task-tree' version '2.1.1' -} - -apply from: 'gradle/jni-heads.gradle' - -java { - toolchain.languageVersion = JavaLanguageVersion.of(17) -} - -tasks.named('wrapper', Wrapper).configure { - distributionType = Wrapper.DistributionType.BIN -} - -version = "${minecraft_version}-${mod_version}" -group = mod_group_id - -repositories { - mavenLocal() - maven { url = "https://libraries.minecraft.net/" } - maven { url = "https://neoforged.forgecdn.net/releases" } - maven { url = "https://neoforged.forgecdn.net/mojang-meta" } - maven { url = "https://maven.neoforged.net/releases" } - maven { - name = "LTD Maven" - url = "https://nexus.bot.leisuretimedock.top/repository/maven-public/" - } - flatDir { - dir "libs" - } -} - -base { - archivesName = mod_id -} - -// Mojang ships Java 17 to end users in 1.20.1, so mods should target Java 17. -java.toolchain.languageVersion = JavaLanguageVersion.of(17) - -legacyForge { - // Specify the version of MinecraftForge to use. - version = project.minecraft_version + '-' + project.forge_version - - parchment { - mappingsVersion = project.parchment_mappings_version - minecraftVersion = project.parchment_minecraft_version - } - - // Default run configurations. - runs { - client { - client() - systemProperty 'forge.enabledGameTestNamespaces', project.mod_id - } - clientAuth{ - devLogin = true - client() - systemProperty 'forge.enabledGameTestNamespaces', project.mod_id - } - - server { - server() - programArgument '--nogui' - systemProperty 'forge.enabledGameTestNamespaces', project.mod_id - } - - gameTestServer { - type = "gameTestServer" - systemProperty 'forge.enabledGameTestNamespaces', project.mod_id - } - - data { - data() - programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() - } - - configureEach { - systemProperty 'forge.logging.markers', 'REGISTRIES' - logLevel = org.slf4j.event.Level.DEBUG - } - } - - mods { - "${mod_id}" { - sourceSet(sourceSets.main) - } - } -} - -// Include resources generated by data generators. -sourceSets.main.resources { srcDir 'src/generated/resources' } - -configurations { - runtimeClasspath.extendsFrom localRuntime -} -obfuscation { - createRemappingConfiguration(configurations.localRuntime) -} - -dependencies { - implementation(jarJar("io.github.llamalad7:mixinextras-forge:[0.4.1,)")) - - implementation(annotationProcessor("io.github.llamalad7:mixinextras-common:0.4.1")) - modImplementation(jarJar("io.github.llamalad7:mixinextras-forge:0.4.1")) - modImplementation("blank:carryon-1.20.1:2.1.2.7") - - implementation 'org.joml:joml:1.10.5' - // 单元测试依赖 - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.3' - testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.3' - testImplementation 'org.junit.jupiter:junit-jupiter-params:5.9.3' - - // 断言库 - testImplementation 'org.assertj:assertj-core:3.24.2' - - // Mock库(可选) - testImplementation 'org.mockito:mockito-core:5.3.1' - testImplementation 'org.mockito:mockito-junit-jupiter:5.3.1' - - // 测试工具 - testImplementation 'org.hamcrest:hamcrest:2.2' -} -mixin { - add sourceSets.main, "${mod_id}.refmap.json" - config "${mod_id}.mixins.json" -} - -jar { - manifest.attributes([ - "MixinConfigs": "${mod_id}.mixins.json" - ]) -} - -dependencies { - annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' -} - -test { - useJUnitPlatform() - - // 显示测试输出 - testLogging { - events "passed", "skipped", "failed" - showStandardStreams = true - } - - // 设置类路径 - classpath = sourceSets.test.runtimeClasspath -} - -// This block of code expands all declared replace properties in the specified resource targets. -var generateModMetadata = tasks.register("generateModMetadata", ProcessResources) { - var replaceProperties = [ - minecraft_version : minecraft_version, - minecraft_version_range : minecraft_version_range, - forge_version : forge_version, - forge_version_range : forge_version_range, - loader_version_range : loader_version_range, - mod_id : mod_id, - mod_name : mod_name, - mod_license : mod_license, - mod_version : mod_version, - mod_authors : mod_authors, - mod_description : mod_description, - mod_credits : mod_credits - ] - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - inputs.properties replaceProperties - expand replaceProperties - from "src/main/templates" - into "build/generated/sources/modMetadata" -} - -sourceSets.main.resources.srcDir generateModMetadata -legacyForge.ideSyncTask generateModMetadata - -// ==================== Javadoc 配置 ==================== -javadoc { - options { - encoding = 'UTF-8' - charSet = 'UTF-8' - author = true - version = true - windowTitle = "Lib39 ${project.mod_version} API" - docTitle = "Lib39 ${project.mod_version} API" - memberLevel = JavadocMemberLevel.PROTECTED - links = [ - 'https://docs.oracle.com/javase/8/docs/api/' - ] - addBooleanOption('Xdoclint:none', true) - addBooleanOption('html5', true) - } - - // 确保有源代码可供生成文档 - if (sourceSets.main.allJava.files.any { it.exists() }) { - source = sourceSets.main.allJava - } - classpath = configurations.compileClasspath - exclude '**/test/**' - exclude '**/internal/**' - - // 确保输出目录存在 - doFirst { - destinationDir.mkdirs() - } -} - -tasks.register('javadocJar', Jar) { - archiveFileName = "${mod_id}-${minecraft_version}-${mod_version}-javadoc.jar" - archiveClassifier.set("javadoc") - from tasks.javadoc - dependsOn tasks.javadoc -} - -tasks.register('sourceJar', Jar) { - from(sourceSets.main.allSource) // java - archiveFileName = "${mod_id}-${minecraft_version}-${mod_version}-sources.jar" - archiveClassifier.set("sources") - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - manifest { - attributes([ - 'Specification-Title' : mod_id, - 'Specification-Vendor' : mod_authors, - 'Specification-Version' : '1', - 'Implementation-Title' : project.name, - 'Implementation-Version' : archiveVersion, - 'Implementation-Vendor' : mod_authors, - 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), - 'MixinConfigs' : "${mod_id}.mixins.json" - ]) - } - dependsOn classes -} - - -tasks.named('publish') { - dependsOn build -} - - -// ==================== 发布配置 ==================== -publishing { - publications { - mavenJava(MavenPublication) { - artifactId = mod_id - artifact reobfJar - artifact sourceJar - artifact javadocJar - - pom { - name = 'Lib39' - description = 'Lib39 is a general-purpose dependency library for Minecraft mods.' - url = 'https://github.com/3944Realms/lib39' - - properties = [ - 'minecraft.version': project.minecraft_version, - 'mod.version': project.mod_version, - 'forge.version': project.forge_version, - 'java.version': '17' - ] - - licenses { - license { - name = 'MIT' - url = 'https://raw.githubusercontent.com/3944Realms/lib39/refs/heads/main/LICENSE' - distribution = 'repo' - } - } - - developers { - developer { - id = 'R3944Realms' - name = "${mod_authors}" - email = 'f256198830@hotmail.com' - } - } - - scm { - connection = 'scm:git:https://github.com/3944Realms/lib39.git' - developerConnection = 'scm:git:ssh://git@github.com:3944Realms/lib39.git' - url = 'https://github.com/3944Realms/lib39' - tag = 'main' - } - - issueManagement { - system = 'GitHub' - url = 'https://github.com/3944Realms/lib39/issues' - } - } - } - } - - repositories { - // 本地仓库 - maven { - name = 'local' - url = layout.buildDirectory.dir("repo") - } - - // Nexus 远程仓库 - maven { - name = 'LTDNexus' - url = 'https://nexus.bot.leisuretimedock.top/repository/maven-releases/' - credentials { - username = System.getenv('LTDNexusUsername') ?: '' - password = System.getenv('LTDNexusPassword') ?: '' - } - } - } - -} - -// ==================== 任务配置 ==================== -tasks.withType(JavaCompile).configureEach { - options.encoding = 'UTF-8' - options.compilerArgs += ['-Xlint:unchecked', '-Xlint:deprecation'] -} - -// 配置 Javadoc JAR - 使用标准 javadoc 任务输出 -tasks.named('javadocJar') { - from javadoc.destinationDir -} -// ==================== 验证任务 ==================== -tasks.register('verifyNexusCredentials') { - doLast { - def username = System.getenv('LTDNexusUsername') - def password = System.getenv('LTDNexusPassword') - - // 安全地显示用户名和密码(只显示最后两位) - def displayUsername = username ? "***${username.length() > 2 ? username.substring(username.length() - 2) : '**'}" : 'NOT SET' - def displayPassword = password ? "***${password.length() > 2 ? password.substring(password.length() - 2) : '**'}" : 'NOT SET' - - println "Nexus Username: ${displayUsername}" - println "Nexus Password: ${displayPassword}" - - if (!username || !password) { - throw new GradleException('LTDNexusUsername or LTDNexusPassword environment variables are not set') - } - } -} - -tasks.register('checkPublicationContents') { - doLast { - def publication = publishing.publications.mavenJava - println "=== Publication Details ===" - println "Group: ${publication.groupId}" - println "Artifact: ${publication.artifactId}" - println "Version: ${publication.version}" - println "Artifacts:" - publication.artifacts.each { artifact -> - def file = artifact.file - def exists = file.exists() - println " - ${file.name} (${artifact.classifier ?: 'main'}) - Exists: ${exists}" - - if (!exists) { - throw new GradleException("Publication artifact missing: ${file.absolutePath}") - } - } - } -} - -// ==================== 任务依赖 ==================== -tasks.named('publishMavenJavaPublicationToLTDNexusRepository') { - dependsOn verifyNexusCredentials - dependsOn checkPublicationContents -} - -tasks.withType(PublishToMavenRepository) { - dependsOn assemble - dependsOn javadocJar -} - -// ==================== 便捷任务 ==================== -tasks.register('publishToNexus') { - group = 'publishing' - description = 'Publishes all publications to LTD Nexus' - dependsOn 'publishMavenJavaPublicationToLTDNexusRepository' -} -tasks.named('build') { - dependsOn javadocJar, sourceJar -} - - -tasks.register('publishLocal') { - group = 'publishing' - description = 'Publishes all publications to the local Maven repository' - dependsOn 'publishToMavenLocal' -} - -tasks.register('cleanRepo', Delete) { - delete layout.buildDirectory.dir("repo") -} - -tasks.named('clean') { - dependsOn cleanRepo -} - -// ==================== IDEA 配置 ==================== -idea { - module { - downloadSources = true - downloadJavadoc = true - } -} - -// 禁用模块元数据生成 -tasks.withType(GenerateModuleMetadata) { - enabled = false -} -tasks.register('showTaskTree') { - doLast { - def showTaskDeps - showTaskDeps = { task, prefix = '' -> - println "${prefix}${task.name}" - task.getTaskDependencies().getDependencies(task).each { dep -> - showTaskDeps(dep, prefix + ' ') - } - } - - def targetTask = tasks.findByName('build') - if (targetTask) { - println "构建任务依赖树:" - showTaskDeps(targetTask) - } else { - println "未找到 build 任务" - } - } -} -/**
- build
- ├── check
- │   └── test
- │       ├── compileTestJava
- │       │   ├── classes
- │       │   │   ├── compileJava
- │       │   │   │   └── createMcpToSrg
- │       │   │   │       └── extractSrg
- │       │   │   │           └── downloadMcpConfig
- │       │   │   └── processResources
- │       │   └── compileJava
- │       │       └── createMcpToSrg
- │       │           └── extractSrg
- │       │               └── downloadMcpConfig
- │       ├── testClasses
- │       │   ├── processTestResources
- │       │   └── compileTestJava
- │       │       ├── classes
- │       │       │   ├── compileJava
- │       │       │   │   └── createMcpToSrg
- │       │       │   │       └── extractSrg
- │       │       │   │           └── downloadMcpConfig
- │       │       │   └── processResources
- │       │       └── compileJava
- │       │           └── createMcpToSrg
- │       │               └── extractSrg
- │       │                   └── downloadMcpConfig
- │       ├── classes
- │       │   ├── compileJava
- │       │   │   └── createMcpToSrg
- │       │   │       └── extractSrg
- │       │   │           └── downloadMcpConfig
- │       │   └── processResources
- │       └── compileJava
- │           └── createMcpToSrg
- │               └── extractSrg
- │                   └── downloadMcpConfig
- └── assemble
- ├── reobfJarJar
- │   ├── createMcpToSrg
- │   │   └── extractSrg
- │   │       └── downloadMcpConfig
- │   ├── configureReobfTaskForReobfJarJar
- │   └── proguard
- │       └── jarJar
- │           ├── classes
- │           │   ├── compileJava
- │           │   │   └── createMcpToSrg
- │           │   │       └── extractSrg
- │           │   │           └── downloadMcpConfig
- │           │   └── processResources
- │           ├── compileJava
- │           │   └── createMcpToSrg
- │           │       └── extractSrg
- │           │           └── downloadMcpConfig
- │           └── addMixinsToJarJar
- │               └── compileJava
- │                   └── createMcpToSrg
- │                       └── extractSrg
- │                           └── downloadMcpConfig
- ├── reobfJar
- │   ├── jar
- │   │   ├── classes
- │   │   │   ├── compileJava
- │   │   │   │   └── createMcpToSrg
- │   │   │   │       └── extractSrg
- │   │   │   │           └── downloadMcpConfig
- │   │   │   └── processResources
- │   │   ├── compileJava
- │   │   │   └── createMcpToSrg
- │   │   │       └── extractSrg
- │   │   │           └── downloadMcpConfig
- │   │   └── addMixinsToJar
- │   │       └── compileJava
- │   │           └── createMcpToSrg
- │   │               └── extractSrg
- │   │                   └── downloadMcpConfig
- │   ├── createMcpToSrg
- │   │   └── extractSrg
- │   │       └── downloadMcpConfig
- │   └── configureReobfTaskForReobfJar
- └── jar
- ├── classes
- │   ├── compileJava
- │   │   └── createMcpToSrg
- │   │       └── extractSrg
- │   │           └── downloadMcpConfig
- │   └── processResources
- ├── compileJava
- │   └── createMcpToSrg
- │       └── extractSrg
- │           └── downloadMcpConfig
- └── addMixinsToJar
- └── compileJava
- └── createMcpToSrg
- └── extractSrg
- └── downloadMcpConfig
- 
- */ \ No newline at end of file + id 'fabric-loom' version '1.9-SNAPSHOT' apply(false) + id 'net.neoforged.moddev.legacyforge' version '2.0.103' apply(false) +} \ No newline at end of file diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 0000000..6784052 --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,3 @@ +plugins { + id 'groovy-gradle-plugin' +} diff --git a/buildSrc/src/main/groovy/multiloader-common.gradle b/buildSrc/src/main/groovy/multiloader-common.gradle new file mode 100644 index 0000000..b904f1c --- /dev/null +++ b/buildSrc/src/main/groovy/multiloader-common.gradle @@ -0,0 +1,211 @@ +plugins { + id 'java-library' + id 'maven-publish' +} + +base { + archivesName = "${mod_id}-${project.name}-${minecraft_version}" +} + +java { + toolchain.languageVersion = JavaLanguageVersion.of(java_version) + withSourcesJar() + withJavadocJar() +} + +repositories { + mavenCentral() + + // https://docs.gradle.org/current/userguide/declaring_repositories.html#declaring_content_exclusively_found_in_one_repository + exclusiveContent { + forRepository { + maven { + name = 'Sponge' + url = 'https://repo.spongepowered.org/repository/maven-public' + } + } + filter { includeGroupAndSubgroups('org.spongepowered') } + } + exclusiveContent { + forRepositories( + maven { + name = 'ParchmentMC' + url = 'https://maven.parchmentmc.org/' + }, + maven { url = "https://neoforged.forgecdn.net/releases" }, + maven { url = "https://neoforged.forgecdn.net/mojang-meta" } + ) + filter { includeGroup('org.parchmentmc.data') } + } + maven { url = "https://libraries.minecraft.net/" } + + + maven { + url "https://cursemaven.com" + content { includeGroup "curse.maven" } + } + maven { + name = 'BlameJared' + url = 'https://maven.blamejared.com' + } + + maven { + url "https://maven.blamejared.com/" + } +} + +// Declare capabilities on the outgoing configurations. +// Read more about capabilities here: https://docs.gradle.org/current/userguide/component_capabilities.html#sec:declaring-additional-capabilities-for-a-local-component +['apiElements', 'runtimeElements', 'sourcesElements', 'javadocElements'].each { variant -> + configurations."$variant".outgoing { + capability("$group:${project.name}:$version") + capability("$group:${base.archivesName.get()}:$version") + capability("$group:$mod_id-${project.name}-${minecraft_version}:$version") + capability("$group:$mod_id:$version") + } + publishing.publications.configureEach { + suppressPomMetadataWarningsFor(variant) + } +} + +sourcesJar { + from(rootProject.file('LICENSE')) { + rename { "${it}_${mod_name}" } + } +} + +jar { + from(rootProject.file('LICENSE')) { + rename { "${it}_${mod_name}" } + } + + manifest { + attributes([ + 'Specification-Title' : mod_name, + 'Specification-Vendor' : mod_author, + 'Specification-Version' : project.jar.archiveVersion, + 'Implementation-Title' : project.name, + 'Implementation-Version': project.jar.archiveVersion, + 'Implementation-Vendor' : mod_author, + 'Built-On-Minecraft' : minecraft_version + ]) + } +} + +processResources { + var expandProps = [ + 'version' : version, + 'group' : project.group, //Else we target the task's group. + 'minecraft_version' : minecraft_version, + 'minecraft_version_range' : minecraft_version_range, + 'fabric_version' : fabric_version, + 'fabric_loader_version' : fabric_loader_version, + 'mod_name' : mod_name, + 'mod_author' : mod_author, + 'mod_id' : mod_id, + 'license' : license, + 'description' : project.description, + "forge_version" : forge_version, + "forge_loader_version_range" : forge_loader_version_range, + 'credits' : credits, + 'java_version' : java_version + ] + + var jsonExpandProps = expandProps.collectEntries { + key, value -> [(key): value instanceof String ? value.replace("\n", "\\\\n") : value] + } + + filesMatching(['META-INF/mods.toml']) { + expand expandProps + } + + filesMatching(['pack.mcmeta', 'fabric.mod.json', '*.mixins.json']) { + expand jsonExpandProps + } + + inputs.properties(expandProps) +} + +publishing { + publications { + register('mavenJava', MavenPublication) { + artifactId base.archivesName.get() + from components.java + pom { + name = 'Lib39' + description = 'Lib39 is a general-purpose dependency library for Minecraft mods.' + url = 'https://github.com/3944Realms/lib39' + + properties = [ + 'minecraft.version': project.minecraft_version, + 'mod.version': project.version, + 'forge.version': project.forge_version, + 'java.version': '17' + ] + + licenses { + license { + name = 'MIT' + url = 'https://raw.githubusercontent.com/3944Realms/lib39/refs/heads/main/LICENSE' + distribution = 'repo' + } + } + + developers { + developer { + id = 'R3944Realms' + name = "${mod_author}" + email = 'f256198830@hotmail.com' + } + } + + scm { + connection = 'scm:git:https://github.com/3944Realms/lib39.git' + developerConnection = 'scm:git:ssh://git@github.com:3944Realms/lib39.git' + url = 'https://github.com/3944Realms/lib39' + tag = 'main' + } + + issueManagement { + system = 'GitHub' + url = 'https://github.com/3944Realms/lib39/issues' + } + } + } + } + repositories { + // 本地仓库 + maven { + name = 'local' + url = layout.buildDirectory.dir("repo") + } + // Nexus 远程仓库 + maven { + name = 'LTDNexus' + url = 'https://nexus.bot.leisuretimedock.top/repository/maven-releases/' + credentials { + username = System.getenv('LTDNexusUsername') ?: '' + password = System.getenv('LTDNexusPassword') ?: '' + } + } + } +} + +// ==================== 任务依赖 ==================== + +tasks.withType(PublishToMavenRepository) { + dependsOn assemble + dependsOn javadoc +} + +tasks.named('build') { + dependsOn javadoc, sourcesJar +} + +tasks.register('cleanRepo', Delete) { + delete layout.buildDirectory.dir("repo") +} + +tasks.named('clean') { + dependsOn cleanRepo +} diff --git a/buildSrc/src/main/groovy/multiloader-loader.gradle b/buildSrc/src/main/groovy/multiloader-loader.gradle new file mode 100644 index 0000000..b84af2a --- /dev/null +++ b/buildSrc/src/main/groovy/multiloader-loader.gradle @@ -0,0 +1,50 @@ +plugins { + id 'multiloader-common' +} + +configurations { + commonJava{ + canBeResolved = true + } + commonResources{ + canBeResolved = true + } +} + +dependencies { + compileOnly(project(':common')) { + capabilities { + requireCapability "$group:$mod_id" + } + } + commonJava project(path: ':common', configuration: 'commonJava') + commonResources project(path: ':common', configuration: 'commonResources') +} + +tasks.named('compileJava', JavaCompile) { + dependsOn(configurations.commonJava) + source(configurations.commonJava) +} + +processResources { + dependsOn(configurations.commonResources) + from(configurations.commonResources) +} + +tasks.named('javadoc', Javadoc).configure { + dependsOn(configurations.commonJava) + source(configurations.commonJava) + options.encoding = 'UTF-8' + options.charSet = 'UTF-8' + options.links("https://docs.oracle.com/en/java/javase/17/docs/api/") + options.memberLevel = JavadocMemberLevel.PUBLIC + options.addBooleanOption('Xdoclint:none', true) + options.addStringOption('doctitle', "${mod_id} ${minecraft_version} ${version} Javadoc") +} + +tasks.named('sourcesJar', Jar) { + dependsOn(configurations.commonJava) + from(configurations.commonJava) + dependsOn(configurations.commonResources) + from(configurations.commonResources) +} diff --git a/common/build.gradle b/common/build.gradle new file mode 100644 index 0000000..614a019 --- /dev/null +++ b/common/build.gradle @@ -0,0 +1,43 @@ +plugins { + id 'multiloader-common' + id 'net.neoforged.moddev.legacyforge' +} + +legacyForge { + mcpVersion = minecraft_version + if (file("src/main/resources/META-INF/accesstransformer.cfg").exists()) { + accessTransformers = ["src/main/resources/META-INF/accesstransformer.cfg"] + } + parchment { + minecraftVersion = parchment_minecraft + mappingsVersion = parchment_version + } +} + +dependencies { + compileOnly(group: 'org.spongepowered', name: 'mixin', version: '0.8.5') + implementation(group: 'tschipp.carryon', name: 'carryon-common-1.20.1', version: '2.1.2') { + transitive = false + } + implementation(annotationProcessor("io.github.llamalad7:mixinextras-common:0.2.0")) + implementation(group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.1') +} +configurations { + commonJava { + canBeResolved = false + canBeConsumed = true + } + commonResources { + canBeResolved = false + canBeConsumed = true + } +} + +artifacts { + commonJava sourceSets.main.java.sourceDirectories.singleFile + commonResources sourceSets.main.resources.sourceDirectories.singleFile, file('src/generated/resources') +} + +clean { + delete 'generated' +} diff --git a/common/src/main/java/top/r3944realms/lib39/Lib39.java b/common/src/main/java/top/r3944realms/lib39/Lib39.java new file mode 100644 index 0000000..d224270 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/Lib39.java @@ -0,0 +1,58 @@ +package top.r3944realms.lib39; + +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import top.r3944realms.lib39.platform.Services; + +public class Lib39 { + public static final String MOD_ID = "lib39"; + public static final String MOD_NAME = "3944Realms 's Lib Mod"; + public static final Logger LOGGER = LoggerFactory.getLogger(MOD_NAME); + /** + * The constant ENABLE_EXAMPLES_PROPERTY_KEY. + */ + public static final String ENABLE_EXAMPLES_PROPERTY_KEY = "lib39.enable_examples"; + public static void initialize() { + + } + /** + * Rl resource location. + * + * @param path the path + * @return the resource location + */ + @Contract("_ -> new") + public static @NotNull ResourceLocation rl(String path) { + return new ResourceLocation(Lib39.MOD_ID, path); + } + + /** + * Rl resource location. + * + * @param modId the mod id + * @param path the path + * @return the resource location + */ + @Contract("_, _ -> new") + public static @NotNull ResourceLocation rl(String modId, String path) { + return new ResourceLocation(modId, path); + } + + /** + * Mrl resource location. + * + * @param path the path + * @return the resource location + */ + @Contract("_ -> new") + public static @NotNull ResourceLocation mrl(String path) { + return new ResourceLocation(path); + } + + public static boolean isClientEnvironment() { + return Services.PLATFORM.isClientEnvironment(); + } +} \ No newline at end of file diff --git a/common/src/main/java/top/r3944realms/lib39/content/block/DollBlock.java b/common/src/main/java/top/r3944realms/lib39/content/block/DollBlock.java new file mode 100644 index 0000000..4cfb4b5 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/content/block/DollBlock.java @@ -0,0 +1,235 @@ +package top.r3944realms.lib39.content.block; + +import com.mojang.authlib.GameProfile; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.level.block.state.properties.NoteBlockInstrument; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.level.material.PushReaction; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.lib39.content.block.blockentity.DollBlockEntity; +import top.r3944realms.lib39.content.block.property.DollPose; +import top.r3944realms.lib39.core.register.Lib39BlockEntities; +import top.r3944realms.lib39.core.register.Lib39Items; +import top.r3944realms.lib39.core.register.Lib39SoundEvents; +import top.r3944realms.lib39.util.GameProfileHelper; + +import java.util.List; + +/** + * The type Doll block. + */ +@SuppressWarnings("deprecation") +public class DollBlock extends HorizontalDirectionalBlock implements SimpleWaterloggedBlock, EntityBlock { + private static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; + /** + * The constant POSE. + */ + public static final EnumProperty POSE = EnumProperty.create("pose", DollPose.class); + + private static final VoxelShape DOLL_SHAPE = Block.box(2.0d, 0.0d, 2.0d, 14.0d, 12.0d, 14.0d); + private static final Properties properties = Properties.of() + .instrument(NoteBlockInstrument.BASEDRUM) + .sound(SoundType.WOOL) + .pushReaction(PushReaction.DESTROY) + .strength(0f, 10f) + .noOcclusion(); + + private static final double PARTICLE_OFFSET_RANGE = 0.25; + private static final double PARTICLE_HEIGHT_OFFSET = 1.0; + private static final double PARTICLE_HEIGHT_VARIANCE = 0.2; + private static final float NOTE_COLOR_DIVISOR = 24.0F; + private static final int MAX_NOTE_COLORS = 4; + + private static final float BASE_VOLUME = 1.0f; + private static final float PITCH_VARIANCE = 0.5f; + private static final float BASE_PITCH = 0.75f; + + /** + * Instantiates a new Doll block. + */ + public DollBlock() { + super(properties); + this.registerDefaultState(this.stateDefinition.any().setValue(FACING, Direction.SOUTH) + .setValue(POSE, DollPose.DEFAULT) + .setValue(WATERLOGGED, false)); + } + + @Override + public boolean canBeReplaced(@NotNull BlockState state, @NotNull BlockPlaceContext useContext) { + return false; + } + + @Override + public @NotNull BlockState updateShape(@NotNull BlockState currentState, @NotNull Direction direction, @NotNull BlockState neighborState, + @NotNull LevelAccessor level, @NotNull BlockPos currentPos, @NotNull BlockPos neighborPos) { + if (currentState.getValue(WATERLOGGED)) { + level.scheduleTick(currentPos, Fluids.WATER, Fluids.WATER.getTickDelay(level)); + } + return super.updateShape(currentState, direction, neighborState, level, currentPos, neighborPos); + } + + @Override + public @NotNull FluidState getFluidState(@NotNull BlockState blockState) { + return blockState.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(blockState); + } + + @Override + public @NotNull InteractionResult use(@NotNull BlockState blockState, @NotNull Level level, @NotNull BlockPos blockPos, @NotNull Player player, + @NotNull InteractionHand hand, @NotNull BlockHitResult hitResult) { + if (level instanceof ServerLevel serverLevel) { + // 播放粒子效果 + spawnNoteParticles(serverLevel, blockPos); + // 播放音效 + playDollSound(serverLevel, blockPos); + } + return InteractionResult.SUCCESS; + } + + /** + * 在玩偶位置生成音符粒子效果 + */ + private void spawnNoteParticles(ServerLevel serverLevel, BlockPos blockPos) { + Vec3 particlePosition = calculateParticlePosition(serverLevel, blockPos); + float noteColor = calculateNoteColor(serverLevel); + + serverLevel.sendParticles(ParticleTypes.NOTE, + particlePosition.x(), particlePosition.y(), particlePosition.z(), + 0, noteColor, 0, 0, 1); + } + + /** + * 计算粒子生成位置,添加随机偏移 + */ + private @NotNull Vec3 calculateParticlePosition(@NotNull ServerLevel serverLevel, BlockPos blockPos) { + return Vec3.atBottomCenterOf(blockPos).add( + (serverLevel.getRandom().nextFloat() - 0.5) * PARTICLE_OFFSET_RANGE * 2, + PARTICLE_HEIGHT_OFFSET + serverLevel.getRandom().nextFloat() * PARTICLE_HEIGHT_VARIANCE, + (serverLevel.getRandom().nextFloat() - 0.5) * PARTICLE_OFFSET_RANGE * 2 + ); + } + + /** + * 计算音符粒子的颜色 + */ + private float calculateNoteColor(@NotNull ServerLevel serverLevel) { + return serverLevel.getRandom().nextInt(MAX_NOTE_COLORS) / NOTE_COLOR_DIVISOR; + } + + /** + * 播放玩偶音效 + */ + private void playDollSound(@NotNull ServerLevel serverLevel, BlockPos blockPos) { + float pitch = BASE_PITCH + serverLevel.random.nextFloat() * PITCH_VARIANCE; + serverLevel.playSound(null, blockPos, Lib39SoundEvents.DUCK_TOY.get(), + SoundSource.BLOCKS, BASE_VOLUME, pitch); + } + + @Override + public @Nullable BlockState getStateForPlacement(@NotNull BlockPlaceContext context) { + FluidState fluidState = context.getLevel().getFluidState(context.getClickedPos()); + boolean isWaterlogged = fluidState.getType() == Fluids.WATER; + + return this.defaultBlockState() + .setValue(FACING, context.getHorizontalDirection().getOpposite()) + .setValue(WATERLOGGED, isWaterlogged) + .setValue(POSE, DollPose.DEFAULT); + } + + @Override + public @NotNull VoxelShape getShape(@NotNull BlockState blockState, @NotNull BlockGetter level, @NotNull BlockPos blockPos, @NotNull CollisionContext context) { + return DOLL_SHAPE; + } + + @Override + protected void createBlockStateDefinition(StateDefinition.@NotNull Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(FACING, WATERLOGGED, POSE); + } + + @Nullable + @Override + public BlockEntity newBlockEntity(@NotNull BlockPos blockPos, @NotNull BlockState blockState) { + return Lib39BlockEntities.DOLL_BLOCK_ENTITY.get().create(blockPos, blockState); + } + + @SuppressWarnings("deprecation") + @Override + public @NotNull RenderShape getRenderShape(@NotNull BlockState state) { + return RenderShape.ENTITYBLOCK_ANIMATED; + } + + @Override + public ItemStack getCloneItemStack(BlockGetter level, BlockPos pos, BlockState state) { + ItemStack stack = super.getCloneItemStack(level, pos, state); + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof DollBlockEntity doll) { + GameProfile profile = doll.getOwnerProfile(); + if (profile != null) { + GameProfileHelper.saveProfileToItemStack(stack, profile); + } + } + return stack; + } + + /** + * 最重要的方法:重写掉落逻辑 + */ + @Override + @NotNull + public List getDrops(@NotNull BlockState state, @NotNull LootParams.Builder params) { + // 获取方块实体 + BlockEntity blockEntity = params.getOptionalParameter(LootContextParams.BLOCK_ENTITY); + + if (blockEntity instanceof DollBlockEntity dollEntity) { + List customDrops = getCustomDrops(dollEntity, params); + if (customDrops != null) return customDrops; + } + return super.getDrops(state, params); + } + + /** + * 生成自定义掉落物 + */ + @Nullable + private List getCustomDrops(DollBlockEntity dollEntity, LootParams.Builder params) { + if (params.getOptionalParameter(LootContextParams.THIS_ENTITY) instanceof Player player) { + if (player.isCreative()) { + return List.of(); + } + } + GameProfile profile = dollEntity.getOwnerProfile(); + if (profile != null) { + ItemStack instance = Lib39Items.DOLL.get().getDefaultInstance(); + GameProfileHelper.saveProfileToItemStack(instance, profile); + return List.of(instance); + } + return null; + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/content/block/blockentity/DollBlockEntity.java b/common/src/main/java/top/r3944realms/lib39/content/block/blockentity/DollBlockEntity.java new file mode 100644 index 0000000..3814512 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/content/block/blockentity/DollBlockEntity.java @@ -0,0 +1,98 @@ +package top.r3944realms.lib39.content.block.blockentity; + +import com.mojang.authlib.GameProfile; +import net.minecraft.Util; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.SkullBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.core.register.Lib39BlockEntities; +import top.r3944realms.lib39.util.GameProfileHelper; +import top.r3944realms.lib39.util.nbt.NBTReader; +import top.r3944realms.lib39.util.nbt.NBTWriter; + +import javax.annotation.Nullable; + +/** + * The type Doll block entity. + */ +public class DollBlockEntity extends BlockEntity { + + @Nullable + private GameProfile owner; + + /** + * Instantiates a new Doll block entity. + * + * @param pos the pos + * @param blockState the block state + */ + public DollBlockEntity(BlockPos pos, BlockState blockState) { + super(Lib39BlockEntities.DOLL_BLOCK_ENTITY.get(), pos, blockState); + } + + protected void saveAdditional(@NotNull CompoundTag tag) { + super.saveAdditional(tag); + NBTWriter.of(tag) + .compoundIf(GameProfileHelper.TAG_OWN_PROFILE, owner != null, () -> NbtUtils.writeGameProfile(new CompoundTag(), this.owner)); + } + + public void load(@NotNull CompoundTag tag) { + super.load(tag); + NBTReader.of(tag) + .compound(GameProfileHelper.TAG_OWN_PROFILE, compoundTag -> setOwner(NbtUtils.readGameProfile(compoundTag))); + } + + /** + * Gets owner profile. + * + * @return the owner profile + */ + @Nullable + public GameProfile getOwnerProfile() { + return this.owner; + } + + + public ClientboundBlockEntityDataPacket getUpdatePacket() { + return ClientboundBlockEntityDataPacket.create(this); + } + + public @NotNull CompoundTag getUpdateTag() { + return this.saveWithoutMetadata(); + } + + /** + * Sets owner. + * + * @param owner the owner + */ + public void setOwner(@Nullable GameProfile owner) { + synchronized (this) { + this.owner = owner; + } + + this.updateOwnerProfile(); + } + + /** + * Sets owner. + * + * @param ownerName the owner name + */ + public void setOwner(@Nullable String ownerName) { + setOwner(new GameProfile(Util.NIL_UUID, ownerName)); + } + + private void updateOwnerProfile() { + SkullBlockEntity.updateGameprofile(this.owner, gameProfile -> { + this.owner = gameProfile; + this.setChanged(); + }); + } + +} diff --git a/common/src/main/java/top/r3944realms/lib39/content/block/property/DollPose.java b/common/src/main/java/top/r3944realms/lib39/content/block/property/DollPose.java new file mode 100644 index 0000000..c69b7d7 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/content/block/property/DollPose.java @@ -0,0 +1,28 @@ +package top.r3944realms.lib39.content.block.property; + +import net.minecraft.util.StringRepresentable; +import org.jetbrains.annotations.NotNull; + +/** + * The enum Doll pose. + */ +public enum DollPose implements StringRepresentable { + /** + * Default doll pose. + */ + DEFAULT("default"), + /** + * further support + */ + FURTHER("further"), + ; + private final String name; + DollPose(String name) { + this.name = name; + } + + @Override + public @NotNull String getSerializedName() { + return name; + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/content/item/DollItem.java b/common/src/main/java/top/r3944realms/lib39/content/item/DollItem.java new file mode 100644 index 0000000..c518943 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/content/item/DollItem.java @@ -0,0 +1,40 @@ +package top.r3944realms.lib39.content.item; + +import com.mojang.authlib.GameProfile; +import net.minecraft.network.chat.Component; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.Level; +import net.minecraftforge.client.extensions.common.IClientItemExtensions; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.lib39.client.renderer.item.DollItemRenderer; +import top.r3944realms.lib39.core.register.Lib39Blocks; +import top.r3944realms.lib39.util.GameProfileHelper; + +import java.util.List; + +/** + * The type Doll item. + */ +public class DollItem extends BlockItem { + /** + * Instantiates a new Doll item. + * + * @param properties the properties + */ + public DollItem(Properties properties) { + super(Lib39Blocks.DOLL.get(), properties); + } + + + @Override + public void appendHoverText(@NotNull ItemStack stack, @Nullable Level level, @NotNull List tooltip, @NotNull TooltipFlag flag) { + GameProfile profileFromItemStack = GameProfileHelper.getProfileFromItemStack(stack); + if (profileFromItemStack != null && profileFromItemStack.getName() != null) { + tooltip.add(Component.translatable("tooltip.lib39.content.doll.hover.1", profileFromItemStack.getName())); + } + tooltip.add(Component.translatable("tooltip.lib39.content.doll.hover.2")); + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/register/Lib39BlockEntities.java b/common/src/main/java/top/r3944realms/lib39/core/register/Lib39BlockEntities.java new file mode 100644 index 0000000..1a4397e --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/register/Lib39BlockEntities.java @@ -0,0 +1,17 @@ +package top.r3944realms.lib39.core.register; + +import net.minecraft.world.level.block.entity.BlockEntityType; +import top.r3944realms.lib39.content.block.blockentity.DollBlockEntity; + +import java.util.function.Supplier; + +/** + * The type Lib 39 block entities. + */ +public class Lib39BlockEntities { + /** + * The constant DOLL_BLOCK_ENTITY. + */ + public static Supplier> DOLL_BLOCK_ENTITY; + +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/register/Lib39Blocks.java b/common/src/main/java/top/r3944realms/lib39/core/register/Lib39Blocks.java new file mode 100644 index 0000000..a3d3710 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/register/Lib39Blocks.java @@ -0,0 +1,17 @@ +package top.r3944realms.lib39.core.register; + +import net.minecraft.world.level.block.Block; + +import java.util.function.Supplier; + +/** + * The type Lib 39 blocks. + */ +public class Lib39Blocks { + + /** + * The constant DOLL. + */ + public static Supplier DOLL; + +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/register/Lib39Items.java b/common/src/main/java/top/r3944realms/lib39/core/register/Lib39Items.java new file mode 100644 index 0000000..d65d414 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/register/Lib39Items.java @@ -0,0 +1,15 @@ +package top.r3944realms.lib39.core.register; + +import net.minecraft.world.item.Item; + +import java.util.function.Supplier; + +/** + * The type Ex lib 39 items. + */ +public class Lib39Items { + /** + * The constant DOLL. + */ + public static Supplier DOLL; +} diff --git a/common/src/main/java/top/r3944realms/lib39/core/register/Lib39SoundEvents.java b/common/src/main/java/top/r3944realms/lib39/core/register/Lib39SoundEvents.java new file mode 100644 index 0000000..9116dbe --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/core/register/Lib39SoundEvents.java @@ -0,0 +1,31 @@ +package top.r3944realms.lib39.core.register; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.SoundEvent; +import top.r3944realms.lib39.Lib39; + +import java.util.function.Supplier; + +/** + * The type Lib 39 sound events. + */ +public class Lib39SoundEvents { + /** + * The constant RL_DUCK_TOY. + */ + public static final ResourceLocation RL_DUCK_TOY = Lib39.rl("duck_toy"); + /** + * The constant DUCK_TOY. + */ + public static Supplier DUCK_TOY; + + /** + * Gets sub title translate key. + * + * @param name the name + * @return the sub title translate key + */ + public static String getSubTitleTranslateKey(String name) { + return "sound." + Lib39.MOD_ID + ".subtitle." + name; + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/datagen/provider/LanguageProvider.java b/common/src/main/java/top/r3944realms/lib39/datagen/provider/LanguageProvider.java new file mode 100644 index 0000000..10b7f6d --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/datagen/provider/LanguageProvider.java @@ -0,0 +1,105 @@ +package top.r3944realms.lib39.datagen.provider; + +import com.google.gson.JsonObject; +import java.nio.file.Path; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import net.minecraft.data.CachedOutput; +import net.minecraft.data.DataProvider; +import net.minecraft.data.PackOutput; +import net.minecraft.data.PackOutput.Target; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.level.block.Block; +import org.jetbrains.annotations.NotNull; + +public abstract class LanguageProvider implements DataProvider { + private final Map data = new TreeMap<>(); + private final PackOutput output; + private final String modid; + private final String locale; + + public LanguageProvider(PackOutput output, String modid, String locale) { + this.output = output; + this.modid = modid; + this.locale = locale; + } + + protected abstract void addTranslations(); + + public @NotNull CompletableFuture run(@NotNull CachedOutput cache) { + this.addTranslations(); + return !this.data.isEmpty() ? this.save(cache, this.output.getOutputFolder(Target.RESOURCE_PACK).resolve(this.modid).resolve("lang").resolve(this.locale + ".json")) : CompletableFuture.allOf(); + } + + public @NotNull String getName() { + return "Languages: " + this.locale; + } + + private @NotNull CompletableFuture save(CachedOutput cache, Path target) { + JsonObject json = new JsonObject(); + Objects.requireNonNull(json); + this.data.forEach(json::addProperty); + return DataProvider.saveStable(cache, json, target); + } + + public void addBlock(@NotNull Supplier key, String name) { + this.add(key.get(), name); + } + + public void add(@NotNull Block key, String name) { + this.add(key.getDescriptionId(), name); + } + + public void addItem(@NotNull Supplier key, String name) { + this.add(key.get(), name); + } + + public void add(@NotNull Item key, String name) { + this.add(key.getDescriptionId(), name); + } + + public void addItemStack(@NotNull Supplier key, String name) { + this.add(key.get(), name); + } + + public void add(@NotNull ItemStack key, String name) { + this.add(key.getDescriptionId(), name); + } + + public void addEnchantment(@NotNull Supplier key, String name) { + this.add(key.get(), name); + } + + public void add(@NotNull Enchantment key, String name) { + this.add(key.getDescriptionId(), name); + } + + public void addEffect(@NotNull Supplier key, String name) { + this.add(key.get(), name); + } + + public void add(@NotNull MobEffect key, String name) { + this.add(key.getDescriptionId(), name); + } + + public void addEntityType(@NotNull Supplier> key, String name) { + this.add(key.get(), name); + } + + public void add(@NotNull EntityType key, String name) { + this.add(key.getDescriptionId(), name); + } + + public void add(String key, String value) { + if (this.data.put(key, value) != null) { + throw new IllegalStateException("Duplicate translation key " + key); + } + } +} \ No newline at end of file diff --git a/common/src/main/java/top/r3944realms/lib39/datagen/provider/SimpleLanguageProvider.java b/common/src/main/java/top/r3944realms/lib39/datagen/provider/SimpleLanguageProvider.java new file mode 100644 index 0000000..266ef53 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/datagen/provider/SimpleLanguageProvider.java @@ -0,0 +1,104 @@ +package top.r3944realms.lib39.datagen.provider; + +import net.minecraft.data.PackOutput; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.datagen.value.ILangKeyValue; +import top.r3944realms.lib39.datagen.value.ILangKeyValueCollection; +import top.r3944realms.lib39.datagen.value.McLocale; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The type Simple language provider. + */ +public class SimpleLanguageProvider extends LanguageProvider { + private final McLocale language; + private final ILangKeyValueCollection langKeyValueCollection; + @Nullable + private ILangKeyValueCollection[] langKeyValueCollections; + private final Map translationMap; // Better naming + private final List orderedKeys; // Better naming than "objects" + + /** + * Instantiates a new Simple language provider. + * + * @param output the output + * @param modId the mod id + * @param language the language + * @param langKeyValueCollection the lang key value collection + */ + public SimpleLanguageProvider(PackOutput output, String modId, + @NotNull McLocale language, + ILangKeyValueCollection langKeyValueCollection) { + super(output, modId, language.mcCode()); + this.language = language; + this.langKeyValueCollection = langKeyValueCollection; + this.translationMap = new HashMap<>(); + this.orderedKeys = new ArrayList<>(); + initializeTranslations(); + } + + /** + * Instantiates a new Simple language provider. + * + * @param output the output + * @param modId the mod id + * @param language the language + * @param langKeyValueCollection the lang key value collection + */ + public SimpleLanguageProvider(PackOutput output, String modId, + @NotNull McLocale language, + ILangKeyValueCollection... langKeyValueCollection) { + super(output, modId, language.mcCode()); + this.language = language; + this.langKeyValueCollection = null; + this.langKeyValueCollections = langKeyValueCollection; + this.translationMap = new HashMap<>(); + this.orderedKeys = new ArrayList<>(); + initializeTranslations(); + } + + private void initializeTranslations() { + if (langKeyValueCollection != null) { + addToTranslationMap(langKeyValueCollection); + } else if (langKeyValueCollections != null) { + for (ILangKeyValueCollection keyValueCollection : langKeyValueCollections) { + if (keyValueCollection != null) { + addToTranslationMap(keyValueCollection); + } + } + } + } + + private void addToTranslationMap(ILangKeyValueCollection keyValueCollection) { + for (ILangKeyValue langKeyValue : keyValueCollection.getValues()) { + String key = langKeyValue.getKey(); + String value = langKeyValue.getLang(language); + + if (!translationMap.containsKey(key)) { + orderedKeys.add(key); + } + translationMap.put(key, value); + } + } + + @Override + protected void addTranslations() { + orderedKeys.forEach(key -> add(key, translationMap.get(key))); + validateTranslations(); + } + + private void validateTranslations() { + long addedCount = orderedKeys.stream() + .filter(translationMap::containsKey) + .count(); + + Lib39.LOGGER.info("Added {}/{} translations for {}", + addedCount, orderedKeys.size(), language.mcCode()); + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/datagen/value/ILangKeyValue.java b/common/src/main/java/top/r3944realms/lib39/datagen/value/ILangKeyValue.java new file mode 100644 index 0000000..241aaa1 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/datagen/value/ILangKeyValue.java @@ -0,0 +1,22 @@ +package top.r3944realms.lib39.datagen.value; + +/** + * The interface Lang key value. + */ +public interface ILangKeyValue { + /** + * Gets key. + * + * @return the key + */ + String getKey(); + + /** + * Gets lang. + * + * @param locale the locale + * @return the lang + */ + String getLang(McLocale locale); + +} diff --git a/common/src/main/java/top/r3944realms/lib39/datagen/value/ILangKeyValueCollection.java b/common/src/main/java/top/r3944realms/lib39/datagen/value/ILangKeyValueCollection.java new file mode 100644 index 0000000..4785e4d --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/datagen/value/ILangKeyValueCollection.java @@ -0,0 +1,30 @@ +package top.r3944realms.lib39.datagen.value; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * The interface Lang key value collection. + */ +public interface ILangKeyValueCollection { + + /** + * Gets values. + * + * @return the values + */ + List getValues(); + + /** + * Gets lang. + * + * @param locale the locale + * @param key the key + * @return the lang + */ + static String getLang(McLocale locale, @NotNull ILangKeyValue key) { + return key.getLang(locale); + } + +} diff --git a/common/src/main/java/top/r3944realms/lib39/datagen/value/ILocaleEntry.java b/common/src/main/java/top/r3944realms/lib39/datagen/value/ILocaleEntry.java new file mode 100644 index 0000000..ea4a83b --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/datagen/value/ILocaleEntry.java @@ -0,0 +1,22 @@ +package top.r3944realms.lib39.datagen.value; + +import java.util.Locale; + +/** + * The interface Locale entry. + */ +public interface ILocaleEntry { + /** + * Mc code string. + * + * @return the string + */ + String mcCode(); + + /** + * Java locale locale. + * + * @return the locale + */ + Locale javaLocale(); +} diff --git a/common/src/main/java/top/r3944realms/lib39/datagen/value/LangKeyValue.java b/common/src/main/java/top/r3944realms/lib39/datagen/value/LangKeyValue.java new file mode 100644 index 0000000..84c7769 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/datagen/value/LangKeyValue.java @@ -0,0 +1,617 @@ +package top.r3944realms.lib39.datagen.value; + +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.function.Supplier; + +/** + * The type Lang key value. + */ +@SuppressWarnings("unused") +public class LangKeyValue implements ILangKeyValue { + /** + * The Supplier. + */ + protected final Supplier supplier; + /** + * The Key. + */ + protected final String key; + /** + * The Us en. + */ + protected final String US_EN; + /** + * The Sim cn. + */ + protected final String SIM_CN; + /** + * The Tra cn. + */ + protected final String TRA_CN; + /** + * The Lzh. + */ + protected final String LZH; + /** + * The Default. + */ + protected final Boolean Default; + /** + * The Mpe. + */ + protected final ModPartEnum MPE; + + /** + * Instantiates a new Lang key value. + * + * @param builder the builder + */ + protected LangKeyValue(Builder builder) { + this.supplier = builder.supplier; + this.key = builder.key; + this.MPE = builder.MPE; + this.US_EN = builder.US_EN; + this.SIM_CN = builder.SIM_CN; + this.TRA_CN = builder.TRA_CN; + this.LZH = builder.LZH; + this.Default = builder.Default; + } + + /** + * Builder for LangKeyValue + */ + public static class Builder { + private Supplier supplier; + private String key; + private ModPartEnum MPE; + private String US_EN; + private String SIM_CN; + private String TRA_CN; + private String LZH; + private Boolean Default = false; + + /** + * Set supplier + * + * @param supplier the supplier + * @return the builder + */ + @Contract("_ -> this") + public Builder supplier(Supplier supplier) { + this.supplier = supplier; + return this; + } + + /** + * Set key + * + * @param key the key + * @return the builder + */ + @Contract("_ -> this") + public Builder key(String key) { + this.key = key; + return this; + } + + /** + * Set mod part enum + * + * @param MPE the mpe + * @return the builder + */ + @Contract("_ -> this") + public Builder MPE(ModPartEnum MPE) { + this.MPE = MPE; + return this; + } + + /** + * Set US English translation + * + * @param US_EN the us en + * @return the builder + */ + @Contract("_ -> this") + public Builder US_EN(String US_EN) { + this.US_EN = US_EN; + return this; + } + + /** + * Set Simplified Chinese translation + * + * @param SIM_CN the sim cn + * @return the builder + */ + @Contract("_ -> this") + public Builder SIM_CN(String SIM_CN) { + this.SIM_CN = SIM_CN; + return this; + } + + /** + * Set Traditional Chinese translation + * + * @param TRA_CN the tra cn + * @return the builder + */ + @Contract("_ -> this") + public Builder TRA_CN(String TRA_CN) { + this.TRA_CN = TRA_CN; + return this; + } + + /** + * Set Literary Chinese translation + * + * @param LZH the lzh + * @return the builder + */ + @Contract("_ -> this") + public Builder LZH(String LZH) { + this.LZH = LZH; + return this; + } + + /** + * Set as default + * + * @param isDefault the is default + * @return the builder + */ + @Contract("_ -> this") + public Builder isDefault(Boolean isDefault) { + this.Default = isDefault; + return this; + } + + /** + * Build the LangKeyValue instance + * + * @return the lang key value + */ + @NotNull + public LangKeyValue build() { + // Validate required fields + if (MPE == null) { + throw new IllegalStateException("MPE (ModPartEnum) is required"); + } + if (US_EN == null) { + throw new IllegalStateException("US_EN translation is required"); + } + if (SIM_CN == null) { + throw new IllegalStateException("SIM_CN translation is required"); + } + if (TRA_CN == null) { + throw new IllegalStateException("TRA_CN translation is required"); + } + // Either supplier or key must be provided, but not both + if (supplier == null && key == null) { + throw new IllegalStateException("Either supplier or key must be provided"); + } + if (supplier != null && key != null) { + throw new IllegalStateException("Cannot provide both supplier and key"); + } + return new LangKeyValue(this); + } + } + + /** + * Create a new builder instance + * + * @return the builder + */ + @Contract(" -> new") + public static @NotNull Builder builder() { + return new Builder(); + } + + /** + * Create builder with supplier + * + * @param supplier the supplier + * @return the builder + */ + @Contract("_ -> new") + public static @NotNull Builder withSupplier(Supplier supplier) { + return new Builder().supplier(supplier); + } + + /** + * Create builder with key + * + * @param key the key + * @return the builder + */ + @Contract("_ -> new") + public static @NotNull Builder withKey(String key) { + return new Builder().key(key); + } + + // 保持原有的静态工厂方法作为便捷方法 + + /** + * Of supplier lang key value. + * + * @param supplier the supplier + * @param MPE the mpe + * @param US_EN the us en + * @param SIM_CN the sim cn + * @param TRA_CN the tra cn + * @return the lang key value + */ + @Contract(value = "_, _, _, _, _ -> new", pure = true) + public static @NotNull LangKeyValue ofSupplier(Supplier supplier, ModPartEnum MPE, + String US_EN, String SIM_CN, String TRA_CN) { + return builder() + .supplier(supplier) + .MPE(MPE) + .US_EN(US_EN) + .SIM_CN(SIM_CN) + .TRA_CN(TRA_CN) + .build(); + } + + /** + * Of supplier lang key value. + * + * @param supplier the supplier + * @param MPE the mpe + * @param US_EN the us en + * @param SIM_CN the sim cn + * @param TRA_CN the tra cn + * @param isDefault the is default + * @return the lang key value + */ + @Contract(value = "_, _, _, _, _, _ -> new", pure = true) + public static @NotNull LangKeyValue ofSupplier(Supplier supplier, ModPartEnum MPE, + String US_EN, String SIM_CN, String TRA_CN, boolean isDefault) { + return builder() + .supplier(supplier) + .MPE(MPE) + .US_EN(US_EN) + .SIM_CN(SIM_CN) + .TRA_CN(TRA_CN) + .isDefault(isDefault) + .build(); + } + + /** + * Of supplier lang key value. + * + * @param supplier the supplier + * @param MPE the mpe + * @param US_EN the us en + * @param SIM_CN the sim cn + * @param TRA_CN the tra cn + * @param LZH the lzh + * @return the lang key value + */ + @Contract(value = "_, _, _, _, _, _ -> new", pure = true) + public static @NotNull LangKeyValue ofSupplier(Supplier supplier, ModPartEnum MPE, + String US_EN, String SIM_CN, String TRA_CN, String LZH) { + return builder() + .supplier(supplier) + .MPE(MPE) + .US_EN(US_EN) + .SIM_CN(SIM_CN) + .TRA_CN(TRA_CN) + .LZH(LZH) + .build(); + } + + /** + * Of supplier lang key value. + * + * @param supplier the supplier + * @param MPE the mpe + * @param US_EN the us en + * @param SIM_CN the sim cn + * @param TRA_CN the tra cn + * @param LZH the lzh + * @param isDefault the is default + * @return the lang key value + */ + @Contract(value = "_, _, _, _, _, _, _ -> new", pure = true) + public static @NotNull LangKeyValue ofSupplier(Supplier supplier, ModPartEnum MPE, + String US_EN, String SIM_CN, String TRA_CN, String LZH, boolean isDefault) { + return builder() + .supplier(supplier) + .MPE(MPE) + .US_EN(US_EN) + .SIM_CN(SIM_CN) + .TRA_CN(TRA_CN) + .LZH(LZH) + .isDefault(isDefault) + .build(); + } + + /** + * Of key lang key value. + * + * @param key the key + * @param MPE the mpe + * @param US_EN the us en + * @param SIM_CN the sim cn + * @param TRA_CN the tra cn + * @return the lang key value + */ + @Contract(value = "_, _, _, _, _ -> new", pure = true) + public static @NotNull LangKeyValue ofKey(String key, ModPartEnum MPE, + String US_EN, String SIM_CN, String TRA_CN) { + return builder() + .key(key) + .MPE(MPE) + .US_EN(US_EN) + .SIM_CN(SIM_CN) + .TRA_CN(TRA_CN) + .build(); + } + + /** + * Of key lang key value. + * + * @param key the key + * @param MPE the mpe + * @param US_EN the us en + * @param SIM_CN the sim cn + * @param TRA_CN the tra cn + * @param isDefault the is default + * @return the lang key value + */ + @Contract(value = "_, _, _, _, _, _ -> new", pure = true) + public static @NotNull LangKeyValue ofKey(String key, ModPartEnum MPE, + String US_EN, String SIM_CN, String TRA_CN, boolean isDefault) { + return builder() + .key(key) + .MPE(MPE) + .US_EN(US_EN) + .SIM_CN(SIM_CN) + .TRA_CN(TRA_CN) + .isDefault(isDefault) + .build(); + } + + /** + * Of key lang key value. + * + * @param key the key + * @param MPE the mpe + * @param US_EN the us en + * @param SIM_CN the sim cn + * @param TRA_CN the tra cn + * @param LZH the lzh + * @return the lang key value + */ + @Contract(value = "_, _, _, _, _, _ -> new", pure = true) + public static @NotNull LangKeyValue ofKey(String key, ModPartEnum MPE, + String US_EN, String SIM_CN, String TRA_CN, String LZH) { + return builder() + .key(key) + .MPE(MPE) + .US_EN(US_EN) + .SIM_CN(SIM_CN) + .TRA_CN(TRA_CN) + .LZH(LZH) + .build(); + } + + /** + * Of key lang key value. + * + * @param key the key + * @param MPE the mpe + * @param US_EN the us en + * @param SIM_CN the sim cn + * @param TRA_CN the tra cn + * @param LZH the lzh + * @param isDefault the is default + * @return the lang key value + */ + @Contract(value = "_, _, _, _, _, _, _ -> new", pure = true) + public static @NotNull LangKeyValue ofKey(String key, ModPartEnum MPE, + String US_EN, String SIM_CN, String TRA_CN, String LZH, boolean isDefault) { + return builder() + .key(key) + .MPE(MPE) + .US_EN(US_EN) + .SIM_CN(SIM_CN) + .TRA_CN(TRA_CN) + .LZH(LZH) + .isDefault(isDefault) + .build(); + } + + /** + * Copy of lang key value. + * + * @param supplier the supplier + * @param modPartEnum the mod part enum + * @param other the other + * @return the lang key value + */ + public static @NotNull LangKeyValue copyOf(Supplier supplier, ModPartEnum modPartEnum, @NotNull LangKeyValue other) { + return builder() + .supplier(supplier) + .MPE(modPartEnum) + .US_EN(other.US_EN) + .SIM_CN(other.SIM_CN) + .TRA_CN(other.TRA_CN) + .LZH(other.LZH) + .build(); + } + + /** + * Copy of lang key value. + * + * @param key the key + * @param modPartEnum the mod part enum + * @param other the other + * @return the lang key value + */ + public static @NotNull LangKeyValue copyOf(String key, ModPartEnum modPartEnum, @NotNull LangKeyValue other) { + return builder() + .key(key) + .MPE(modPartEnum) + .US_EN(other.US_EN) + .SIM_CN(other.SIM_CN) + .TRA_CN(other.TRA_CN) + .LZH(other.LZH) + .build(); + } + + /** + * Copy of lang key value. + * + * @param supplier the supplier + * @param modPartEnum the mod part enum + * @param other the other + * @param isDefault the is default + * @return the lang key value + */ + public static @NotNull LangKeyValue copyOf(Supplier supplier, ModPartEnum modPartEnum, @NotNull LangKeyValue other, boolean isDefault) { + return builder() + .supplier(supplier) + .MPE(modPartEnum) + .US_EN(other.US_EN) + .SIM_CN(other.SIM_CN) + .TRA_CN(other.TRA_CN) + .LZH(other.LZH) + .isDefault(isDefault) + .build(); + } + + /** + * Copy of lang key value. + * + * @param key the key + * @param modPartEnum the mod part enum + * @param other the other + * @param isDefault the is default + * @return the lang key value + */ + public static @NotNull LangKeyValue copyOf(String key, ModPartEnum modPartEnum, @NotNull LangKeyValue other, boolean isDefault) { + return builder() + .key(key) + .MPE(modPartEnum) + .US_EN(other.US_EN) + .SIM_CN(other.SIM_CN) + .TRA_CN(other.TRA_CN) + .LZH(other.LZH) + .isDefault(isDefault) + .build(); + } + + + + @Override + public String getKey() { + return Objects.requireNonNullElseGet(key, () -> switch (MPE) { + case ITEM -> getItem().getDescriptionId(); + case BLOCK -> getBlock().getDescriptionId(); + default -> + throw new UnsupportedOperationException("The Key value is NULL! Please use the correct constructor and write the parameters correctly"); + }); + } + + @Override + public String getLang(@NotNull McLocale locale) { + return switch (locale) { + case EN_US, JA_JP, KO_KR, RU_RU, DE_DE, ES_ES, FR_FR -> US_EN; + case ZH_CN -> SIM_CN; + case ZH_TW -> TRA_CN; + case LZH -> LZH != null ? LZH : TRA_CN; // Fallback to TRA_CN if LZH is null + }; + } + + /** + * Gets supplier. + * + * @return the supplier + */ +// Getters for all fields + public Supplier getSupplier() { return supplier; } + + /** + * Gets us en. + * + * @return the us en + */ + public String getUS_EN() { return US_EN; } + + /** + * Gets sim cn. + * + * @return the sim cn + */ + public String getSIM_CN() { return SIM_CN; } + + /** + * Gets tra cn. + * + * @return the tra cn + */ + public String getTRA_CN() { return TRA_CN; } + + /** + * Gets lzh. + * + * @return the lzh + */ + public String getLZH() { return LZH; } + + /** + * Is default boolean. + * + * @return the boolean + */ + public Boolean isDefault() { return Default; } + + /** + * Gets mpe. + * + * @return the mpe + */ + public ModPartEnum getMPE() { return MPE; } + + /** + * Gets item. + * + * @return the item + * @throws IllegalArgumentException the illegal argument exception + */ + public Item getItem() throws IllegalArgumentException { + if(MPE == ModPartEnum.ITEM) { + return (Item) supplier.get(); + } + else throw new IllegalCallerException("Target's MPE is not ModPartEnum#ITEM."); + } + + /** + * Gets block. + * + * @return the block + * @throws IllegalArgumentException the illegal argument exception + */ + public Block getBlock() throws IllegalArgumentException { + if(MPE == ModPartEnum.BLOCK) { + return (Block) supplier.get(); + } + else throw new IllegalCallerException("Target's MPE is not ModPartEnum#BLOCK."); + } + @Override + public String toString() { + return "LangKeyValue{" + + "key='" + key + '\'' + + ", US_EN='" + US_EN + '\'' + + ", SIM_CN='" + SIM_CN + '\'' + + ", MPE=" + MPE + + '}'; + } +} \ No newline at end of file diff --git a/common/src/main/java/top/r3944realms/lib39/datagen/value/McLocale.java b/common/src/main/java/top/r3944realms/lib39/datagen/value/McLocale.java new file mode 100644 index 0000000..91cd9b6 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/datagen/value/McLocale.java @@ -0,0 +1,63 @@ +package top.r3944realms.lib39.datagen.value; + +import java.util.Locale; + +/** + * The enum Mc locale. + */ +public enum McLocale implements ILocaleEntry { + /** + * En us mc locale. + */ + EN_US("en_us", Locale.US), + /** + * Zh cn mc locale. + */ + ZH_CN("zh_cn", Locale.SIMPLIFIED_CHINESE), + /** + * Zh tw mc locale. + */ + ZH_TW("zh_tw", Locale.TRADITIONAL_CHINESE), + /** + * The Lzh. + */ + LZH("lzh", new Locale("lzh", "ZH")), + /** + * Ja jp mc locale. + */ + JA_JP("ja_jp", Locale.JAPAN), + /** + * Ko kr mc locale. + */ + KO_KR("ko_kr", Locale.KOREA), + /** + * The Ru ru. + */ + RU_RU("ru_ru", new Locale("ru", "RU")), + /** + * Fr fr mc locale. + */ + FR_FR("fr_fr", Locale.FRANCE), + /** + * De de mc locale. + */ + DE_DE("de_de", Locale.GERMANY), + /** + * The Es es. + */ + ES_ES("es_es", new Locale("es", "ES")); + + private final String mcCode; + private final Locale javaLocale; + + McLocale(String mcCode, Locale javaLocale) { + this.mcCode = mcCode; + this.javaLocale = javaLocale; + } + + @Override + public String mcCode() { return mcCode; } + + @Override + public Locale javaLocale() { return javaLocale; } +} diff --git a/common/src/main/java/top/r3944realms/lib39/datagen/value/ModPartEnum.java b/common/src/main/java/top/r3944realms/lib39/datagen/value/ModPartEnum.java new file mode 100644 index 0000000..fe8ea91 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/datagen/value/ModPartEnum.java @@ -0,0 +1,173 @@ +package top.r3944realms.lib39.datagen.value; + +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * 模组各部分的类型枚举,用于数据生成与分类。 + */ +public enum ModPartEnum { + /** + * 默认/未指定类型 + */ + DEFAULT, + + /** + * 物品 + */ + ITEM(Item.class), + + /** + * 方块 + */ + BLOCK(Block.class), + + /** + * 附魔 + */ + ENCHANTMENT, + + /** + * 进度标题 + */ + ADVANCEMENT_TITLE, + + /** + * 成就描述 + */ + ADVANCEMENT_DESCRIPTION, + + /** + * 创造模式物品栏 + */ + CREATIVE_TAB, + + /** + * 配置项 + */ + CONFIG, + + /** + * 实体(生物、载具等) + */ + ENTITY, + + /** + * 图形界面 + */ + GUI, + /** + * 容器 + */ + CONTAINER, + /** + * 画作描述 + */ + PAINTING_TITLE, + /** + * 画作作者 + */ + PAINTING_AUTHOR, + + /** + * 标题 + */ + TITLE, + + /** + * 名称 + */ + NAME, + + /** + * 游戏规则(/gamerule) + */ + GAME_RULE, + + /** + * 描述文本 + */ + DESCRIPTION, + + /** + * 一般信息 + */ + INFO, + + /** + * 消息(聊天、提示等) + */ + MESSAGE, + + /** + * 生物群系 + */ + BIOME, + + /** + * 命令 + */ + COMMAND, + + /** + * 声音资源 + */ + SOUND; + ; + @Nullable + private final Class clazz; + ModPartEnum() { + clazz = null; + } + ModPartEnum(@Nullable Class clazz) { + this.clazz = clazz; + } + + /** + * Gets full key. + * + * @param modId the mod id + * @param name the name + * @return the full key + */ + @Contract(pure = true) + public @NotNull String getFullKey(String modId, String name) { + return switch (this) { + case ITEM -> "item." + modId + "." + name; + case BLOCK -> "block." + modId + "." + name; + case ENCHANTMENT -> "enchantment." + modId + "." + name; + case ADVANCEMENT_TITLE -> "advancement." + modId + "." + name + ".title"; + case ADVANCEMENT_DESCRIPTION -> "advancement." + modId + "." + name + ".description"; + case CREATIVE_TAB -> "creativetab." + modId + "." + name; + case BIOME -> "biome." + modId + "." + name; + case CONFIG -> "config." + modId + "." + name; + case ENTITY -> "entity." + modId + "." + name; + case GUI -> "gui." + modId + "." + name; + case CONTAINER -> "container." + modId + "." + name; + case PAINTING_AUTHOR -> "painting." + modId + "." + name + ".author"; + case PAINTING_TITLE -> "painting." + modId + "." + name + ".title"; + case TITLE -> "title." + modId + "." + name; + case NAME -> "name." + modId + "." + name; + case GAME_RULE -> "gamerule."+ modId + "." + name; + case DESCRIPTION -> "description." + modId + "." + name; + case INFO -> "info." + modId + "." + name; + case MESSAGE -> "message." + modId + "." + name; + case COMMAND -> "command." + modId + "." + name; + case SOUND -> "sound." + modId + "." + name; + default -> modId + "." + name; + }; + } + + + /** + * Gets clazz. + * + * @return the clazz + */ + public @Nullable Class getClazz() { + return clazz; + } +} diff --git a/common/src/main/java/top/r3944realms/lib39/mixin/carryon/MixinCarriedObjectRender.java b/common/src/main/java/top/r3944realms/lib39/mixin/carryon/MixinCarriedObjectRender.java new file mode 100644 index 0000000..acbdac6 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/mixin/carryon/MixinCarriedObjectRender.java @@ -0,0 +1,51 @@ +package top.r3944realms.lib39.mixin.carryon; + +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import top.r3944realms.lib39.content.item.DollItem; +import top.r3944realms.lib39.util.GameProfileHelper; +import tschipp.carryon.client.render.CarriedObjectRender; +import tschipp.carryon.common.carry.CarryOnDataManager; + +/** + * The type Mixin carried object render. + */ +@Pseudo +@Mixin(value = CarriedObjectRender.class, remap = false) +public class MixinCarriedObjectRender { + @ModifyVariable( + method = "drawFirstPersonBlock", + at = @At( + value = "LOAD", + target = "Ltschipp/carryon/client/render/CarryRenderHelper;renderBakedModel(Lnet/minecraft/world/item/ItemStack;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/client/resources/model/BakedModel;)V" + ) + ) + private static ItemStack warpDollItem$1(ItemStack stack, @Local(ordinal = 0, argsOnly = true) Player player) { + if (stack.getItem() instanceof DollItem) { + CompoundTag compound = CarryOnDataManager.getCarryData(player).getNbt().getCompound("tile").getCompound(GameProfileHelper.TAG_OWN_PROFILE); + stack.getOrCreateTag().put(GameProfileHelper.TAG_OWN_PROFILE, compound); + } + return stack; + } + @ModifyVariable( + method = "drawThirdPerson", + at = @At( + value = "LOAD", + target = "Ltschipp/carryon/client/render/CarryRenderHelper;renderBakedModel(Lnet/minecraft/world/item/ItemStack;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/client/resources/model/BakedModel;)V" + ) + ) + private static ItemStack warpDollItem$2(ItemStack stack, @Local(ordinal = 0) Player player) { + if (stack.getItem() instanceof DollItem) { + CompoundTag compound = CarryOnDataManager.getCarryData(player).getNbt().getCompound("tile").getCompound(GameProfileHelper.TAG_OWN_PROFILE); + stack.getOrCreateTag().put(GameProfileHelper.TAG_OWN_PROFILE, compound); + } + return stack; + } + +} diff --git a/common/src/main/java/top/r3944realms/lib39/platform/Services.java b/common/src/main/java/top/r3944realms/lib39/platform/Services.java new file mode 100644 index 0000000..86b995b --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/platform/Services.java @@ -0,0 +1,30 @@ +package top.r3944realms.lib39.platform; + +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.platform.services.IPlatformHelper; + +import java.util.ServiceLoader; + +// Service loaders are a built-in Java feature that allow us to locate implementations of an interface that vary from one +// environment to another. In the context of MultiLoader we use this feature to access a mock API in the common code that +// is swapped out for the platform specific implementation at runtime. +public class Services { + + // In this example we provide a platform helper which provides information about what platform the mod is running on. + // For example this can be used to check if the code is running on Forge vs Fabric, or to ask the modloader if another + // mod is loaded. + public static final IPlatformHelper PLATFORM = load(IPlatformHelper.class); + + // This code is used to load a service for the current environment. Your implementation of the service must be defined + // manually by including a text file in META-INF/services named with the fully qualified class name of the service. + // Inside the file you should write the fully qualified class name of the implementation to load for the platform. For + // example our file on Forge points to ForgePlatformHelper while Fabric points to FabricPlatformHelper. + public static T load(Class clazz) { + + final T loadedService = ServiceLoader.load(clazz) + .findFirst() + .orElseThrow(() -> new NullPointerException("Failed to load service for " + clazz.getName())); + Lib39.LOGGER.debug("Loaded {} for service {}", loadedService, clazz); + return loadedService; + } +} \ No newline at end of file diff --git a/common/src/main/java/top/r3944realms/lib39/platform/services/IPlatformHelper.java b/common/src/main/java/top/r3944realms/lib39/platform/services/IPlatformHelper.java new file mode 100644 index 0000000..f7fee96 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/platform/services/IPlatformHelper.java @@ -0,0 +1,48 @@ +package top.r3944realms.lib39.platform.services; + +/** + * The interface Platform helper. + */ +public interface IPlatformHelper { + + /** + * Gets the name of the current platform + * + * @return The name of the current platform. + */ + String getPlatformName(); + + /** + * Checks if a mod with the given id is loaded. + * + * @param modId The mod to check if it is loaded. + * @return True if the mod is loaded, false otherwise. + */ + boolean isModLoaded(String modId); + + /** + * Check if the game is currently in a development environment. + * + * @return True if in a development environment, false otherwise. + */ + boolean isDevelopmentEnvironment(); + + /** + * Gets the name of the environment type as a string. + * + * @return The name of the environment type. + */ + default String getEnvironmentName() { + return isDevelopmentEnvironment() ? "development" : "production"; + } + + boolean isClientEnvironment(); + + + /** + * Gets mod version. + * + * @return the mod version + */ + String getModVersion(); +} \ No newline at end of file diff --git a/common/src/main/java/top/r3944realms/lib39/util/GameProfileHelper.java b/common/src/main/java/top/r3944realms/lib39/util/GameProfileHelper.java new file mode 100644 index 0000000..53eaf87 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/util/GameProfileHelper.java @@ -0,0 +1,342 @@ +package top.r3944realms.lib39.util; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.PlayerInfo; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.client.resources.DefaultPlayerSkin; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.lib39.util.nbt.NBTReader; +import top.r3944realms.lib39.util.nbt.NBTWriter; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Collection; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +/** + * The type GameProfile helper. + */ +public class GameProfileHelper { + /** + * Client Only Class + */ + public static class ClientOpt implements IClientOnly { + public static @NotNull ResourceLocation resolveSkinTexture(@NotNull GameProfile gameProfile) { + return IClientOnly.check(() -> + Minecraft.getInstance().getSkinManager() + .getInsecureSkinLocation(gameProfile)); + } + + public static ResourceLocation getSkinTexture(@Nullable GameProfile gameProfile) { + return IClientOnly.check(() -> { + if (gameProfile == null) { + return DefaultPlayerSkin.getDefaultSkin(); + } + return resolveSkinTexture(gameProfile); + }); + } + + public static boolean hasSlimArmsClient(Player player) { + return IClientOnly.check(() -> { + if (player instanceof AbstractClientPlayer clientPlayer) { + PlayerInfo playerInfo = Objects.requireNonNull(Minecraft.getInstance() + .getConnection()) + .getPlayerInfo(clientPlayer.getUUID()); + return playerInfo != null && "slim".equals(playerInfo.getModelName()); + } + return false; + }); + } + + public static @NotNull String getSkinModelName(@NotNull Player player) { + return IClientOnly.check(() -> { + if (player.level().isClientSide && player instanceof AbstractClientPlayer) { + PlayerInfo info = Objects.requireNonNull(Minecraft.getInstance().getConnection()) + .getPlayerInfo(player.getUUID()); + return info != null ? info.getModelName() : "default"; + } + return "default"; + }); + } + + } + /** + * The constant TAG_BE. + */ + public static final String TAG_BE = "BlockEntityTag"; + /** + * The constant TAG_OWN_PROFILE. + */ + public static final String TAG_OWN_PROFILE = "OwnerProfile"; + + public static ResourceLocation getSkinTexture(@Nullable GameProfile gameProfile) { + return ClientOpt.getSkinTexture(gameProfile); + } + + + public static @NotNull ResourceLocation resolveSkinTexture(@NotNull GameProfile gameProfile) { + return ClientOpt.resolveSkinTexture(gameProfile); + } + + /** + * Has slim arms boolean. + * + * @param player the player + * @return the boolean + */ + public static boolean hasSlimArms(@NotNull Player player) { + if (player.level().isClientSide) { + return hasSlimArmsClient(player); + } else { + return hasSlimArmsServer(player); + } + } + + + private static boolean hasSlimArmsClient(Player player) { + return ClientOpt.hasSlimArmsClient(player); + } + + // 服务器端判断 + private static boolean hasSlimArmsServer(@NotNull Player player) { + GameProfile profile = player.getGameProfile(); + for (Property property : profile.getProperties().get("textures")) { + try { + String json = new String(Base64.getDecoder().decode(property.getValue())); + JsonObject obj = JsonParser.parseString(json).getAsJsonObject(); + JsonObject textures = obj.getAsJsonObject("textures"); + JsonObject skin = textures.getAsJsonObject("SKIN"); + + if (skin.has("metadata")) { + JsonObject metadata = skin.getAsJsonObject("metadata"); + if (metadata.has("model")) { + return "slim".equals(metadata.get("model").getAsString()); + } + } + } catch (Exception e) { + // 解析失败,使用默认 + } + } + return false; + } + + /** + * Gets skin model name. + * + * @param player the player + * @return the skin model name + */ + public static @NotNull String getSkinModelName(@NotNull Player player) { + return ClientOpt.getSkinModelName(player); + } + + /** + * 判断玩家是否为纤细手臂(Alex模型) + * + * @param profile 玩家的GameProfile + * @return true =纤细手臂,false=正常手臂 + */ + public static boolean isSlimArms(GameProfile profile) { + if (profile == null) { + return false; + } + + // 获取textures属性 + Collection textures = profile.getProperties().get("textures"); + if (textures.isEmpty()) { + return false; // 没有皮肤数据,使用默认 + } + + // 获取第一个texture属性(通常是皮肤) + Property textureProperty = textures.iterator().next(); + String value = textureProperty.getValue(); + + try { + return isSlimFromTextureData(value); + } catch (Exception e) { + // 解析失败,使用默认 + return false; + } + } + + /** + * 从Base64编码的皮肤数据判断 + * @param encodedTexture Base64编码的皮肤数据 + * @return true=纤细手臂 + */ + private static boolean isSlimFromTextureData(String encodedTexture) { + if (encodedTexture == null || encodedTexture.isEmpty()) { + return false; + } + + try { + // 1. Base64解码 + byte[] decodedBytes = Base64.getDecoder().decode(encodedTexture); + String jsonString = new String(decodedBytes, StandardCharsets.UTF_8); + + // 2. 解析JSON + JsonObject root = JsonParser.parseString(jsonString).getAsJsonObject(); + + // 3. 导航到textures -> SKIN + JsonObject textures = root.getAsJsonObject("textures"); + if (textures == null) { + return false; + } + + JsonObject skin = textures.getAsJsonObject("SKIN"); + if (skin == null) { + return false; + } + + // 4. 检查metadata -> model + JsonObject metadata = skin.getAsJsonObject("metadata"); + if (metadata == null) { + return false; // 没有metadata,使用默认 + } + + String model = metadata.get("model").getAsString(); + return "slim".equals(model); + + } catch (Exception e) { + // 解析过程中出现任何错误,返回默认值 + return false; + } + } + + /** + * 获取皮肤模型名称 + * + * @param profile GameProfile + * @return "slim" 或 "default" + */ + public static String getSkinModelName(GameProfile profile) { + if (profile == null) { + return "default"; + } + + Collection textures = profile.getProperties().get("textures"); + if (textures.isEmpty()) { + return "default"; + } + + Property textureProperty = textures.iterator().next(); + String value = textureProperty.getValue(); + + try { + byte[] decodedBytes = Base64.getDecoder().decode(value); + String jsonString = new String(decodedBytes, StandardCharsets.UTF_8); + JsonObject root = JsonParser.parseString(jsonString).getAsJsonObject(); + JsonObject texturesObj = root.getAsJsonObject("textures"); + JsonObject skin = texturesObj.getAsJsonObject("SKIN"); + + if (skin.has("metadata")) { + JsonObject metadata = skin.getAsJsonObject("metadata"); + if (metadata.has("model")) { + return metadata.get("model").getAsString(); + } + } + } catch (Exception e) { + // 忽略错误 + } + + return "default"; + } + + /** + * 从ItemStack的NBT中读取GameProfile + * + * @param stack the stack + * @return the profile from item stack + */ + @Nullable + public static GameProfile getProfileFromItemStack(ItemStack stack) { + if (stack.isEmpty()) { + return null; + } + + CompoundTag tag = stack.getTag(); + if (tag == null) { + return null; + } + AtomicReference profileRef = new AtomicReference<>(); + // 检查方块实体数据 + NBTReader.of(tag) + .compound(TAG_BE, compoundTag -> + NBTReader.of(compoundTag) + .compound("OwnerProfile", ct -> profileRef.set(NbtUtils.readGameProfile(ct))) + ) + .compound("OwnerProfile", ct -> { + if (profileRef.get() == null) { //兼容写法 + profileRef.set(NbtUtils.readGameProfile(ct)); + } + }); + return profileRef.get(); + } + + /** + * 将GameProfile保存到ItemStack的NBT + * + * @param stack the stack + * @param profile the profile + */ + public static void saveProfileToItemStack(@NotNull ItemStack stack, @Nullable GameProfile profile) { + if (stack.isEmpty()) { + return; + } + + CompoundTag tag = stack.getOrCreateTag(); + + if (profile == null) { + // 移除现有数据 + NBTReader.of(tag) + .compound(TAG_BE, ct -> tag.remove(TAG_OWN_PROFILE)); + tag.remove(TAG_BE); + tag.remove(TAG_OWN_PROFILE); + return; + } + + // 创建方块实体数据 + NBTWriter.of(tag) + .compound(TAG_BE, writer -> + writer + .compound(TAG_OWN_PROFILE, NbtUtils.writeGameProfile(new CompoundTag(), profile)) + ); + } + + /** + * 检查ItemStack是否有保存的皮肤数据 + * + * @param stack the stack + * @return the boolean + */ + public static boolean hasProfileData(@NotNull ItemStack stack) { + if (stack.isEmpty()) { + return false; + } + + CompoundTag tag = stack.getTag(); + if (tag == null) { + return false; + } + + if (tag.contains(TAG_BE)) { + CompoundTag blockEntityTag = tag.getCompound(TAG_BE); + return blockEntityTag.contains(TAG_OWN_PROFILE); + } + + return tag.contains(TAG_OWN_PROFILE); + } + +} + diff --git a/common/src/main/java/top/r3944realms/lib39/util/IClientOnly.java b/common/src/main/java/top/r3944realms/lib39/util/IClientOnly.java new file mode 100644 index 0000000..4816df7 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/util/IClientOnly.java @@ -0,0 +1,36 @@ +package top.r3944realms.lib39.util; + +import top.r3944realms.lib39.Lib39; + +import java.util.function.Supplier; + +public interface IClientOnly { + static void check(Runnable runnable) { + if (Lib39.isClientEnvironment()) { + runnable.run(); + return; + } + throw new RuntimeException("This method should be called in ClientEnvironment"); + } + + static void check(Runnable runnable, Runnable fallback) { + if (Lib39.isClientEnvironment()) { + runnable.run(); + return; + } + fallback.run(); + } + static T check(Supplier supplier) { + if (Lib39.isClientEnvironment()) { + return supplier.get(); + } + throw new RuntimeException("This method should be called in ClientEnvironment"); + } + + static T check(Supplier supplier, Supplier fallback) { + if (Lib39.isClientEnvironment()) { + return supplier.get(); + } + return fallback.get(); + } +} diff --git a/src/main/java/top/r3944realms/lib39/util/ILevelHelper.java b/common/src/main/java/top/r3944realms/lib39/util/ILevelHelper.java similarity index 91% rename from src/main/java/top/r3944realms/lib39/util/ILevelHelper.java rename to common/src/main/java/top/r3944realms/lib39/util/ILevelHelper.java index 0aa5051..7d4735e 100644 --- a/src/main/java/top/r3944realms/lib39/util/ILevelHelper.java +++ b/common/src/main/java/top/r3944realms/lib39/util/ILevelHelper.java @@ -1,8 +1,6 @@ package top.r3944realms.lib39.util; import net.minecraft.world.level.Level; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; @@ -66,7 +64,6 @@ public interface ILevelHelper { * @return the client level */ @Nullable - @OnlyIn(Dist.CLIENT) static Level getClientLevel() { return LevelHelper.CLIENT.getLevel(); } diff --git a/src/main/java/top/r3944realms/lib39/util/IUniPosOwner.java b/common/src/main/java/top/r3944realms/lib39/util/IUniPosOwner.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/IUniPosOwner.java rename to common/src/main/java/top/r3944realms/lib39/util/IUniPosOwner.java diff --git a/src/main/java/top/r3944realms/lib39/util/MathUtil.java b/common/src/main/java/top/r3944realms/lib39/util/MathUtil.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/MathUtil.java rename to common/src/main/java/top/r3944realms/lib39/util/MathUtil.java diff --git a/src/main/java/top/r3944realms/lib39/util/PlantHelper.java b/common/src/main/java/top/r3944realms/lib39/util/PlantHelper.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/PlantHelper.java rename to common/src/main/java/top/r3944realms/lib39/util/PlantHelper.java diff --git a/common/src/main/java/top/r3944realms/lib39/util/block/BlockRegistryBuilder.java b/common/src/main/java/top/r3944realms/lib39/util/block/BlockRegistryBuilder.java new file mode 100644 index 0000000..f6fc4e8 --- /dev/null +++ b/common/src/main/java/top/r3944realms/lib39/util/block/BlockRegistryBuilder.java @@ -0,0 +1,142 @@ +package top.r3944realms.lib39.util.block; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.CreativeModeTabs; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.function.BiFunction; +import java.util.function.Supplier; + +/** + * The type Block registry builder. + */ +@SuppressWarnings({"UnusedReturnValue", "unused"}) +public abstract class BlockRegistryBuilder { + private String registryName; + private Supplier blockObject; + private BiFunction, Supplier> blockItemRegister; + private boolean needBuildItem; + private Item.Properties properties; + + /** + * 创建新的构建器实例 + * + * @return the block registry builder + */ + @Contract(value = " -> new", pure = true) + public static @NotNull BlockRegistryBuilder create() { + return new BlockRegistryBuilder(); + } + + /** + * 设置注册名称 + * + * @param name the name + * @return the block registry builder + */ + public BlockRegistryBuilder withName(String name) { + this.registryName = name; + return this; + } + + /** + * 注册方块(不自动注册物品) + * + * @param blockRegister the block register + * @param blockSupplier the block supplier + * @return the block registry builder + */ + public BlockRegistryBuilder registerBlock(@NotNull BiFunction,Supplier> blockRegister, Supplier blockSupplier) { + this.blockObject = blockRegister.apply(this.registryName, blockSupplier); + return this; + } + + /** + * 注册对应的方块物品 + * + * @param itemRegister the item deferred register + * @return the block registry builder + */ + public BlockRegistryBuilder registerItem(BiFunction,Supplier> itemRegister) { + this.blockItemRegister = itemRegister; + needBuildItem = true; + return this; + } + + /** + * 注册对应的方块带有对应属性物品 + * + * @param itemRegister the item deferred register + * @param properties the item properties + * @return the block registry builder + */ + public BlockRegistryBuilder registerItemWithProperties(BiFunction,Supplier> itemRegister, Item.Properties properties) { + this.blockItemRegister = itemRegister; + this.properties = properties; + needBuildItem = true; + return this; + } + + /** + * 对应的方块物品属性 + * + * @param properties the item properties + * @return the block registry builder + */ + public BlockRegistryBuilder ItemProperties(Item.Properties properties) { + this.properties = properties; + return this; + } + + /** + * 内部方法:注册对应的方块物品 + */ + @SafeVarargs + private void registerBlockItem(Supplier blockObject, ResourceKey... creativeTabs) { + + } + + /** + * 注册方块和物品到建筑标签页 + * + * @param blockRegister the block register + * @param blockSupplier the block supplier + * @return the block registry builder + */ + public BlockRegistryBuilder registerWithBuildingTab(BiFunction,Supplier> blockRegister, Supplier blockSupplier) { + registerBlock(blockRegister, blockSupplier); + registerBlockItem(this.blockObject, CreativeModeTabs.BUILDING_BLOCKS); + return this; + } + + /** + * 注册方块和物品到功能标签页 + * + * @param blockRegister the block register + * @param blockSupplier the block supplier + * @return the block registry builder + */ + public BlockRegistryBuilder registerWithFunctionalTab(BiFunction,Supplier> blockRegister, Supplier blockSupplier) { + registerBlock(blockRegister, blockSupplier); + registerBlockItem(this.blockObject, CreativeModeTabs.FUNCTIONAL_BLOCKS); + return this; + } + + /** + * 获取注册的方块对象 + * + * @return the registry object + */ + public Supplier build() { + if (needBuildItem) { + blockItemRegister.apply(this.registryName, () -> new BlockItem(this.blockObject.get(), properties == null ? new Item.Properties() : properties)); + } + return this.blockObject; + } + +} diff --git a/src/main/java/top/r3944realms/lib39/util/command/CommandAliasHelper.java b/common/src/main/java/top/r3944realms/lib39/util/command/CommandAliasHelper.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/command/CommandAliasHelper.java rename to common/src/main/java/top/r3944realms/lib39/util/command/CommandAliasHelper.java diff --git a/src/main/java/top/r3944realms/lib39/util/lang/FourConsumer.java b/common/src/main/java/top/r3944realms/lib39/util/lang/FourConsumer.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/lang/FourConsumer.java rename to common/src/main/java/top/r3944realms/lib39/util/lang/FourConsumer.java diff --git a/src/main/java/top/r3944realms/lib39/util/lang/Pair.java b/common/src/main/java/top/r3944realms/lib39/util/lang/Pair.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/lang/Pair.java rename to common/src/main/java/top/r3944realms/lib39/util/lang/Pair.java diff --git a/src/main/java/top/r3944realms/lib39/util/lang/Triple.java b/common/src/main/java/top/r3944realms/lib39/util/lang/Triple.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/lang/Triple.java rename to common/src/main/java/top/r3944realms/lib39/util/lang/Triple.java diff --git a/src/main/java/top/r3944realms/lib39/util/lang/Tuple.java b/common/src/main/java/top/r3944realms/lib39/util/lang/Tuple.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/lang/Tuple.java rename to common/src/main/java/top/r3944realms/lib39/util/lang/Tuple.java diff --git a/src/main/java/top/r3944realms/lib39/util/nbt/NBTReader.java b/common/src/main/java/top/r3944realms/lib39/util/nbt/NBTReader.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/nbt/NBTReader.java rename to common/src/main/java/top/r3944realms/lib39/util/nbt/NBTReader.java diff --git a/src/main/java/top/r3944realms/lib39/util/nbt/NBTWriter.java b/common/src/main/java/top/r3944realms/lib39/util/nbt/NBTWriter.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/nbt/NBTWriter.java rename to common/src/main/java/top/r3944realms/lib39/util/nbt/NBTWriter.java diff --git a/src/main/java/top/r3944realms/lib39/util/resolve/EntityListResolve.java b/common/src/main/java/top/r3944realms/lib39/util/resolve/EntityListResolve.java similarity index 98% rename from src/main/java/top/r3944realms/lib39/util/resolve/EntityListResolve.java rename to common/src/main/java/top/r3944realms/lib39/util/resolve/EntityListResolve.java index ec3c7a9..9867d5d 100644 --- a/src/main/java/top/r3944realms/lib39/util/resolve/EntityListResolve.java +++ b/common/src/main/java/top/r3944realms/lib39/util/resolve/EntityListResolve.java @@ -17,7 +17,7 @@ public abstract class EntityListResolve { /** * The Result. */ - protected EntityListResolve.EntityResolveResult result; + protected EntityResolveResult result; /** * The type Entity resolve result. diff --git a/src/main/java/top/r3944realms/lib39/util/resolve/EntityMapResolve.java b/common/src/main/java/top/r3944realms/lib39/util/resolve/EntityMapResolve.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/resolve/EntityMapResolve.java rename to common/src/main/java/top/r3944realms/lib39/util/resolve/EntityMapResolve.java diff --git a/src/main/java/top/r3944realms/lib39/util/riding/RidingApplier.java b/common/src/main/java/top/r3944realms/lib39/util/riding/RidingApplier.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/riding/RidingApplier.java rename to common/src/main/java/top/r3944realms/lib39/util/riding/RidingApplier.java diff --git a/src/main/java/top/r3944realms/lib39/util/riding/RidingCycleException.java b/common/src/main/java/top/r3944realms/lib39/util/riding/RidingCycleException.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/riding/RidingCycleException.java rename to common/src/main/java/top/r3944realms/lib39/util/riding/RidingCycleException.java diff --git a/src/main/java/top/r3944realms/lib39/util/riding/RidingDismounts.java b/common/src/main/java/top/r3944realms/lib39/util/riding/RidingDismounts.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/riding/RidingDismounts.java rename to common/src/main/java/top/r3944realms/lib39/util/riding/RidingDismounts.java diff --git a/src/main/java/top/r3944realms/lib39/util/riding/RidingFinder.java b/common/src/main/java/top/r3944realms/lib39/util/riding/RidingFinder.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/riding/RidingFinder.java rename to common/src/main/java/top/r3944realms/lib39/util/riding/RidingFinder.java diff --git a/src/main/java/top/r3944realms/lib39/util/riding/RidingRelationship.java b/common/src/main/java/top/r3944realms/lib39/util/riding/RidingRelationship.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/riding/RidingRelationship.java rename to common/src/main/java/top/r3944realms/lib39/util/riding/RidingRelationship.java diff --git a/src/main/java/top/r3944realms/lib39/util/riding/RidingSaver.java b/common/src/main/java/top/r3944realms/lib39/util/riding/RidingSaver.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/riding/RidingSaver.java rename to common/src/main/java/top/r3944realms/lib39/util/riding/RidingSaver.java diff --git a/src/main/java/top/r3944realms/lib39/util/riding/RidingSerializer.java b/common/src/main/java/top/r3944realms/lib39/util/riding/RidingSerializer.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/riding/RidingSerializer.java rename to common/src/main/java/top/r3944realms/lib39/util/riding/RidingSerializer.java diff --git a/src/main/java/top/r3944realms/lib39/util/riding/RidingValidator.java b/common/src/main/java/top/r3944realms/lib39/util/riding/RidingValidator.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/riding/RidingValidator.java rename to common/src/main/java/top/r3944realms/lib39/util/riding/RidingValidator.java diff --git a/src/main/java/top/r3944realms/lib39/util/shape/Quaternions.java b/common/src/main/java/top/r3944realms/lib39/util/shape/Quaternions.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/shape/Quaternions.java rename to common/src/main/java/top/r3944realms/lib39/util/shape/Quaternions.java diff --git a/src/main/java/top/r3944realms/lib39/util/shape/ShapeUtil.java b/common/src/main/java/top/r3944realms/lib39/util/shape/ShapeUtil.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/shape/ShapeUtil.java rename to common/src/main/java/top/r3944realms/lib39/util/shape/ShapeUtil.java diff --git a/src/main/java/top/r3944realms/lib39/util/sound/SoundUtil.java b/common/src/main/java/top/r3944realms/lib39/util/sound/SoundUtil.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/sound/SoundUtil.java rename to common/src/main/java/top/r3944realms/lib39/util/sound/SoundUtil.java diff --git a/src/main/java/top/r3944realms/lib39/util/villager/TradeBuilder.java b/common/src/main/java/top/r3944realms/lib39/util/villager/TradeBuilder.java similarity index 100% rename from src/main/java/top/r3944realms/lib39/util/villager/TradeBuilder.java rename to common/src/main/java/top/r3944realms/lib39/util/villager/TradeBuilder.java diff --git a/common/src/main/resources/assets/lib39/models/block/base_doll.json b/common/src/main/resources/assets/lib39/models/block/base_doll.json new file mode 100644 index 0000000..c8574d2 --- /dev/null +++ b/common/src/main/resources/assets/lib39/models/block/base_doll.json @@ -0,0 +1,50 @@ +{ + "format_version": "1.9.0", + "credit": "3D Model © 2025 LeisureTimeDock", + "parent": "builtin/entity", + "textures": { + "particle": "minecraft:block/white_wool" + }, + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 124, 0], + "translation": [2, 3, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 120, 0], + "translation": [1.5, 2.75, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 2, 0], + "scale": [0.5, 0.5, 0.5] + }, + "gui": { + "rotation": [30, -135, 0], + "translation": [0.75, -1, 0], + "scale": [0.625, 0.625, 0.625] + }, + "head": { + "translation": [0, 14, -0.75] + }, + "fixed": { + "translation": [0, 0, -2.75], + "scale": [0.5, 0.5, 0.5] + }, + "on_shelf": { + "rotation": [0, -180, 0], + "translation": [0, 0, 5.25] + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/lib39/shaders/core/ring.fsh b/common/src/main/resources/assets/lib39/shaders/core/ring.fsh new file mode 100644 index 0000000..39aa703 --- /dev/null +++ b/common/src/main/resources/assets/lib39/shaders/core/ring.fsh @@ -0,0 +1,49 @@ +#version 150 + +in vec4 vertexColor; + +uniform vec4 ColorModulator; +uniform vec2 Center; +uniform float InnerRadius; +uniform float OuterRadius; +uniform float AntiAliasing; + +out vec4 fragColor; + +void main() { + float dist = distance(gl_FragCoord.xy, Center); + float alpha = 0.0; + + // 确保内外半径合理 + if (OuterRadius <= InnerRadius) { + vec4 color = vertexColor; + color.a = 0; + fragColor = color; + } + + // 计算环形 alpha + if (dist >= InnerRadius && dist <= OuterRadius) { + alpha = 1.0; + + // 内边缘抗锯齿 + if (dist < InnerRadius + AntiAliasing) { + float fade = (dist - InnerRadius) / AntiAliasing; + alpha *= fade; + } + + // 外边缘抗锯齿 + if (dist > OuterRadius - AntiAliasing) { + float fade = 1.0 - (dist - (OuterRadius - AntiAliasing)) / AntiAliasing; + alpha *= fade; + } + } + + vec4 color = vertexColor; + color.a *= alpha; + + if (alpha > 0.0) { + fragColor = color * ColorModulator; + } else { + discard; + } +} \ No newline at end of file diff --git a/common/src/main/resources/assets/lib39/shaders/core/ring.json b/common/src/main/resources/assets/lib39/shaders/core/ring.json new file mode 100644 index 0000000..756ea0c --- /dev/null +++ b/common/src/main/resources/assets/lib39/shaders/core/ring.json @@ -0,0 +1,44 @@ +{ + "vertex": "position_color", + "fragment": "lib39:ring", + "samplers": [ + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { + "name": "Center", + "type": "float", + "count": 2, + "values": [ + 0.0, + 0.0 + ] + }, + { + "name": "InnerRadius", + "type": "float", + "count": 1, + "values": [ + 0.0 + ] + }, + { + "name": "OuterRadius", + "type": "float", + "count": 1, + "values": [ + 0.0 + ] + }, + { + "name": "AntiAliasing", + "type": "float", + "count": 1, + "values": [ + 1.25 + ] + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/assets/lib39/shaders/core/selection.fsh b/common/src/main/resources/assets/lib39/shaders/core/selection.fsh new file mode 100644 index 0000000..27e9904 --- /dev/null +++ b/common/src/main/resources/assets/lib39/shaders/core/selection.fsh @@ -0,0 +1,23 @@ +#version 150 + +in vec4 vertexColor; + +uniform vec4 ColorModulator; +uniform vec2 FramebufferSize; +uniform vec2 Center; +uniform float Radius; +uniform float AntiAliasingRadius; + +out vec4 fragColor; + +void main() { + vec2 fragPos = vec2(gl_FragCoord.x, FramebufferSize.y - gl_FragCoord.y); + float distance = distance(fragPos, Center); + vec4 color = vec4(0, 0, 0, 0); + if (distance <= Radius) { + color = vertexColor; + color.a = smoothstep(Radius, 0.0, distance) * vertexColor.a; + } + + fragColor = color * ColorModulator; +} \ No newline at end of file diff --git a/common/src/main/resources/assets/lib39/shaders/core/selection.json b/common/src/main/resources/assets/lib39/shaders/core/selection.json new file mode 100644 index 0000000..b9a2e52 --- /dev/null +++ b/common/src/main/resources/assets/lib39/shaders/core/selection.json @@ -0,0 +1,45 @@ +{ + "vertex": "position_color", + "fragment": "lib39:selection", + "samplers": [ + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { + "name": "Center", + "type": "float", + "count": 2, + "values": [ + 0.0, + 0.0 + ] + }, + { + "name": "FramebufferSize", + "type": "float", + "count": 2, + "values": [ + 0.0, + 0.0 + ] + }, + { + "name": "Radius", + "type": "float", + "count": 1, + "values": [ + 0.0 + ] + }, + { + "name": "AntiAliasingRadius", + "type": "float", + "count": 1, + "values": [ + 1.5 + ] + } + ] +} \ No newline at end of file diff --git a/common/src/main/resources/assets/lib39/sounds/duck_toy.ogg b/common/src/main/resources/assets/lib39/sounds/duck_toy.ogg new file mode 100644 index 0000000..b74d006 Binary files /dev/null and b/common/src/main/resources/assets/lib39/sounds/duck_toy.ogg differ diff --git a/common/src/main/resources/assets/lib39/textures/item/fabric.png b/common/src/main/resources/assets/lib39/textures/item/fabric.png new file mode 100644 index 0000000..4ab8370 Binary files /dev/null and b/common/src/main/resources/assets/lib39/textures/item/fabric.png differ diff --git a/common/src/main/resources/assets/lib39/textures/item/forge.png b/common/src/main/resources/assets/lib39/textures/item/forge.png new file mode 100644 index 0000000..22a6c8f Binary files /dev/null and b/common/src/main/resources/assets/lib39/textures/item/forge.png differ diff --git a/common/src/main/resources/assets/lib39/textures/item/neoforge.png b/common/src/main/resources/assets/lib39/textures/item/neoforge.png new file mode 100644 index 0000000..9a58fc6 Binary files /dev/null and b/common/src/main/resources/assets/lib39/textures/item/neoforge.png differ diff --git a/common/src/main/resources/lib39.mixins.json b/common/src/main/resources/lib39.mixins.json new file mode 100644 index 0000000..e622f45 --- /dev/null +++ b/common/src/main/resources/lib39.mixins.json @@ -0,0 +1,18 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "top.r3944realms.lib39.mixin", + "refmap": "${mod_id}.refmap.json", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "carryon.MixinCarriedObjectRender" + ], + "client": [ + ], + "server": [ + ], + "injectors": { + "defaultRequire": 1 + } +} + diff --git a/common/src/main/resources/lib39_logo.png b/common/src/main/resources/lib39_logo.png new file mode 100644 index 0000000..bf1977e Binary files /dev/null and b/common/src/main/resources/lib39_logo.png differ diff --git a/common/src/main/resources/pack.mcmeta b/common/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..52854ec --- /dev/null +++ b/common/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "${mod_name}", + "pack_format": 8 + } +} \ No newline at end of file diff --git a/config/jni-classes.txt b/config/jni-classes.txt deleted file mode 100644 index fc4ab0d..0000000 --- a/config/jni-classes.txt +++ /dev/null @@ -1,8 +0,0 @@ -# JNI 头文件生成配置 -# 每行一个类全限定名,例如: -# com.example.MyNativeClass -# com.example.NativeUtils -top.r3944realms.lib39.core.lang.ClassEncryptor -top.r3944realms.lib39.core.lang.EncryptedClassLoader -# 或者使用正则表达式自动匹配: -# #auto:.*Native.* diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt deleted file mode 100644 index 30c97d4..0000000 --- a/cpp/CMakeLists.txt +++ /dev/null @@ -1,241 +0,0 @@ -cmake_minimum_required(VERSION 3.28) -project(ENCRYPTED_CPP VERSION 1.0.0) - -# 设置C++标准 -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - -# ========== 目录设置 ========== -# 项目根目录 -set(PROJECT_ROOT "${CMAKE_SOURCE_DIR}") - -# 源代码目录 - 使用cpp-src目录 -if(EXISTS "${PROJECT_ROOT}/cpp-src") - set(SOURCE_DIR "${PROJECT_ROOT}/cpp-src") - set(HEADER_DIR "${PROJECT_ROOT}/cpp-src") - message(STATUS "Using cpp-src directory: ${CPP_SRC_DIR}") -else() - # 如果没有cpp-src目录,使用当前目录 - set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") - set(HEADER_DIR "${CMAKE_CURRENT_SOURCE_DIR}") - message(STATUS "Using current directory as source directory") -endif() - -# ========== 手动设置Java路径 ========== -# 查找Java HOME -if(DEFINED ENV{JAVA_HOME}) - set(JAVA_HOME $ENV{JAVA_HOME}) - message(STATUS "Found JAVA_HOME: ${JAVA_HOME}") -else() - # 尝试通过which或where命令查找java - if(WIN32) - execute_process( - COMMAND where java - OUTPUT_VARIABLE JAVA_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - else() - execute_process( - COMMAND which java - OUTPUT_VARIABLE JAVA_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - endif() - - if(JAVA_PATH) - # 从java路径推断JAVA_HOME - get_filename_component(JAVA_HOME "${JAVA_PATH}" DIRECTORY) - get_filename_component(JAVA_HOME "${JAVA_HOME}" DIRECTORY) - message(STATUS "Inferred JAVA_HOME: ${JAVA_HOME}") - else() - message(WARNING "Java not found in PATH") - set(JAVA_HOME "C:/Program Files/Java/jdk-21") # Windows默认路径 - endif() -endif() - -# 设置Java包含目录 -set(JAVA_INCLUDE_DIRS - "${JAVA_HOME}/include" -) - -# 添加平台特定include目录 -if(WIN32) - list(APPEND JAVA_INCLUDE_DIRS "${JAVA_HOME}/include/win32") -elseif(APPLE) - list(APPEND JAVA_INCLUDE_DIRS "${JAVA_HOME}/include/darwin") -else() - list(APPEND JAVA_INCLUDE_DIRS "${JAVA_HOME}/include/linux") -endif() - -# 验证Java包含目录是否存在 -foreach(dir IN LISTS JAVA_INCLUDE_DIRS) - if(NOT EXISTS "${dir}") - message(WARNING "Java include directory not found: ${dir}") - else() - message(STATUS "Found Java include: ${dir}") - endif() -endforeach() - -# ========== 查找源文件和头文件 ========== -# 查找所有的C++源文件 -file(GLOB SOURCE_FILES - "${SOURCE_DIR}/*.cpp" - "${SOURCE_DIR}/*.cxx" -) - -if(NOT SOURCE_FILES) - # 如果没找到,尝试递归查找 - file(GLOB_RECURSE SOURCE_FILES - "${SOURCE_DIR}/*.cpp" - "${SOURCE_DIR}/*.cxx" - "${SOURCE_DIR}/*.cc" - ) -endif() - -if(SOURCE_FILES) - message(STATUS "Found source files:") - foreach(file ${SOURCE_FILES}) - message(STATUS " ${file}") - endforeach() -else() - message(FATAL_ERROR "No source files found in ${SOURCE_DIR}") -endif() - -# 查找头文件 -file(GLOB HEADER_FILES - "${HEADER_DIR}/*.h" - "${HEADER_DIR}/*.hpp" -) - -# ========== 创建库目标 ========== -# 库名称 -if(WIN32) - set(LIBRARY_NAME "ClassEncrypt") -else() - set(LIBRARY_NAME "ClassEncrypt") -endif() - -# 添加库目标 -add_library(${LIBRARY_NAME} SHARED ${SOURCE_FILES} - "src/EnhancedEncryptionMagic.cpp" - src/guard/JByteArrayGuard.cpp) - -# 设置输出名称和位置 -set_target_properties(${LIBRARY_NAME} PROPERTIES - OUTPUT_NAME ${LIBRARY_NAME} - DEBUG_POSTFIX "d" -) - -# 设置输出目录 -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) - -# ========== 包含目录 ========== -target_include_directories(${LIBRARY_NAME} PRIVATE - ${JAVA_INCLUDE_DIRS} - ${HEADER_DIR} -) - -# ========== 编译选项 ========== -if(MSVC) - target_compile_options(${LIBRARY_NAME} PRIVATE - /W3 # 警告级别3 - /WX- # 不将警告视为错误 - /EHsc # C++异常处理 - $<$:/MDd> # 调试版本使用MDd - $<$:/MD> # 发布版本使用MD - $<$:/MD> - $<$:/MD> - ) - - # MSVC预处理器定义 - target_compile_definitions(${LIBRARY_NAME} PRIVATE - _CRT_SECURE_NO_WARNINGS - _WINSOCK_DEPRECATED_NO_WARNINGS - BUILDING_DLL - JNIEXPORT=__declspec(dllexport) - ) -else() - target_compile_options(${LIBRARY_NAME} PRIVATE - -Wall - -Wextra - -Wno-unused-parameter - -fPIC - $<$:-g -O0> - $<$:-O2> - $<$:-O2 -g> - $<$:-Os> - ) - - # GCC/Clang预处理器定义 - target_compile_definitions(${LIBRARY_NAME} PRIVATE - BUILDING_DLL - ) - - if(APPLE) - target_compile_options(${LIBRARY_NAME} PRIVATE - -stdlib=libc++ - ) - endif() -endif() - -# ========== 链接库 ========== -if(WIN32) - # Windows链接库 - target_link_libraries(${LIBRARY_NAME} PRIVATE - kernel32.lib - user32.lib - gdi32.lib - winspool.lib - shell32.lib - ole32.lib - oleaut32.lib - uuid.lib - comdlg32.lib - advapi32.lib - ) -else() - # Linux/macOS链接库 - target_link_libraries(${LIBRARY_NAME} PRIVATE - pthread - dl - ) - - if(APPLE) - target_link_options(${LIBRARY_NAME} PRIVATE - -undefined dynamic_lookup - ) - endif() -endif() - -# ========== 安装配置(可选) ========== -if(NOT DEFINED CMAKE_SKIP_INSTALL_RULES) - install(TARGETS ${LIBRARY_NAME} - LIBRARY DESTINATION lib - RUNTIME DESTINATION bin - ARCHIVE DESTINATION lib - ) - - # 安装头文件 - if(HEADER_FILES) - install(FILES ${HEADER_FILES} DESTINATION include) - endif() -endif() - -# ========== 输出总结信息 ========== -message(STATUS "") -message(STATUS "=========================================") -message(STATUS "Project Configuration Summary") -message(STATUS "=========================================") -message(STATUS "Project: ${PROJECT_NAME}") -message(STATUS "Version: ${PROJECT_VERSION}") -message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") -message(STATUS "Source directory: ${SOURCE_DIR}") -message(STATUS "Header directory: ${HEADER_DIR}") -message(STATUS "Java include dirs: ${JAVA_INCLUDE_DIRS}") -message(STATUS "Target library: ${LIBRARY_NAME}") -message(STATUS "C++ standard: ${CMAKE_CXX_STANDARD}") -message(STATUS "Output directory: ${CMAKE_BINARY_DIR}") -message(STATUS "=========================================") \ No newline at end of file diff --git a/cpp/Config.cmake.in b/cpp/Config.cmake.in deleted file mode 100644 index d02f9f4..0000000 --- a/cpp/Config.cmake.in +++ /dev/null @@ -1,17 +0,0 @@ -# Config.cmake.in -@PACKAGE_INIT@ - -include(CMakeFindDependencyMacro) - -# 查找依赖 -find_dependency(Java COMPONENTS Development) - -if(@USE_OPENSSL@) - find_dependency(OpenSSL REQUIRED) -endif() - -# 导入目标 -include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") - -# 检查目标是否存在 -check_required_components(@PROJECT_NAME@) \ No newline at end of file diff --git a/cpp/header/CMakeLists.txt b/cpp/header/CMakeLists.txt deleted file mode 100644 index 38b882e..0000000 --- a/cpp/header/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -cmake_minimum_required(VERSION 3.28) - -add_library( HEADER - top_r3944realms_lib39_core_lang_ClassEncryptor.h - top_r3944realms_lib39_core_lang_EncryptedClassLoader.h -) \ No newline at end of file diff --git a/cpp/header/top_r3944realms_lib39_core_lang_ClassEncryptor.h b/cpp/header/top_r3944realms_lib39_core_lang_ClassEncryptor.h deleted file mode 100644 index 3998ee3..0000000 --- a/cpp/header/top_r3944realms_lib39_core_lang_ClassEncryptor.h +++ /dev/null @@ -1,37 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class top_r3944realms_lib39_core_lang_ClassEncryptor */ - -#ifndef _Included_top_r3944realms_lib39_core_lang_ClassEncryptor -#define _Included_top_r3944realms_lib39_core_lang_ClassEncryptor -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: top_r3944realms_lib39_core_lang_ClassEncryptor - * Method: encryptClass - * Signature: ([BLjava/lang/String;)[B - */ -JNIEXPORT jbyteArray JNICALL Java_top_r3944realms_lib39_core_lang_ClassEncryptor_encryptClass - (JNIEnv *, jobject, jbyteArray, jstring); - -/* - * Class: top_r3944realms_lib39_core_lang_ClassEncryptor - * Method: decryptClass - * Signature: ([BLjava/lang/String;)[B - */ -JNIEXPORT jbyteArray JNICALL Java_top_r3944realms_lib39_core_lang_ClassEncryptor_decryptClass - (JNIEnv *, jobject, jbyteArray, jstring); - -/* - * Class: top_r3944realms_lib39_core_lang_ClassEncryptor - * Method: isEncryptedFile - * Signature: ([B)Z - */ -JNIEXPORT jboolean JNICALL Java_top_r3944realms_lib39_core_lang_ClassEncryptor_isEncryptedFile - (JNIEnv *, jobject, jbyteArray); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/cpp/header/top_r3944realms_lib39_core_lang_EncryptedClassLoader.h b/cpp/header/top_r3944realms_lib39_core_lang_EncryptedClassLoader.h deleted file mode 100644 index 29284f9..0000000 --- a/cpp/header/top_r3944realms_lib39_core_lang_EncryptedClassLoader.h +++ /dev/null @@ -1,21 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class top_r3944realms_lib39_core_lang_EncryptedClassLoader */ - -#ifndef _Included_top_r3944realms_lib39_core_lang_EncryptedClassLoader -#define _Included_top_r3944realms_lib39_core_lang_EncryptedClassLoader -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: top_r3944realms_lib39_core_lang_EncryptedClassLoader - * Method: decryptClass - * Signature: ([BLjava/lang/String;)[B - */ -JNIEXPORT jbyteArray JNICALL Java_top_r3944realms_lib39_core_lang_EncryptedClassLoader_decryptClass - (JNIEnv *, jobject, jbyteArray, jstring); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/cpp/lib/CMakeLists.txt b/cpp/lib/CMakeLists.txt deleted file mode 100644 index b55c47b..0000000 --- a/cpp/lib/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -cmake_minimum_required(VERSION 3.28) diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt deleted file mode 100644 index 1b02383..0000000 --- a/cpp/src/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -cmake_minimum_required(VERSION 3.28) - - -add_subdirectory(header) -add_subdirectory(lib) - -set_target_properties(CONST_LIB PROPERTIES LINKER_LANGUAGE CXX) \ No newline at end of file diff --git a/cpp/src/EnhancedEncryptionMagic.cpp b/cpp/src/EnhancedEncryptionMagic.cpp deleted file mode 100644 index 33c9ef4..0000000 --- a/cpp/src/EnhancedEncryptionMagic.cpp +++ /dev/null @@ -1,451 +0,0 @@ -#pragma clang diagnostic push -#pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions" -#pragma once -#include -#include -#include -#ifndef HEADER_JNI_H_ -#define HEADER_JNI_H_ -#include -#endif -#ifndef HEADER_P_H_ -#define HEADER_P_H_ -#include "guard/JByteArrayGuard.cpp" -#endif -#include - -// 字节序转换宏(跨平台) -#pragma clang diagnostic push -#pragma ide diagnostic ignored "UnreachableCallsOfFunction" -#if defined(_WIN32) -#include -#pragma commen+t(lib, "ws2_32.lib") - -#define htobe32(x) htonl(x) -#define be32toh(x) ntohl(x) -#define htobe16(x) htons(x) -#define be16toh(x) ntohs(x) - -// Windows下64位字节序转换需要自己实现 -static inline uint64_t htobe64(uint64_t x) { -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - return ((uint64_t)htonl(x & 0xFFFFFFFF) << 32) | htonl(x >> 32); -#else - return x; -#endif -} - -static inline uint64_t be64toh(uint64_t x) { -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - return ((uint64_t)ntohl(x & 0xFFFFFFFF) << 32) | ntohl(x >> 32); -#else - return x; -#endif -} - -#elif defined(__APPLE__) || defined(__FreeBSD__) -#include - #define htobe32(x) OSSwapHostToBigInt32(x) - #define be32toh(x) OSSwapBigToHostInt32(x) - #define htobe16(x) OSSwapHostToBigInt16(x) - #define be16toh(x) OSSwapBigToHostInt16(x) - #define htobe64(x) OSSwapHostToBigInt64(x) - #define be64toh(x) OSSwapBigToHostInt64(x) - -#elif defined(__linux__) || defined(__ANDROID__) - #include - // Linux下endian.h已经定义了这些宏 - -#else - // 通用实现 - #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - // GCC/Clang内置函数 - #define htobe32(x) __builtin_bswap32(x) - #define be32toh(x) __builtin_bswap32(x) - #define htobe16(x) __builtin_bswap16(x) - #define be16toh(x) __builtin_bswap16(x) - #define htobe64(x) __builtin_bswap64(x) - #define be64toh(x) __builtin_bswap64(x) - #else - #define htobe32(x) (x) - #define be32toh(x) (x) - #define htobe16(x) (x) - #define be16toh(x) (x) - #define htobe64(x) (x) - #define be64toh(x) (x) - #endif -#endif - -// 手动字节序转换函数(备用) -static inline uint64_t manual_htobe64(uint64_t x) { -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - return ((uint64_t)__builtin_bswap32(x & 0xFFFFFFFF) << 32) | __builtin_bswap32(x >> 32); -#else - return x; -#endif -} - -static inline uint64_t manual_be64toh(uint64_t x) { - return manual_htobe64(x); // 对称操作 -} - -namespace EnhancedEncryptionMagic { - // 主魔数:0x4C494233 (ASCII: "LIB3") - static const uint32_t MAGIC = 0x4C494233; // "LIB3" in hex - - // 文件头结构 - 调整为实际大小 - struct EnhancedFileHeader { - uint32_t magic; // 魔数: 0x4C494233 "LIB3" (4字节) - uint16_t version_major; // 主版本号 (2字节) - uint16_t version_minor; // 次版本号 (2字节) - uint32_t flags; // 标志位 (4字节) - uint32_t original_size; // 原始数据大小 (4字节) - uint32_t encrypted_size; // 加密数据大小 (4字节) - uint64_t timestamp; // 时间戳 (8字节) - uint32_t checksum; // 校验和 (4字节) - uint32_t reserved; // 保留字段 (4字节) - // 总计: 4+2+2+4+4+4+8+4+4 = 36字节 - - // 编译器可能添加4字节填充到40字节,但我们应该按36字节处理 - }; - - // 计算实际结构体大小 - static const size_t CALCULATED_HEADER_SIZE = - sizeof(uint32_t) + // magic - sizeof(uint16_t) + // version_major - sizeof(uint16_t) + // version_minor - sizeof(uint32_t) + // flags - sizeof(uint32_t) + // original_size - sizeof(uint32_t) + // encrypted_size - sizeof(uint64_t) + // timestamp - sizeof(uint32_t) + // checksum - sizeof(uint32_t); // reserved - - static const size_t HEADER_SIZE = CALCULATED_HEADER_SIZE; - - // 标志位定义 - namespace Flags { - static const uint32_t COMPRESSED = 0x00000001; // 是否压缩 - static const uint32_t SIGNED = 0x00000002; // 是否签名 - static const uint32_t ENCRYPTED = 0x00000004; // 是否加密 - static const uint32_t VALIDATED = 0x00000008; // 是否验证 - }; - - // 创建文件头 - static inline EnhancedFileHeader createHeader(uint32_t originalSize, uint32_t encryptedSize) { - EnhancedFileHeader header; - memset(&header, 0, sizeof(header)); // 清零初始化 - - header.magic = MAGIC; - header.version_major = 1; - header.version_minor = 0; - header.flags = Flags::ENCRYPTED; - header.original_size = originalSize; - header.encrypted_size = encryptedSize; - header.timestamp = static_cast(time(nullptr)); - header.checksum = 0; // 将在之后计算 - header.reserved = 0; - - return header; - } - - // 字节序安全的内存复制函数 - static inline void writeUint32(jbyte* buffer, uint32_t value, size_t offset) { - uint32_t networkValue = htobe32(value); - memcpy(buffer + offset, &networkValue, sizeof(uint32_t)); - } - - static inline void writeUint16(jbyte* buffer, uint16_t value, size_t offset) { - uint16_t networkValue = htobe16(value); - memcpy(buffer + offset, &networkValue, sizeof(uint16_t)); - } - - static inline void writeUint64(jbyte* buffer, uint64_t value, size_t offset) { - uint64_t networkValue = htobe64(value); - memcpy(buffer + offset, &networkValue, sizeof(uint64_t)); - } - - static inline uint32_t readUint32(const jbyte* buffer, size_t offset) { - uint32_t networkValue; - memcpy(&networkValue, buffer + offset, sizeof(uint32_t)); - return be32toh(networkValue); - } - - static inline uint16_t readUint16(const jbyte* buffer, size_t offset) { - uint16_t networkValue; - memcpy(&networkValue, buffer + offset, sizeof(uint16_t)); - return be16toh(networkValue); - } - - static inline uint64_t readUint64(const jbyte* buffer, size_t offset) { - uint64_t networkValue; - memcpy(&networkValue, buffer + offset, sizeof(uint64_t)); - return be64toh(networkValue); - } - - // 验证文件头 - static inline bool validateHeader(const EnhancedFileHeader& header) { - return header.magic == MAGIC && - header.version_major == 1 && - header.version_minor == 0 && - (header.flags & Flags::ENCRYPTED) != 0; - } - - // 计算校验和(简单的CRC32替代) - static inline uint32_t calculateChecksum(const jbyte* data, jsize length) { - if (!data || length <= 0) { - return 0; - } - - uint32_t crc = 0xFFFFFFFF; - const uint8_t* bytes = reinterpret_cast(data); - - for (jsize i = 0; i < length; i++) { - crc ^= bytes[i]; - for (int j = 0; j < 8; j++) { - if (crc & 1) { - crc = (crc >> 1) ^ 0xEDB88320; - } else { - crc = crc >> 1; - } - } - } - - return ~crc; - } - - // 更新文件头的校验和 - static inline void updateChecksum(EnhancedFileHeader& header, const jbyte* data, jsize length) { - header.checksum = calculateChecksum(data, length); - } - - // 验证数据校验和 - static inline bool verifyChecksum(const EnhancedFileHeader& header, const jbyte* data, jsize length) { - uint32_t calculated = calculateChecksum(data, length); - return header.checksum == calculated; - } - - // 将文件头写入字节数组(使用网络字节序) - static inline void writeHeaderToBytes(const EnhancedFileHeader& header, jbyte* buffer) { - if (!buffer) return; - - size_t offset = 0; - writeUint32(buffer, header.magic, offset); offset += 4; - writeUint16(buffer, header.version_major, offset); offset += 2; - writeUint16(buffer, header.version_minor, offset); offset += 2; - writeUint32(buffer, header.flags, offset); offset += 4; - writeUint32(buffer, header.original_size, offset); offset += 4; - writeUint32(buffer, header.encrypted_size, offset); offset += 4; - writeUint64(buffer, header.timestamp, offset); offset += 8; - writeUint32(buffer, header.checksum, offset); offset += 4; - writeUint32(buffer, header.reserved, offset); offset += 4; - } - - // 从字节数组读取文件头 - static inline EnhancedFileHeader readHeaderFromBytes(const jbyte* buffer) { - EnhancedFileHeader header{}; - memset(&header, 0, sizeof(header)); - - if (!buffer) return header; - - size_t offset = 0; - header.magic = readUint32(buffer, offset); offset += 4; - header.version_major = readUint16(buffer, offset); offset += 2; - header.version_minor = readUint16(buffer, offset); offset += 2; - header.flags = readUint32(buffer, offset); offset += 4; - header.original_size = readUint32(buffer, offset); offset += 4; - header.encrypted_size = readUint32(buffer, offset); offset += 4; - header.timestamp = readUint64(buffer, offset); offset += 8; - header.checksum = readUint32(buffer, offset); offset += 4; - header.reserved = readUint32(buffer, offset); offset += 4; - - return header; - } - - // 将文件头格式化为可读字符串 - static inline std::string headerToString(const EnhancedFileHeader& header) { - char magicStr[5] = {0}; - memcpy(magicStr, &header.magic, 4); - - char buffer[256]; - snprintf(buffer, sizeof(buffer), - "Magic: %s (0x%08X)\n" - "Version: %d.%d\n" - "Flags: 0x%08X\n" - "Original Size: %u bytes\n" - "Encrypted Size: %u bytes\n" - "Timestamp: %llu\n" - "Checksum: 0x%08X\n" - "Reserved: 0x%08X", - magicStr, header.magic, - header.version_major, header.version_minor, - header.flags, - header.original_size, - header.encrypted_size, - (unsigned long long)header.timestamp, - header.checksum, - header.reserved); - - return std::string(buffer); - } - - // 验证文件是否完整 - static inline bool validateFileIntegrity(const EnhancedFileHeader& header, - const jbyte* encryptedData, - jsize encryptedDataSize) { - // 检查大小是否匹配 - if (header.encrypted_size != encryptedDataSize) { - return false; - } - - // 检查校验和 - return verifyChecksum(header, encryptedData, encryptedDataSize); - } - - // 加密函数指针类型 - typedef void (*EncryptFunc)(jbyte*, jsize, const char*, int); - - // 创建完整的加密文件 - static inline jbyteArray createEncryptedFile(JNIEnv* env, - const jbyte* originalData, - jsize originalSize, - const char* key, - size_t keyLen, - EncryptFunc encryptFunc) { - - if (!env || !originalData || originalSize <= 0 || !key || keyLen <= 0 || !encryptFunc) { - return nullptr; - } - - // 1. 创建加密数据数组 - jbyteArray encryptedDataArray = env->NewByteArray(originalSize); - if (!encryptedDataArray) { - return nullptr; - } - - { - // 使用局部作用域确保 encryptedDataGuard 在加密后释放 - JByteArrayGuard encryptedDataGuard(env, encryptedDataArray, false, JNI_ABORT); - if (!encryptedDataGuard.isValid()) { - env->DeleteLocalRef(encryptedDataArray); - return nullptr; - } - - // 复制并加密数据 - memcpy(encryptedDataGuard.get(), originalData, originalSize); - encryptFunc(encryptedDataGuard.get(), originalSize, key, keyLen); - - // encryptedDataGuard 析构函数会自动以 JNI_ABORT 模式释放 - } - - // 注意:这里已经释放了加密数据,需要重新获取 - JByteArrayGuard encryptedDataGuard2(env, encryptedDataArray); - if (!encryptedDataGuard2.isValid()) { - env->DeleteLocalRef(encryptedDataArray); - return nullptr; - } - - // 2. 创建文件头 - EnhancedFileHeader header = createHeader(originalSize, originalSize); - updateChecksum(header, encryptedDataGuard2.get(), originalSize); - - // 3. 创建最终结果 - jsize totalSize = HEADER_SIZE + originalSize; - jbyteArray result = env->NewByteArray(totalSize); - if (!result) { - return nullptr; - } - - JByteArrayGuard resultGuard(env, result); - if (!resultGuard.isValid()) { - env->DeleteLocalRef(result); - return nullptr; - } - - // 4. 写入数据 - writeHeaderToBytes(header, resultGuard.get()); - memcpy(resultGuard.get() + HEADER_SIZE, encryptedDataGuard2.get(), originalSize); - - // 5. 显式提交修改 - resultGuard.commit(); // 提交修改到 Java 端 - // resultGuard 析构时不会再释放 - - // 6. 清理中间数组 - env->DeleteLocalRef(encryptedDataArray); - - return result; - } - - // 从加密文件中提取数据 - static inline jbyteArray extractFromEncryptedFile(JNIEnv* env, - const jbyte* fileData, - jsize fileSize, - const char* key, - size_t keyLen, - EncryptFunc decryptFunc, - bool* isValid) { - - if (isValid) *isValid = false; - - if (!env || !fileData || fileSize < HEADER_SIZE || !key || keyLen <= 0 || !decryptFunc) { - return nullptr; - } - - // 读取文件头 - EnhancedFileHeader header = readHeaderFromBytes(fileData); - - // 验证文件头 - if (!validateHeader(header)) { - return nullptr; - } - - // 检查文件大小 - jsize expectedSize = HEADER_SIZE + header.encrypted_size; - if (fileSize != expectedSize) { - return nullptr; - } - - // 提取加密数据 - const jbyte* encryptedData = fileData + HEADER_SIZE; - - // 验证完整性 - if (!validateFileIntegrity(header, encryptedData, header.encrypted_size)) { - return nullptr; - } - - // 创建结果数组 - jbyteArray result = env->NewByteArray(header.original_size); - if (!result) { - return nullptr; - } - JByteArrayGuard resultDataGuard(env, result); - - if (!resultDataGuard.isValid()) { - env->DeleteLocalRef(result); - return nullptr; - } - - jbyte* resultData = resultDataGuard.get(); - - // 复制加密数据 - memcpy(resultData, encryptedData, header.original_size); - - // 解密数据 - decryptFunc(resultData, header.original_size, key, keyLen); - - if (isValid) *isValid = true; - return result; - } - - // 验证是否为加密文件(不读取整个文件) - static inline bool isEncryptedFile(const jbyte* fileData, jsize fileSize) { - if (fileSize < HEADER_SIZE || !fileData) { - return false; - } - - EnhancedFileHeader header = readHeaderFromBytes(fileData); - return validateHeader(header); - } -} -#pragma clang diagnostic pop -#pragma clang diagnostic pop \ No newline at end of file diff --git a/cpp/src/SimpleClassEncrypt.cpp b/cpp/src/SimpleClassEncrypt.cpp deleted file mode 100644 index 22b6ee3..0000000 --- a/cpp/src/SimpleClassEncrypt.cpp +++ /dev/null @@ -1,234 +0,0 @@ -#pragma once -#include -#include -#include
-#include
-#include "EnhancedEncryptionMagic.cpp" - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wsign-compare" -#ifdef _WIN32 -#include -#include -#include - -#define JNIEXPORT __declspec(dllexport) -#else -#include - #define JNIEXPORT -#endif - -/** - * 简单的XOR加密/解密 - */ -static void xorEncrypt(jbyte* data, jsize dataLen, const char* key, int keyLen) { - if (!data || !key || keyLen == 0) return; - - for (jsize i = 0; i < dataLen; i++) { - data[i] ^= key[i % keyLen]; - } -} - -static int safeStrlen(const char* str) { - return str ? (int)strlen(str) : 0; -} - -/** - * 验证数据是否为有效的Java类文件 - * @param data 字节码 - * @param dataLen 字节码长度 - * @return 是否有效 - */ -static bool isValidJavaClass(const jbyte* data, jsize dataLen) { - // Java类文件魔数:0xCAFEBABE - return dataLen >= 4 && - data[0] == (jbyte)0xCA && - data[1] == (jbyte)0xFE && - data[2] == (jbyte)0xBA && - data[3] == (jbyte)0xBE; -} - -/** - * 记录错误信息 - */ -void logError(const char* message) { -#ifdef DEBUG - std::cerr << "[JNI Error] " << message << std::endl; -#endif -} - -/** - * 安全的字符串转换 - */ -std::string jstringToString(JNIEnv* env, jstring jstr) { - if (!jstr) return ""; - - const char* chars = env->GetStringUTFChars(jstr, nullptr); - if (!chars) return ""; - - std::string result(chars); - env->ReleaseStringUTFChars(jstr, chars); - return result; -} - -uint32_t calculateChecksum(const jbyte* data, jsize length) { - uint32_t checksum = 0; - for (jsize i = 0; i < length; i++) { - checksum += static_cast(data[i]); - checksum = (checksum << 1) | (checksum >> 31); // 简单旋转 - } - return checksum; -} - - -// ==================== JNI函数实现 ==================== -using namespace EnhancedEncryptionMagic; - -/* - * Class: top_r3944realms_lib39_core_lang_ClassEncryptor - * Method: decryptClass - * Signature: ([BLjava/lang/String;)[B - */ -JNIEXPORT jbyteArray JNICALL Java_top_r3944realms_lib39_core_lang_ClassEncryptor_decryptClass - (JNIEnv *env, jobject obj, jbyteArray encryptedData, jstring key) { - - if (!encryptedData || !key) { - return nullptr; - } - - jsize fileSize = env->GetArrayLength(encryptedData); - if (fileSize == 0) { - return nullptr; - } - JByteArrayGuard fileDataGuard(env, encryptedData); - jbyte* fileData = fileDataGuard.get(); - if (!fileDataGuard.isValid()) { - return nullptr; - } - std::string keyStr = jstringToString(env, key); - size_t keyLen = keyStr.length(); - bool isValid = keyLen > 0; - // 尝试从加密文件中提取数据 - jbyteArray result = EnhancedEncryptionMagic::extractFromEncryptedFile( - env, fileData, fileSize, keyStr.c_str(), keyLen, xorEncrypt, &isValid); - if (!isValid || !result) { - // 如果不是有效的加密文件,返回原始数据 - return encryptedData; - } - - // 验证解密后的数据是否为有效的Java类文件 - JByteArrayGuard resultGuard(env, result); - jsize resultLen = resultGuard.size(); - jbyte* resultData = resultGuard.get(); - - if (!resultGuard.isValid()) { - env->DeleteLocalRef(result); - return nullptr; - } - - bool validClass = isValidJavaClass(resultData, resultLen); - - if (!validClass) { - env->DeleteLocalRef(result); - return nullptr; // 解密后的数据不是有效的Java类 - } - - return result; -} - - -/* - * Class: top_r3944realms_lib39_core_lang_ClassEncryptor - * Method: encryptClass - * Signature: ([BLjava/lang/String;)[B - */ -JNIEXPORT jbyteArray JNICALL Java_top_r3944realms_lib39_core_lang_ClassEncryptor_encryptClass - (JNIEnv *env, jobject obj, jbyteArray classData, jstring key) { - - if (!classData || !key) { - return nullptr; - } - - JByteArrayGuard jGuard(env, classData); - if (jGuard.isValid()) { - return nullptr; - } - - jsize dataLen = jGuard.size(); - jbyte* data = jGuard.get(); - - const char* keyStr = env->GetStringUTFChars(key, nullptr); - if (!keyStr) { - return nullptr; - } - - // 验证输入数据是否为有效的Java类文件 - if (!isValidJavaClass(data, dataLen)) { - env->ReleaseStringUTFChars(key, keyStr); - return nullptr; - } - - int keyLen = safeStrlen(keyStr); - - // 使用增强版创建加密文件 - jbyteArray result = EnhancedEncryptionMagic::createEncryptedFile( - env, data, dataLen, keyStr, keyLen, xorEncrypt); - - env->ReleaseStringUTFChars(key, keyStr); - - return result; -} - -/* - * Class: top_r3944realms_lib39_core_lang_ClassEncryptor - * Method: isEncryptedFile - * Signature: ([B)Z - */ -JNIEXPORT jboolean JNICALL Java_top_r3944realms_lib39_core_lang_ClassEncryptor_isEncryptedFile - (JNIEnv *env, jobject obj, jbyteArray fileData) { - - if (!fileData) { - return JNI_FALSE; - } - JByteArrayGuard dataGuard(env, fileData); - if (!dataGuard.isValid()) { - return JNI_FALSE; - } - - if (dataGuard.size() < EnhancedEncryptionMagic::HEADER_SIZE) { - return JNI_FALSE; - } - - // 验证是否为加密文件 - bool isEncrypted = EnhancedEncryptionMagic::isEncryptedFile(dataGuard.get(), dataGuard.size()); - - return isEncrypted ? JNI_TRUE : JNI_FALSE; -} - -/* - * Class: top_r3944realms_lib39_core_lang_EncryptedClassLoader - * Method: decryptClass - * Signature: ([BLjava/lang/String;)[B - */ -JNIEXPORT jbyteArray JNICALL Java_top_r3944realms_lib39_core_lang_EncryptedClassLoader_decryptClass - (JNIEnv *env, jobject obj, jbyteArray encryptedData, jstring key) { - return Java_top_r3944realms_lib39_core_lang_ClassEncryptor_decryptClass(env, obj, encryptedData, key); -} - -// JNI库初始化和卸载函数 -JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { -#ifdef _WIN32 - // Windows下需要初始化Winsock - WSADATA wsaData; - WSAStartup(MAKEWORD(2, 2), &wsaData); -#endif - - return JNI_VERSION_1_8; -} - -JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) { -#ifdef _WIN32 - WSACleanup(); -#endif -} -#pragma clang diagnostic pop \ No newline at end of file diff --git a/cpp/src/guard/CMakeLists.txt b/cpp/src/guard/CMakeLists.txt deleted file mode 100644 index 39bdf67..0000000 --- a/cpp/src/guard/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -cmake_minimum_required(VERSION 3.28) - -add_library(J_GUARD - JByteArrayGuard.cpp -) \ No newline at end of file diff --git a/cpp/src/guard/JByteArrayGuard.cpp b/cpp/src/guard/JByteArrayGuard.cpp deleted file mode 100644 index 13a0413..0000000 --- a/cpp/src/guard/JByteArrayGuard.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once -#ifndef HEADER_JNI_H_ -#define HEADER_JNI_H_ -#include -#endif -class JByteArrayGuard { -private: - JNIEnv* env; - jbyteArray array; - jbyte* data; - jsize length; - bool isCritical; - jint releaseMode; -public: - JByteArrayGuard(JNIEnv* env, jbyteArray array, bool critical = false, jint releaseMode = 0) - : env(env), array(array), data(nullptr), length(0), isCritical(critical), releaseMode(releaseMode) - { - if (array) { - length = env->GetArrayLength(array); - if (isCritical) { - data = (jbyte*) env->GetPrimitiveArrayCritical(array, nullptr); - } else { - data = env->GetByteArrayElements(array, nullptr); - } - } - } - ~JByteArrayGuard() { - release(); - } - // 显式释放方法 - void release() { - if (data && array) { - if (isCritical) { - env->ReleasePrimitiveArrayCritical(array, data, releaseMode); - } else { - env->ReleaseByteArrayElements(array, data, releaseMode); - } - data = nullptr; // 防止重复释放 - array = nullptr; - } - } - - // 提交修改但不释放(用于返回结果的情况) - void commit() { - if (data && array) { - if (isCritical) { - env->ReleasePrimitiveArrayCritical(array, data, 0); - } else { - env->ReleaseByteArrayElements(array, data, 0); - } - data = nullptr; // 标记为已释放,防止析构函数再次释放 - array = nullptr; - } - } - - // 丢弃修改 - void abort() { - if (data && array) { - if (isCritical) { - env->ReleasePrimitiveArrayCritical(array, data, JNI_ABORT); - } else { - env->ReleaseByteArrayElements(array, data, JNI_ABORT); - } - data = nullptr; - array = nullptr; - } - } - jbyte* get() { return data; } - jsize size() const { return length; } - bool isValid() { return data != nullptr; } - - JByteArrayGuard(const JByteArrayGuard&) = delete; - JByteArrayGuard& operator=(const JByteArrayGuard&) = delete; - JByteArrayGuard(JByteArrayGuard&& other) noexcept - : env(other.env), array(other.array), data(other.data), - length(other.length), isCritical(other.isCritical), - releaseMode(other.releaseMode) { - other.array = nullptr; - other.data = nullptr; - other.length = 0; - } -}; \ No newline at end of file diff --git a/fabric/build.gradle b/fabric/build.gradle new file mode 100644 index 0000000..464db32 --- /dev/null +++ b/fabric/build.gradle @@ -0,0 +1,170 @@ +import net.fabricmc.loom.task.RemapJarTask + +plugins { + id 'multiloader-loader' + id 'fabric-loom' +} +dependencies { + minecraft "com.mojang:minecraft:${minecraft_version}" + mappings loom.layered { + officialMojangMappings() + parchment("org.parchmentmc.data:parchment-${parchment_minecraft}:${parchment_version}@zip") + } + modImplementation "net.fabricmc:fabric-loader:${fabric_loader_version}" + modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_version}" + implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.1' + implementation project(":common") +} + +loom { + if (project(":common").file("src/main/resources/${mod_id}.accesswidener").exists()) { + accessWidenerPath.set(project(":common").file("src/main/resources/${mod_id}.accesswidener")) + } + mixin { + defaultRefmapName.set("${mod_id}.refmap.json") + } + runs { + client { + client() + setConfigName("Fabric Client") + ideConfigGenerated(true) + runDir("run") + } + server { + server() + setConfigName("Fabric Server") + ideConfigGenerated(true) + runDir("run") + } + } +} +tasks.named('sourcesJar', Jar) { + dependsOn classes + dependsOn project(':common').tasks.named('sourcesJar') // 显式依赖common的source + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + archiveClassifier.set('sources') + from sourceSets.main.allSource + from project(':common').sourceSets.main.allSource +} + +// 配置javadoc任务 +tasks.named('javadoc', Javadoc) { + source project(':common').sourceSets.main.allJava + dependsOn project(':common').tasks.named('javadoc') // 显式依赖common的javadoc + source sourceSets.main.allJava + classpath = configurations.compileClasspath + classpath += project(':common').sourceSets.main.compileClasspath + options.encoding = 'UTF-8' + options.charSet = 'UTF-8' + options.links("https://docs.oracle.com/en/java/javase/17/docs/api/") + options.memberLevel = JavadocMemberLevel.PUBLIC + options.addBooleanOption('Xdoclint:none', true) + options.addStringOption('doctitle', "${mod_id} ${minecraft_version} ${version} Javadoc") +} + +// 配置javadocJar任务 +tasks.named('javadocJar', Jar) { + dependsOn javadoc + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + archiveClassifier.set('javadoc') + from javadoc.destinationDir + from project(':common').javadoc.destinationDir +} + +// 确保build任务包含所有需要的jar +tasks.named('build') { + dependsOn tasks.named('sourcesJar') + dependsOn tasks.named('javadocJar') +} + +// 配置remap任务以包含sources和javadoc +remapJar { + dependsOn tasks.named('sourcesJar') + dependsOn tasks.named('javadocJar') + inputFile.set(tasks.named('jar').get().archiveFile) + addNestedDependencies = false +} + +remapSourcesJar { + dependsOn tasks.named('sourcesJar') + inputFile.set(tasks.named('sourcesJar').get().archiveFile) +} + +// 为javadocJar创建remap任务 +tasks.register('remapJavadocJar', RemapJarTask) { + dependsOn tasks.named('javadocJar') + inputFile.set(tasks.named('javadocJar').get().archiveFile) + archiveClassifier.set('javadoc') + addNestedDependencies = false +} + +// 将remapped artifacts添加到发布配置 +publishing { + publications { + mavenJava(MavenPublication) { + // 重置artifactsId + artifactId = "${mod_id}-fabric-${minecraft_version}" + artifacts.clear() + // 手动添加需要的artifacts + artifact(remapJar) { + builtBy remapJar + } + artifact(remapSourcesJar) { + builtBy remapSourcesJar + classifier = 'sources' + } + artifact(remapJavadocJar) { + builtBy remapJavadocJar + classifier = 'javadoc' + } + pom { + name = 'Lib39' + description = 'Lib39 is a general-purpose dependency library for Minecraft mods.' + url = 'https://github.com/3944Realms/lib39' + + properties = [ + 'minecraft.version': project.minecraft_version, + 'mod.version': project.version, + 'fabric.version': project.fabric_version, + 'java.version': '17' + ] + + licenses { + license { + name = 'MIT' + url = 'https://raw.githubusercontent.com/3944Realms/lib39/refs/heads/main/LICENSE' + distribution = 'repo' + } + } + + developers { + developer { + id = 'R3944Realms' + name = "${mod_author}" + email = 'f256198830@hotmail.com' + } + } + + scm { + connection = 'scm:git:https://github.com/3944Realms/lib39.git' + developerConnection = 'scm:git:ssh://git@github.com:3944Realms/lib39.git' + url = 'https://github.com/3944Realms/lib39' + tag = 'main' + } + + issueManagement { + system = 'GitHub' + url = 'https://github.com/3944Realms/lib39/issues' + } + } + } + + } +} + +tasks.named('generateMetadataFileForMavenJavaPublication') { + dependsOn tasks.named('remapJavadocJar') + dependsOn tasks.named('remapJar') + dependsOn tasks.named('remapSourcesJar') +} + diff --git a/fabric/src/main/java/top/r3944realms/lib39/Lib39Fabric.java b/fabric/src/main/java/top/r3944realms/lib39/Lib39Fabric.java new file mode 100644 index 0000000..5190a66 --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/Lib39Fabric.java @@ -0,0 +1,18 @@ +package top.r3944realms.lib39; + +import net.fabricmc.api.ModInitializer; + +public class Lib39Fabric implements ModInitializer { + + @Override + public void onInitialize() { + Lib39.initialize(); + // This method is invoked by the Fabric mod loader when it is ready + // to load your mod. You can access Fabric and Common code in this + // project. + + // Use Fabric to bootstrap the Common mod. + Lib39.LOGGER.info("Hello Fabric world!"); + + } +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/core/register/FabricLib39BlockEntities.java b/fabric/src/main/java/top/r3944realms/lib39/core/register/FabricLib39BlockEntities.java new file mode 100644 index 0000000..5e8c6f6 --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/core/register/FabricLib39BlockEntities.java @@ -0,0 +1,17 @@ +package top.r3944realms.lib39.core.register; + +import net.minecraft.world.level.block.entity.BlockEntityType; +import top.r3944realms.lib39.content.block.blockentity.DollBlockEntity; + +import java.util.function.Supplier; + +/** + * The type Lib 39 block entities. + */ +public class FabricLib39BlockEntities { + /** + * The constant DOLL_BLOCK_ENTITY. + */ + public static Supplier> DOLL_BLOCK_ENTITY; + +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/core/register/FabricLib39Blocks.java b/fabric/src/main/java/top/r3944realms/lib39/core/register/FabricLib39Blocks.java new file mode 100644 index 0000000..d4b8f3b --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/core/register/FabricLib39Blocks.java @@ -0,0 +1,17 @@ +package top.r3944realms.lib39.core.register; + +import net.minecraft.world.level.block.Block; + +import java.util.function.Supplier; + +/** + * The type Lib 39 blocks. + */ +public class FabricLib39Blocks { + + /** + * The constant DOLL. + */ + public static Supplier DOLL; + +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/core/register/FabricLib39Items.java b/fabric/src/main/java/top/r3944realms/lib39/core/register/FabricLib39Items.java new file mode 100644 index 0000000..ee97a62 --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/core/register/FabricLib39Items.java @@ -0,0 +1,15 @@ +package top.r3944realms.lib39.core.register; + +import net.minecraft.world.item.Item; + +import java.util.function.Supplier; + +/** + * The type Ex lib 39 items. + */ +public class FabricLib39Items { + /** + * The constant DOLL. + */ + public static Supplier DOLL; +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/core/register/FabricLib39SoundEvents.java b/fabric/src/main/java/top/r3944realms/lib39/core/register/FabricLib39SoundEvents.java new file mode 100644 index 0000000..7aa2a33 --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/core/register/FabricLib39SoundEvents.java @@ -0,0 +1,31 @@ +package top.r3944realms.lib39.core.register; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.SoundEvent; +import top.r3944realms.lib39.Lib39; + +import java.util.function.Supplier; + +/** + * The type Lib 39 sound events. + */ +public class FabricLib39SoundEvents { + /** + * The constant RL_DUCK_TOY. + */ + public static final ResourceLocation RL_DUCK_TOY = Lib39.rl("duck_toy"); + /** + * The constant DUCK_TOY. + */ + public static Supplier DUCK_TOY; + + /** + * Gets sub title translate key. + * + * @param name the name + * @return the sub title translate key + */ + public static String getSubTitleTranslateKey(String name) { + return "sound." + Lib39.MOD_ID + ".subtitle." + name; + } +} diff --git a/fabric/src/main/java/top/r3944realms/lib39/platform/FabricPlatformHelper.java b/fabric/src/main/java/top/r3944realms/lib39/platform/FabricPlatformHelper.java new file mode 100644 index 0000000..8d9d2a2 --- /dev/null +++ b/fabric/src/main/java/top/r3944realms/lib39/platform/FabricPlatformHelper.java @@ -0,0 +1,43 @@ +package top.r3944realms.lib39.platform; + +import net.fabricmc.api.EnvType; +import net.fabricmc.loader.api.ModContainer; +import net.fabricmc.loader.api.metadata.ModMetadata; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.platform.services.IPlatformHelper; +import net.fabricmc.loader.api.FabricLoader; + +import java.util.Objects; + +public class FabricPlatformHelper implements IPlatformHelper { + + @Override + public String getPlatformName() { + return "Fabric"; + } + + @Override + public boolean isModLoaded(String modId) { + return FabricLoader.getInstance().isModLoaded(modId); + } + + @Override + public boolean isDevelopmentEnvironment() { + + return FabricLoader.getInstance().isDevelopmentEnvironment(); + } + + @Override + public boolean isClientEnvironment() { + return FabricLoader.getInstance().getEnvironmentType().equals(EnvType.CLIENT); + } + + @Override + public String getModVersion() { + return FabricLoader.getInstance().getModContainer(Lib39.MOD_ID) + .map(ModContainer::getMetadata) + .map(ModMetadata::getVersion) + .map(Objects::toString) + .orElse("NONE"); + } +} diff --git a/fabric/src/main/resources/META-INF/services/top.r3944realms.lib39.platform.services.IPlatformHelper b/fabric/src/main/resources/META-INF/services/top.r3944realms.lib39.platform.services.IPlatformHelper new file mode 100644 index 0000000..f080f90 --- /dev/null +++ b/fabric/src/main/resources/META-INF/services/top.r3944realms.lib39.platform.services.IPlatformHelper @@ -0,0 +1 @@ +top.r3944realms.lib39.platform.ForgePlatformHelper \ No newline at end of file diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..e40f6ac --- /dev/null +++ b/fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,36 @@ +{ + "schemaVersion": 1, + "id": "mod_id", + "version": "${version}", + "name": "${mod_name}", + "description": "${description}", + "authors": [ + "${mod_author}" + ], + "contact": { + "homepage": "https://fabricmc.net/", + "sources": "https://github.com/FabricMC/fabric-example-mod" + }, + "license": "${license}", + "icon": "lib39_logo.png", + "environment": "*", + "entrypoints": { + "main": [ + "top.r3944realms.lib39.Lib39Fabric" + ] + }, + "mixins": [ + "${mod_id}.mixins.json", + "${mod_id}.fabric.mixins.json" + ], + "depends": { + "fabricloader": ">=0.14", + "fabric": "*", + "minecraft": "${minecraft_version}", + "java": ">=17" + }, + "suggests": { + "another-mod": "*" + } +} + \ No newline at end of file diff --git a/fabric/src/main/resources/lib39.fabric.mixins.json b/fabric/src/main/resources/lib39.fabric.mixins.json new file mode 100644 index 0000000..a514a6f --- /dev/null +++ b/fabric/src/main/resources/lib39.fabric.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "top.r3944realms.lib39.mixin", + "refmap": "${mod_id}.refmap.json", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + ], + "server": [ + ], + "injectors": { + "defaultRequire": 1 + } + } + diff --git a/forge/build.gradle b/forge/build.gradle new file mode 100644 index 0000000..0c09784 --- /dev/null +++ b/forge/build.gradle @@ -0,0 +1,193 @@ +plugins { + id 'multiloader-loader' + id 'net.neoforged.moddev.legacyforge' +} + +mixin { + add(sourceSets.main, "${mod_id}.refmap.json") + + config("${mod_id}.mixins.json") + config("${mod_id}.forge.mixins.json") +} + +legacyForge { + version = "${minecraft_version}-${forge_version}" + + validateAccessTransformers = true + + def at = project(':common').file('src/main/resources/META-INF/accesstransformer.cfg') + def generated = project(':common').file('src/generated/resources/') + if (at.exists()) { + accessTransformers = ["src/main/resources/META-INF/accesstransformer.cfg"] + } + parchment { + minecraftVersion = parchment_minecraft + mappingsVersion = parchment_version + } + runs { + client { + client() + } + data { + data() + programArguments.addAll '--mod', project.mod_id, '--all', '--output', generated.getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + } + server { + server() + } + } + + mods { + "${mod_id}" { + sourceSet sourceSets.main + } + } +} + +sourceSets.main.resources.srcDir project(':common').file('src/generated/resources') + +dependencies { + compileOnly project(":common") + annotationProcessor("org.spongepowered:mixin:0.8.5-SNAPSHOT:processor") + implementation(jarJar("io.github.llamalad7:mixinextras-forge:0.2.0")) +} + +jar { + finalizedBy('reobfJar') + manifest.attributes([ + "MixinConfigs": "${mod_id}.mixins.json,${mod_id}.forge.mixins.json" + ]) +} + +// 配置sourceJar任务 +tasks.named('sourcesJar', Jar) { + dependsOn classes + dependsOn project(':common').tasks.named('sourcesJar') // 显式依赖common的source + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + archiveClassifier.set('sources') + from sourceSets.main.allSource + from project(':common').sourceSets.main.allSource +} + +// 配置javadoc任务 +tasks.named('javadoc', Javadoc) { + source project(':common').sourceSets.main.allJava + source sourceSets.main.allJava + classpath = configurations.compileClasspath + classpath += project(':common').sourceSets.main.compileClasspath + options.encoding = 'UTF-8' + options.charSet = 'UTF-8' + options.links("https://docs.oracle.com/en/java/javase/17/docs/api/") + options.memberLevel = JavadocMemberLevel.PUBLIC + options.addBooleanOption('Xdoclint:none', true) + options.addStringOption('doctitle', "${mod_id} ${minecraft_version} ${version} Javadoc") +} + +// 配置javadocJar任务 +tasks.named('javadocJar', Jar) { + dependsOn javadoc + dependsOn project(':common').tasks.named('javadoc') // 显式依赖common的javadoc + + archiveClassifier.set('javadoc') + from javadoc.destinationDir + from project(':common').javadoc.destinationDir + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + +// 确保build任务包含所有需要的jar +tasks.named('build') { + dependsOn tasks.named('sourcesJar') + dependsOn tasks.named('javadocJar') +} + +// 处理reobf +tasks.named('reobfJar') { + dependsOn tasks.named('sourcesJar') + dependsOn tasks.named('javadocJar') +} + +// 发布配置 +publishing { + publications { + mavenJava(MavenPublication) { + artifactId = "${mod_id}-forge-${minecraft_version}" + artifacts.clear() + artifact(tasks.named('reobfJar').get()) { + builtBy tasks.named('reobfJar') + } + artifact(tasks.named('sourcesJar').get()) { + builtBy tasks.named('sourcesJar') + classifier = 'sources' + } + artifact(tasks.named('javadocJar').get()) { + builtBy tasks.named('javadocJar') + classifier = 'javadoc' + } + pom { + name = 'Lib39' + description = 'Lib39 is a general-purpose dependency library for Minecraft mods.' + url = 'https://github.com/3944Realms/lib39' + + properties = [ + 'minecraft.version': project.minecraft_version, + 'mod.version': project.version, + 'forge.version': project.forge_version, + 'java.version': '17' + ] + + licenses { + license { + name = 'MIT' + url = 'https://raw.githubusercontent.com/3944Realms/lib39/refs/heads/main/LICENSE' + distribution = 'repo' + } + } + + developers { + developer { + id = 'R3944Realms' + name = "${mod_author}" + email = 'f256198830@hotmail.com' + } + } + + scm { + connection = 'scm:git:https://github.com/3944Realms/lib39.git' + developerConnection = 'scm:git:ssh://git@github.com:3944Realms/lib39.git' + url = 'https://github.com/3944Realms/lib39' + tag = 'main' + } + + issueManagement { + system = 'GitHub' + url = 'https://github.com/3944Realms/lib39/issues' + } + } + } + } +} + +// 处理资源 +processResources { + from project(':common').sourceSets.main.resources + inputs.property "version", project.version + inputs.property "minecraft_version", minecraft_version + inputs.property "forge_version", forge_version + inputs.property "mod_id", mod_id + inputs.property "mod_name", mod_name + inputs.property "description", description + inputs.property "mod_author", mod_author + + filesMatching(['META-INF/mods.toml', 'pack.mcmeta']) { + expand([ + version: project.version, + minecraft_version: minecraft_version, + forge_version: forge_version, + mod_id: mod_id, + mod_name: mod_name, + description: description, + mod_author: mod_author + ]) + } + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} diff --git a/forge/src/main/java/top/r3944realms/lib39/Lib39Forge.java b/forge/src/main/java/top/r3944realms/lib39/Lib39Forge.java new file mode 100644 index 0000000..1105278 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/Lib39Forge.java @@ -0,0 +1,32 @@ +package top.r3944realms.lib39; + +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; + +/** + * The type Lib 39. + */ +@Mod(top.r3944realms.lib39.Lib39.MOD_ID) +public class Lib39Forge { + /** + * Instantiates a new Lib 39. + */ + public Lib39Forge() { + Lib39.initialize(); + initialize(); + } + + + + /** + * Initialize. + */ + public static void initialize() { + Lib39.LOGGER.info("[Lib39] Initializing Lib39"); + IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); + + Lib39.LOGGER.info("[Lib39] Initialized Lib39"); + + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/core/register/ForgeLib39BlockEntities.java b/forge/src/main/java/top/r3944realms/lib39/core/register/ForgeLib39BlockEntities.java new file mode 100644 index 0000000..37c47f3 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/core/register/ForgeLib39BlockEntities.java @@ -0,0 +1,37 @@ +package top.r3944realms.lib39.core.register; + +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.content.block.blockentity.DollBlockEntity; + +/** + * The type Lib 39 block entities. + */ +public class ForgeLib39BlockEntities { + /** + * The constant BLOCK_ENTITY_TYPES. + */ + public static final DeferredRegister> BLOCK_ENTITY_TYPES = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, Lib39.MOD_ID); + /** + * The constant DOLL_BLOCK_ENTITY. + */ + @SuppressWarnings("DataFlowIssue") + public static final RegistryObject> DOLL_BLOCK_ENTITY = BLOCK_ENTITY_TYPES.register("doll", + () -> BlockEntityType.Builder + .of(DollBlockEntity::new, ForgeLib39Blocks.DOLL.get()) + .build(null) + ); + + /** + * Register. + * + * @param bus the bus + */ + public static void register(IEventBus bus) { + BLOCK_ENTITY_TYPES.register(bus); + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/core/register/ForgeLib39Blocks.java b/forge/src/main/java/top/r3944realms/lib39/core/register/ForgeLib39Blocks.java new file mode 100644 index 0000000..739cf3a --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/core/register/ForgeLib39Blocks.java @@ -0,0 +1,37 @@ +package top.r3944realms.lib39.core.register; + +import net.minecraft.world.level.block.Block; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.content.block.DollBlock; +import top.r3944realms.lib39.util.block.BlockRegistryBuilder; + +/** + * The type Lib 39 blocks. + */ +public class ForgeLib39Blocks { + /** + * The constant BLOCKS. + */ + public static final DeferredRegister BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, Lib39.MOD_ID); + + static { + Lib39Blocks.DOLL = BlockRegistryBuilder + .create() + .withName("doll") + .registerBlock(BLOCKS, DollBlock::new) + .build(); + + } + /** + * Register. + * + * @param bus the bus + */ + public static void register(IEventBus bus) { + BLOCKS.register(bus); + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/core/register/ForgeLib39Items.java b/forge/src/main/java/top/r3944realms/lib39/core/register/ForgeLib39Items.java new file mode 100644 index 0000000..eaa2ece --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/core/register/ForgeLib39Items.java @@ -0,0 +1,33 @@ +package top.r3944realms.lib39.core.register; + +import net.minecraft.world.item.Item; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.content.item.DollItem; + +/** + * The type Ex lib 39 items. + */ +public class ForgeLib39Items { + /** + * The constant ITEMS. + */ + public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, Lib39.MOD_ID); + + static { + Lib39Items.DOLL = ITEMS.register("doll", () -> new DollItem(new Item.Properties())); + } + + /** + * Register. + * + * @param bus the bus + */ + public static void register(IEventBus bus) { + ITEMS.register(bus); + } + +} diff --git a/forge/src/main/java/top/r3944realms/lib39/core/register/ForgeLib39SoundEvents.java b/forge/src/main/java/top/r3944realms/lib39/core/register/ForgeLib39SoundEvents.java new file mode 100644 index 0000000..5daf90f --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/core/register/ForgeLib39SoundEvents.java @@ -0,0 +1,44 @@ +package top.r3944realms.lib39.core.register; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.SoundEvent; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; +import top.r3944realms.lib39.Lib39; + +/** + * The type Lib 39 sound events. + */ +public class ForgeLib39SoundEvents { + /** + * The constant SOUND_EVENTS. + */ + public static final DeferredRegister SOUND_EVENTS = DeferredRegister.create(ForgeRegistries.SOUND_EVENTS, Lib39.MOD_ID); + + static { + Lib39SoundEvents.DUCK_TOY = SOUND_EVENTS.register("duck_toy", + () -> SoundEvent.createFixedRangeEvent(Lib39SoundEvents.RL_DUCK_TOY, 32.0f)); + } + + /** + * Register. + * + * @param bus the bus + */ + public static void register(IEventBus bus) { + SOUND_EVENTS.register(bus); + } + + + /** + * Gets sub title translate key. + * + * @param name the name + * @return the sub title translate key + */ + public static String getSubTitleTranslateKey(String name) { + return "sound." + Lib39.MOD_ID + ".subtitle." + name; + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/datagen/provider/SimpleLootTableProvider.java b/forge/src/main/java/top/r3944realms/lib39/datagen/provider/SimpleLootTableProvider.java new file mode 100644 index 0000000..4bc1dcf --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/datagen/provider/SimpleLootTableProvider.java @@ -0,0 +1,42 @@ +package top.r3944realms.lib39.datagen.provider; + +import net.minecraft.data.PackOutput; +import net.minecraft.data.loot.LootTableProvider; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.ValidationContext; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Set; + +/** + * The type Simple loot table provider. + */ +public class SimpleLootTableProvider extends LootTableProvider { + /** + * Instantiates a new Simple loot table provider. + * + * @param output the output + * @param subProvidersWrapper the sub providers wrapper + */ + public SimpleLootTableProvider(PackOutput output, @NotNull SubProvidersWrapper subProvidersWrapper) { + super(output, Set.of(), subProvidersWrapper.entries); + } + + /** + * Instantiates a new Simple loot table provider. + * + * @param output the output + * @param requiredTables the required tables + * @param subProvidersWrapper the sub providers wrapper + */ + public SimpleLootTableProvider(PackOutput output, Set requiredTables, @NotNull SubProvidersWrapper subProvidersWrapper) { + super(output, requiredTables, subProvidersWrapper.entries); + } + + @Override + protected void validate(@NotNull Map map, @NotNull ValidationContext validationcontext) { + map.forEach((id, table) -> table.validate(validationcontext)); + } +} diff --git a/forge/src/main/java/top/r3944realms/lib39/datagen/provider/SubProvidersWrapper.java b/forge/src/main/java/top/r3944realms/lib39/datagen/provider/SubProvidersWrapper.java new file mode 100644 index 0000000..43ff20d --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/datagen/provider/SubProvidersWrapper.java @@ -0,0 +1,60 @@ +package top.r3944realms.lib39.datagen.provider; + +import net.minecraft.data.loot.LootTableProvider; +import net.minecraft.data.loot.LootTableSubProvider; +import net.minecraft.world.level.storage.loot.parameters.LootContextParamSet; +import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; +import top.r3944realms.lib39.datagen.provider.subprovider.BlockLootTables; + +import java.util.ArrayList; +import java.util.List; + +/** + * The type Sub providers wrapper. + */ +public class SubProvidersWrapper { + /** + * The Entries. + */ + public List entries = new ArrayList<>(); + + /** + * Instantiates a new Sub providers wrapper. + */ + public SubProvidersWrapper() {} + + /** + * Add entry sub providers wrapper. + * + * @param entry the entry + * @return the sub providers wrapper + */ + public SubProvidersWrapper addEntry(LootTableProvider.SubProviderEntry entry) { + entries.add(entry); + return this; + } + + /** + * Add entry sub providers wrapper. + * + * @param subProvider the sub provider + * @param subParamSet the sub param set + * @return the sub providers wrapper + */ + public SubProvidersWrapper addEntry(LootTableSubProvider subProvider, LootContextParamSet subParamSet) { + entries.add(new LootTableProvider.SubProviderEntry(() -> subProvider, subParamSet)); + return this; + } + + /** + * Add block entry sub providers wrapper. + * + * @param blockLootTables the block loot tables + * @return the sub providers wrapper + */ + public SubProvidersWrapper addBlockEntry(BlockLootTables blockLootTables) { + entries.add(new LootTableProvider.SubProviderEntry(() -> blockLootTables, LootContextParamSets.BLOCK)); + return this; + } + +} diff --git a/forge/src/main/java/top/r3944realms/lib39/datagen/provider/subprovider/BlockLootTables.java b/forge/src/main/java/top/r3944realms/lib39/datagen/provider/subprovider/BlockLootTables.java new file mode 100644 index 0000000..767fa9a --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/datagen/provider/subprovider/BlockLootTables.java @@ -0,0 +1,385 @@ +package top.r3944realms.lib39.datagen.provider.subprovider; + +import net.minecraft.advancements.critereon.ItemPredicate; +import net.minecraft.data.loot.BlockLootSubProvider; +import net.minecraft.world.flag.FeatureFlags; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.FlowerPotBlock; +import net.minecraft.world.level.block.state.properties.Property; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.entries.LootItem; +import net.minecraft.world.level.storage.loot.functions.ApplyBonusCount; +import net.minecraft.world.level.storage.loot.functions.SetItemCountFunction; +import net.minecraft.world.level.storage.loot.predicates.LootItemBlockStatePropertyCondition; +import net.minecraft.world.level.storage.loot.predicates.MatchTool; +import net.minecraft.world.level.storage.loot.providers.number.ConstantValue; +import net.minecraft.world.level.storage.loot.providers.number.UniformGenerator; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.RegistryObject; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * The type Block loot tables. + */ +@SuppressWarnings("unused") +public class BlockLootTables extends BlockLootSubProvider { + + private final List blockEntries = new ArrayList<>(); + private final DeferredRegister knowBlocks; + + /** + * Instantiates a new Block loot tables. + * + * @param deferredRegister the deferred register + */ + public BlockLootTables(DeferredRegister deferredRegister) { + super(Set.of(), FeatureFlags.REGISTRY.allFlags()); + knowBlocks = deferredRegister; + } + + @Override + protected @NotNull Iterable getKnownBlocks() { + return knowBlocks.getEntries().stream().map(RegistryObject::get).collect(Collectors.toSet()); + } + + // ==================== 流畅 API 构建方法 ==================== + + /** + * 添加自掉落的方块 + * + * @param block the block + */ + public void dropSelf(RegistryObject block) { + addEntry(block, this::createSingleItemTable); + } + + /** + * 批量添加自掉落的方块 + * + * @param blocks the blocks + * @return the block loot tables + */ + @Contract("_ -> this") + @SafeVarargs + public final BlockLootTables dropSelf(RegistryObject @NotNull ... blocks) { + for (RegistryObject block : blocks) { + dropSelf(block); + } + return this; + } + + /** + * 添加需要丝绸之触才掉落的方块 + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables dropWhenSilkTouch(RegistryObject block) { + return addEntry(block, BlockLootSubProvider::createSilkTouchOnlyTable); + } + + /** + * 添加掉落其他物品的方块 + * + * @param block the block + * @param item the item + * @return the block loot tables + */ + public BlockLootTables dropOther(RegistryObject block, RegistryObject item) { + return addEntry(block, pBlock -> this.createSingleItemTable(item.get())); + } + + /** + * 添加只能被剪子剪下的方块 + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables dropWhenShears(RegistryObject block) { + return addEntry(block, BlockLootSubProvider::createShearsOnlyDrop); + } + + /** + * 添加矿物的掉落表(支持时运附魔) + * + * @param block the block + * @param oreItem the ore item + * @return the block loot tables + */ + public BlockLootTables dropOre(RegistryObject block, RegistryObject oreItem) { + return addEntry(block, b -> this.createOreDrop(b, oreItem.get())); + } + + /** + * 添加红石矿石掉落表 + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables dropRedstoneOre(RegistryObject block) { + return addEntry(block, this::createRedstoneOreDrops); + } + + /** + * 添加青金石矿石掉落表 + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables dropLapisOre(RegistryObject block) { + return addEntry(block, this::createLapisOreDrops); + } + + /** + * 添加铜矿石掉落表 + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables dropCopperOre(RegistryObject block) { + return addEntry(block, this::createCopperOreDrops); + } + + /** + * 添加地毯类方块的掉落(一次掉落2个) + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables dropCarpet(RegistryObject block) { + return addEntry(block, b -> LootTable.lootTable() + .withPool(LootPool.lootPool() + .setRolls(ConstantValue.exactly(1)) + .add(LootItem.lootTableItem(b) + .apply(SetItemCountFunction.setCount(ConstantValue.exactly(2.0F)))))); + } + + /** + * 添加台阶方块的掉落 + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables dropSlab(RegistryObject block) { + return addEntry(block, this::createSlabItemTable); + } + + /** + * 添加门方块的掉落(只掉落下半部分) + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables dropDoor(RegistryObject block) { + return addEntry(block, this::createDoorTable); + } + + /** + * 添加花盆的掉落 + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables dropFlowerPot(RegistryObject block) { + return addEntry(block, (pBlock) -> this.createPotFlowerItemTable(((FlowerPotBlock)pBlock).getContent())); + } + + /** + * 添加树叶的掉落 + * + * @param leavesBlock the leaves block + * @param saplingBlock the sapling block + * @param chances the chances + * @return the block loot tables + */ + public BlockLootTables dropLeaves(RegistryObject leavesBlock, + RegistryObject saplingBlock, + float... chances) { + return addEntry(leavesBlock, b -> this.createLeavesDrops(b, saplingBlock.get(), chances)); + } + + /** + * 添加橡树叶的掉落(包含苹果) + * + * @param leavesBlock the leaves block + * @param saplingBlock the sapling block + * @param chances the chances + * @return the block loot tables + */ + public BlockLootTables dropOakLeaves(RegistryObject leavesBlock, + RegistryObject saplingBlock, + float... chances) { + return addEntry(leavesBlock, b -> this.createOakLeavesDrops(b, saplingBlock.get(), chances)); + } + + /** + * 添加农作物的掉落 + * + * @param cropBlock the crop block + * @param cropItem the crop item + * @param seedsItem the seeds item + * @param ageProperty the age property + * @param maxAge the max age + * @return the block loot tables + */ + public BlockLootTables dropCrop(RegistryObject cropBlock, + RegistryObject cropItem, + RegistryObject seedsItem, + Property ageProperty, + int maxAge) { + return addEntry(cropBlock, b -> this.createCropDrops( + b, + cropItem.get(), + seedsItem.get(), + LootItemBlockStatePropertyCondition.hasBlockStateProperties(b) + .setProperties(net.minecraft.advancements.critereon.StatePropertiesPredicate.Builder.properties() + .hasProperty(ageProperty, maxAge)) + )); + } + + /** + * 自定义掉落表 + * + * @param block the block + * @param factory the factory + * @return the block loot tables + */ + public BlockLootTables custom(RegistryObject block, Function factory) { + return addEntry(block, factory); + } + + /** + * 没有掉落 + * + * @param block the block + * @return the block loot tables + */ + public BlockLootTables noDrop(RegistryObject block) { + return addEntry(block, b -> noDrop()); + } + + // ==================== 批量操作方法 ==================== + + /** + * 批量操作一系列方块 + * + * @param blocks the blocks + * @param operation the operation + * @return the block loot tables + */ + public BlockLootTables batch(@NotNull Iterable> blocks, + Function, BlockLootTables> operation) { + for (RegistryObject block : blocks) { + operation.apply(block); + } + return this; + } + + /** + * 对一组方块应用相同的操作 + * + * @param operation the operation + * @param blocks the blocks + * @return the block loot tables + */ + @Contract("_, _ -> this") + @SafeVarargs + public final BlockLootTables applyToAll(Function, BlockLootTables> operation, + RegistryObject @NotNull ... blocks) { + for (RegistryObject block : blocks) { + operation.apply(block); + } + return this; + } + + // ==================== 构建方法 ==================== + + /** + * 构建并返回自身(用于流畅API链式调用) + * + * @return the block loot tables + */ + public BlockLootTables build() { + return this; + } + + @Override + protected void generate() { + for (BlockEntry entry : blockEntries) { + this.add(entry.block.get(), entry.factory); + } + } + + // ==================== 内部类和方法 ==================== + + private BlockLootTables addEntry(RegistryObject block, Function factory) { + blockEntries.add(new BlockEntry(block, factory)); + return this; + } + + private record BlockEntry(RegistryObject block, Function factory) {} + + // ==================== 静态工厂方法 ==================== + + /** + * 创建新的战利品表生成器 + * + * @param deferredRegister the deferred register + * @return the block loot tables + */ + @Contract("_ -> new") + public static @NotNull BlockLootTables create(DeferredRegister deferredRegister) { + return new BlockLootTables(deferredRegister); + } + + /** + * 快速创建基本矿物的掉落表 + * + * @param oreBlock the ore block + * @param oreItem the ore item + * @return the loot table .@ not null builder + */ + public static LootTable.@NotNull Builder simpleOreDrop(Block oreBlock, Item oreItem) { + return LootTable.lootTable() + .withPool(LootPool.lootPool() + .setRolls(ConstantValue.exactly(1)) + .add(LootItem.lootTableItem(oreItem) + .when(MatchTool.toolMatches(ItemPredicate.Builder.item() + .hasEnchantment(new net.minecraft.advancements.critereon.EnchantmentPredicate( + Enchantments.SILK_TOUCH, + net.minecraft.advancements.critereon.MinMaxBounds.Ints.atLeast(1) + ))).invert()) + .apply(SetItemCountFunction.setCount(UniformGenerator.between(1, 1))) + .apply(ApplyBonusCount.addOreBonusCount(Enchantments.BLOCK_FORTUNE)))); + } + + /** + * 快速创建石质方块的掉落表(需要镐子) + * + * @param stoneBlock the stone block + * @param dropItem the drop item + * @return the loot table .@ not null builder + */ + public static LootTable.@NotNull Builder stoneDrop(Block stoneBlock, Item dropItem) { + return LootTable.lootTable() + .withPool(LootPool.lootPool() + .setRolls(ConstantValue.exactly(1)) + .add(LootItem.lootTableItem(dropItem) + .when(MatchTool.toolMatches(ItemPredicate.Builder.item() + .hasEnchantment(new net.minecraft.advancements.critereon.EnchantmentPredicate( + Enchantments.SILK_TOUCH, + net.minecraft.advancements.critereon.MinMaxBounds.Ints.atLeast(1) + )))))); + } +} \ No newline at end of file diff --git a/forge/src/main/java/top/r3944realms/lib39/platform/ForgePlatformHelper.java b/forge/src/main/java/top/r3944realms/lib39/platform/ForgePlatformHelper.java new file mode 100644 index 0000000..f3f3609 --- /dev/null +++ b/forge/src/main/java/top/r3944realms/lib39/platform/ForgePlatformHelper.java @@ -0,0 +1,38 @@ +package top.r3944realms.lib39.platform; + +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.loading.FMLEnvironment; +import net.minecraftforge.fml.loading.FMLLoader; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.platform.services.IPlatformHelper; + +public class ForgePlatformHelper implements IPlatformHelper { + + @Override + public String getPlatformName() { + return "Forge"; + } + + @Override + public boolean isModLoaded(String modId) { + return ModList.get().isLoaded(modId); + } + + @Override + public boolean isDevelopmentEnvironment() { + return !FMLLoader.isProduction(); + } + + @Override + public boolean isClientEnvironment() { + return FMLEnvironment.dist.isClient(); + } + + @Override + public String getModVersion() { + return ModList.get() + .getModContainerById(Lib39.MOD_ID) + .map(c -> c.getModInfo().getVersion().toString()) + .orElse("UNKNOWN"); + } +} diff --git a/forge/src/main/resources/META-INF/mods.toml b/forge/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..0e3dd3b --- /dev/null +++ b/forge/src/main/resources/META-INF/mods.toml @@ -0,0 +1,26 @@ +modLoader = "javafml" #mandatory +loaderVersion = "${forge_loader_version_range}" #mandatory This is typically bumped every Minecraft version by Forge. See https://files.minecraftforge.net/ for a list of versions. +license = "${license}" # Review your options at https://choosealicense.com/. +#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional +[[mods]] #mandatory +modId = "${mod_id}" #mandatory +version = "${version}" #mandatory +displayName = "${mod_name}" #mandatory +#updateJSONURL="https://change.me.example.invalid/updates.json" #optional (see https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/) +#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional (displayed in the mod UI) +logoFile = "lib39_logo.png" #optional (needs to be in the root of your mod jar (root of your 'resources' folder)) +credits = "Thanks for this example mod goes to Java" #optional +authors = "${mod_author}" #optional +description = '''${description}''' #mandatory (Supports multiline text) +[[dependencies.${mod_id}]] #optional +modId = "forge" #mandatory +mandatory = true #mandatory +versionRange = "[${forge_version},)" #mandatory +ordering = "NONE" # The order that this dependency should load in relation to your mod, required to be either 'BEFORE' or 'AFTER' if the dependency is not mandatory +side = "BOTH" # Side this dependency is applied on - 'BOTH', 'CLIENT' or 'SERVER' +[[dependencies.${mod_id}]] +modId = "minecraft" +mandatory = true +versionRange = "${minecraft_version_range}" +ordering = "NONE" +side = "BOTH" diff --git a/forge/src/main/resources/META-INF/services/top.r3944realms.lib39.platform.services.IPlatformHelper b/forge/src/main/resources/META-INF/services/top.r3944realms.lib39.platform.services.IPlatformHelper new file mode 100644 index 0000000..f080f90 --- /dev/null +++ b/forge/src/main/resources/META-INF/services/top.r3944realms.lib39.platform.services.IPlatformHelper @@ -0,0 +1 @@ +top.r3944realms.lib39.platform.ForgePlatformHelper \ No newline at end of file diff --git a/forge/src/main/resources/lib39.forge.mixins.json b/forge/src/main/resources/lib39.forge.mixins.json new file mode 100644 index 0000000..c953842 --- /dev/null +++ b/forge/src/main/resources/lib39.forge.mixins.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "top.r3944realms.lib39.mixin", + "refmap": "${mod_id}.refmap.json", + "compatibilityLevel": "JAVA_17", + "mixins": [ + ], + "client": [ + + ], + "server": [ + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 73200c0..c902b6e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,46 +1,33 @@ -# Sets default memory used for gradle commands. Can be overridden by user or command line properties. +# Important Notes: +# fabric.mod.json's modid field cannot be expanded, you must change it manually. +# Every field you add must be added to buildSrc/src/main/groovy/multiloader-common.gradle expandProps map. + +# Project +version=0.5.0 +group=top.r3944realms.lib39 +java_version=17 + +# Common +minecraft_version=1.20.1 +mod_name=3944Realms 's Lib Mod +mod_author=R3944Realms +mod_id=lib39 +license=MIT +credits=Logo created by Shanyi43, edited by R3944Realms +description=Lib39 is a general-purpose dependency library that provides utility methods and core functionality for other mods. +minecraft_version_range=[1.20.1, 1.22) +# The version of ParchmentMC that is used, see https://parchmentmc.org/docs/getting-started#choose-a-version for new versions +parchment_minecraft=1.20.1 +parchment_version=2023.09.03 + +# Fabric +fabric_version=0.92.1+1.20.1 +fabric_loader_version=0.16.9 + +# Forge +forge_version=47.2.30 +forge_loader_version_range=[47.1.3,) + +# Gradle org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false -org.gradle.parallel=true -org.gradle.caching=true -org.gradle.configuration-cache=false - -#read more on this at https://github.com/neoforged/ModDevGradle?tab=readme-ov-file#better-minecraft-parameter-names--javadoc-parchment -# you can also find the latest versions at: https://parchmentmc.org/docs/getting-started -parchment_minecraft_version=1.20.1 -parchment_mappings_version=2023.09.03 -# Environment Properties -# You can find the latest versions here: https://files.minecraftforge.net/net/minecraftforge/forge/index_1.20.1.html -# The Minecraft version must agree with the Forge version to get a valid artifact -minecraft_version=1.20.1 -# The Minecraft version range can use any release version of Minecraft as bounds. -# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly -# as they do not follow standard versioning conventions. -minecraft_version_range=[1.20.1, 1.21) -# The Forge version must agree with the Minecraft version to get a valid artifact -forge_version=47.1.3 -# The Forge version range can use any version of Forge as bounds -forge_version_range=[47.1.3,) -# The loader version range can only use the major version of FML as bounds -loader_version_range=[47,) - -## Mod Properties -# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63} -# Must match the String constant located in the main mod class annotated with @Mod. -mod_id=lib39 -# The human-readable display name for the mod. -mod_name=3944Realms 's Lib Mod -# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. -mod_license=MIT -# The mod version. See https://semver.org/ -mod_version=0.4.1 -# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. -# This should match the base package used for the mod sources. -# See https://maven.apache.org/guides/mini/guide-naming-conventions.html -mod_group_id=top.r3944realms.lib39 -# The authors of the mod. This is a simple text string that is used for display purposes in the mod list. -mod_authors=R3944Realms -# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list. -mod_description=Lib39 is a general-purpose dependency library that provides utility methods and core functionality for other mods. - -mod_credits=Logo created by Shanyi43, edited by R3944Realms \ No newline at end of file diff --git a/gradle/.jni-config.groovy b/gradle/.jni-config.groovy deleted file mode 100644 index 68739e6..0000000 --- a/gradle/.jni-config.groovy +++ /dev/null @@ -1,21 +0,0 @@ -ext.jniConfig = { - // 输出目录 - outputDir = project.file("native/include") - - // 配置文件路径 - configFile = project.file("jni/jni-classes.txt") - - // 是否在构建时自动生成 - autoGenerateOnBuild = true - - // 是否启用详细日志 - verbose = true - - // 自定义匹配模式 - defaultPatterns = [ - '.*Native.*', - '.*JNI.*', - '.*native.*', - 'com\\.mymod\\..*Impl' // 匹配特定包下的实现类 - ] -} \ No newline at end of file diff --git a/gradle/jni-heads.gradle b/gradle/jni-heads.gradle deleted file mode 100644 index 372c629..0000000 --- a/gradle/jni-heads.gradle +++ /dev/null @@ -1,458 +0,0 @@ -// 配置 -def outputDir = project.file("cpp/header") -def configFile = project.file("config/jni-classes.txt") - -// 日志函数 -def log(msg) { - println "[JNI] $msg" -} - -def logError(msg) { - println "[JNI ERROR] $msg" -} - -def logWarn(msg) { - println "[JNI WARN] $msg" -} - -// 创建配置任务 -tasks.register('createJniConfig') { - group = 'jni' - description = '创建 JNI 配置文件模板' - - doLast { - configFile.parentFile.mkdirs() - if (!configFile.exists()) { - configFile.text = """# JNI 头文件生成配置 -# 每行一个类全限定名,例如: -# com.example.MyNativeClass -# com.example.NativeUtils - -# 或者使用正则表达式自动匹配: -# #auto:.*Native.* -""" - log "配置文件已创建: ${configFile.absolutePath}" - log "请编辑此文件并添加包含 native 方法的类" - } - } -} - -// 生成头文件任务 -tasks.register('generateJniHeaders') { - group = 'jni' - description = '生成 JNI 头文件' - - dependsOn 'compileJava' - - doLast { - // 确保目录存在 - outputDir.mkdirs() - - // 读取配置 - def targetClasses = [] - if (configFile.exists()) { - configFile.eachLine { line -> - def trimmed = line.trim() - if (trimmed && !trimmed.startsWith('#')) { - targetClasses.add(trimmed) - } - } - } - - if (targetClasses.isEmpty()) { - logError "没有配置任何 JNI 类" - logError "请先运行: ./gradlew createJniConfig" - logError "然后编辑 ${configFile.absolutePath} 添加类名" - return - } - - log "开始生成 JNI 头文件..." - log "目标类 (${targetClasses.size()} 个):" - targetClasses.eachWithIndex { className, i -> - log " ${i + 1}. $className" - } - - // 准备类路径 - def classesDir = project.sourceSets.main.output.classesDirs.singleFile - def classpath = project.configurations.runtimeClasspath.asPath + - File.pathSeparator + - classesDir.absolutePath - - // 查找对应的 Java 源文件 - def sourceFiles = [] - def sourceDirs = project.sourceSets.main.java.srcDirs - - targetClasses.each { className -> - def found = false - def relativePath = className.replace('.', '/') + '.java' - - sourceDirs.each { srcDir -> - def sourceFile = new File(srcDir, relativePath) - if (sourceFile.exists()) { - sourceFiles.add(sourceFile) - found = true - log "找到源文件: ${sourceFile.absolutePath}" - } - } - - if (!found) { - logWarn "警告: 未找到类 $className 的源文件" - } - } - - if (sourceFiles.isEmpty()) { - logError "错误: 未找到任何源文件" - logError "请确保源文件存在于 src/main/java/ 目录中" - return - } - - // 使用 javac -h 命令(正确的方式) - def javaHome = System.getProperty('java.home') - def javacPath = "${javaHome}/bin/javac" - - if (!new File(javacPath).exists()) { - javacPath = "${javaHome}/bin/javac" - } - - log "使用 javac -h 生成头文件..." - - // 构建临时目录用于编译输出 - def tempOutputDir = new File(project.buildDir, "tmp/jni-headers") - tempOutputDir.mkdirs() - - try { - // 方式1:逐个类生成(更可靠) - def successCount = 0 - def failCount = 0 - - sourceFiles.each { sourceFile -> - try { - // 构建 javac 命令 - def processArgs = [ - javacPath, - '-h', outputDir.absolutePath, // 头文件输出目录 - '-cp', classpath, // 类路径 - '-d', tempOutputDir.absolutePath, // 类文件输出目录 - sourceFile.absolutePath // 源文件 - ] - - log "处理: ${sourceFile.name}" - - def process = processArgs.execute() - def stdout = new StringBuilder() - def stderr = new StringBuilder() - - process.consumeProcessOutput(stdout, stderr) - def exitCode = process.waitFor() - - if (exitCode == 0) { - successCount++ - if (stdout.length() > 0) { - log " 输出: ${stdout.toString().trim()}" - } - } else { - failCount++ - logError " 处理失败: ${sourceFile.name}" - if (stderr.length() > 0) { - logError " 错误: ${stderr.toString().trim()}" - } - } - - } catch (Exception e) { - failCount++ - logError " 处理异常: ${e.message}" - } - } - - log "处理完成: 成功 ${successCount} 个, 失败 ${failCount} 个" - - if (successCount > 0) { - // 检查生成了哪些头文件 - def headerFiles = outputDir.listFiles({ dir, name -> name.endsWith('.h') } as FilenameFilter) - if (headerFiles && headerFiles.size() > 0) { - log "=" * 60 - log "JNI 头文件生成成功!" - log "=" * 60 - log "输出目录: ${outputDir.absolutePath}" - log "生成的头文件 (${headerFiles.size()} 个):" - - headerFiles.sort { it.name }.each { file -> - def size = file.length() - def sizeStr = size < 1024 ? "${size} B" : "${String.format("%.1f", size / 1024.0)} KB" - log " ✓ ${file.name} ($sizeStr)" - - // 显示文件开头几行 - try { - def lines = file.readLines() - if (lines.size() > 0) { - def headerGuard = lines.find { it.contains('#ifndef') } - if (headerGuard) { - log " 头文件保护: ${headerGuard.trim()}" - } - } - } catch (Exception e) { - // 忽略读取错误 - } - } - - log "=" * 60 - log "🎉 头文件已成功生成!" - log "" - log "使用建议:" - log " 1. 将生成的头文件复制到你的 C/C++ 项目中" - log " 2. 在 C/C++ 源文件中包含这些头文件" - log " 3. 实现头文件中声明的 JNI 函数" - log "" - log "示例 C++ 代码:" - log " #include \"${headerFiles[0].name}\"" - log " JNIEXPORT void JNICALL Java_com_example_MyClass_nativeMethod(JNIEnv* env, jobject obj) {" - log " // 你的实现代码" - log " }" - - } else { - logWarn "警告: 未生成任何 .h 头文件" - logWarn "可能的原因:" - logWarn " 1. 源文件中没有 native 方法声明" - logWarn " 2. javac 版本不支持 -h 选项" - logWarn " 3. 类路径配置不正确" - } - } else { - logError "所有处理都失败,未生成任何头文件" - } - - } finally { - // 清理临时目录 - tempOutputDir.deleteDir() - } - } -} - -// 备选方案:使用传统 javah(如果 javac -h 失败) -tasks.register('generateJniHeadersLegacy') { - group = 'jni' - description = '使用传统 javah 生成 JNI 头文件' - - dependsOn 'compileJava' - - doLast { - outputDir.mkdirs() - - def targetClasses = [] - if (configFile.exists()) { - configFile.eachLine { line -> - def trimmed = line.trim() - if (trimmed && !trimmed.startsWith('#')) { - targetClasses.add(trimmed) - } - } - } - - if (targetClasses.isEmpty()) { - logError "没有配置任何 JNI 类" - return - } - - log "使用传统 javah 生成头文件..." - - def classesDir = project.sourceSets.main.output.classesDirs.singleFile - def classpath = project.configurations.runtimeClasspath.asPath + - File.pathSeparator + - classesDir.absolutePath - - def javaHome = System.getProperty('java.home') - def javahPath = "${javaHome}/bin/javah" - - if (!new File(javahPath).exists()) { - javahPath = "${javaHome}/../bin/javah" - } - - if (!new File(javahPath).exists()) { - logError "找不到 javah 工具" - logError "请使用 Java 8-9 或使用 generateJniHeaders 任务" - return - } - - def processArgs = [javahPath, '-classpath', classpath, '-d', outputDir.absolutePath] - processArgs.addAll(targetClasses) - - log "执行命令: ${processArgs.join(' ')}" - - def process = processArgs.execute() - def exitCode = process.waitFor() - - if (exitCode == 0) { - def files = outputDir.listFiles({ dir, name -> name.endsWith('.h') } as FilenameFilter) - log "生成成功!创建了 ${files?.size() ?: 0} 个头文件" - if (files) { - files.each { log " - ${it.name}" } - } - } else { - logError "生成失败" - } - } -} - -// 扫描 native 方法任务 -tasks.register('scanForNativeMethods') { - group = 'jni' - description = '扫描项目中的 native 方法' - - doLast { - log "扫描项目中可能包含 native 方法的类..." - - def sourceDirs = project.sourceSets.main.java.srcDirs - def foundClasses = [] - - sourceDirs.each { srcDir -> - if (srcDir.exists()) { - srcDir.eachFileRecurse(groovy.io.FileType.FILES) { file -> - if (file.name.endsWith('.java')) { - def content = file.text - if (content.contains('native ') || content.contains(' native')) { - // 提取类名 - def packageMatch = content =~ /package\s+([\w.]+)\s*;/ - def classMatch = content =~ /class\s+(\w+)/ - - if (packageMatch.find() && classMatch.find()) { - def packageName = packageMatch.group(1) - def className = classMatch.group(1) - def fullClassName = "${packageName}.${className}" - - if (!foundClasses.contains(fullClassName)) { - foundClasses.add(fullClassName) - log "发现: $fullClassName" - } - } - } - } - } - } - } - - if (foundClasses.isEmpty()) { - log "未发现包含 native 方法的类" - } else { - log "=" * 60 - log "发现 ${foundClasses.size()} 个可能包含 native 方法的类:" - foundClasses.sort().eachWithIndex { cls, i -> - log " ${i + 1}. $cls" - } - log "" - log "你可以将这些类添加到 ${configFile.name} 中" - } - } -} - -// 清理任务 -tasks.register('cleanJniHeaders', Delete) { - group = 'jni' - description = '清理 JNI 头文件' - delete outputDir - - doLast { - log "已清理 JNI 头文件目录: ${outputDir.absolutePath}" - } -} - -// 验证任务 -tasks.register('verifyJniSetup') { - group = 'jni' - description = '验证 JNI 配置' - - doLast { - log "验证 JNI 配置..." - log "Java 版本: ${System.getProperty('java.version')}" - log "Java Home: ${System.getProperty('java.home')}" - - // 检查 javac - def javaHome = System.getProperty('java.home') - def javacPath = "${javaHome}/bin/javac" - def javacExists = new File(javacPath).exists() || new File("${javaHome}/../bin/javac").exists() - log "javac 工具: ${javacExists ? '找到 ✓' : '未找到 ✗'}" - - // 检查 javah(传统方式) - def javahPath = "${javaHome}/bin/javah" - def javahExists = new File(javahPath).exists() || new File("${javaHome}/../bin/javah").exists() - log "javah 工具: ${javahExists ? '找到 ✓' : '未找到 ✗'}" - - // 检查配置文件 - if (configFile.exists()) { - def classes = configFile.readLines() - .findAll { it.trim() && !it.trim().startsWith('#') } - log "配置文件: 已找到 (${classes.size()} 个类)" - if (classes.size() > 0) { - classes.each { log " - $it" } - } - } else { - log "配置文件: 未找到 ✗" - } - - // 检查输出目录 - log "输出目录: ${outputDir.absolutePath}" - } -} - -// 帮助任务 -tasks.register('jniHelp') { - group = 'help' - description = 'JNI 帮助' - - doLast { - println """ -${'=' * 70} -JNI 头文件生成系统 -${'=' * 70} - -📋 概述: - 为包含 native 方法的 Java 类生成 C/C++ 头文件。 - -🚀 快速开始: - 1. ./gradlew createJniConfig # 创建配置文件 - 2. 编辑 config/jni-classes.txt # 添加你的 JNI 类 - 3. ./gradlew generateJniHeaders # 生成头文件(推荐) - 4. 头文件输出到: cpp/header/ - -🔧 可用任务: - jniHelp - 显示此帮助 - createJniConfig - 创建配置文件 - generateJniHeaders - 生成头文件(现代方式) - generateJniHeadersLegacy - 传统方式(Java 8-9) - scanForNativeMethods - 扫描 native 方法 - verifyJniSetup - 验证配置 - cleanJniHeaders - 清理生成的文件 - -📝 配置文件格式 (config/jni-classes.txt): - # 注释 - com.example.MyNativeClass # 直接指定类名 - #auto:.*Native.* # 自动匹配(以 #auto: 开头) - -⚠️ 注意事项: - • 确保类中包含 native 方法声明 - • 先编译项目再生成头文件 - • 对于 Java 10+ 使用 generateJniHeaders - • 对于 Java 8-9 使用 generateJniHeadersLegacy - -🔍 调试: - • 运行 verifyJniSetup 检查环境 - • 运行 scanForNativeMethods 发现 native 类 - • 确保源文件中有 native 关键字 - -${'=' * 70} -""" - } -} - -// 可选:自动集成到构建 -// tasks.named('build') { -// dependsOn tasks.named('generateJniHeaders') -// } - -// 清理时包含 JNI 头文件 -tasks.named('clean') { - dependsOn tasks.named('cleanJniHeaders') -} - -log "JNI 模块已加载" -log "输出目录: ${outputDir.absolutePath}" -log "配置文件: ${configFile.absolutePath}" -log "使用 ./gradlew jniHelp 查看详细帮助" \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d4081da..94113f2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew deleted file mode 100755 index f5feea6..0000000 --- a/gradlew +++ /dev/null @@ -1,252 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index 9b42019..0000000 --- a/gradlew.bat +++ /dev/null @@ -1,94 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/libs/carryon-1.20.1-2.1.2.7.jar b/libs/carryon-1.20.1-2.1.2.7.jar deleted file mode 100644 index 922380f..0000000 Binary files a/libs/carryon-1.20.1-2.1.2.7.jar and /dev/null differ diff --git a/res/alex.png b/res/alex.png deleted file mode 100644 index b513908..0000000 Binary files a/res/alex.png and /dev/null differ diff --git a/res/doll_default.bbmodel b/res/doll_default.bbmodel deleted file mode 100644 index ec1eae1..0000000 --- a/res/doll_default.bbmodel +++ /dev/null @@ -1 +0,0 @@ -{"meta":{"format_version":"5.0","model_format":"java_block","box_uv":false},"name":"doll_default","parent":"","java_block_version":"1.9.0","ambientocclusion":true,"front_gui_light":false,"visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"unhandled_root_fields":{"format_version":"1.21.6"},"resolution":{"width":16,"height":16},"elements":[{"name":"Toggle_Helmet","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[3.5,8.8,7.5],"to":[12.5,17.8,16.5],"autouv":0,"color":0,"origin":[8,9.3,12],"faces":{"north":{"uv":[10,2,12,4],"texture":0},"east":{"uv":[8,2,10,4],"texture":0},"south":{"uv":[14,2,16,4],"texture":0},"west":{"uv":[12,2,14,4],"texture":0},"up":{"uv":[10,2,12,0],"texture":0},"down":{"uv":[12,0,14,2],"texture":0}},"type":"cube","uuid":"4034240c-1bb7-3096-56d0-f9d9158bd705"},{"name":"Head","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[4,9.3,8],"to":[12,17.3,16],"autouv":0,"color":0,"origin":[8,9.3,12],"faces":{"north":{"uv":[2,2,4,4],"texture":0},"east":{"uv":[0,2,2,4],"texture":0},"south":{"uv":[6,2,8,4],"texture":0},"west":{"uv":[4,2,6,4],"texture":0},"up":{"uv":[4,2,2,0],"texture":0},"down":{"uv":[6,0,4,2],"texture":0}},"type":"cube","uuid":"5eca2c83-4481-ddf5-fafe-19abeb647c57"},{"name":"Toggle_Chest_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[4.75,2.05,9.75],"to":[11.25,9.55,13.25],"autouv":0,"color":0,"origin":[8,5.8,11.5],"faces":{"north":{"uv":[5,9,7,12],"texture":0},"east":{"uv":[4,9,5,12],"texture":0},"south":{"uv":[8,9,10,12],"texture":0},"west":{"uv":[7,9,8,12],"texture":0},"up":{"uv":[5,8,7,9],"texture":0},"down":{"uv":[7,8,9,9],"texture":0}},"type":"cube","uuid":"8ea34c7a-dcd8-ead4-07d1-ccab4438046e"},{"name":"Body","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[5,2.3,10],"to":[11,9.3,13],"autouv":0,"color":3,"origin":[8,5.8,11.5],"faces":{"north":{"uv":[5,5,7,8],"texture":0},"east":{"uv":[4,5,5,8],"texture":0},"south":{"uv":[8,5,10,8],"texture":0},"west":{"uv":[7,5,8,8],"texture":0},"up":{"uv":[7,5,5,4],"texture":0},"down":{"uv":[9,4,7,5],"texture":0}},"type":"cube","uuid":"fd16d848-4320-9a3f-6b3d-3f4884a92fbe"},{"name":"Toggle_Left_Arm_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[2.75,6.25,3.8],"to":[5.25,9.75,13.3],"autouv":0,"color":3,"rotation":[0,-22.5,0],"origin":[3,8,12.5],"faces":{"north":{"uv":[13.75,12,14.5,13],"rotation":180,"texture":0},"east":{"uv":[12,13,13,16],"rotation":270,"texture":0},"south":{"uv":[13,12,13.75,13],"texture":0},"west":{"uv":[13.75,13,14.75,16],"rotation":90,"texture":0},"up":{"uv":[13,13,13.75,16],"rotation":180,"texture":0},"down":{"uv":[14.75,13,15.5,16],"texture":0}},"type":"cube","uuid":"06ccbe71-f249-9062-076b-ea3f530fa945"},{"name":"Left_arm","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[3,6.5,3.8],"to":[5,9.5,12.8],"autouv":0,"color":6,"rotation":[0,-22.5,0],"origin":[4,8,12.5],"faces":{"north":{"uv":[10.5,12,9.75,13],"rotation":180,"texture":0},"east":{"uv":[8,13,9,16],"rotation":270,"texture":0},"south":{"uv":[9.75,13,9,12],"texture":0},"west":{"uv":[9.75,13,10.5,16],"rotation":90,"texture":0},"up":{"uv":[9,13,9.75,16],"rotation":180,"texture":0},"down":{"uv":[10.5,13,11.5,16],"texture":0}},"type":"cube","uuid":"15980f9e-f82b-d64a-eaa4-38789e0635cc"},{"name":"Toggle_Right_Arm_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[10.75,6.25,3.8],"to":[13.25,9.75,13.3],"autouv":0,"color":6,"rotation":[0,22.5,0],"origin":[12,8,11.5],"faces":{"north":{"uv":[11.75,8,12.5,9],"rotation":180,"texture":0},"east":{"uv":[10,9,11,12],"rotation":270,"texture":0},"south":{"uv":[11,8,11.75,9],"texture":0},"west":{"uv":[11.75,9,12.75,12],"rotation":90,"texture":0},"up":{"uv":[11,9,11.75,12],"rotation":180,"texture":0},"down":{"uv":[12.75,9,13.5,12],"texture":0}},"type":"cube","uuid":"7b6c1629-5993-798e-8a6e-bb9303f0e8da"},{"name":"Right_arm","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[11,6.5,3.8],"to":[13,9.5,12.8],"autouv":0,"color":0,"rotation":[0,22.5,0],"origin":[12,8,11.5],"faces":{"north":{"uv":[12.5,4,11.75,5],"rotation":180,"texture":0},"east":{"uv":[10,5,11,8],"rotation":270,"texture":0},"south":{"uv":[11.75,5,11,4],"texture":0},"west":{"uv":[11.75,5,12.5,8],"rotation":90,"texture":0},"up":{"uv":[11,5,11.75,8],"rotation":180,"texture":0},"down":{"uv":[12.5,5,13.5,8],"texture":0}},"type":"cube","uuid":"e5bd3372-f4e5-9cc0-1f38-726db3f86001"},{"name":"Toggle_Left_Leg_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[5.2,-0.25,3.05],"to":[8.7,3.25,12.55],"autouv":0,"color":1,"rotation":[0,22.5,0],"origin":[5.7,2,13],"faces":{"north":{"uv":[2,12,3,13],"rotation":180,"texture":0},"east":{"uv":[0,13,1,16],"rotation":270,"texture":0},"south":{"uv":[1,12,2,13],"texture":0},"west":{"uv":[2,13,3,16],"rotation":90,"texture":0},"up":{"uv":[1,13,2,16],"rotation":180,"texture":0},"down":{"uv":[3,13,4,16],"texture":0}},"type":"cube","uuid":"859ab26f-304d-1fc2-e695-7c65fa9746c5"},{"name":"Left_leg","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[5.5,0,3.3],"to":[8.5,3,12.3],"autouv":0,"color":5,"rotation":[0,22.5,0],"origin":[6,2,13],"faces":{"north":{"uv":[7,12,6,13],"rotation":180,"texture":0},"east":{"uv":[4,13,5,16],"rotation":270,"texture":0},"south":{"uv":[5.95,13,5,12],"texture":0},"west":{"uv":[6,13,7,16],"rotation":90,"texture":0},"up":{"uv":[5,13,6,16],"rotation":180,"texture":0},"down":{"uv":[7,13,8,16],"texture":0}},"type":"cube","uuid":"1fb83a23-bc95-d6de-1a60-b721c8922407"},{"name":"Toggle_Right_Leg_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[7.2,-0.25,3.05],"to":[10.7,3.25,12.55],"autouv":0,"color":7,"rotation":[0,-22.5,0],"origin":[8.7,2,13],"faces":{"north":{"uv":[2,8,3,9],"rotation":180,"texture":0},"east":{"uv":[0,9,1,12],"rotation":270,"texture":0},"south":{"uv":[1,8,2,9],"texture":0},"west":{"uv":[2,9,3,12],"rotation":90,"texture":0},"up":{"uv":[1,9,2,12],"rotation":180,"texture":0},"down":{"uv":[3,9,4,12],"texture":0}},"type":"cube","uuid":"7fb46a5a-407b-6e68-5395-edf7ec58c151"},{"name":"Right_leg","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[7.5,0,3.3],"to":[10.5,3,12.3],"autouv":0,"color":0,"rotation":[0,-22.5,0],"origin":[9,2,13],"faces":{"north":{"uv":[3,4,2,5],"rotation":180,"texture":0},"east":{"uv":[0,5,1,8],"rotation":270,"texture":0},"south":{"uv":[2,5,1,4],"texture":0},"west":{"uv":[2,5,3,8],"rotation":90,"texture":0},"up":{"uv":[1,5,2,8],"rotation":180,"texture":0},"down":{"uv":[3,5,4,8],"texture":0}},"type":"cube","uuid":"2977e3b7-0d36-5caf-3a69-4c0fb7895a71"}],"groups":[{"uuid":"eda1d026-dfe0-f980-152d-71db37d8a5b3","export":true,"locked":false,"origin":[3,-6.7,6],"rotation":[0,0,0],"color":0,"name":"Player","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"5399b7c3-2eac-47ae-fb88-873e7e24a5d6","export":true,"locked":false,"origin":[8,16,8],"rotation":[0,0,0],"color":0,"name":"Head","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":false,"primary_selected":false},{"uuid":"0c759830-21cb-f6ec-e6d7-af3998e4bbd2","export":true,"locked":false,"origin":[8,11,8],"rotation":[0,0,0],"color":0,"name":"Body","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"b31348f5-2c4e-cfd0-cec3-4c11d4a5bee2","export":true,"locked":false,"origin":[5,15,6],"rotation":[0,0,0],"color":0,"name":"Left_Arm","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"eb158d47-d395-3084-947f-4101fa1eb833","export":true,"locked":false,"origin":[11,15,6],"rotation":[0,0,0],"color":0,"name":"Right_Arm","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"0022c2e4-2add-b938-2d45-0a8b8b95d53a","export":true,"locked":false,"origin":[7,13,7],"rotation":[0,0,0],"color":0,"name":"Left_Leg","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"c752460c-4cd8-aaae-17c7-d45bf46741e4","export":true,"locked":false,"origin":[10,13,7],"rotation":[0,0,0],"color":0,"name":"Right_Leg","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":false,"primary_selected":false}],"outliner":[{"uuid":"eda1d026-dfe0-f980-152d-71db37d8a5b3","isOpen":true,"children":[{"uuid":"5399b7c3-2eac-47ae-fb88-873e7e24a5d6","isOpen":false,"children":["4034240c-1bb7-3096-56d0-f9d9158bd705","5eca2c83-4481-ddf5-fafe-19abeb647c57"]},{"uuid":"0c759830-21cb-f6ec-e6d7-af3998e4bbd2","isOpen":true,"children":["8ea34c7a-dcd8-ead4-07d1-ccab4438046e","fd16d848-4320-9a3f-6b3d-3f4884a92fbe"]},{"uuid":"b31348f5-2c4e-cfd0-cec3-4c11d4a5bee2","isOpen":true,"children":["06ccbe71-f249-9062-076b-ea3f530fa945","15980f9e-f82b-d64a-eaa4-38789e0635cc"]},{"uuid":"eb158d47-d395-3084-947f-4101fa1eb833","isOpen":true,"children":["7b6c1629-5993-798e-8a6e-bb9303f0e8da","e5bd3372-f4e5-9cc0-1f38-726db3f86001"]},{"uuid":"0022c2e4-2add-b938-2d45-0a8b8b95d53a","isOpen":true,"children":["859ab26f-304d-1fc2-e695-7c65fa9746c5","1fb83a23-bc95-d6de-1a60-b721c8922407"]},{"uuid":"c752460c-4cd8-aaae-17c7-d45bf46741e4","isOpen":false,"children":["7fb46a5a-407b-6e68-5395-edf7ec58c151","2977e3b7-0d36-5caf-3a69-4c0fb7895a71"]}]}],"textures":[{"name":"author.png","relative_path":"H:/Download/2d9f724107b509db.png","folder":"H:/Download","namespace":"","id":"0","group":"","width":64,"height":64,"uv_width":16,"uv_height":16,"particle":true,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":true,"uuid":"f5a406a9-0e83-e8e7-427a-4cf3e6991f20","source":"data:image/png;base64,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"},{"name":"allium.png","path":"","folder":"","namespace":"","id":"1","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"b68aca7b-f859-4cdb-5c04-2e5b1fcfe0b6","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAHlBMVEUAAADoz/7Spva4eO1Vqy2mXuFSmi5Kjyh7TqAXfAQSwnSGAAAAAXRSTlMAQObYZgAAADlJREFUeNpjwAqUAyA0k5AFhKGoGNQAZhgZmUIYrMrBUMXtBlAGO0w7G4zBgsFgQzDQdXEyMDAwAACVCARQ+FH48gAAAABJRU5ErkJggg=="},{"name":"acacia_sapling.png","path":"","folder":"","namespace":"","id":"2","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"98bb7bd9-64b8-fac7-1eb5-c24a9483675f","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAr0lEQVQ4y6WTMQrCQBBFt0xtk0YQL+QR0mpnYyk5QbDObTc84YVB0NnFgSGTyf7/5w+bUpJ4LMdaeuI6jzWC5/Vcm0kAAyAleE6Hd00vkn8Fq0oCJv32up9yEg6REPGEQDLemyYR6DS3y7ATp3tQTTCEWqH3U50DcQccRl0b9lKSuAvV0wXG+AQyRTOYiDughkB76bXVgiDr5gkEeYEAu8iuf0L/WiD/Iii94diZ+gZo3N9oOnqLFAAAAABJRU5ErkJggg=="},{"name":"amethyst_cluster.png","path":"","folder":"","namespace":"","id":"3","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"68cf694e-cb45-9f14-68eb-4bc01d190d1d","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAwElEQVQ4y7WSwQ3CMBAE0x1F8OVFCfQAJSB6QFQBEq+0QAO8wWgiDVqcCGEQkS6Ondvx3tld98/nvLuWn8T346XsN19AEAtYL0+lyYniBDRBFJZbPwQlNAFW88MgIhAxB8D4MQBBDVjMttMA7BJpUZEAIgGWSbxMhCjCRQ0Y5fNiNxpm90nOUqzf/+Q+GyuAgOpu6YJ5HufoaF1EYN3pwu9s6uhYs2FZtwA3EDB5Iia5I6Pu0tHbO0CiodVcqy/UAxZem9kikfZjAAAAAElFTkSuQmCC"},{"name":"bamboo_stage0.png","path":"","folder":"","namespace":"","id":"4","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"0428cfd4-3770-fa8b-84d7-1abf7a12826d","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAvUlEQVQ4y7WQLQ7CMBzFewGynoAlmyJoWNIKZggOBdRgEUWDY2cAQTIuwDkffWWgoFlLEP+kou/3PgQA8cuJvwKUzaBtlg5YXgfQe5kOsPcJAcEUQUB7XoEp3MUDnCs2sxzzkQS3UF9SBPtTzBRJAHa/XdbYbYeYFgkACnmsURUS0RvQ1TgxrznqOACd2X0xdht0NaIBLwgrMA3fvQFVKWHq3ENOB+VTsEpvwLu/g9DZdCmiAV5cP4dkqk9/H0aurnV3nkVCAAAAAElFTkSuQmCC"},{"name":"brain_coral.png","path":"","folder":"","namespace":"","id":"5","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"6ff54ec1-7a00-bff3-1847-f6b2a75fcf21","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxklEQVQ4y7VTsQ3DMAzLR/2iF+SCPpGtJ3TIGnTqFZmKPhGge4Z84YAGaNAy00z1YkuWSIpxuu4fa3t80q/4dD0vQ2LTafN6nw8Lv7dXivfv61jXLf2U2bAzpgqcAcJzHKnkkFAQXWBUUMQELQoQUGr0opEs6qzzREaMZjWTd05pTiobvwRHY7NVpQaioMwnJqsnsa9xFn4oSDSOKiojdV6co6n0xLK7ZMUg46mhDYgmlZF53e37V2bOzCbk3Zs4/Jkcu6vdAQWuLZv4B6flAAAAAElFTkSuQmCC"},{"name":"redstone_torch.png","path":"","folder":"","namespace":"","id":"6","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"85835a8f-9619-0277-9e27-12059db4f1f9","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAWUlEQVQ4y2NgGAW0BX8ZGP6DMNma/98A4n4yDQEb8H/6fxCgyBXrydUMAlNL3f6HuuqRb8D8+oD/TqZKlBlAkQso9kJuuNl/WwNZyrxgrSdNmQvMtcTxGgAA/k03zVOyqGEAAAAASUVORK5CYII="}],"display":{"thirdperson_righthand":{"rotation":[75,45,0],"translation":[0,2.5,0],"scale":[0.375,0.375,0.375]},"thirdperson_lefthand":{"rotation":[75,45,0],"translation":[0,2.5,0],"scale":[0.375,0.375,0.375]},"firstperson_righthand":{"rotation":[0,124,0],"translation":[2,3,0],"scale":[0.4,0.4,0.4]},"firstperson_lefthand":{"rotation":[0,120,0],"translation":[1.5,2.75,0],"scale":[0.4,0.4,0.4]},"ground":{"translation":[0,2,0],"scale":[0.5,0.5,0.5]},"gui":{"rotation":[30,-135,0],"translation":[0.75,-1,0],"scale":[0.625,0.625,0.625]},"head":{"translation":[0,14,-0.75]},"fixed":{"translation":[0,0,-2.75],"scale":[0.5,0.5,0.5]},"on_shelf":{"rotation":[0,-180,0],"translation":[0,0,5.25]}}} \ No newline at end of file diff --git a/res/doll_default.json b/res/doll_default.json deleted file mode 100644 index adf40c9..0000000 --- a/res/doll_default.json +++ /dev/null @@ -1,265 +0,0 @@ -{ - "format_version": "1.9.0", - "credit": "3D Model © 2025 LeisureTimeDock", - "textures": { - "0": "#skin", - "particle": "minecraft:block/white_wool" - }, - "elements": [ - { - "name": "Toggle_Helmet", - "from": [3.5, 8.8, 7.5], - "to": [12.5, 17.8, 16.5], - "rotation": {"angle": 0, "axis": "y", "origin": [8, 9.3, 12]}, - "faces": { - "north": {"uv": [10, 2, 12, 4], "texture": "#0"}, - "east": {"uv": [8, 2, 10, 4], "texture": "#0"}, - "south": {"uv": [14, 2, 16, 4], "texture": "#0"}, - "west": {"uv": [12, 2, 14, 4], "texture": "#0"}, - "up": {"uv": [10, 2, 12, 0], "texture": "#0"}, - "down": {"uv": [12, 0, 14, 2], "texture": "#0"} - } - }, - { - "name": "Head", - "from": [4, 9.3, 8], - "to": [12, 17.3, 16], - "rotation": {"angle": 0, "axis": "y", "origin": [8, 9.3, 12]}, - "faces": { - "north": {"uv": [2, 2, 4, 4], "texture": "#0"}, - "east": {"uv": [0, 2, 2, 4], "texture": "#0"}, - "south": {"uv": [6, 2, 8, 4], "texture": "#0"}, - "west": {"uv": [4, 2, 6, 4], "texture": "#0"}, - "up": {"uv": [4, 2, 2, 0], "texture": "#0"}, - "down": {"uv": [6, 0, 4, 2], "texture": "#0"} - } - }, - { - "name": "Toggle_Chest_Armor", - "from": [4.75, 2.05, 9.75], - "to": [11.25, 9.55, 13.25], - "rotation": {"angle": 0, "axis": "y", "origin": [8, 5.8, 11.5]}, - "faces": { - "north": {"uv": [5, 9, 7, 12], "texture": "#0"}, - "east": {"uv": [4, 9, 5, 12], "texture": "#0"}, - "south": {"uv": [8, 9, 10, 12], "texture": "#0"}, - "west": {"uv": [7, 9, 8, 12], "texture": "#0"}, - "up": {"uv": [5, 8, 7, 9], "texture": "#0"}, - "down": {"uv": [7, 8, 9, 9], "texture": "#0"} - } - }, - { - "name": "Body", - "from": [5, 2.3, 10], - "to": [11, 9.3, 13], - "rotation": {"angle": 0, "axis": "y", "origin": [8, 5.8, 11.5]}, - "faces": { - "north": {"uv": [5, 5, 7, 8], "texture": "#0"}, - "east": {"uv": [4, 5, 5, 8], "texture": "#0"}, - "south": {"uv": [8, 5, 10, 8], "texture": "#0"}, - "west": {"uv": [7, 5, 8, 8], "texture": "#0"}, - "up": {"uv": [7, 5, 5, 4], "texture": "#0"}, - "down": {"uv": [9, 4, 7, 5], "texture": "#0"} - } - }, - { - "name": "Toggle_Left_Arm_Armor", - "from": [2.75, 6.25, 3.8], - "to": [5.25, 9.75, 13.3], - "rotation": {"angle": -22.5, "axis": "y", "origin": [3, 8, 12.5]}, - "faces": { - "north": {"uv": [13.75, 12, 14.5, 13], "rotation": 180, "texture": "#0"}, - "east": {"uv": [12, 13, 13, 16], "rotation": 270, "texture": "#0"}, - "south": {"uv": [13, 12, 13.75, 13], "texture": "#0"}, - "west": {"uv": [13.75, 13, 14.75, 16], "rotation": 90, "texture": "#0"}, - "up": {"uv": [13, 13, 13.75, 16], "rotation": 180, "texture": "#0"}, - "down": {"uv": [14.75, 13, 15.5, 16], "texture": "#0"} - } - }, - { - "name": "Left_arm", - "from": [3, 6.5, 3.8], - "to": [5, 9.5, 12.8], - "rotation": {"angle": -22.5, "axis": "y", "origin": [4, 8, 12.5]}, - "faces": { - "north": {"uv": [10.5, 12, 9.75, 13], "rotation": 180, "texture": "#0"}, - "east": {"uv": [8, 13, 9, 16], "rotation": 270, "texture": "#0"}, - "south": {"uv": [9.75, 13, 9, 12], "texture": "#0"}, - "west": {"uv": [9.75, 13, 10.5, 16], "rotation": 90, "texture": "#0"}, - "up": {"uv": [9, 13, 9.75, 16], "rotation": 180, "texture": "#0"}, - "down": {"uv": [10.5, 13, 11.5, 16], "texture": "#0"} - } - }, - { - "name": "Toggle_Right_Arm_Armor", - "from": [10.75, 6.25, 3.8], - "to": [13.25, 9.75, 13.3], - "rotation": {"angle": 22.5, "axis": "y", "origin": [12, 8, 11.5]}, - "faces": { - "north": {"uv": [11.75, 8, 12.5, 9], "rotation": 180, "texture": "#0"}, - "east": {"uv": [10, 9, 11, 12], "rotation": 270, "texture": "#0"}, - "south": {"uv": [11, 8, 11.75, 9], "texture": "#0"}, - "west": {"uv": [11.75, 9, 12.75, 12], "rotation": 90, "texture": "#0"}, - "up": {"uv": [11, 9, 11.75, 12], "rotation": 180, "texture": "#0"}, - "down": {"uv": [12.75, 9, 13.5, 12], "texture": "#0"} - } - }, - { - "name": "Right_arm", - "from": [11, 6.5, 3.8], - "to": [13, 9.5, 12.8], - "rotation": {"angle": 22.5, "axis": "y", "origin": [12, 8, 11.5]}, - "faces": { - "north": {"uv": [12.5, 4, 11.75, 5], "rotation": 180, "texture": "#0"}, - "east": {"uv": [10, 5, 11, 8], "rotation": 270, "texture": "#0"}, - "south": {"uv": [11.75, 5, 11, 4], "texture": "#0"}, - "west": {"uv": [11.75, 5, 12.5, 8], "rotation": 90, "texture": "#0"}, - "up": {"uv": [11, 5, 11.75, 8], "rotation": 180, "texture": "#0"}, - "down": {"uv": [12.5, 5, 13.5, 8], "texture": "#0"} - } - }, - { - "name": "Toggle_Left_Leg_Armor", - "from": [5.2, -0.25, 3.05], - "to": [8.7, 3.25, 12.55], - "rotation": {"angle": 22.5, "axis": "y", "origin": [5.7, 2, 13]}, - "faces": { - "north": {"uv": [2, 12, 3, 13], "rotation": 180, "texture": "#0"}, - "east": {"uv": [0, 13, 1, 16], "rotation": 270, "texture": "#0"}, - "south": {"uv": [1, 12, 2, 13], "texture": "#0"}, - "west": {"uv": [2, 13, 3, 16], "rotation": 90, "texture": "#0"}, - "up": {"uv": [1, 13, 2, 16], "rotation": 180, "texture": "#0"}, - "down": {"uv": [3, 13, 4, 16], "texture": "#0"} - } - }, - { - "name": "Left_leg", - "from": [5.5, 0, 3.3], - "to": [8.5, 3, 12.3], - "rotation": {"angle": 22.5, "axis": "y", "origin": [6, 2, 13]}, - "faces": { - "north": {"uv": [7, 12, 6, 13], "rotation": 180, "texture": "#0"}, - "east": {"uv": [4, 13, 5, 16], "rotation": 270, "texture": "#0"}, - "south": {"uv": [5.95, 13, 5, 12], "texture": "#0"}, - "west": {"uv": [6, 13, 7, 16], "rotation": 90, "texture": "#0"}, - "up": {"uv": [5, 13, 6, 16], "rotation": 180, "texture": "#0"}, - "down": {"uv": [7, 13, 8, 16], "texture": "#0"} - } - }, - { - "name": "Toggle_Right_Leg_Armor", - "from": [7.2, -0.25, 3.05], - "to": [10.7, 3.25, 12.55], - "rotation": {"angle": -22.5, "axis": "y", "origin": [8.7, 2, 13]}, - "faces": { - "north": {"uv": [2, 8, 3, 9], "rotation": 180, "texture": "#0"}, - "east": {"uv": [0, 9, 1, 12], "rotation": 270, "texture": "#0"}, - "south": {"uv": [1, 8, 2, 9], "texture": "#0"}, - "west": {"uv": [2, 9, 3, 12], "rotation": 90, "texture": "#0"}, - "up": {"uv": [1, 9, 2, 12], "rotation": 180, "texture": "#0"}, - "down": {"uv": [3, 9, 4, 12], "texture": "#0"} - } - }, - { - "name": "Right_leg", - "from": [7.5, 0, 3.3], - "to": [10.5, 3, 12.3], - "rotation": {"angle": -22.5, "axis": "y", "origin": [9, 2, 13]}, - "faces": { - "north": {"uv": [3, 4, 2, 5], "rotation": 180, "texture": "#0"}, - "east": {"uv": [0, 5, 1, 8], "rotation": 270, "texture": "#0"}, - "south": {"uv": [2, 5, 1, 4], "texture": "#0"}, - "west": {"uv": [2, 5, 3, 8], "rotation": 90, "texture": "#0"}, - "up": {"uv": [1, 5, 2, 8], "rotation": 180, "texture": "#0"}, - "down": {"uv": [3, 5, 4, 8], "texture": "#0"} - } - } - ], - "display": { - "thirdperson_righthand": { - "rotation": [75, 45, 0], - "translation": [0, 2.5, 0], - "scale": [0.375, 0.375, 0.375] - }, - "thirdperson_lefthand": { - "rotation": [75, 45, 0], - "translation": [0, 2.5, 0], - "scale": [0.375, 0.375, 0.375] - }, - "firstperson_righthand": { - "rotation": [0, 124, 0], - "translation": [2, 3, 0], - "scale": [0.4, 0.4, 0.4] - }, - "firstperson_lefthand": { - "rotation": [0, 120, 0], - "translation": [1.5, 2.75, 0], - "scale": [0.4, 0.4, 0.4] - }, - "ground": { - "translation": [0, 2, 0], - "scale": [0.5, 0.5, 0.5] - }, - "gui": { - "rotation": [30, -135, 0], - "translation": [0.75, -1, 0], - "scale": [0.625, 0.625, 0.625] - }, - "head": { - "translation": [0, 14, -0.75] - }, - "fixed": { - "translation": [0, 0, -2.75], - "scale": [0.5, 0.5, 0.5] - }, - "on_shelf": { - "rotation": [0, -180, 0], - "translation": [0, 0, 5.25] - } - }, - "groups": [ - { - "name": "Player", - "origin": [3, -6.7, 6], - "color": 0, - "children": [ - { - "name": "Head", - "origin": [8, 16, 8], - "color": 0, - "children": [0, 1] - }, - { - "name": "Body", - "origin": [8, 11, 8], - "color": 0, - "children": [2, 3] - }, - { - "name": "Left_Arm", - "origin": [5, 15, 6], - "color": 0, - "children": [4, 5] - }, - { - "name": "Right_Arm", - "origin": [11, 15, 6], - "color": 0, - "children": [6, 7] - }, - { - "name": "Left_Leg", - "origin": [7, 13, 7], - "color": 0, - "children": [8, 9] - }, - { - "name": "Right_Leg", - "origin": [10, 13, 7], - "color": 0, - "children": [10, 11] - } - ] - } - ] -} \ No newline at end of file diff --git a/res/doll_item.bbmodel b/res/doll_item.bbmodel deleted file mode 100644 index 3944b14..0000000 --- a/res/doll_item.bbmodel +++ /dev/null @@ -1 +0,0 @@ -{"meta":{"format_version":"5.0","model_format":"java_block","box_uv":false},"name":"doll_item","parent":"","java_block_version":"1.9.0","ambientocclusion":true,"front_gui_light":false,"visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"unhandled_root_fields":{},"resolution":{"width":16,"height":16},"elements":[{"name":"cube","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[7,6,1],"to":[7,14,9],"autouv":0,"color":8,"rotation":[0,45,0],"origin":[7,10,3.5],"faces":{"north":{"uv":[0,0,0,0],"texture":null},"east":{"uv":[0,0,16,16],"texture":0},"south":{"uv":[0,0,0,0],"texture":null},"west":{"uv":[0,0,16,16],"texture":0},"up":{"uv":[0,0,0,0],"texture":null},"down":{"uv":[0,0,0,0],"texture":null}},"type":"cube","uuid":"f9f45372-1441-1b47-bbb1-278cfff92370"},{"name":"cube","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[9,6,1],"to":[9,14,9],"autouv":0,"color":0,"rotation":[0,-45,0],"origin":[9,10,3.5],"faces":{"north":{"uv":[0,0,0,0],"texture":null},"east":{"uv":[0,0,16,16],"texture":0},"south":{"uv":[0,0,0,0],"texture":null},"west":{"uv":[0,0,16,16],"texture":0},"up":{"uv":[0,0,0,0],"texture":null},"down":{"uv":[0,0,0,0],"texture":null}},"type":"cube","uuid":"04196842-4494-bee4-1b44-b302f4a2475a"}],"groups":[{"uuid":"2b55d183-11b7-4e36-1f45-11cc7c5d7445","export":true,"locked":false,"origin":[0,4,2.5],"rotation":[0,0,0],"color":0,"name":"flower_item","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":true}],"outliner":[{"uuid":"2b55d183-11b7-4e36-1f45-11cc7c5d7445","isOpen":true,"children":["f9f45372-1441-1b47-bbb1-278cfff92370","04196842-4494-bee4-1b44-b302f4a2475a"]}],"textures":[{"name":"amethyst_cluster.png","path":"","folder":"","namespace":"","id":"3","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":true,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"65a7492d-58ab-bfad-b419-fd46962220fb","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAwElEQVQ4y7WSwQ3CMBAE0x1F8OVFCfQAJSB6QFQBEq+0QAO8wWgiDVqcCGEQkS6Ondvx3tld98/nvLuWn8T346XsN19AEAtYL0+lyYniBDRBFJZbPwQlNAFW88MgIhAxB8D4MQBBDVjMttMA7BJpUZEAIgGWSbxMhCjCRQ0Y5fNiNxpm90nOUqzf/+Q+GyuAgOpu6YJ5HufoaF1EYN3pwu9s6uhYs2FZtwA3EDB5Iia5I6Pu0tHbO0CiodVcqy/UAxZem9kikfZjAAAAAElFTkSuQmCC"}]} \ No newline at end of file diff --git a/res/doll_item.json b/res/doll_item.json deleted file mode 100644 index 3d3caae..0000000 --- a/res/doll_item.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "format_version": "1.9.0", - "credit": "3D Model © 2025 LeisureTimeDock", - "textures": { - "3": "#item", - "particle": "#item" - }, - "elements": [ - { - "from": [7, 6, 1], - "to": [7, 14, 9], - "rotation": {"angle": 45, "axis": "y", "origin": [7, 10, 3.5]}, - "faces": { - "east": {"uv": [0, 0, 16, 16], "texture": "#3"}, - "west": {"uv": [0, 0, 16, 16], "texture": "#3"} - } - }, - { - "from": [9, 6, 1], - "to": [9, 14, 9], - "rotation": {"angle": -45, "axis": "y", "origin": [9, 10, 3.5]}, - "faces": { - "east": {"uv": [0, 0, 16, 16], "texture": "#3"}, - "west": {"uv": [0, 0, 16, 16], "texture": "#3"} - } - } - ], - "display": {}, - "groups": [ - { - "name": "item", - "origin": [0, 4, 2.5], - "color": 0, - "children": [0, 1] - } - ] -} \ No newline at end of file diff --git a/res/doll_without_item.bbmodel b/res/doll_without_item.bbmodel deleted file mode 100644 index 6c0fe1e..0000000 --- a/res/doll_without_item.bbmodel +++ /dev/null @@ -1 +0,0 @@ -{"meta":{"format_version":"5.0","model_format":"java_block","box_uv":false},"name":"doll_without_item","parent":"","java_block_version":"1.9.0","ambientocclusion":true,"front_gui_light":false,"visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"unhandled_root_fields":{"format_version":"1.21.6"},"resolution":{"width":16,"height":16},"elements":[{"name":"Toggle_Helmet","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[3.5,8.8,7.5],"to":[12.5,17.8,16.5],"autouv":0,"color":0,"origin":[8,9.3,12],"faces":{"north":{"uv":[10,2,12,4],"texture":0},"east":{"uv":[8,2,10,4],"texture":0},"south":{"uv":[14,2,16,4],"texture":0},"west":{"uv":[12,2,14,4],"texture":0},"up":{"uv":[10,2,12,0],"texture":0},"down":{"uv":[12,0,14,2],"texture":0}},"type":"cube","uuid":"4034240c-1bb7-3096-56d0-f9d9158bd705"},{"name":"Head","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[4,9.3,8],"to":[12,17.3,16],"autouv":0,"color":0,"origin":[8,9.3,12],"faces":{"north":{"uv":[2,2,4,4],"texture":0},"east":{"uv":[0,2,2,4],"texture":0},"south":{"uv":[6,2,8,4],"texture":0},"west":{"uv":[4,2,6,4],"texture":0},"up":{"uv":[4,2,2,0],"texture":0},"down":{"uv":[6,0,4,2],"texture":0}},"type":"cube","uuid":"5eca2c83-4481-ddf5-fafe-19abeb647c57"},{"name":"Toggle_Chest_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[4.75,2.05,9.75],"to":[11.25,9.55,13.25],"autouv":0,"color":0,"origin":[8,5.8,11.5],"faces":{"north":{"uv":[5,9,7,12],"texture":0},"east":{"uv":[4,9,5,12],"texture":0},"south":{"uv":[8,9,10,12],"texture":0},"west":{"uv":[7,9,8,12],"texture":0},"up":{"uv":[5,8,7,9],"texture":0},"down":{"uv":[7,8,9,9],"texture":0}},"type":"cube","uuid":"8ea34c7a-dcd8-ead4-07d1-ccab4438046e"},{"name":"Body","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[5,2.3,10],"to":[11,9.3,13],"autouv":0,"color":3,"origin":[8,5.8,11.5],"faces":{"north":{"uv":[5,5,7,8],"texture":0},"east":{"uv":[4,5,5,8],"texture":0},"south":{"uv":[8,5,10,8],"texture":0},"west":{"uv":[7,5,8,8],"texture":0},"up":{"uv":[7,5,5,4],"texture":0},"down":{"uv":[9,4,7,5],"texture":0}},"type":"cube","uuid":"fd16d848-4320-9a3f-6b3d-3f4884a92fbe"},{"name":"Toggle_Left_Arm_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[2.75,0.3,9.75],"to":[5.25,9.8,13.25],"autouv":0,"color":3,"origin":[3,8,12.5],"faces":{"north":{"uv":[13,13,13.75,16],"texture":0},"east":{"uv":[12,13,13,16],"texture":0},"south":{"uv":[14.75,13,15.5,16],"texture":0},"west":{"uv":[13.75,13,14.75,16],"texture":0},"up":{"uv":[13,12,13.75,13],"texture":0},"down":{"uv":[13.75,12,14.5,13],"texture":0}},"type":"cube","uuid":"06ccbe71-f249-9062-076b-ea3f530fa945"},{"name":"Left_arm","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[3,0.3,10],"to":[5,9.3,13],"autouv":0,"color":6,"origin":[4,8,12.5],"faces":{"north":{"uv":[9,13,9.75,16],"texture":0},"east":{"uv":[8,13,9,16],"texture":0},"south":{"uv":[10.5,13,11.5,16],"texture":0},"west":{"uv":[9.75,13,10.5,16],"texture":0},"up":{"uv":[9.75,13,9,12],"texture":0},"down":{"uv":[10.5,12,9.75,13],"texture":0}},"type":"cube","uuid":"15980f9e-f82b-d64a-eaa4-38789e0635cc"},{"name":"Toggle_Right_Arm_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[10.75,0.3,9.75],"to":[13.25,9.8,13.25],"autouv":0,"color":6,"origin":[12,8,11.5],"faces":{"north":{"uv":[11,9,11.75,12],"texture":0},"east":{"uv":[10,9,11,12],"texture":0},"south":{"uv":[12.75,9,13.5,12],"texture":0},"west":{"uv":[11.75,9,12.75,12],"texture":0},"up":{"uv":[11,8,11.75,9],"texture":0},"down":{"uv":[11.75,8,12.5,9],"texture":0}},"type":"cube","uuid":"7b6c1629-5993-798e-8a6e-bb9303f0e8da"},{"name":"Right_arm","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[11,0.3,10],"to":[13,9.3,13],"autouv":0,"color":0,"origin":[12,8,11.5],"faces":{"north":{"uv":[11,5,11.75,8],"texture":0},"east":{"uv":[10,5,11,8],"texture":0},"south":{"uv":[12.5,5,13.5,8],"texture":0},"west":{"uv":[11.75,5,12.5,8],"texture":0},"up":{"uv":[11.75,5,11,4],"texture":0},"down":{"uv":[12.5,4,11.75,5],"texture":0}},"type":"cube","uuid":"e5bd3372-f4e5-9cc0-1f38-726db3f86001"},{"name":"Toggle_Left_Leg_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[5.2,-0.25,3.05],"to":[8.7,3.25,12.55],"autouv":0,"color":1,"rotation":[0,22.5,0],"origin":[5.7,2,13],"faces":{"north":{"uv":[2,12,3,13],"rotation":180,"texture":0},"east":{"uv":[0,13,1,16],"rotation":270,"texture":0},"south":{"uv":[1,12,2,13],"texture":0},"west":{"uv":[2,13,3,16],"rotation":90,"texture":0},"up":{"uv":[1,13,2,16],"rotation":180,"texture":0},"down":{"uv":[3,13,4,16],"texture":0}},"type":"cube","uuid":"859ab26f-304d-1fc2-e695-7c65fa9746c5"},{"name":"Left_leg","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[5.5,0,3.3],"to":[8.5,3,12.3],"autouv":0,"color":5,"rotation":[0,22.5,0],"origin":[6,2,13],"faces":{"north":{"uv":[7,12,6,13],"rotation":180,"texture":0},"east":{"uv":[4,13,5,16],"rotation":270,"texture":0},"south":{"uv":[5.95,13,5,12],"texture":0},"west":{"uv":[6,13,7,16],"rotation":90,"texture":0},"up":{"uv":[5,13,6,16],"rotation":180,"texture":0},"down":{"uv":[7,13,8,16],"texture":0}},"type":"cube","uuid":"1fb83a23-bc95-d6de-1a60-b721c8922407"},{"name":"Toggle_Right_Leg_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[7.2,-0.25,3.05],"to":[10.7,3.25,12.55],"autouv":0,"color":7,"rotation":[0,-22.5,0],"origin":[8.7,2,13],"faces":{"north":{"uv":[2,8,3,9],"rotation":180,"texture":0},"east":{"uv":[0,9,1,12],"rotation":270,"texture":0},"south":{"uv":[1,8,2,9],"texture":0},"west":{"uv":[2,9,3,12],"rotation":90,"texture":0},"up":{"uv":[1,9,2,12],"rotation":180,"texture":0},"down":{"uv":[3,9,4,12],"texture":0}},"type":"cube","uuid":"7fb46a5a-407b-6e68-5395-edf7ec58c151"},{"name":"Right_leg","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[7.5,0,3.3],"to":[10.5,3,12.3],"autouv":0,"color":0,"rotation":[0,-22.5,0],"origin":[9,2,13],"faces":{"north":{"uv":[3,4,2,5],"rotation":180,"texture":0},"east":{"uv":[0,5,1,8],"rotation":270,"texture":0},"south":{"uv":[2,5,1,4],"texture":0},"west":{"uv":[2,5,3,8],"rotation":90,"texture":0},"up":{"uv":[1,5,2,8],"rotation":180,"texture":0},"down":{"uv":[3,5,4,8],"texture":0}},"type":"cube","uuid":"2977e3b7-0d36-5caf-3a69-4c0fb7895a71"},{"name":"cube","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[7,6,1],"to":[7,14,9],"autouv":0,"color":8,"visibility":false,"export":false,"rotation":[0,45,0],"origin":[7,10,3.5],"faces":{"north":{"uv":[0,0,0,0],"texture":null},"east":{"uv":[0,0,16,16],"texture":1},"south":{"uv":[0,0,0,0],"texture":null},"west":{"uv":[0,0,16,16],"texture":1},"up":{"uv":[0,0,0,0],"texture":null},"down":{"uv":[0,0,0,0],"texture":null}},"type":"cube","uuid":"ba8da4eb-b48e-0c2f-8240-95d681f387ff"},{"name":"cube","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[9,6,1],"to":[9,14,9],"autouv":0,"color":0,"visibility":false,"export":false,"rotation":[0,-45,0],"origin":[9,10,3.5],"faces":{"north":{"uv":[0,0,0,0],"texture":null},"east":{"uv":[0,0,16,16],"texture":1},"south":{"uv":[0,0,0,0],"texture":null},"west":{"uv":[0,0,16,16],"texture":1},"up":{"uv":[0,0,0,0],"texture":null},"down":{"uv":[0,0,0,0],"texture":null}},"type":"cube","uuid":"3644a889-b638-1c56-700d-f80c64cc474c"}],"groups":[{"uuid":"eda1d026-dfe0-f980-152d-71db37d8a5b3","export":true,"locked":false,"origin":[3,-6.7,6],"rotation":[0,0,0],"color":0,"name":"Player","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"5399b7c3-2eac-47ae-fb88-873e7e24a5d6","export":true,"locked":false,"origin":[8,16,8],"rotation":[0,0,0],"color":0,"name":"Head","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":false,"primary_selected":false},{"uuid":"0c759830-21cb-f6ec-e6d7-af3998e4bbd2","export":true,"locked":false,"origin":[8,11,8],"rotation":[0,0,0],"color":0,"name":"Body","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":false,"primary_selected":false},{"uuid":"b31348f5-2c4e-cfd0-cec3-4c11d4a5bee2","export":true,"locked":false,"origin":[5,15,6],"rotation":[0,0,0],"color":0,"name":"Left_Arm","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"eb158d47-d395-3084-947f-4101fa1eb833","export":true,"locked":false,"origin":[11,15,6],"rotation":[0,0,0],"color":0,"name":"Right_Arm","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"0022c2e4-2add-b938-2d45-0a8b8b95d53a","export":true,"locked":false,"origin":[7,13,7],"rotation":[0,0,0],"color":0,"name":"Left_Leg","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"c752460c-4cd8-aaae-17c7-d45bf46741e4","export":true,"locked":false,"origin":[10,13,7],"rotation":[0,0,0],"color":0,"name":"Right_Leg","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":false,"primary_selected":false},{"uuid":"6e676a00-56e1-2ec4-ecb9-00710aada58d","export":false,"locked":false,"origin":[0,4,2.5],"rotation":[0,0,0],"color":0,"name":"flower_item","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":false,"autouv":0,"isOpen":true,"primary_selected":false}],"outliner":[{"uuid":"eda1d026-dfe0-f980-152d-71db37d8a5b3","isOpen":true,"children":[{"uuid":"5399b7c3-2eac-47ae-fb88-873e7e24a5d6","isOpen":false,"children":["4034240c-1bb7-3096-56d0-f9d9158bd705","5eca2c83-4481-ddf5-fafe-19abeb647c57"]},{"uuid":"0c759830-21cb-f6ec-e6d7-af3998e4bbd2","isOpen":false,"children":["8ea34c7a-dcd8-ead4-07d1-ccab4438046e","fd16d848-4320-9a3f-6b3d-3f4884a92fbe"]},{"uuid":"b31348f5-2c4e-cfd0-cec3-4c11d4a5bee2","isOpen":true,"children":["06ccbe71-f249-9062-076b-ea3f530fa945","15980f9e-f82b-d64a-eaa4-38789e0635cc"]},{"uuid":"eb158d47-d395-3084-947f-4101fa1eb833","isOpen":true,"children":["7b6c1629-5993-798e-8a6e-bb9303f0e8da","e5bd3372-f4e5-9cc0-1f38-726db3f86001"]},{"uuid":"0022c2e4-2add-b938-2d45-0a8b8b95d53a","isOpen":true,"children":["859ab26f-304d-1fc2-e695-7c65fa9746c5","1fb83a23-bc95-d6de-1a60-b721c8922407"]},{"uuid":"c752460c-4cd8-aaae-17c7-d45bf46741e4","isOpen":false,"children":["7fb46a5a-407b-6e68-5395-edf7ec58c151","2977e3b7-0d36-5caf-3a69-4c0fb7895a71"]}]},{"uuid":"6e676a00-56e1-2ec4-ecb9-00710aada58d","isOpen":true,"children":["ba8da4eb-b48e-0c2f-8240-95d681f387ff","3644a889-b638-1c56-700d-f80c64cc474c"]}],"textures":[{"name":"author.png","relative_path":"H:/Download/2d9f724107b509db.png","folder":"H:/Download","namespace":"","id":"0","group":"","width":64,"height":64,"uv_width":16,"uv_height":16,"particle":true,"use_as_default":false,"layers_enabled":false,"sync_to_project":"db1cee84-e800-465d-d5f1-b3a244a724aa","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":true,"uuid":"f5a406a9-0e83-e8e7-427a-4cf3e6991f20","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAOZ0lEQVR4AcyafWyV1R3Hf/dKW8rLtVAVKRSKDgWiVVmUCGhUdEZwVqJsWVYzndvYHDFz6pap29Rs/KHEMBNd3HTTbHFmThP+UBMZ4hDdNJnzLSooDixcKFq4XITSVnl2Pr/e3+25p899a3lZ088939/LOc9zzvNynreklPl7qqElemPSqdH2X39cAD5iz1z7nwjubXs18sEHZZovG8789pMIwkR8EPqrtcsOwMmj6wba7ByQKIu9u7sXs4A4X0FCFUb3ru5B2XG+QUkVOMoOwIQbVkvLHeulfny9vJ7t0ibR+IiV6mipmDZUwU/DD45PTLx9SiJMxUcs9Fdrlx0AGmS0QTvk9gI0EJs1rpYillKx2ApHwVnRAIjr9Osf9G999gLVzsf66qA4sfTGXvFxLrEY+v+Vigagfma9zJszWdiis6c3qsZHp/DRcbQPPmIXzr4s8vFzjob21wVd0QCwu8PsVKOuMxowzrvmMwoZ2dxQAE5i17Uvk4XNd8q6K66Ury5bhvuosvb15xI+ZQdgzNKUnHhbs7Dbs/tzOAA+Yg/dXysjT2sRmXpCAfiIXXPTQmk/Z6KMvHqW3HTdwiF1PnJ/YUXnikLfUOwkczlz+vYb3Txv5Ob8vZ9myi7k+4+cKl0PR9K1oqeAd2dfLCf94+vy9Lhp0vngJfLhqqmyY3mHbM+1zTJZNnM5yCH+o02fsHmLJZnLTx4jwjENJDLNARr6PnhHzl8xDamM+UVK8KnhfsgFJ3W6RNOmtj26TtB+DI2PONoOJ7QPWxkS7o+NYYOHdq4EMfDrmPbb9HUYTzKfb+o/jC2WL7dl9gsc6MjIulv+q34OAzQ+YqABfnIzA5I2aRvQ+HzwEdv75yuFOd2Pmf6sa48ANsukZHA/uzuL1JjF1eH90CZtA9oLqcRHLIm17ZInKPIwYrDpV50CL/1pjHBC88FHDMiFfANO+G3SUc4bzt3/7wZKfc7q2vyp+x34f/n0i6KOefP00Bt7XEMC2PI6pbp6ZLIR2IWJAT7qUBdt0DaYHZbEdAAI0AGgcV3Z3MKIrXjrNmm7dTlSQeNTgx9yHdSlDcDtQ8xsX5+5bUP+Ku+LNV+L5j57kjS//LL66DhwAr511Rz10faiR89KdL/XnT+f0C51qEsb2EDbgI6DWJIG86PrOkFi47110ni7Lk/oqE1lxMCmNGLY5FIHbYNHm7RtEIvTdJAYHLPgr4lE88P9C8bB+jjoMObE+90lsbMLdM7GR13aQANtG77t66SusPOwVYAVly075cA7m/VKbmZ9s+SnsuaG/rk+N6URI59c6qBpA1yTom2zgg5iZvtafZosEt490g7kwlroIKgSvTcJ4wVtuOVq+5TUoQRPJ2mAKzZdKQKO1cs/d7/9/9OOu176dm6VxlvcXWFurt89/RT1TXOx/iwRvw5t0SZtG+TFaXzE4mDLQ0Gncon4iEHONaigbYMgGnydpAHgGKNseeM2afj3zfLw9TfL3I9+Jut33ChLrrhalpx/hVx+5gVSWzchoUxoTlAHUgvOS1AXTeN0nkFAAxofev/aW4RlbHhxqaDhvpZzoienLojQ5ITQtvmiXdsj4ITc29kR9W58O8queUlPmuT4uWjDYti+zp8EccbRnc0KpLu6ZHdfX1zKIJ9dMvuBOJ8fr1Zz6V1zwmSpmX5a0aq93QeicpQdgPpUSupTKWlqbJRxNTVFF2YBv6NsJYiLme9wl+lJMwRsOaatLDsAVrGa0o4zttLY1qla1XxqHIIfLsQ4N/lXpMWabdr2fsEg+HlJjj0fP4j+yf7RYvxyxEThYoNjlpJ6nIzA7iWo49N3cGBWwz+/7Y8Cp17wkIy6cIWC3nzmctW0xbNFSujt6Yx8+kYnBWhLZx8nGGjOA2C5zp3/Z2szCDgosdGQbFu9Utpee7Qf9MbH5ey/PybLnn9ETl95VyzEzn7qN0Jddnl289FtW/LTEj4aZyvZSuJjLwByqYPPID9WZ7znjWg3RTPlkg+6DHepjlaCOL6w09j4Iam3sQ3usRYwzeH1NXYIecYEkfTUkxXO9iDOx1l/7EXzBdD4iIHl4zMsB9vXsicj2mE6hnbrUuNOfIkzTpc4iLmUgn+2uHUaDZYw+BzAKBuWVabsSmcF5t58rMDnz35DbErbOn++anzEgFwo06yG6VDIhk3b9SaNG7EQYuRrZfdDx8FJ/UcbOJI9e+ukpvYEBd1zzFjR6cVNMbalwtLPpxEj84dugREL/yIc039ruVj2PfakanzEwPIrKbnbo1OAhkkNo4pWJUaOJTS7GczAZ5oSO5lpWyRspTcnzxA0YL/y0gbpWtw+AFs5Z7/SukCIk8ul5l1LF8s9t36L9vKwG/9+VFp++s3F+iwxH3CCXOpQN4/zF/xzyQrOSafAyar/O9x1DPgVfTvZsOoZgX3jJ2mJhtZZJ4o88OAAtJCzW9c+LcTJw80FEhdKPDAFOnXn+iV6/cBFFBofMSCXOtQtgA6D5xzTeKzE4aUUSMs1J1sazKb07aRVmPvWmkELIjkOq0PJWX1l/RQBOgkfrb1WbaZPpk5i+IgBNlDX4ORoy0Kb33xhyTkkjjCPrQ3mR4PZg0+CFnFlY1NK4nCh/D/3DWaw0trRnGNKqkcgZwoxcsymLphNDMz2Y+artmzOnQOsXmiXHACrVKqkg0brB8u0w9jp7MAFEBof+DnYtE1HuUGiBNPEOKHFQSwOy7UYW9vAZ5oSO2kV4sq4XQyfn0sjw4FBANqgBF/riRaHAw1OivzwBsX2ULOJ5XOcwRZ3hRQrk+2LLpWrzjtXKEM4Uxs/+vblYtrPa7p7kvxr60gFDZOfmCkdib1CCaaJQTX5k9evz5+b0BNeeFrqxvbkr1CbtmwSsKtWzkvk0WkoR3LVq68mnn/vvQRlCH6oT/XfDaLBz+MR1JItaxKAhoR7rPXjza/p4y1fEwNyAQ1+jq81xvNvD30W4Z5J8AwiDkv1O25b33y+XdE5wDpsDZQruTEKc+J8Yc7hsDnWwdpGg9kVDUDbnDkRWKVSZamOloqVavNwxioagGpXwJ/KrG6cz2JHovS3Ossze9DzAO7xudcnyUr/EDAfJbnkAVsX0KUgB0rlHKpYbf3IRDmSS3InML+c9/YLOolb6a+Q+SipYzEeVwNbmvt6HmpYDI2PGDlgMUobEErwfejDybAPAV48+CuoT10niN4ARR3fiXhTw40R9/ka85LzdV2+uikBw0p0AC9EeUEaBzHSeXLsk8lkIh9yYNgDQCMhvHikswc3ZqXjnzsEjS/Mw84PAkYAMToUwlTHO8E4iJEvOzeLsmenfPrFCHmxZZZkpp+VxxaVZBTZSmwtNNAAvP9hOoqDGJBrDfklK85uv/+M30nLHesSaHx+TqWaDoWUq0v+9tRUUeqOl/SML8v4pB7Vg6omGUW2ElsLDTQAM77UlIiDGJDL3Z21yvEL2Lwk0bgz0K4QYoBWuPU11OH9FPN7KaVk7xnzBPa0nisHokj68q9OCmvpIXDMc18IV12FoSFYHLfAyofV8RGDMGY2OWC2K9nT4nCh2H/L7Y4OCnQdPCi7HCJF9gBxfx9n69zv0P45s+drsvKOAl8uqD4X8/cYfEYuLV8U8+cTyois2+LQ47b+zroaSY+ul043EIZV1z2AKc0c1Zac4KyOrbTvsxg+i/s+/GbHxS1Wbbnf7QGwtXaE9HR3S/pAtzsMDgo+sPZ0ADD4wsIHHxc7BnY56AzwQoNcaw+Njxhg+5iPEohZybkmDnLisFx2f6Dz++pqpdcNQtYNCj6wujoArChfWPjQcfYMA9sq+SV1Q3jo4eeg8YV5vk2O2b7mmMYGNKCZgQANaECTs3D3tgR8L9uZuOmTjsTP93WpjQ/IAx0AOo5hcONzz6h9ZmrJQKhwP8TBSf2chfohfszXYZ7Zfo6v2aLYgAY0MwygAQ1oy0GXQwcgTPKv/YnRWUBDGMdnsKfc13JORPmke+f/lZkz9U6S+mB5xUre79kVHJr3/3wHQH46nY7ebJwSrRs/yZ3i8Ayf2AEImy3V4TA3tHn8zWNwHo9DGA9tXnT6Pt7y8KIGX7k5nZxqqWgA2HJQbePkj6up0W8L6lMpfU+Ab6iUm9OH0m5FAzCUhodah7e9fl3e//MdAL75uzoSl2W2JRbs3hp/VUNSlQwaAI5d2qDkGIb2HWME0EDMctA++H2+u79J61IfrL6dJ/LHufetD+//YeT8U8TeXtt7f0rq+MsYjh40AHa2p+R+Pw5iLJTSnsb65bLnH9FvDNo2Pq7fEBR8f7B6pfBtATnUoR22OqAVXoVDpvS3AZo7zJ9BAzDM9vqr8+0A3xhgmcZG+7h4yXf8fA/AQADa5ddMP02/C3BS/5n7Q7gO0GDMDzHyLXR4BoAtZ9iSipTh+31sXoXT0TiIkWPNMfeHcB1AR+MgRr7VT5a732e0eFbAMwM0WMPUDb8dwGbaAr414JsD/3sC3ybXVsQveRVub5/oMJhNzM8tpuloHGF+stz9PqPFswKeGaDBGqaufkOQzg58R7C4Xb8d4BsDXlEB3x74Nt8W8I0BdcMVCm06DKH/UNkVHQI8K+CZQexCH3iw302Zo3XWicK3A0bctwd8Y6DfH/TXLvjl9VYpCpJjDNtDi5V+lYoGgArVPDMIVz7u2wNyaJeXrSH4S0F+qXg1sYoHgCkvrmF7O+uXcXlxPurE+Y+kLz8A4cWM3ZpaeSRXyk54xcpy62LnqGKlX18HgM6zhQ1su021koHwK5pmdwwptuKhn3q812dPALTiGufkCU7qPxrU4NsAFSLMSjmp2mw7/i3m25ZDTAeAjmMY97hnAeHNDwNhcWKAzTcD/rcD2O2LLpWQq2K+QSCXq8GmLYXv+OvG9gjfAfCen3MFoAFNHZYNzEqUgAa0bX00+Lbl4P8fAAAA///puc/hAAAABklEQVQDAPJ9OAtGaSsyAAAAAElFTkSuQmCC"},{"name":"allium.png","path":"","folder":"","namespace":"","id":"1","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"b68aca7b-f859-4cdb-5c04-2e5b1fcfe0b6","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAHlBMVEUAAADoz/7Spva4eO1Vqy2mXuFSmi5Kjyh7TqAXfAQSwnSGAAAAAXRSTlMAQObYZgAAADlJREFUeNpjwAqUAyA0k5AFhKGoGNQAZhgZmUIYrMrBUMXtBlAGO0w7G4zBgsFgQzDQdXEyMDAwAACVCARQ+FH48gAAAABJRU5ErkJggg=="},{"name":"azure_bluet.png","path":"","folder":"","namespace":"","id":"2","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"80bfc883-e993-6956-527a-1979a672b3fa","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAvklEQVQ4y2NgGAWDEGit+81wb9dvhv2lvxmuvXjx/8mrTwxam/4waG74y6C25Dd+zY5v/zJYPf3L8P+N///v37//B9EgQ461/mbYEv2bwfDeX8IugNkcNEvvf+hq3f/3DjcxwAwj6AKYzZ8+zQEbAmKDMMggEJ+g7TDFN+49Y/Dq1wBrAhkKcg1RAQiyGaQRZAhIU/4WXwaYZpg3iDIIphgWDiAxor0BMwCkGKQJhslKEzCvkJ2oQOEBCgd8AAB7qakILgzq0QAAAABJRU5ErkJggg=="}],"display":{"thirdperson_righthand":{"rotation":[75,45,0],"translation":[0,2.5,0],"scale":[0.375,0.375,0.375]},"thirdperson_lefthand":{"rotation":[75,45,0],"translation":[0,2.5,0],"scale":[0.375,0.375,0.375]},"firstperson_righthand":{"rotation":[0,124,0],"translation":[2,3,0],"scale":[0.4,0.4,0.4]},"firstperson_lefthand":{"rotation":[0,120,0],"translation":[1.5,2.75,0],"scale":[0.4,0.4,0.4]},"ground":{"translation":[0,2,0],"scale":[0.5,0.5,0.5]},"gui":{"rotation":[30,-135,0],"translation":[0.75,-1,0],"scale":[0.625,0.625,0.625]},"head":{"translation":[0,14,-0.75]},"fixed":{"translation":[0,0,-2.75],"scale":[0.5,0.5,0.5]},"on_shelf":{"rotation":[0,-180,0],"translation":[0,0,5.25]}}} \ No newline at end of file diff --git a/res/doll_without_item.json b/res/doll_without_item.json deleted file mode 100644 index e4333a5..0000000 --- a/res/doll_without_item.json +++ /dev/null @@ -1,265 +0,0 @@ -{ - "format_version": "1.9.0", - "credit": "3D Model © 2025 LeisureTimeDock", - "textures": { - "0": "#skin", - "particle": "minecraft:block/white_wool" - }, - "elements": [ - { - "name": "Toggle_Helmet", - "from": [3.5, 8.8, 7.5], - "to": [12.5, 17.8, 16.5], - "rotation": {"angle": 0, "axis": "y", "origin": [8, 9.3, 12]}, - "faces": { - "north": {"uv": [10, 2, 12, 4], "texture": "#0"}, - "east": {"uv": [8, 2, 10, 4], "texture": "#0"}, - "south": {"uv": [14, 2, 16, 4], "texture": "#0"}, - "west": {"uv": [12, 2, 14, 4], "texture": "#0"}, - "up": {"uv": [10, 2, 12, 0], "texture": "#0"}, - "down": {"uv": [12, 0, 14, 2], "texture": "#0"} - } - }, - { - "name": "Head", - "from": [4, 9.3, 8], - "to": [12, 17.3, 16], - "rotation": {"angle": 0, "axis": "y", "origin": [8, 9.3, 12]}, - "faces": { - "north": {"uv": [2, 2, 4, 4], "texture": "#0"}, - "east": {"uv": [0, 2, 2, 4], "texture": "#0"}, - "south": {"uv": [6, 2, 8, 4], "texture": "#0"}, - "west": {"uv": [4, 2, 6, 4], "texture": "#0"}, - "up": {"uv": [4, 2, 2, 0], "texture": "#0"}, - "down": {"uv": [6, 0, 4, 2], "texture": "#0"} - } - }, - { - "name": "Toggle_Chest_Armor", - "from": [4.75, 2.05, 9.75], - "to": [11.25, 9.55, 13.25], - "rotation": {"angle": 0, "axis": "y", "origin": [8, 5.8, 11.5]}, - "faces": { - "north": {"uv": [5, 9, 7, 12], "texture": "#0"}, - "east": {"uv": [4, 9, 5, 12], "texture": "#0"}, - "south": {"uv": [8, 9, 10, 12], "texture": "#0"}, - "west": {"uv": [7, 9, 8, 12], "texture": "#0"}, - "up": {"uv": [5, 8, 7, 9], "texture": "#0"}, - "down": {"uv": [7, 8, 9, 9], "texture": "#0"} - } - }, - { - "name": "Body", - "from": [5, 2.3, 10], - "to": [11, 9.3, 13], - "rotation": {"angle": 0, "axis": "y", "origin": [8, 5.8, 11.5]}, - "faces": { - "north": {"uv": [5, 5, 7, 8], "texture": "#0"}, - "east": {"uv": [4, 5, 5, 8], "texture": "#0"}, - "south": {"uv": [8, 5, 10, 8], "texture": "#0"}, - "west": {"uv": [7, 5, 8, 8], "texture": "#0"}, - "up": {"uv": [7, 5, 5, 4], "texture": "#0"}, - "down": {"uv": [9, 4, 7, 5], "texture": "#0"} - } - }, - { - "name": "Toggle_Left_Arm_Armor", - "from": [2.75, 0.3, 9.75], - "to": [5.25, 9.8, 13.25], - "rotation": {"angle": 0, "axis": "x", "origin": [3, 8, 12.5]}, - "faces": { - "north": {"uv": [13, 13, 13.75, 16], "texture": "#0"}, - "east": {"uv": [12, 13, 13, 16], "texture": "#0"}, - "south": {"uv": [14.75, 13, 15.5, 16], "texture": "#0"}, - "west": {"uv": [13.75, 13, 14.75, 16], "texture": "#0"}, - "up": {"uv": [13, 12, 13.75, 13], "texture": "#0"}, - "down": {"uv": [13.75, 12, 14.5, 13], "texture": "#0"} - } - }, - { - "name": "Left_arm", - "from": [3, 0.3, 10], - "to": [5, 9.3, 13], - "rotation": {"angle": 0, "axis": "x", "origin": [4, 8, 12.5]}, - "faces": { - "north": {"uv": [9, 13, 9.75, 16], "texture": "#0"}, - "east": {"uv": [8, 13, 9, 16], "texture": "#0"}, - "south": {"uv": [10.5, 13, 11.5, 16], "texture": "#0"}, - "west": {"uv": [9.75, 13, 10.5, 16], "texture": "#0"}, - "up": {"uv": [9.75, 13, 9, 12], "texture": "#0"}, - "down": {"uv": [10.5, 12, 9.75, 13], "texture": "#0"} - } - }, - { - "name": "Toggle_Right_Arm_Armor", - "from": [10.75, 0.3, 9.75], - "to": [13.25, 9.8, 13.25], - "rotation": {"angle": 0, "axis": "x", "origin": [12, 8, 11.5]}, - "faces": { - "north": {"uv": [11, 9, 11.75, 12], "texture": "#0"}, - "east": {"uv": [10, 9, 11, 12], "texture": "#0"}, - "south": {"uv": [12.75, 9, 13.5, 12], "texture": "#0"}, - "west": {"uv": [11.75, 9, 12.75, 12], "texture": "#0"}, - "up": {"uv": [11, 8, 11.75, 9], "texture": "#0"}, - "down": {"uv": [11.75, 8, 12.5, 9], "texture": "#0"} - } - }, - { - "name": "Right_arm", - "from": [11, 0.3, 10], - "to": [13, 9.3, 13], - "rotation": {"angle": 0, "axis": "x", "origin": [12, 8, 11.5]}, - "faces": { - "north": {"uv": [11, 5, 11.75, 8], "texture": "#0"}, - "east": {"uv": [10, 5, 11, 8], "texture": "#0"}, - "south": {"uv": [12.5, 5, 13.5, 8], "texture": "#0"}, - "west": {"uv": [11.75, 5, 12.5, 8], "texture": "#0"}, - "up": {"uv": [11.75, 5, 11, 4], "texture": "#0"}, - "down": {"uv": [12.5, 4, 11.75, 5], "texture": "#0"} - } - }, - { - "name": "Toggle_Left_Leg_Armor", - "from": [5.2, -0.25, 3.05], - "to": [8.7, 3.25, 12.55], - "rotation": {"angle": 22.5, "axis": "y", "origin": [5.7, 2, 13]}, - "faces": { - "north": {"uv": [2, 12, 3, 13], "rotation": 180, "texture": "#0"}, - "east": {"uv": [0, 13, 1, 16], "rotation": 270, "texture": "#0"}, - "south": {"uv": [1, 12, 2, 13], "texture": "#0"}, - "west": {"uv": [2, 13, 3, 16], "rotation": 90, "texture": "#0"}, - "up": {"uv": [1, 13, 2, 16], "rotation": 180, "texture": "#0"}, - "down": {"uv": [3, 13, 4, 16], "texture": "#0"} - } - }, - { - "name": "Left_leg", - "from": [5.5, 0, 3.3], - "to": [8.5, 3, 12.3], - "rotation": {"angle": 22.5, "axis": "y", "origin": [6, 2, 13]}, - "faces": { - "north": {"uv": [7, 12, 6, 13], "rotation": 180, "texture": "#0"}, - "east": {"uv": [4, 13, 5, 16], "rotation": 270, "texture": "#0"}, - "south": {"uv": [5.95, 13, 5, 12], "texture": "#0"}, - "west": {"uv": [6, 13, 7, 16], "rotation": 90, "texture": "#0"}, - "up": {"uv": [5, 13, 6, 16], "rotation": 180, "texture": "#0"}, - "down": {"uv": [7, 13, 8, 16], "texture": "#0"} - } - }, - { - "name": "Toggle_Right_Leg_Armor", - "from": [7.2, -0.25, 3.05], - "to": [10.7, 3.25, 12.55], - "rotation": {"angle": -22.5, "axis": "y", "origin": [8.7, 2, 13]}, - "faces": { - "north": {"uv": [2, 8, 3, 9], "rotation": 180, "texture": "#0"}, - "east": {"uv": [0, 9, 1, 12], "rotation": 270, "texture": "#0"}, - "south": {"uv": [1, 8, 2, 9], "texture": "#0"}, - "west": {"uv": [2, 9, 3, 12], "rotation": 90, "texture": "#0"}, - "up": {"uv": [1, 9, 2, 12], "rotation": 180, "texture": "#0"}, - "down": {"uv": [3, 9, 4, 12], "texture": "#0"} - } - }, - { - "name": "Right_leg", - "from": [7.5, 0, 3.3], - "to": [10.5, 3, 12.3], - "rotation": {"angle": -22.5, "axis": "y", "origin": [9, 2, 13]}, - "faces": { - "north": {"uv": [3, 4, 2, 5], "rotation": 180, "texture": "#0"}, - "east": {"uv": [0, 5, 1, 8], "rotation": 270, "texture": "#0"}, - "south": {"uv": [2, 5, 1, 4], "texture": "#0"}, - "west": {"uv": [2, 5, 3, 8], "rotation": 90, "texture": "#0"}, - "up": {"uv": [1, 5, 2, 8], "rotation": 180, "texture": "#0"}, - "down": {"uv": [3, 5, 4, 8], "texture": "#0"} - } - } - ], - "display": { - "thirdperson_righthand": { - "rotation": [75, 45, 0], - "translation": [0, 2.5, 0], - "scale": [0.375, 0.375, 0.375] - }, - "thirdperson_lefthand": { - "rotation": [75, 45, 0], - "translation": [0, 2.5, 0], - "scale": [0.375, 0.375, 0.375] - }, - "firstperson_righthand": { - "rotation": [0, 124, 0], - "translation": [2, 3, 0], - "scale": [0.4, 0.4, 0.4] - }, - "firstperson_lefthand": { - "rotation": [0, 120, 0], - "translation": [1.5, 2.75, 0], - "scale": [0.4, 0.4, 0.4] - }, - "ground": { - "translation": [0, 2, 0], - "scale": [0.5, 0.5, 0.5] - }, - "gui": { - "rotation": [30, -135, 0], - "translation": [0.75, -1, 0], - "scale": [0.625, 0.625, 0.625] - }, - "head": { - "translation": [0, 14, -0.75] - }, - "fixed": { - "translation": [0, 0, -2.75], - "scale": [0.5, 0.5, 0.5] - }, - "on_shelf": { - "rotation": [0, -180, 0], - "translation": [0, 0, 5.25] - } - }, - "groups": [ - { - "name": "Player", - "origin": [3, -6.7, 6], - "color": 0, - "children": [ - { - "name": "Head", - "origin": [8, 16, 8], - "color": 0, - "children": [0, 1] - }, - { - "name": "Body", - "origin": [8, 11, 8], - "color": 0, - "children": [2, 3] - }, - { - "name": "Left_Arm", - "origin": [5, 15, 6], - "color": 0, - "children": [4, 5] - }, - { - "name": "Right_Arm", - "origin": [11, 15, 6], - "color": 0, - "children": [6, 7] - }, - { - "name": "Left_Leg", - "origin": [7, 13, 7], - "color": 0, - "children": [8, 9] - }, - { - "name": "Right_Leg", - "origin": [10, 13, 7], - "color": 0, - "children": [10, 11] - } - ] - } - ] -} \ No newline at end of file diff --git a/res/model.bbmodel b/res/model.bbmodel deleted file mode 100644 index 2a4f6cd..0000000 --- a/res/model.bbmodel +++ /dev/null @@ -1 +0,0 @@ -{"meta":{"format_version":"5.0","model_format":"free","box_uv":true},"name":"model","model_identifier":"","visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"timeline_setups":[],"unhandled_root_fields":{},"resolution":{"width":64,"height":64},"elements":[{"name":"Head","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-4,24,-4],"to":[4,32,4],"autouv":0,"color":0,"origin":[0,0,0],"faces":{"north":{"uv":[8,8,16,16],"texture":0},"east":{"uv":[0,8,8,16],"texture":0},"south":{"uv":[24,8,32,16],"texture":0},"west":{"uv":[16,8,24,16],"texture":0},"up":{"uv":[16,8,8,0],"texture":0},"down":{"uv":[24,0,16,8],"texture":0}},"type":"cube","uuid":"ecbccf02-17d8-13e5-651b-97fd3f72ed50"},{"name":"Hat Layer","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-4,24,-4],"to":[4,32,4],"autouv":0,"color":0,"inflate":0.5,"origin":[0,0,0],"uv_offset":[32,0],"faces":{"north":{"uv":[40,8,48,16],"texture":0},"east":{"uv":[32,8,40,16],"texture":0},"south":{"uv":[56,8,64,16],"texture":0},"west":{"uv":[48,8,56,16],"texture":0},"up":{"uv":[48,8,40,0],"texture":0},"down":{"uv":[56,0,48,8],"texture":0}},"type":"cube","uuid":"354012ee-7f44-9c57-6484-261bb7a9e271"},{"name":"Body","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-4,12,-2],"to":[4,24,2],"autouv":0,"color":0,"origin":[0,0,0],"uv_offset":[16,16],"faces":{"north":{"uv":[20,20,28,32],"texture":0},"east":{"uv":[16,20,20,32],"texture":0},"south":{"uv":[32,20,40,32],"texture":0},"west":{"uv":[28,20,32,32],"texture":0},"up":{"uv":[28,20,20,16],"texture":0},"down":{"uv":[36,16,28,20],"texture":0}},"type":"cube","uuid":"3d9de341-9979-34f0-cbad-9f44b802a10b"},{"name":"Body Layer","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-4,12,-2],"to":[4,24,2],"autouv":0,"color":0,"inflate":0.25,"origin":[0,0,0],"uv_offset":[16,32],"faces":{"north":{"uv":[20,36,28,48],"texture":0},"east":{"uv":[16,36,20,48],"texture":0},"south":{"uv":[32,36,40,48],"texture":0},"west":{"uv":[28,36,32,48],"texture":0},"up":{"uv":[28,36,20,32],"texture":0},"down":{"uv":[36,32,28,36],"texture":0}},"type":"cube","uuid":"c98332cc-c990-4a78-cb50-fd4f797213bb"},{"name":"Right Arm","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[4,12,-2],"to":[7,24,2],"autouv":0,"color":0,"origin":[0,0,0],"uv_offset":[40,16],"faces":{"north":{"uv":[44,20,47,32],"texture":0},"east":{"uv":[40,20,44,32],"texture":0},"south":{"uv":[51,20,54,32],"texture":0},"west":{"uv":[47,20,51,32],"texture":0},"up":{"uv":[47,20,44,16],"texture":0},"down":{"uv":[50,16,47,20],"texture":0}},"type":"cube","uuid":"aaae9fa5-faf3-ee54-9182-5f7f2da53878"},{"name":"Right Arm Layer","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[4,12,-2],"to":[7,24,2],"autouv":0,"color":0,"inflate":0.25,"origin":[0,0,0],"uv_offset":[40,32],"faces":{"north":{"uv":[44,36,47,48],"texture":0},"east":{"uv":[40,36,44,48],"texture":0},"south":{"uv":[51,36,54,48],"texture":0},"west":{"uv":[47,36,51,48],"texture":0},"up":{"uv":[47,36,44,32],"texture":0},"down":{"uv":[50,32,47,36],"texture":0}},"type":"cube","uuid":"1ff0636c-2e37-5ab0-42e6-6d25b317eda4"},{"name":"Left Arm","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-7,12,-2],"to":[-4,24,2],"autouv":0,"color":0,"origin":[0,0,0],"uv_offset":[32,48],"faces":{"north":{"uv":[36,52,39,64],"texture":0},"east":{"uv":[32,52,36,64],"texture":0},"south":{"uv":[43,52,46,64],"texture":0},"west":{"uv":[39,52,43,64],"texture":0},"up":{"uv":[39,52,36,48],"texture":0},"down":{"uv":[42,48,39,52],"texture":0}},"type":"cube","uuid":"862c4ddf-a5e7-0436-7b4f-9b8b6a0ab813"},{"name":"Left Arm Layer","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-7,12,-2],"to":[-4,24,2],"autouv":0,"color":0,"inflate":0.25,"origin":[0,0,0],"uv_offset":[48,48],"faces":{"north":{"uv":[52,52,55,64],"texture":0},"east":{"uv":[48,52,52,64],"texture":0},"south":{"uv":[59,52,62,64],"texture":0},"west":{"uv":[55,52,59,64],"texture":0},"up":{"uv":[55,52,52,48],"texture":0},"down":{"uv":[58,48,55,52],"texture":0}},"type":"cube","uuid":"e17ef606-508e-443e-0070-a4c2f25ca168"},{"name":"Right Leg","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-0.1,0,-2],"to":[3.9,12,2],"autouv":0,"color":0,"origin":[0,0,0],"uv_offset":[0,16],"faces":{"north":{"uv":[4,20,8,32],"texture":0},"east":{"uv":[0,20,4,32],"texture":0},"south":{"uv":[12,20,16,32],"texture":0},"west":{"uv":[8,20,12,32],"texture":0},"up":{"uv":[8,20,4,16],"texture":0},"down":{"uv":[12,16,8,20],"texture":0}},"type":"cube","uuid":"12a1223d-2d3e-ad8e-d713-ff7b590e9190"},{"name":"Right Leg Layer","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-0.1,0,-2],"to":[3.9,12,2],"autouv":0,"color":0,"inflate":0.25,"origin":[0,0,0],"uv_offset":[0,32],"faces":{"north":{"uv":[4,36,8,48],"texture":0},"east":{"uv":[0,36,4,48],"texture":0},"south":{"uv":[12,36,16,48],"texture":0},"west":{"uv":[8,36,12,48],"texture":0},"up":{"uv":[8,36,4,32],"texture":0},"down":{"uv":[12,32,8,36],"texture":0}},"type":"cube","uuid":"86c41d76-bd1e-c435-ebf2-8738e7b52c4b"},{"name":"Left Leg","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-3.9,0,-2],"to":[0.1,12,2],"autouv":0,"color":0,"origin":[0,0,0],"uv_offset":[16,48],"faces":{"north":{"uv":[20,52,24,64],"texture":0},"east":{"uv":[16,52,20,64],"texture":0},"south":{"uv":[28,52,32,64],"texture":0},"west":{"uv":[24,52,28,64],"texture":0},"up":{"uv":[24,52,20,48],"texture":0},"down":{"uv":[28,48,24,52],"texture":0}},"type":"cube","uuid":"6232e1ad-27b0-6116-94ff-405cf1479a71"},{"name":"Left Leg Layer","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-3.9,0,-2],"to":[0.1,12,2],"autouv":0,"color":0,"inflate":0.25,"origin":[0,0,0],"uv_offset":[0,48],"faces":{"north":{"uv":[4,52,8,64],"texture":0},"east":{"uv":[0,52,4,64],"texture":0},"south":{"uv":[12,52,16,64],"texture":0},"west":{"uv":[8,52,12,64],"texture":0},"up":{"uv":[8,52,4,48],"texture":0},"down":{"uv":[12,48,8,52],"texture":0}},"type":"cube","uuid":"ec99bc3f-8466-3550-174c-a4d2e97a4c60"}],"groups":[{"uuid":"bff4eb5d-5156-45ba-55a0-e606ac5ee407","export":true,"locked":false,"origin":[0,12,0],"rotation":[0,0,0],"color":0,"name":"Waist","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"d3a4765a-9e70-2832-b06c-f83e1e86751f","export":true,"locked":false,"origin":[0,24,0],"rotation":[0,0,0],"color":0,"name":"Head","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"67cfc0fc-3f6d-0b85-c627-dd87f8aa31fc","export":true,"locked":false,"origin":[0,24,0],"rotation":[0,0,0],"color":0,"name":"Body","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"031da658-7419-e3d1-ce01-1f31a531da71","export":true,"locked":false,"origin":[5,22,0],"rotation":[0,0,0],"color":0,"name":"Right Arm","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":true},{"uuid":"f9046dbf-effe-caae-f779-52fb1f9f1641","export":true,"locked":false,"origin":[-5,22,0],"rotation":[0,0,0],"color":0,"name":"Left Arm","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"f95be5e7-1abd-a6d9-0a8b-4a358bd90bf1","export":true,"locked":false,"origin":[1.9,12,0],"rotation":[0,0,0],"color":0,"name":"Right Leg","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"1ca1fd5f-01f4-830b-edaa-8b64993d94c2","export":true,"locked":false,"origin":[-1.9,12,0],"rotation":[0,0,0],"color":0,"name":"Left Leg","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false}],"outliner":[{"uuid":"bff4eb5d-5156-45ba-55a0-e606ac5ee407","isOpen":true,"children":[{"uuid":"d3a4765a-9e70-2832-b06c-f83e1e86751f","isOpen":true,"children":["ecbccf02-17d8-13e5-651b-97fd3f72ed50","354012ee-7f44-9c57-6484-261bb7a9e271"]},{"uuid":"67cfc0fc-3f6d-0b85-c627-dd87f8aa31fc","isOpen":true,"children":["3d9de341-9979-34f0-cbad-9f44b802a10b","c98332cc-c990-4a78-cb50-fd4f797213bb"]},{"uuid":"f9046dbf-effe-caae-f779-52fb1f9f1641","isOpen":true,"children":["862c4ddf-a5e7-0436-7b4f-9b8b6a0ab813","e17ef606-508e-443e-0070-a4c2f25ca168"]},{"uuid":"031da658-7419-e3d1-ce01-1f31a531da71","isOpen":true,"children":["aaae9fa5-faf3-ee54-9182-5f7f2da53878","1ff0636c-2e37-5ab0-42e6-6d25b317eda4"]}]},{"uuid":"f95be5e7-1abd-a6d9-0a8b-4a358bd90bf1","isOpen":true,"children":["12a1223d-2d3e-ad8e-d713-ff7b590e9190","86c41d76-bd1e-c435-ebf2-8738e7b52c4b"]},{"uuid":"1ca1fd5f-01f4-830b-edaa-8b64993d94c2","isOpen":true,"children":["6232e1ad-27b0-6116-94ff-405cf1479a71","ec99bc3f-8466-3550-174c-a4d2e97a4c60"]}],"textures":[{"name":"alex.png","relative_path":"alex.png","folder":"","namespace":"","id":"0","group":"","width":64,"height":64,"uv_width":64,"uv_height":64,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":true,"uuid":"977536e4-9ca7-ad3c-7604-f4bbce003c18","source":"data:image/png;base64,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"}]} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index bcbdfe2..309bbc5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,12 +1,30 @@ -//file:noinspection GroovyAssignabilityCheck pluginManagement { repositories { - mavenLocal() + gradlePluginPortal() + maven { + name = 'Forge' + url = 'https://maven.minecraftforge.net/' + } + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + maven { + name = 'Sponge Snapshots' + url = 'https://repo.spongepowered.org/repository/maven-public/' + } gradlePluginPortal() maven { url = 'https://maven.neoforged.net/releases' } maven { url = 'https://maven.parchmentmc.org' } // Add this line } } + plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' -} \ No newline at end of file + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' +} + +// This should match the folder name of the project, or else IDEA may complain (see https://youtrack.jetbrains.com/issue/IDEA-317606) +rootProject.name = 'MultiLoader-Template' +include("common") +include("fabric") +include("forge") diff --git a/src/main/java/top/r3944realms/lib39/content/block/DollBlock.java b/src/main/java/top/r3944realms/lib39/content/block/DollBlock.java index 1a154e7..9d2958b 100644 --- a/src/main/java/top/r3944realms/lib39/content/block/DollBlock.java +++ b/src/main/java/top/r3944realms/lib39/content/block/DollBlock.java @@ -5,7 +5,6 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.server.level.ServerLevel; -import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; @@ -17,7 +16,6 @@ import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.*; import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.piston.PistonBaseBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.BlockStateProperties;