> $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 extends Block> 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 extends Item> 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 extends Enchantment> 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 extends MobEffect> 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 extends EntityType>> 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 extends ILangKeyValue> 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 extends ItemLike> 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,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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":"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;