feat: 让Lib39跨加载器化,未完成

This commit is contained in:
叁玖领域 2026-03-09 19:44:09 +08:00
parent b616b4535e
commit eac118e9a4
119 changed files with 4325 additions and 3128 deletions

View File

@ -26,14 +26,44 @@ jobs:
- name: Make gradlew executable - name: Make gradlew executable
run: chmod +x ./gradlew run: chmod +x ./gradlew
- name: Run Forge data generation
run: |
echo "=== 运行 Forge 数据生成 ==="
./gradlew runData --no-daemon
continue-on-error: false
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew build run: ./gradlew build --no-daemon
- name: Prepare release files - name: Prepare release files
run: | run: |
mkdir -p release-files mkdir -p release-files
cp build/libs/*.jar release-files/ 2>/dev/null || true
echo "准备发布的文件:" # 收集所有模块的构建产物
echo "=== 收集 common 模块构建产物 ==="
if [ -d "common/build/libs" ]; then
cp common/build/libs/*.jar release-files/ 2>/dev/null || echo "common 模块没有 jar 文件"
fi
echo "=== 收集 fabric 模块构建产物 ==="
if [ -d "fabric/build/libs" ]; then
cp fabric/build/libs/*-dev.jar release-files/ 2>/dev/null || true # 排除dev jar
cp fabric/build/libs/*-sources.jar release-files/ 2>/dev/null || true
cp fabric/build/libs/*-javadoc.jar release-files/ 2>/dev/null || true
# 只复制主jar没有sources/javadoc/dev classifier的jar
find fabric/build/libs -name "*.jar" ! -name "*-sources.jar" ! -name "*-javadoc.jar" ! -name "*-dev.jar" -exec cp {} release-files/ \;
fi
echo "=== 收集 forge 模块构建产物 ==="
if [ -d "forge/build/libs" ]; then
cp forge/build/libs/*-sources.jar release-files/ 2>/dev/null || true
cp forge/build/libs/*-javadoc.jar release-files/ 2>/dev/null || true
# 只复制主jar没有sources/javadoc classifier的jar
find forge/build/libs -name "*.jar" ! -name "*-sources.jar" ! -name "*-javadoc.jar" -exec cp {} release-files/ \;
fi
echo "=== 准备发布的文件 ==="
ls -la release-files/ ls -la release-files/
- name: Upload release artifacts - name: Upload release artifacts
@ -60,6 +90,14 @@ jobs:
name: release-files name: release-files
path: ./dist path: ./dist
- name: Extract version info
id: version_info
run: |
# 从tag中提取版本号去掉v前缀
VERSION="${GITHUB_REF_NAME#v}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "minecraft_version=$(grep "^minecraft_version=" gradle.properties | cut -d'=' -f2)" >> $GITHUB_OUTPUT
- name: Generate CZ-compliant changelog - name: Generate CZ-compliant changelog
id: generate_changelog id: generate_changelog
run: | run: |
@ -177,19 +215,59 @@ jobs:
echo "- 发布日期: $(date '+%Y年%m月%d日')" >> $TEMP_FILE echo "- 发布日期: $(date '+%Y年%m月%d日')" >> $TEMP_FILE
echo "- 当前版本: $CURRENT_TAG" >> $TEMP_FILE echo "- 当前版本: $CURRENT_TAG" >> $TEMP_FILE
echo "- Minecraft版本: ${{ steps.version_info.outputs.minecraft_version }}" >> $TEMP_FILE
echo "" >> $TEMP_FILE echo "" >> $TEMP_FILE
echo "---" >> $TEMP_FILE echo "---" >> $TEMP_FILE
echo "" >> $TEMP_FILE echo "" >> $TEMP_FILE
echo "## 📦 下载文件" >> $TEMP_FILE
echo "" >> $TEMP_FILE
echo "本次发布包含以下平台的构建文件:" >> $TEMP_FILE
echo "" >> $TEMP_FILE
# 列出所有发布的文件
echo "### Fabric 版本" >> $TEMP_FILE
echo "\`\`\`" >> $TEMP_FILE
ls -1 dist/*fabric*.jar 2>/dev/null | grep -v "sources\|javadoc" | xargs -n1 basename || echo "无 Fabric 主文件" >> $TEMP_FILE
echo "\`\`\`" >> $TEMP_FILE
echo "" >> $TEMP_FILE
echo "### Forge 版本" >> $TEMP_FILE
echo "\`\`\`" >> $TEMP_FILE
ls -1 dist/*forge*.jar 2>/dev/null | grep -v "sources\|javadoc" | xargs -n1 basename || echo "无 Forge 主文件" >> $TEMP_FILE
echo "\`\`\`" >> $TEMP_FILE
echo "" >> $TEMP_FILE
echo "### 通用模块" >> $TEMP_FILE
echo "\`\`\`" >> $TEMP_FILE
ls -1 dist/*common*.jar 2>/dev/null | grep -v "sources\|javadoc" | xargs -n1 basename || echo "无通用模块" >> $TEMP_FILE
echo "\`\`\`" >> $TEMP_FILE
echo "" >> $TEMP_FILE
echo "### 源码和文档" >> $TEMP_FILE
echo "\`\`\`" >> $TEMP_FILE
ls -1 dist/*-sources.jar 2>/dev/null | xargs -n1 basename || echo "无源码文件" >> $TEMP_FILE
ls -1 dist/*-javadoc.jar 2>/dev/null | xargs -n1 basename || echo "无文档文件" >> $TEMP_FILE
echo "\`\`\`" >> $TEMP_FILE
echo "" >> $TEMP_FILE
echo "### 📜 详细提交历史" >> $TEMP_FILE echo "### 📜 详细提交历史" >> $TEMP_FILE
echo "" >> $TEMP_FILE echo "" >> $TEMP_FILE
echo "<details>" >> $TEMP_FILE
echo "<summary>点击展开查看完整提交历史</summary>" >> $TEMP_FILE
echo "" >> $TEMP_FILE
echo "\`\`\`" >> $TEMP_FILE
# 显示所有提交的详细列表包含scope信息 # 显示所有提交的详细列表
if [ -z "$PREV_TAG" ]; then if [ -z "$PREV_TAG" ]; then
git log --pretty=format:"- **%h** %s - %an (%ad)" --date=short --reverse | head -100 >> $TEMP_FILE git log --pretty=format:"%h %s - %an (%ad)" --date=short --reverse >> $TEMP_FILE
else else
git log --pretty=format:"- **%h** %s - %an (%ad)" --date=short $PREV_TAG..HEAD | head -100 >> $TEMP_FILE git log --pretty=format:"%h %s - %an (%ad)" --date=short $PREV_TAG..HEAD >> $TEMP_FILE
fi fi
echo "\`\`\`" >> $TEMP_FILE
echo "</details>" >> $TEMP_FILE
# 将文件内容输出到变量 # 将文件内容输出到变量
CHANGELOG_CONTENT=$(cat $TEMP_FILE) CHANGELOG_CONTENT=$(cat $TEMP_FILE)
echo "changelog<<EOF" >> $GITHUB_OUTPUT echo "changelog<<EOF" >> $GITHUB_OUTPUT
@ -199,9 +277,13 @@ jobs:
- name: Create Release - name: Create Release
uses: ncipollo/release-action@v1 uses: ncipollo/release-action@v1
with: with:
artifacts: "dist/*.jar" artifacts: |
dist/*.jar
tag: ${{ github.ref_name }} tag: ${{ github.ref_name }}
name: "版本 ${{ github.ref_name }}" name: "${{ steps.version_info.outputs.minecraft_version }} - ${{ github.ref_name }}"
body: ${{ steps.generate_changelog.outputs.changelog }} body: ${{ steps.generate_changelog.outputs.changelog }}
draft: false draft: false
prerelease: false prerelease: false
token: ${{ secrets.GITHUB_TOKEN }}
allowUpdates: true
removeArtifacts: true

View File

@ -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.

View File

@ -1,535 +1,4 @@
//file:noinspection GroovyAssignabilityCheck
plugins { plugins {
id 'java' id 'fabric-loom' version '1.9-SNAPSHOT' apply(false)
id 'idea' id 'net.neoforged.moddev.legacyforge' version '2.0.103' apply(false)
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 任务"
}
}
}
/**<pre>
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
</pre>
*/

3
buildSrc/build.gradle Normal file
View File

@ -0,0 +1,3 @@
plugins {
id 'groovy-gradle-plugin'
}

View File

@ -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
}

View File

@ -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)
}

43
common/build.gradle Normal file
View File

@ -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'
}

View File

@ -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();
}
}

View File

@ -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<DollPose> 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<Block, BlockState> 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<ItemStack> getDrops(@NotNull BlockState state, @NotNull LootParams.Builder params) {
// 获取方块实体
BlockEntity blockEntity = params.getOptionalParameter(LootContextParams.BLOCK_ENTITY);
if (blockEntity instanceof DollBlockEntity dollEntity) {
List<ItemStack> customDrops = getCustomDrops(dollEntity, params);
if (customDrops != null) return customDrops;
}
return super.getDrops(state, params);
}
/**
* 生成自定义掉落物
*/
@Nullable
private List<ItemStack> 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;
}
}

View File

@ -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();
});
}
}

View File

@ -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;
}
}

View File

@ -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<Component> 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"));
}
}

View File

@ -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<BlockEntityType<DollBlockEntity>> DOLL_BLOCK_ENTITY;
}

View File

@ -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<Block> DOLL;
}

View File

@ -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<Item> DOLL;
}

View File

@ -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<SoundEvent> 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;
}
}

View File

@ -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<String, String> 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<ItemStack> 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);
}
}
}

View File

@ -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<String, String> translationMap; // Better naming
private final List<String> 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());
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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 +
'}';
}
}

View File

@ -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; }
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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> T load(Class<T> 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;
}
}

View File

@ -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();
}

View File

@ -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<Property> 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<Property> 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<GameProfile> 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);
}
}

View File

@ -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> T check(Supplier<T> supplier) {
if (Lib39.isClientEnvironment()) {
return supplier.get();
}
throw new RuntimeException("This method should be called in ClientEnvironment");
}
static <T> T check(Supplier<T> supplier, Supplier<T> fallback) {
if (Lib39.isClientEnvironment()) {
return supplier.get();
}
return fallback.get();
}
}

View File

@ -1,8 +1,6 @@
package top.r3944realms.lib39.util; package top.r3944realms.lib39.util;
import net.minecraft.world.level.Level; 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.ApiStatus;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -66,7 +64,6 @@ public interface ILevelHelper {
* @return the client level * @return the client level
*/ */
@Nullable @Nullable
@OnlyIn(Dist.CLIENT)
static Level getClientLevel() { static Level getClientLevel() {
return LevelHelper.CLIENT.getLevel(); return LevelHelper.CLIENT.getLevel();
} }

View File

@ -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<Block> blockObject;
private BiFunction<String, Supplier<Item>, Supplier<Item>> 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<String, Supplier<Block>,Supplier<Block>> blockRegister, Supplier<Block> 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<String, Supplier<Item>,Supplier<Item>> 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<String, Supplier<Item>,Supplier<Item>> 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<Block> blockObject, ResourceKey<CreativeModeTab>... creativeTabs) {
}
/**
* 注册方块和物品到建筑标签页
*
* @param blockRegister the block register
* @param blockSupplier the block supplier
* @return the block registry builder
*/
public BlockRegistryBuilder registerWithBuildingTab(BiFunction<String, Supplier<Block>,Supplier<Block>> blockRegister, Supplier<Block> 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<String, Supplier<Block>,Supplier<Block>> blockRegister, Supplier<Block> blockSupplier) {
registerBlock(blockRegister, blockSupplier);
registerBlockItem(this.blockObject, CreativeModeTabs.FUNCTIONAL_BLOCKS);
return this;
}
/**
* 获取注册的方块对象
*
* @return the registry object
*/
public Supplier<Block> build() {
if (needBuildItem) {
blockItemRegister.apply(this.registryName, () -> new BlockItem(this.blockObject.get(), properties == null ? new Item.Properties() : properties));
}
return this.blockObject;
}
}

View File

@ -17,7 +17,7 @@ public abstract class EntityListResolve {
/** /**
* The Result. * The Result.
*/ */
protected EntityListResolve.EntityResolveResult result; protected EntityResolveResult result;
/** /**
* The type Entity resolve result. * The type Entity resolve result.

View File

@ -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]
}
}
}

View File

@ -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;
}
}

View File

@ -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
]
}
]
}

View File

@ -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;
}

View File

@ -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
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

View File

@ -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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 KiB

View File

@ -0,0 +1,6 @@
{
"pack": {
"description": "${mod_name}",
"pack_format": 8
}
}

View File

@ -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.*

View File

@ -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()
# whichwherejava
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)
# javaJAVA_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++
$<$<CONFIG:Debug>:/MDd> # 使MDd
$<$<CONFIG:Release>:/MD> # 使MD
$<$<CONFIG:RelWithDebInfo>:/MD>
$<$<CONFIG:MinSizeRel>:/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
$<$<CONFIG:Debug>:-g -O0>
$<$<CONFIG:Release>:-O2>
$<$<CONFIG:RelWithDebInfo>:-O2 -g>
$<$<CONFIG:MinSizeRel>:-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 "=========================================")

View File

@ -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@)

View File

@ -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
)

View File

@ -1,37 +0,0 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* 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

View File

@ -1,21 +0,0 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* 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

View File

@ -1 +0,0 @@
cmake_minimum_required(VERSION 3.28)

View File

@ -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)

View File

@ -1,451 +0,0 @@
#pragma clang diagnostic push
#pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions"
#pragma once
#include <cstdint>
#include <ctime>
#include <cstring>
#ifndef HEADER_JNI_H_
#define HEADER_JNI_H_
#include <jni.h>
#endif
#ifndef HEADER_P_H_
#define HEADER_P_H_
#include "guard/JByteArrayGuard.cpp"
#endif
#include <string>
// 字节序转换宏(跨平台)
#pragma clang diagnostic push
#pragma ide diagnostic ignored "UnreachableCallsOfFunction"
#if defined(_WIN32)
#include <winsock2.h>
#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 <libkern/OSByteOrder.h>
#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 <endian.h>
// 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<uint64_t>(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<const uint8_t*>(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

View File

@ -1,234 +0,0 @@
#pragma once
#include <cstring>
#include <string>
#include <header/top_r3944realms_lib39_core_lang_ClassEncryptor.h>
#include <header/top_r3944realms_lib39_core_lang_EncryptedClassLoader.h>
#include "EnhancedEncryptionMagic.cpp"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wsign-compare"
#ifdef _WIN32
#include <windows.h>
#include <wincrypt.h>
#include <cstdint>
#define JNIEXPORT __declspec(dllexport)
#else
#include <dlfcn.h>
#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<uint8_t>(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

View File

@ -1,5 +0,0 @@
cmake_minimum_required(VERSION 3.28)
add_library(J_GUARD
JByteArrayGuard.cpp
)

View File

@ -1,82 +0,0 @@
#pragma once
#ifndef HEADER_JNI_H_
#define HEADER_JNI_H_
#include <jni.h>
#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;
}
};

170
fabric/build.gradle Normal file
View File

@ -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')
}

View File

@ -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!");
}
}

View File

@ -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<BlockEntityType<DollBlockEntity>> DOLL_BLOCK_ENTITY;
}

View File

@ -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<Block> DOLL;
}

View File

@ -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<Item> DOLL;
}

View File

@ -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<SoundEvent> 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;
}
}

View File

@ -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");
}
}

View File

@ -0,0 +1 @@
top.r3944realms.lib39.platform.ForgePlatformHelper

View File

@ -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": "*"
}
}

View File

@ -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
}
}

193
forge/build.gradle Normal file
View File

@ -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
}

View File

@ -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");
}
}

View File

@ -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<BlockEntityType<?>> BLOCK_ENTITY_TYPES = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, Lib39.MOD_ID);
/**
* The constant DOLL_BLOCK_ENTITY.
*/
@SuppressWarnings("DataFlowIssue")
public static final RegistryObject<BlockEntityType<DollBlockEntity>> 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);
}
}

View File

@ -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<Block> 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);
}
}

View File

@ -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<Item> 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);
}
}

View File

@ -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<SoundEvent> 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;
}
}

View File

@ -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<ResourceLocation> requiredTables, @NotNull SubProvidersWrapper subProvidersWrapper) {
super(output, requiredTables, subProvidersWrapper.entries);
}
@Override
protected void validate(@NotNull Map<ResourceLocation, LootTable> map, @NotNull ValidationContext validationcontext) {
map.forEach((id, table) -> table.validate(validationcontext));
}
}

View File

@ -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<LootTableProvider.SubProviderEntry> 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;
}
}

View File

@ -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<BlockEntry> blockEntries = new ArrayList<>();
private final DeferredRegister<Block> knowBlocks;
/**
* Instantiates a new Block loot tables.
*
* @param deferredRegister the deferred register
*/
public BlockLootTables(DeferredRegister<Block> deferredRegister) {
super(Set.of(), FeatureFlags.REGISTRY.allFlags());
knowBlocks = deferredRegister;
}
@Override
protected @NotNull Iterable<Block> getKnownBlocks() {
return knowBlocks.getEntries().stream().map(RegistryObject::get).collect(Collectors.toSet());
}
// ==================== 流畅 API 构建方法 ====================
/**
* 添加自掉落的方块
*
* @param block the block
*/
public void dropSelf(RegistryObject<Block> block) {
addEntry(block, this::createSingleItemTable);
}
/**
* 批量添加自掉落的方块
*
* @param blocks the blocks
* @return the block loot tables
*/
@Contract("_ -> this")
@SafeVarargs
public final BlockLootTables dropSelf(RegistryObject<Block> @NotNull ... blocks) {
for (RegistryObject<Block> block : blocks) {
dropSelf(block);
}
return this;
}
/**
* 添加需要丝绸之触才掉落的方块
*
* @param block the block
* @return the block loot tables
*/
public BlockLootTables dropWhenSilkTouch(RegistryObject<Block> block) {
return addEntry(block, BlockLootSubProvider::createSilkTouchOnlyTable);
}
/**
* 添加掉落其他物品的方块
*
* @param block the block
* @param item the item
* @return the block loot tables
*/
public BlockLootTables dropOther(RegistryObject<Block> 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> 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> block, RegistryObject<Item> oreItem) {
return addEntry(block, b -> this.createOreDrop(b, oreItem.get()));
}
/**
* 添加红石矿石掉落表
*
* @param block the block
* @return the block loot tables
*/
public BlockLootTables dropRedstoneOre(RegistryObject<Block> block) {
return addEntry(block, this::createRedstoneOreDrops);
}
/**
* 添加青金石矿石掉落表
*
* @param block the block
* @return the block loot tables
*/
public BlockLootTables dropLapisOre(RegistryObject<Block> block) {
return addEntry(block, this::createLapisOreDrops);
}
/**
* 添加铜矿石掉落表
*
* @param block the block
* @return the block loot tables
*/
public BlockLootTables dropCopperOre(RegistryObject<Block> block) {
return addEntry(block, this::createCopperOreDrops);
}
/**
* 添加地毯类方块的掉落一次掉落2个
*
* @param block the block
* @return the block loot tables
*/
public BlockLootTables dropCarpet(RegistryObject<Block> 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> block) {
return addEntry(block, this::createSlabItemTable);
}
/**
* 添加门方块的掉落只掉落下半部分
*
* @param block the block
* @return the block loot tables
*/
public BlockLootTables dropDoor(RegistryObject<Block> block) {
return addEntry(block, this::createDoorTable);
}
/**
* 添加花盆的掉落
*
* @param block the block
* @return the block loot tables
*/
public BlockLootTables dropFlowerPot(RegistryObject<Block> 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<Block> leavesBlock,
RegistryObject<Block> 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<Block> leavesBlock,
RegistryObject<Block> 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<Block> cropBlock,
RegistryObject<Item> cropItem,
RegistryObject<Item> seedsItem,
Property<Integer> 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> block, Function<Block, LootTable.Builder> factory) {
return addEntry(block, factory);
}
/**
* 没有掉落
*
* @param block the block
* @return the block loot tables
*/
public BlockLootTables noDrop(RegistryObject<Block> block) {
return addEntry(block, b -> noDrop());
}
// ==================== 批量操作方法 ====================
/**
* 批量操作一系列方块
*
* @param blocks the blocks
* @param operation the operation
* @return the block loot tables
*/
public BlockLootTables batch(@NotNull Iterable<RegistryObject<Block>> blocks,
Function<RegistryObject<Block>, BlockLootTables> operation) {
for (RegistryObject<Block> 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<RegistryObject<Block>, BlockLootTables> operation,
RegistryObject<Block> @NotNull ... blocks) {
for (RegistryObject<Block> 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> block, Function<Block, LootTable.Builder> factory) {
blockEntries.add(new BlockEntry(block, factory));
return this;
}
private record BlockEntry(RegistryObject<Block> block, Function<Block, LootTable.Builder> factory) {}
// ==================== 静态工厂方法 ====================
/**
* 创建新的战利品表生成器
*
* @param deferredRegister the deferred register
* @return the block loot tables
*/
@Contract("_ -> new")
public static @NotNull BlockLootTables create(DeferredRegister<Block> 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)
))))));
}
}

View File

@ -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");
}
}

View File

@ -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"

Some files were not shown because too many files have changed in this diff Show More