更新内容

1. 加解密ClassLoader
2. 帮助指令管理器
This commit is contained in:
叁玖领域 2025-12-23 20:35:40 +08:00
parent 2b0a14d0e9
commit f062be7f51
131 changed files with 6987 additions and 261 deletions

1
.gitignore vendored
View File

@ -5,6 +5,7 @@ bin
.metadata
.classpath
.project
cpp/cmake-build-debug
# idea
out

View File

@ -9,6 +9,8 @@ plugins {
id 'com.dorongold.task-tree' version '2.1.1'
}
apply from: 'gradle/jni-heads.gradle'
java {
toolchain.languageVersion = JavaLanguageVersion.of(17)
}
@ -103,6 +105,11 @@ obfuscation {
}
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"))
implementation 'org.joml:joml:1.10.5'
//
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.3'
@ -119,6 +126,13 @@ dependencies {
//
testImplementation 'org.hamcrest:hamcrest:2.2'
}
mixin {
add sourceSets.main, "${mod_id}.refmap.json"
config "${mod_id}.mixins.json"
}
dependencies {
annotationProcessor 'org.spongepowered:mixin:0.8.5:processor'
}
test {
useJUnitPlatform()
@ -212,7 +226,8 @@ tasks.register('deobfJar', Jar) {
'Implementation-Title': project.name,
'Implementation-Version': archiveVersion,
'Implementation-Vendor': mod_authors,
'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
'MixinConfigs' : "${mod_id}.mixins.json"
)
}
@ -232,6 +247,7 @@ tasks.register('sourceJar', Jar) {
'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

8
config/jni-classes.txt Normal file
View File

@ -0,0 +1,8 @@
# JNI 头文件生成配置
# 每行一个类全限定名,例如:
# com.example.MyNativeClass
# com.example.NativeUtils
top.r3944realms.lib39.core.lang.ClassEncryptor
top.r3944realms.lib39.core.lang.EncryptedClassLoader
# 或者使用正则表达式自动匹配:
# #auto:.*Native.*

241
cpp/CMakeLists.txt Normal file
View File

@ -0,0 +1,241 @@
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 "=========================================")

17
cpp/Config.cmake.in Normal file
View File

@ -0,0 +1,17 @@
# 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

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

@ -0,0 +1,37 @@
/* 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

@ -0,0 +1,21 @@
/* 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

1
cpp/lib/CMakeLists.txt Normal file
View File

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

7
cpp/src/CMakeLists.txt Normal file
View File

@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.28)
add_subdirectory(header)
add_subdirectory(lib)
set_target_properties(CONST_LIB PROPERTIES LINKER_LANGUAGE CXX)

View File

@ -0,0 +1,451 @@
#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

@ -0,0 +1,234 @@
#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

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

View File

@ -0,0 +1,82 @@
#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;
}
};

21
gradle/.jni-config.groovy Normal file
View File

@ -0,0 +1,21 @@
ext.jniConfig = {
//
outputDir = project.file("native/include")
//
configFile = project.file("jni/jni-classes.txt")
//
autoGenerateOnBuild = true
//
verbose = true
//
defaultPatterns = [
'.*Native.*',
'.*JNI.*',
'.*native.*',
'com\\.mymod\\..*Impl' //
]
}

458
gradle/jni-heads.gradle Normal file
View File

@ -0,0 +1,458 @@
//
def outputDir = project.file("cpp/header")
def configFile = project.file("config/jni-classes.txt")
//
def log(msg) {
println "[JNI] $msg"
}
def logError(msg) {
println "[JNI ERROR] $msg"
}
def logWarn(msg) {
println "[JNI WARN] $msg"
}
//
tasks.register('createJniConfig') {
group = 'jni'
description = '创建 JNI 配置文件模板'
doLast {
configFile.parentFile.mkdirs()
if (!configFile.exists()) {
configFile.text = """# JNI
#
# com.example.MyNativeClass
# com.example.NativeUtils
# 使
# #auto:.*Native.*
"""
log "配置文件已创建: ${configFile.absolutePath}"
log "请编辑此文件并添加包含 native 方法的类"
}
}
}
//
tasks.register('generateJniHeaders') {
group = 'jni'
description = '生成 JNI 头文件'
dependsOn 'compileJava'
doLast {
//
outputDir.mkdirs()
//
def targetClasses = []
if (configFile.exists()) {
configFile.eachLine { line ->
def trimmed = line.trim()
if (trimmed && !trimmed.startsWith('#')) {
targetClasses.add(trimmed)
}
}
}
if (targetClasses.isEmpty()) {
logError "没有配置任何 JNI 类"
logError "请先运行: ./gradlew createJniConfig"
logError "然后编辑 ${configFile.absolutePath} 添加类名"
return
}
log "开始生成 JNI 头文件..."
log "目标类 (${targetClasses.size()} 个):"
targetClasses.eachWithIndex { className, i ->
log " ${i + 1}. $className"
}
//
def classesDir = project.sourceSets.main.output.classesDirs.singleFile
def classpath = project.configurations.runtimeClasspath.asPath +
File.pathSeparator +
classesDir.absolutePath
// Java
def sourceFiles = []
def sourceDirs = project.sourceSets.main.java.srcDirs
targetClasses.each { className ->
def found = false
def relativePath = className.replace('.', '/') + '.java'
sourceDirs.each { srcDir ->
def sourceFile = new File(srcDir, relativePath)
if (sourceFile.exists()) {
sourceFiles.add(sourceFile)
found = true
log "找到源文件: ${sourceFile.absolutePath}"
}
}
if (!found) {
logWarn "警告: 未找到类 $className 的源文件"
}
}
if (sourceFiles.isEmpty()) {
logError "错误: 未找到任何源文件"
logError "请确保源文件存在于 src/main/java/ 目录中"
return
}
// 使 javac -h
def javaHome = System.getProperty('java.home')
def javacPath = "${javaHome}/bin/javac"
if (!new File(javacPath).exists()) {
javacPath = "${javaHome}/bin/javac"
}
log "使用 javac -h 生成头文件..."
//
def tempOutputDir = new File(project.buildDir, "tmp/jni-headers")
tempOutputDir.mkdirs()
try {
// 1
def successCount = 0
def failCount = 0
sourceFiles.each { sourceFile ->
try {
// javac
def processArgs = [
javacPath,
'-h', outputDir.absolutePath, //
'-cp', classpath, //
'-d', tempOutputDir.absolutePath, //
sourceFile.absolutePath //
]
log "处理: ${sourceFile.name}"
def process = processArgs.execute()
def stdout = new StringBuilder()
def stderr = new StringBuilder()
process.consumeProcessOutput(stdout, stderr)
def exitCode = process.waitFor()
if (exitCode == 0) {
successCount++
if (stdout.length() > 0) {
log " 输出: ${stdout.toString().trim()}"
}
} else {
failCount++
logError " 处理失败: ${sourceFile.name}"
if (stderr.length() > 0) {
logError " 错误: ${stderr.toString().trim()}"
}
}
} catch (Exception e) {
failCount++
logError " 处理异常: ${e.message}"
}
}
log "处理完成: 成功 ${successCount} 个, 失败 ${failCount} 个"
if (successCount > 0) {
//
def headerFiles = outputDir.listFiles({ dir, name -> name.endsWith('.h') } as FilenameFilter)
if (headerFiles && headerFiles.size() > 0) {
log "=" * 60
log "JNI 头文件生成成功!"
log "=" * 60
log "输出目录: ${outputDir.absolutePath}"
log "生成的头文件 (${headerFiles.size()} 个):"
headerFiles.sort { it.name }.each { file ->
def size = file.length()
def sizeStr = size < 1024 ? "${size} B" : "${String.format("%.1f", size / 1024.0)} KB"
log " ✓ ${file.name} ($sizeStr)"
//
try {
def lines = file.readLines()
if (lines.size() > 0) {
def headerGuard = lines.find { it.contains('#ifndef') }
if (headerGuard) {
log " 头文件保护: ${headerGuard.trim()}"
}
}
} catch (Exception e) {
//
}
}
log "=" * 60
log "🎉 头文件已成功生成!"
log ""
log "使用建议:"
log " 1. 将生成的头文件复制到你的 C/C++ 项目中"
log " 2. 在 C/C++ 源文件中包含这些头文件"
log " 3. 实现头文件中声明的 JNI 函数"
log ""
log "示例 C++ 代码:"
log " #include \"${headerFiles[0].name}\""
log " JNIEXPORT void JNICALL Java_com_example_MyClass_nativeMethod(JNIEnv* env, jobject obj) {"
log " // 你的实现代码"
log " }"
} else {
logWarn "警告: 未生成任何 .h 头文件"
logWarn "可能的原因:"
logWarn " 1. 源文件中没有 native 方法声明"
logWarn " 2. javac 版本不支持 -h 选项"
logWarn " 3. 类路径配置不正确"
}
} else {
logError "所有处理都失败,未生成任何头文件"
}
} finally {
//
tempOutputDir.deleteDir()
}
}
}
// 使 javah javac -h
tasks.register('generateJniHeadersLegacy') {
group = 'jni'
description = '使用传统 javah 生成 JNI 头文件'
dependsOn 'compileJava'
doLast {
outputDir.mkdirs()
def targetClasses = []
if (configFile.exists()) {
configFile.eachLine { line ->
def trimmed = line.trim()
if (trimmed && !trimmed.startsWith('#')) {
targetClasses.add(trimmed)
}
}
}
if (targetClasses.isEmpty()) {
logError "没有配置任何 JNI 类"
return
}
log "使用传统 javah 生成头文件..."
def classesDir = project.sourceSets.main.output.classesDirs.singleFile
def classpath = project.configurations.runtimeClasspath.asPath +
File.pathSeparator +
classesDir.absolutePath
def javaHome = System.getProperty('java.home')
def javahPath = "${javaHome}/bin/javah"
if (!new File(javahPath).exists()) {
javahPath = "${javaHome}/../bin/javah"
}
if (!new File(javahPath).exists()) {
logError "找不到 javah 工具"
logError "请使用 Java 8-9 或使用 generateJniHeaders 任务"
return
}
def processArgs = [javahPath, '-classpath', classpath, '-d', outputDir.absolutePath]
processArgs.addAll(targetClasses)
log "执行命令: ${processArgs.join(' ')}"
def process = processArgs.execute()
def exitCode = process.waitFor()
if (exitCode == 0) {
def files = outputDir.listFiles({ dir, name -> name.endsWith('.h') } as FilenameFilter)
log "生成成功!创建了 ${files?.size() ?: 0} 个头文件"
if (files) {
files.each { log " - ${it.name}" }
}
} else {
logError "生成失败"
}
}
}
// native
tasks.register('scanForNativeMethods') {
group = 'jni'
description = '扫描项目中的 native 方法'
doLast {
log "扫描项目中可能包含 native 方法的类..."
def sourceDirs = project.sourceSets.main.java.srcDirs
def foundClasses = []
sourceDirs.each { srcDir ->
if (srcDir.exists()) {
srcDir.eachFileRecurse(groovy.io.FileType.FILES) { file ->
if (file.name.endsWith('.java')) {
def content = file.text
if (content.contains('native ') || content.contains(' native')) {
//
def packageMatch = content =~ /package\s+([\w.]+)\s*;/
def classMatch = content =~ /class\s+(\w+)/
if (packageMatch.find() && classMatch.find()) {
def packageName = packageMatch.group(1)
def className = classMatch.group(1)
def fullClassName = "${packageName}.${className}"
if (!foundClasses.contains(fullClassName)) {
foundClasses.add(fullClassName)
log "发现: $fullClassName"
}
}
}
}
}
}
}
if (foundClasses.isEmpty()) {
log "未发现包含 native 方法的类"
} else {
log "=" * 60
log "发现 ${foundClasses.size()} 个可能包含 native 方法的类:"
foundClasses.sort().eachWithIndex { cls, i ->
log " ${i + 1}. $cls"
}
log ""
log "你可以将这些类添加到 ${configFile.name} 中"
}
}
}
//
tasks.register('cleanJniHeaders', Delete) {
group = 'jni'
description = '清理 JNI 头文件'
delete outputDir
doLast {
log "已清理 JNI 头文件目录: ${outputDir.absolutePath}"
}
}
//
tasks.register('verifyJniSetup') {
group = 'jni'
description = '验证 JNI 配置'
doLast {
log "验证 JNI 配置..."
log "Java 版本: ${System.getProperty('java.version')}"
log "Java Home: ${System.getProperty('java.home')}"
// javac
def javaHome = System.getProperty('java.home')
def javacPath = "${javaHome}/bin/javac"
def javacExists = new File(javacPath).exists() || new File("${javaHome}/../bin/javac").exists()
log "javac 工具: ${javacExists ? '找到 ✓' : '未找到 ✗'}"
// javah
def javahPath = "${javaHome}/bin/javah"
def javahExists = new File(javahPath).exists() || new File("${javaHome}/../bin/javah").exists()
log "javah 工具: ${javahExists ? '找到 ✓' : '未找到 ✗'}"
//
if (configFile.exists()) {
def classes = configFile.readLines()
.findAll { it.trim() && !it.trim().startsWith('#') }
log "配置文件: 已找到 (${classes.size()} 个类)"
if (classes.size() > 0) {
classes.each { log " - $it" }
}
} else {
log "配置文件: 未找到 ✗"
}
//
log "输出目录: ${outputDir.absolutePath}"
}
}
//
tasks.register('jniHelp') {
group = 'help'
description = 'JNI 帮助'
doLast {
println """
${'=' * 70}
JNI
${'=' * 70}
📋 :
native Java C/C++
🚀 :
1. ./gradlew createJniConfig #
2. config/jni-classes.txt # JNI
3. ./gradlew generateJniHeaders #
4. : cpp/header/
🔧 :
jniHelp -
createJniConfig -
generateJniHeaders -
generateJniHeadersLegacy - Java 8-9
scanForNativeMethods - native
verifyJniSetup -
cleanJniHeaders -
📝 (config/jni-classes.txt):
#
com.example.MyNativeClass #
#auto:.*Native.* # #auto:
:
native
Java 10+ 使 generateJniHeaders
Java 8-9 使 generateJniHeadersLegacy
🔍 :
verifyJniSetup
scanForNativeMethods native
native
${'=' * 70}
"""
}
}
//
// tasks.named('build') {
// dependsOn tasks.named('generateJniHeaders')
// }
// JNI
tasks.named('clean') {
dependsOn tasks.named('cleanJniHeaders')
}
log "JNI 模块已加载"
log "输出目录: ${outputDir.absolutePath}"
log "配置文件: ${configFile.absolutePath}"
log "使用 ./gradlew jniHelp 查看详细帮助"

BIN
res/alex.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

1
res/doll_default.bbmodel Normal file

File diff suppressed because one or more lines are too long

265
res/doll_default.json Normal file
View File

@ -0,0 +1,265 @@
{
"format_version": "1.9.0",
"credit": "3D Model © 2025 LeisureTimeDock",
"textures": {
"0": "#skin",
"particle": "minecraft:block/white_wool"
},
"elements": [
{
"name": "Toggle_Helmet",
"from": [3.5, 8.8, 7.5],
"to": [12.5, 17.8, 16.5],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 9.3, 12]},
"faces": {
"north": {"uv": [10, 2, 12, 4], "texture": "#0"},
"east": {"uv": [8, 2, 10, 4], "texture": "#0"},
"south": {"uv": [14, 2, 16, 4], "texture": "#0"},
"west": {"uv": [12, 2, 14, 4], "texture": "#0"},
"up": {"uv": [10, 2, 12, 0], "texture": "#0"},
"down": {"uv": [12, 0, 14, 2], "texture": "#0"}
}
},
{
"name": "Head",
"from": [4, 9.3, 8],
"to": [12, 17.3, 16],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 9.3, 12]},
"faces": {
"north": {"uv": [2, 2, 4, 4], "texture": "#0"},
"east": {"uv": [0, 2, 2, 4], "texture": "#0"},
"south": {"uv": [6, 2, 8, 4], "texture": "#0"},
"west": {"uv": [4, 2, 6, 4], "texture": "#0"},
"up": {"uv": [4, 2, 2, 0], "texture": "#0"},
"down": {"uv": [6, 0, 4, 2], "texture": "#0"}
}
},
{
"name": "Toggle_Chest_Armor",
"from": [4.75, 2.05, 9.75],
"to": [11.25, 9.55, 13.25],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 5.8, 11.5]},
"faces": {
"north": {"uv": [5, 9, 7, 12], "texture": "#0"},
"east": {"uv": [4, 9, 5, 12], "texture": "#0"},
"south": {"uv": [8, 9, 10, 12], "texture": "#0"},
"west": {"uv": [7, 9, 8, 12], "texture": "#0"},
"up": {"uv": [5, 8, 7, 9], "texture": "#0"},
"down": {"uv": [7, 8, 9, 9], "texture": "#0"}
}
},
{
"name": "Body",
"from": [5, 2.3, 10],
"to": [11, 9.3, 13],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 5.8, 11.5]},
"faces": {
"north": {"uv": [5, 5, 7, 8], "texture": "#0"},
"east": {"uv": [4, 5, 5, 8], "texture": "#0"},
"south": {"uv": [8, 5, 10, 8], "texture": "#0"},
"west": {"uv": [7, 5, 8, 8], "texture": "#0"},
"up": {"uv": [7, 5, 5, 4], "texture": "#0"},
"down": {"uv": [9, 4, 7, 5], "texture": "#0"}
}
},
{
"name": "Toggle_Left_Arm_Armor",
"from": [2.75, 6.25, 3.8],
"to": [5.25, 9.75, 13.3],
"rotation": {"angle": -22.5, "axis": "y", "origin": [3, 8, 12.5]},
"faces": {
"north": {"uv": [13.75, 12, 14.5, 13], "rotation": 180, "texture": "#0"},
"east": {"uv": [12, 13, 13, 16], "rotation": 270, "texture": "#0"},
"south": {"uv": [13, 12, 13.75, 13], "texture": "#0"},
"west": {"uv": [13.75, 13, 14.75, 16], "rotation": 90, "texture": "#0"},
"up": {"uv": [13, 13, 13.75, 16], "rotation": 180, "texture": "#0"},
"down": {"uv": [14.75, 13, 15.5, 16], "texture": "#0"}
}
},
{
"name": "Left_arm",
"from": [3, 6.5, 3.8],
"to": [5, 9.5, 12.8],
"rotation": {"angle": -22.5, "axis": "y", "origin": [4, 8, 12.5]},
"faces": {
"north": {"uv": [10.5, 12, 9.75, 13], "rotation": 180, "texture": "#0"},
"east": {"uv": [8, 13, 9, 16], "rotation": 270, "texture": "#0"},
"south": {"uv": [9.75, 13, 9, 12], "texture": "#0"},
"west": {"uv": [9.75, 13, 10.5, 16], "rotation": 90, "texture": "#0"},
"up": {"uv": [9, 13, 9.75, 16], "rotation": 180, "texture": "#0"},
"down": {"uv": [10.5, 13, 11.5, 16], "texture": "#0"}
}
},
{
"name": "Toggle_Right_Arm_Armor",
"from": [10.75, 6.25, 3.8],
"to": [13.25, 9.75, 13.3],
"rotation": {"angle": 22.5, "axis": "y", "origin": [12, 8, 11.5]},
"faces": {
"north": {"uv": [11.75, 8, 12.5, 9], "rotation": 180, "texture": "#0"},
"east": {"uv": [10, 9, 11, 12], "rotation": 270, "texture": "#0"},
"south": {"uv": [11, 8, 11.75, 9], "texture": "#0"},
"west": {"uv": [11.75, 9, 12.75, 12], "rotation": 90, "texture": "#0"},
"up": {"uv": [11, 9, 11.75, 12], "rotation": 180, "texture": "#0"},
"down": {"uv": [12.75, 9, 13.5, 12], "texture": "#0"}
}
},
{
"name": "Right_arm",
"from": [11, 6.5, 3.8],
"to": [13, 9.5, 12.8],
"rotation": {"angle": 22.5, "axis": "y", "origin": [12, 8, 11.5]},
"faces": {
"north": {"uv": [12.5, 4, 11.75, 5], "rotation": 180, "texture": "#0"},
"east": {"uv": [10, 5, 11, 8], "rotation": 270, "texture": "#0"},
"south": {"uv": [11.75, 5, 11, 4], "texture": "#0"},
"west": {"uv": [11.75, 5, 12.5, 8], "rotation": 90, "texture": "#0"},
"up": {"uv": [11, 5, 11.75, 8], "rotation": 180, "texture": "#0"},
"down": {"uv": [12.5, 5, 13.5, 8], "texture": "#0"}
}
},
{
"name": "Toggle_Left_Leg_Armor",
"from": [5.2, -0.25, 3.05],
"to": [8.7, 3.25, 12.55],
"rotation": {"angle": 22.5, "axis": "y", "origin": [5.7, 2, 13]},
"faces": {
"north": {"uv": [2, 12, 3, 13], "rotation": 180, "texture": "#0"},
"east": {"uv": [0, 13, 1, 16], "rotation": 270, "texture": "#0"},
"south": {"uv": [1, 12, 2, 13], "texture": "#0"},
"west": {"uv": [2, 13, 3, 16], "rotation": 90, "texture": "#0"},
"up": {"uv": [1, 13, 2, 16], "rotation": 180, "texture": "#0"},
"down": {"uv": [3, 13, 4, 16], "texture": "#0"}
}
},
{
"name": "Left_leg",
"from": [5.5, 0, 3.3],
"to": [8.5, 3, 12.3],
"rotation": {"angle": 22.5, "axis": "y", "origin": [6, 2, 13]},
"faces": {
"north": {"uv": [7, 12, 6, 13], "rotation": 180, "texture": "#0"},
"east": {"uv": [4, 13, 5, 16], "rotation": 270, "texture": "#0"},
"south": {"uv": [5.95, 13, 5, 12], "texture": "#0"},
"west": {"uv": [6, 13, 7, 16], "rotation": 90, "texture": "#0"},
"up": {"uv": [5, 13, 6, 16], "rotation": 180, "texture": "#0"},
"down": {"uv": [7, 13, 8, 16], "texture": "#0"}
}
},
{
"name": "Toggle_Right_Leg_Armor",
"from": [7.2, -0.25, 3.05],
"to": [10.7, 3.25, 12.55],
"rotation": {"angle": -22.5, "axis": "y", "origin": [8.7, 2, 13]},
"faces": {
"north": {"uv": [2, 8, 3, 9], "rotation": 180, "texture": "#0"},
"east": {"uv": [0, 9, 1, 12], "rotation": 270, "texture": "#0"},
"south": {"uv": [1, 8, 2, 9], "texture": "#0"},
"west": {"uv": [2, 9, 3, 12], "rotation": 90, "texture": "#0"},
"up": {"uv": [1, 9, 2, 12], "rotation": 180, "texture": "#0"},
"down": {"uv": [3, 9, 4, 12], "texture": "#0"}
}
},
{
"name": "Right_leg",
"from": [7.5, 0, 3.3],
"to": [10.5, 3, 12.3],
"rotation": {"angle": -22.5, "axis": "y", "origin": [9, 2, 13]},
"faces": {
"north": {"uv": [3, 4, 2, 5], "rotation": 180, "texture": "#0"},
"east": {"uv": [0, 5, 1, 8], "rotation": 270, "texture": "#0"},
"south": {"uv": [2, 5, 1, 4], "texture": "#0"},
"west": {"uv": [2, 5, 3, 8], "rotation": 90, "texture": "#0"},
"up": {"uv": [1, 5, 2, 8], "rotation": 180, "texture": "#0"},
"down": {"uv": [3, 5, 4, 8], "texture": "#0"}
}
}
],
"display": {
"thirdperson_righthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"thirdperson_lefthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"firstperson_righthand": {
"rotation": [0, 124, 0],
"translation": [2, 3, 0],
"scale": [0.4, 0.4, 0.4]
},
"firstperson_lefthand": {
"rotation": [0, 120, 0],
"translation": [1.5, 2.75, 0],
"scale": [0.4, 0.4, 0.4]
},
"ground": {
"translation": [0, 2, 0],
"scale": [0.5, 0.5, 0.5]
},
"gui": {
"rotation": [30, -135, 0],
"translation": [0.75, -1, 0],
"scale": [0.625, 0.625, 0.625]
},
"head": {
"translation": [0, 14, -0.75]
},
"fixed": {
"translation": [0, 0, -2.75],
"scale": [0.5, 0.5, 0.5]
},
"on_shelf": {
"rotation": [0, -180, 0],
"translation": [0, 0, 5.25]
}
},
"groups": [
{
"name": "Player",
"origin": [3, -6.7, 6],
"color": 0,
"children": [
{
"name": "Head",
"origin": [8, 16, 8],
"color": 0,
"children": [0, 1]
},
{
"name": "Body",
"origin": [8, 11, 8],
"color": 0,
"children": [2, 3]
},
{
"name": "Left_Arm",
"origin": [5, 15, 6],
"color": 0,
"children": [4, 5]
},
{
"name": "Right_Arm",
"origin": [11, 15, 6],
"color": 0,
"children": [6, 7]
},
{
"name": "Left_Leg",
"origin": [7, 13, 7],
"color": 0,
"children": [8, 9]
},
{
"name": "Right_Leg",
"origin": [10, 13, 7],
"color": 0,
"children": [10, 11]
}
]
}
]
}

1
res/doll_item.bbmodel Normal file
View File

@ -0,0 +1 @@
{"meta":{"format_version":"5.0","model_format":"java_block","box_uv":false},"name":"doll_item","parent":"","java_block_version":"1.9.0","ambientocclusion":true,"front_gui_light":false,"visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"unhandled_root_fields":{},"resolution":{"width":16,"height":16},"elements":[{"name":"cube","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[7,6,1],"to":[7,14,9],"autouv":0,"color":8,"rotation":[0,45,0],"origin":[7,10,3.5],"faces":{"north":{"uv":[0,0,0,0],"texture":null},"east":{"uv":[0,0,16,16],"texture":0},"south":{"uv":[0,0,0,0],"texture":null},"west":{"uv":[0,0,16,16],"texture":0},"up":{"uv":[0,0,0,0],"texture":null},"down":{"uv":[0,0,0,0],"texture":null}},"type":"cube","uuid":"f9f45372-1441-1b47-bbb1-278cfff92370"},{"name":"cube","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[9,6,1],"to":[9,14,9],"autouv":0,"color":0,"rotation":[0,-45,0],"origin":[9,10,3.5],"faces":{"north":{"uv":[0,0,0,0],"texture":null},"east":{"uv":[0,0,16,16],"texture":0},"south":{"uv":[0,0,0,0],"texture":null},"west":{"uv":[0,0,16,16],"texture":0},"up":{"uv":[0,0,0,0],"texture":null},"down":{"uv":[0,0,0,0],"texture":null}},"type":"cube","uuid":"04196842-4494-bee4-1b44-b302f4a2475a"}],"groups":[{"uuid":"2b55d183-11b7-4e36-1f45-11cc7c5d7445","export":true,"locked":false,"origin":[0,4,2.5],"rotation":[0,0,0],"color":0,"name":"flower_item","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":true}],"outliner":[{"uuid":"2b55d183-11b7-4e36-1f45-11cc7c5d7445","isOpen":true,"children":["f9f45372-1441-1b47-bbb1-278cfff92370","04196842-4494-bee4-1b44-b302f4a2475a"]}],"textures":[{"name":"amethyst_cluster.png","path":"","folder":"","namespace":"","id":"3","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":true,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"65a7492d-58ab-bfad-b419-fd46962220fb","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAwElEQVQ4y7WSwQ3CMBAE0x1F8OVFCfQAJSB6QFQBEq+0QAO8wWgiDVqcCGEQkS6Ondvx3tld98/nvLuWn8T346XsN19AEAtYL0+lyYniBDRBFJZbPwQlNAFW88MgIhAxB8D4MQBBDVjMttMA7BJpUZEAIgGWSbxMhCjCRQ0Y5fNiNxpm90nOUqzf/+Q+GyuAgOpu6YJ5HufoaF1EYN3pwu9s6uhYs2FZtwA3EDB5Iia5I6Pu0tHbO0CiodVcqy/UAxZem9kikfZjAAAAAElFTkSuQmCC"}]}

37
res/doll_item.json Normal file
View File

@ -0,0 +1,37 @@
{
"format_version": "1.9.0",
"credit": "3D Model © 2025 LeisureTimeDock",
"textures": {
"3": "#item",
"particle": "#item"
},
"elements": [
{
"from": [7, 6, 1],
"to": [7, 14, 9],
"rotation": {"angle": 45, "axis": "y", "origin": [7, 10, 3.5]},
"faces": {
"east": {"uv": [0, 0, 16, 16], "texture": "#3"},
"west": {"uv": [0, 0, 16, 16], "texture": "#3"}
}
},
{
"from": [9, 6, 1],
"to": [9, 14, 9],
"rotation": {"angle": -45, "axis": "y", "origin": [9, 10, 3.5]},
"faces": {
"east": {"uv": [0, 0, 16, 16], "texture": "#3"},
"west": {"uv": [0, 0, 16, 16], "texture": "#3"}
}
}
],
"display": {},
"groups": [
{
"name": "item",
"origin": [0, 4, 2.5],
"color": 0,
"children": [0, 1]
}
]
}

File diff suppressed because one or more lines are too long

265
res/doll_without_item.json Normal file
View File

@ -0,0 +1,265 @@
{
"format_version": "1.9.0",
"credit": "3D Model © 2025 LeisureTimeDock",
"textures": {
"0": "#skin",
"particle": "minecraft:block/white_wool"
},
"elements": [
{
"name": "Toggle_Helmet",
"from": [3.5, 8.8, 7.5],
"to": [12.5, 17.8, 16.5],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 9.3, 12]},
"faces": {
"north": {"uv": [10, 2, 12, 4], "texture": "#0"},
"east": {"uv": [8, 2, 10, 4], "texture": "#0"},
"south": {"uv": [14, 2, 16, 4], "texture": "#0"},
"west": {"uv": [12, 2, 14, 4], "texture": "#0"},
"up": {"uv": [10, 2, 12, 0], "texture": "#0"},
"down": {"uv": [12, 0, 14, 2], "texture": "#0"}
}
},
{
"name": "Head",
"from": [4, 9.3, 8],
"to": [12, 17.3, 16],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 9.3, 12]},
"faces": {
"north": {"uv": [2, 2, 4, 4], "texture": "#0"},
"east": {"uv": [0, 2, 2, 4], "texture": "#0"},
"south": {"uv": [6, 2, 8, 4], "texture": "#0"},
"west": {"uv": [4, 2, 6, 4], "texture": "#0"},
"up": {"uv": [4, 2, 2, 0], "texture": "#0"},
"down": {"uv": [6, 0, 4, 2], "texture": "#0"}
}
},
{
"name": "Toggle_Chest_Armor",
"from": [4.75, 2.05, 9.75],
"to": [11.25, 9.55, 13.25],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 5.8, 11.5]},
"faces": {
"north": {"uv": [5, 9, 7, 12], "texture": "#0"},
"east": {"uv": [4, 9, 5, 12], "texture": "#0"},
"south": {"uv": [8, 9, 10, 12], "texture": "#0"},
"west": {"uv": [7, 9, 8, 12], "texture": "#0"},
"up": {"uv": [5, 8, 7, 9], "texture": "#0"},
"down": {"uv": [7, 8, 9, 9], "texture": "#0"}
}
},
{
"name": "Body",
"from": [5, 2.3, 10],
"to": [11, 9.3, 13],
"rotation": {"angle": 0, "axis": "y", "origin": [8, 5.8, 11.5]},
"faces": {
"north": {"uv": [5, 5, 7, 8], "texture": "#0"},
"east": {"uv": [4, 5, 5, 8], "texture": "#0"},
"south": {"uv": [8, 5, 10, 8], "texture": "#0"},
"west": {"uv": [7, 5, 8, 8], "texture": "#0"},
"up": {"uv": [7, 5, 5, 4], "texture": "#0"},
"down": {"uv": [9, 4, 7, 5], "texture": "#0"}
}
},
{
"name": "Toggle_Left_Arm_Armor",
"from": [2.75, 0.3, 9.75],
"to": [5.25, 9.8, 13.25],
"rotation": {"angle": 0, "axis": "x", "origin": [3, 8, 12.5]},
"faces": {
"north": {"uv": [13, 13, 13.75, 16], "texture": "#0"},
"east": {"uv": [12, 13, 13, 16], "texture": "#0"},
"south": {"uv": [14.75, 13, 15.5, 16], "texture": "#0"},
"west": {"uv": [13.75, 13, 14.75, 16], "texture": "#0"},
"up": {"uv": [13, 12, 13.75, 13], "texture": "#0"},
"down": {"uv": [13.75, 12, 14.5, 13], "texture": "#0"}
}
},
{
"name": "Left_arm",
"from": [3, 0.3, 10],
"to": [5, 9.3, 13],
"rotation": {"angle": 0, "axis": "x", "origin": [4, 8, 12.5]},
"faces": {
"north": {"uv": [9, 13, 9.75, 16], "texture": "#0"},
"east": {"uv": [8, 13, 9, 16], "texture": "#0"},
"south": {"uv": [10.5, 13, 11.5, 16], "texture": "#0"},
"west": {"uv": [9.75, 13, 10.5, 16], "texture": "#0"},
"up": {"uv": [9.75, 13, 9, 12], "texture": "#0"},
"down": {"uv": [10.5, 12, 9.75, 13], "texture": "#0"}
}
},
{
"name": "Toggle_Right_Arm_Armor",
"from": [10.75, 0.3, 9.75],
"to": [13.25, 9.8, 13.25],
"rotation": {"angle": 0, "axis": "x", "origin": [12, 8, 11.5]},
"faces": {
"north": {"uv": [11, 9, 11.75, 12], "texture": "#0"},
"east": {"uv": [10, 9, 11, 12], "texture": "#0"},
"south": {"uv": [12.75, 9, 13.5, 12], "texture": "#0"},
"west": {"uv": [11.75, 9, 12.75, 12], "texture": "#0"},
"up": {"uv": [11, 8, 11.75, 9], "texture": "#0"},
"down": {"uv": [11.75, 8, 12.5, 9], "texture": "#0"}
}
},
{
"name": "Right_arm",
"from": [11, 0.3, 10],
"to": [13, 9.3, 13],
"rotation": {"angle": 0, "axis": "x", "origin": [12, 8, 11.5]},
"faces": {
"north": {"uv": [11, 5, 11.75, 8], "texture": "#0"},
"east": {"uv": [10, 5, 11, 8], "texture": "#0"},
"south": {"uv": [12.5, 5, 13.5, 8], "texture": "#0"},
"west": {"uv": [11.75, 5, 12.5, 8], "texture": "#0"},
"up": {"uv": [11.75, 5, 11, 4], "texture": "#0"},
"down": {"uv": [12.5, 4, 11.75, 5], "texture": "#0"}
}
},
{
"name": "Toggle_Left_Leg_Armor",
"from": [5.2, -0.25, 3.05],
"to": [8.7, 3.25, 12.55],
"rotation": {"angle": 22.5, "axis": "y", "origin": [5.7, 2, 13]},
"faces": {
"north": {"uv": [2, 12, 3, 13], "rotation": 180, "texture": "#0"},
"east": {"uv": [0, 13, 1, 16], "rotation": 270, "texture": "#0"},
"south": {"uv": [1, 12, 2, 13], "texture": "#0"},
"west": {"uv": [2, 13, 3, 16], "rotation": 90, "texture": "#0"},
"up": {"uv": [1, 13, 2, 16], "rotation": 180, "texture": "#0"},
"down": {"uv": [3, 13, 4, 16], "texture": "#0"}
}
},
{
"name": "Left_leg",
"from": [5.5, 0, 3.3],
"to": [8.5, 3, 12.3],
"rotation": {"angle": 22.5, "axis": "y", "origin": [6, 2, 13]},
"faces": {
"north": {"uv": [7, 12, 6, 13], "rotation": 180, "texture": "#0"},
"east": {"uv": [4, 13, 5, 16], "rotation": 270, "texture": "#0"},
"south": {"uv": [5.95, 13, 5, 12], "texture": "#0"},
"west": {"uv": [6, 13, 7, 16], "rotation": 90, "texture": "#0"},
"up": {"uv": [5, 13, 6, 16], "rotation": 180, "texture": "#0"},
"down": {"uv": [7, 13, 8, 16], "texture": "#0"}
}
},
{
"name": "Toggle_Right_Leg_Armor",
"from": [7.2, -0.25, 3.05],
"to": [10.7, 3.25, 12.55],
"rotation": {"angle": -22.5, "axis": "y", "origin": [8.7, 2, 13]},
"faces": {
"north": {"uv": [2, 8, 3, 9], "rotation": 180, "texture": "#0"},
"east": {"uv": [0, 9, 1, 12], "rotation": 270, "texture": "#0"},
"south": {"uv": [1, 8, 2, 9], "texture": "#0"},
"west": {"uv": [2, 9, 3, 12], "rotation": 90, "texture": "#0"},
"up": {"uv": [1, 9, 2, 12], "rotation": 180, "texture": "#0"},
"down": {"uv": [3, 9, 4, 12], "texture": "#0"}
}
},
{
"name": "Right_leg",
"from": [7.5, 0, 3.3],
"to": [10.5, 3, 12.3],
"rotation": {"angle": -22.5, "axis": "y", "origin": [9, 2, 13]},
"faces": {
"north": {"uv": [3, 4, 2, 5], "rotation": 180, "texture": "#0"},
"east": {"uv": [0, 5, 1, 8], "rotation": 270, "texture": "#0"},
"south": {"uv": [2, 5, 1, 4], "texture": "#0"},
"west": {"uv": [2, 5, 3, 8], "rotation": 90, "texture": "#0"},
"up": {"uv": [1, 5, 2, 8], "rotation": 180, "texture": "#0"},
"down": {"uv": [3, 5, 4, 8], "texture": "#0"}
}
}
],
"display": {
"thirdperson_righthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"thirdperson_lefthand": {
"rotation": [75, 45, 0],
"translation": [0, 2.5, 0],
"scale": [0.375, 0.375, 0.375]
},
"firstperson_righthand": {
"rotation": [0, 124, 0],
"translation": [2, 3, 0],
"scale": [0.4, 0.4, 0.4]
},
"firstperson_lefthand": {
"rotation": [0, 120, 0],
"translation": [1.5, 2.75, 0],
"scale": [0.4, 0.4, 0.4]
},
"ground": {
"translation": [0, 2, 0],
"scale": [0.5, 0.5, 0.5]
},
"gui": {
"rotation": [30, -135, 0],
"translation": [0.75, -1, 0],
"scale": [0.625, 0.625, 0.625]
},
"head": {
"translation": [0, 14, -0.75]
},
"fixed": {
"translation": [0, 0, -2.75],
"scale": [0.5, 0.5, 0.5]
},
"on_shelf": {
"rotation": [0, -180, 0],
"translation": [0, 0, 5.25]
}
},
"groups": [
{
"name": "Player",
"origin": [3, -6.7, 6],
"color": 0,
"children": [
{
"name": "Head",
"origin": [8, 16, 8],
"color": 0,
"children": [0, 1]
},
{
"name": "Body",
"origin": [8, 11, 8],
"color": 0,
"children": [2, 3]
},
{
"name": "Left_Arm",
"origin": [5, 15, 6],
"color": 0,
"children": [4, 5]
},
{
"name": "Right_Arm",
"origin": [11, 15, 6],
"color": 0,
"children": [6, 7]
},
{
"name": "Left_Leg",
"origin": [7, 13, 7],
"color": 0,
"children": [8, 9]
},
{
"name": "Right_Leg",
"origin": [10, 13, 7],
"color": 0,
"children": [10, 11]
}
]
}
]
}

1
res/model.bbmodel Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
// 1.20.1 2025-12-08T00:17:05.2366865 Languages: zh_tw
2167da412113ad2aa18c3e8a9a53480f055e7661 assets/lib39/lang/zh_tw.json
// 1.20.1 2025-12-08T02:17:25.3481161 Languages: zh_tw
53425c42eb07613ff9575cf3562ae0b6c06d801c assets/lib39/lang/zh_tw.json

View File

@ -1,2 +1,2 @@
// 1.20.1 2025-12-08T00:17:05.2366865 Languages: zh_cn
f8dfabafd006b9552df1398d234eb8d23913b8e5 assets/lib39/lang/zh_cn.json
// 1.20.1 2025-12-08T02:17:25.3288872 Languages: zh_cn
4dd73f63979fedb90c9dbe7ef5cbefca10e17066 assets/lib39/lang/zh_cn.json

View File

@ -1,4 +1,5 @@
// 1.20.1 2025-11-22T23:38:13.2527751 Item Models: lib39
// 1.20.1 2025-12-22T20:31:52.8060707 Item Models: lib39
663f22009a9420c3eeae3c829fc9f37d16f0901b assets/lib39/models/item/doll.json
14f581c8f8e7f0de004c57a180f371e60e7b12ae assets/lib39/models/item/fabric.json
70583055336790fc837836ea6b49d16cfc8b64b8 assets/lib39/models/item/forge.json
447b36747d0aa8748dcd86715f4cce2cff19aca7 assets/lib39/models/item/neoforge.json

View File

@ -0,0 +1,2 @@
// 1.20.1 2025-12-22T20:31:52.804071 Block States: lib39
1dda476533f87cc377e800d537c22b48509a25cf assets/lib39/blockstates/doll.json

View File

@ -1,2 +1,2 @@
// 1.20.1 2025-12-08T00:17:05.2366865 Languages: lzh
e098d6e171f79cbb5e41a51547abcad520f70edb assets/lib39/lang/lzh.json
// 1.20.1 2025-12-08T02:17:25.3461162 Languages: lzh
36cdcf9b4b09c9731e504f22094c55b97a20c61c assets/lib39/lang/lzh.json

View File

@ -0,0 +1,52 @@
// 1.20.1 2025-12-22T20:33:59.3988473 Block Models: lib39
c5ecf989c3bbea8d7886f1cc5e6986df20d49698 assets/lib39/models/block/doll_item/acacia_sapling.json
d9ab870bd2b3dade81ab3b7a505b3a197693cc89 assets/lib39/models/block/doll_item/allium.json
6ab8299a91006367c1000578a2410efc740709ab assets/lib39/models/block/doll_item/amethyst_cluster.json
1b00e53ffbb576a6d8fe04f96f7411ef04c2724b assets/lib39/models/block/doll_item/azure_bluet.json
d6428f2e4ef38110c7d59d6bdff2317e69e30426 assets/lib39/models/block/doll_item/bamboo.json
9b488ddadd205146eed679512778abe922fce1c7 assets/lib39/models/block/doll_item/birch_sapling.json
f5f280c1f843e404e9d7d99d63c82a1aae899580 assets/lib39/models/block/doll_item/brain_coral.json
3cfb8312fc32af3adbb459b10b3bfd8150e1403e assets/lib39/models/block/doll_item/brain_coral_fan.json
796a2922e18478c84d1a4306268441c43e6e3fc5 assets/lib39/models/block/doll_item/brown_mushroom.json
2537bff63cdbaab6b6bc43f797a414d20ee42aa5 assets/lib39/models/block/doll_item/bubble_coral.json
d763db2c432f22c852b101ebf191ae1a4b4a79ca assets/lib39/models/block/doll_item/bubble_coral_fan.json
9992fc8b3e3a7372735ebd3c80f82ca38a7457b6 assets/lib39/models/block/doll_item/cherry_sapling.json
f37ceb94c45a68e5c925c307d6a442b86b2b228f assets/lib39/models/block/doll_item/cobweb.json
98a09fb2c8d72f3b3f56b21c71deb4d2bb7f75ec assets/lib39/models/block/doll_item/cornflower.json
351ced2ab09ee4e5a19f3ef58889210ade727cdf assets/lib39/models/block/doll_item/crimson_fungus.json
c548fcaabd73cd290671ee97684f50383fa6156d assets/lib39/models/block/doll_item/crimson_roots_pot.json
904c9ae4b9cc9a7b1afa00644695991850eb3a36 assets/lib39/models/block/doll_item/dandelion.json
5a1ee4a909258d0b825df723620bf06192b6a749 assets/lib39/models/block/doll_item/dark_oak_sapling.json
7d4c5880048705f2f9559d789b882eded4c01fea assets/lib39/models/block/doll_item/dead_brain_coral.json
1a45a06a788a193b16a0c55fae90f37caf26c10b assets/lib39/models/block/doll_item/dead_brain_coral_fan.json
8d62831e32744f5db4e59c3af9d718c3cab8c7c4 assets/lib39/models/block/doll_item/dead_bubble_coral.json
e53df0a329f1783252bf030a5febbcb09ba3ec46 assets/lib39/models/block/doll_item/dead_bubble_coral_fan.json
667f005c0d8bc16041209e07689243394a8d7aa1 assets/lib39/models/block/doll_item/dead_bush.json
b865c16c3b6e2dacbb75584b21c0b26269509e2e assets/lib39/models/block/doll_item/dead_fire_coral.json
be2219050188a813b319353a37413cc36ee38a10 assets/lib39/models/block/doll_item/dead_fire_coral_fan.json
79e23f620de796b6c383a4f953a544049c7209f9 assets/lib39/models/block/doll_item/dead_horn_coral.json
9b32e7a03b4990566268ff52eccf0087fddcd712 assets/lib39/models/block/doll_item/dead_horn_coral_fan.json
3138e486029ec07363d805d4e71caee95502b53a assets/lib39/models/block/doll_item/dead_tube_coral.json
07168397dc495328ca37caf326bbc781b4b0722d assets/lib39/models/block/doll_item/dead_tube_coral_fan.json
b5c8204ed6e9beb363a6ba854d648905a3e7b53e assets/lib39/models/block/doll_item/fire_coral.json
b29f30c717c5d323eaaec63f95e3a8839c6dbfc8 assets/lib39/models/block/doll_item/fire_coral_fan.json
1a54d4d417f72fd4aa9741ff866077abdf34afa6 assets/lib39/models/block/doll_item/horn_coral.json
c16714c69082efa11fbbbf7cd9d9fc7c17ffd5c8 assets/lib39/models/block/doll_item/horn_coral_fan.json
1bb0cbc2f014eaa10c9516c647cd7c09d32dbd3c assets/lib39/models/block/doll_item/jungle_sapling.json
521d825ef6bf88a79a6cd8ccb0a4753dbb74edd6 assets/lib39/models/block/doll_item/lily_of_the_valley.json
deffb3cd7a99f5134ad3bd8cf928af204aa2ef3f assets/lib39/models/block/doll_item/oak_sapling.json
5b80569184a739339f18a5033beed3ee4ebb0504 assets/lib39/models/block/doll_item/orange_tulip.json
e0d2b8614290b05bbf163353f698973bf32ceb25 assets/lib39/models/block/doll_item/oxeye_daisy.json
89140e446bd57faf28a24d46eda361f7145d03a9 assets/lib39/models/block/doll_item/pink_tulip.json
09ab4c411f4c3b17322dcb98446ef31732704402 assets/lib39/models/block/doll_item/poppy.json
a00c7798791d60522fb137102f5ee0306d28ea55 assets/lib39/models/block/doll_item/redstone_torch.json
e13f43e34d6233f61ebc7f7e168a220f84dbd38a assets/lib39/models/block/doll_item/red_mushroom.json
a3816cd847793723d414d9bcca8dda3194b6cf27 assets/lib39/models/block/doll_item/red_tulip.json
26ed0d78ccb1b5da9b11bdc6d5c2112b437bb51c assets/lib39/models/block/doll_item/spruce_sapling.json
d1fab112ede88c85e5488663ddec0dbcb78429e5 assets/lib39/models/block/doll_item/torch.json
5d6724324b565fa3de05a627391d02ccc9d04548 assets/lib39/models/block/doll_item/tube_coral.json
b6c57db3885cb1420f3ac59c8a29e0239e2bae1b assets/lib39/models/block/doll_item/tube_coral_fan.json
bd6a7ab81ab39d958ff5ebcf081448515e0acb9e assets/lib39/models/block/doll_item/warped_fungus.json
d9419e9991d5eca4e184b37e1a4f6416f3adf74e assets/lib39/models/block/doll_item/warped_roots_pot.json
05d7f1b2bc7f71428ed92f4e981a6be3683ee0a5 assets/lib39/models/block/doll_item/white_tulip.json
7cfe81215ed76a9d5dad392a012575f12b59edc6 assets/lib39/models/block/doll_item/wither_rose.json

View File

@ -1,2 +1,2 @@
// 1.20.1 2025-12-08T00:17:05.2366865 Languages: en_us
96726aafcb2b56b6610f7226299b4442132b8f22 assets/lib39/lang/en_us.json
// 1.20.1 2025-12-08T02:17:25.3441085 Languages: en_us
5759567e5c2f2d3410a92a9c47e8d8db63cc583d assets/lib39/lang/en_us.json

View File

@ -0,0 +1,64 @@
{
"variants": {
"facing=east,pose=default,waterlogged=false": {
"model": "lib39:block/base_doll",
"y": 180
},
"facing=east,pose=default,waterlogged=true": {
"model": "lib39:block/base_doll",
"y": 180
},
"facing=east,pose=without_item,waterlogged=false": {
"model": "lib39:block/base_doll",
"y": 180
},
"facing=east,pose=without_item,waterlogged=true": {
"model": "lib39:block/base_doll",
"y": 180
},
"facing=north,pose=default,waterlogged=false": {
"model": "lib39:block/base_doll",
"y": 90
},
"facing=north,pose=default,waterlogged=true": {
"model": "lib39:block/base_doll",
"y": 90
},
"facing=north,pose=without_item,waterlogged=false": {
"model": "lib39:block/base_doll",
"y": 90
},
"facing=north,pose=without_item,waterlogged=true": {
"model": "lib39:block/base_doll",
"y": 90
},
"facing=south,pose=default,waterlogged=false": {
"model": "lib39:block/base_doll",
"y": 270
},
"facing=south,pose=default,waterlogged=true": {
"model": "lib39:block/base_doll",
"y": 270
},
"facing=south,pose=without_item,waterlogged=false": {
"model": "lib39:block/base_doll",
"y": 270
},
"facing=south,pose=without_item,waterlogged=true": {
"model": "lib39:block/base_doll",
"y": 270
},
"facing=west,pose=default,waterlogged=false": {
"model": "lib39:block/base_doll"
},
"facing=west,pose=default,waterlogged=true": {
"model": "lib39:block/base_doll"
},
"facing=west,pose=without_item,waterlogged=false": {
"model": "lib39:block/base_doll"
},
"facing=west,pose=without_item,waterlogged=true": {
"model": "lib39:block/base_doll"
}
}
}

View File

@ -1,7 +1,28 @@
{
"commands.lib39.calculate": "Calculate sum of two numbers",
"commands.lib39.calculate.result": "%d + %d = %d",
"commands.lib39.config": "Show configuration",
"commands.lib39.debug": "Debug information",
"commands.lib39.demo": "Demo command",
"commands.lib39.demo.message": "This is a demo command showing Lib39 features!",
"commands.lib39.game": "Game control",
"commands.lib39.game.pause": "Pause current game",
"commands.lib39.game.pause.success": "Game paused!",
"commands.lib39.game.resume": "Resume paused game",
"commands.lib39.game.resume.success": "Game resumed!",
"commands.lib39.game.start": "Start a game",
"commands.lib39.game.start.success": "Game '%s' started!",
"commands.lib39.game.stop": "Stop current game",
"commands.lib39.game.stop.success": "Game stopped!",
"commands.lib39.greet.basic": "Greet everyone",
"commands.lib39.greet.default": "Hello everyone from Lib39!",
"commands.lib39.greet.player": "Greet specific player",
"commands.lib39.greet.received": "%s greeted you!",
"commands.lib39.help.basic.help": "Show Help Info",
"commands.lib39.help.click_expand": "Click to expand",
"commands.lib39.help.command_not_found": "Command not found: %s",
"commands.lib39.help.header": "===== %s =====",
"commands.lib39.help.hover.copy": "Copy to clipboard",
"commands.lib39.help.no_entries": "No help entries available",
"commands.lib39.help.node.expand": "%d subcommands collapsed",
"commands.lib39.help.node.toggle.collapse": "Collapse",
@ -9,6 +30,25 @@
"commands.lib39.help.page_info": "Page %d of %d",
"commands.lib39.help.subcommands_title": "Subcommands:",
"commands.lib39.help.toggle_failed": "Toggle Failed: No Hash Cached",
"commands.lib39.info": "Show player information",
"commands.lib39.info.dimension": "Dimension: %s",
"commands.lib39.info.message": "=== Player Information ===",
"commands.lib39.info.position": "Position: X=%.1f, Y=%.1f, Z=%.1f",
"commands.lib39.reload": "Reload configuration",
"commands.lib39.root": "Lib39 Command System",
"commands.lib39.settings": "Show settings",
"commands.lib39.team": "Team management",
"commands.lib39.team.create": "Create a new team",
"commands.lib39.team.create.success": "Team '%s' created successfully!",
"commands.lib39.team.join": "Join a team",
"commands.lib39.team.join.success": "Joined team '%s'",
"commands.lib39.team.leave": "Leave current team",
"commands.lib39.team.leave.success": "Left the team",
"commands.lib39.teleport": "Teleport to player (OP only)",
"commands.lib39.teleport.success": "Teleported to %s",
"commands.lib39.test": "Test command",
"commands.lib39.test.success": "Test command executed successfully!",
"commands.lib39.test.with_param": "Test command with parameter: %s",
"item.lib39.fabric": "Fabric",
"item.lib39.forge": "Forge",
"item.lib39.neoforge": "NeoForge"

View File

@ -1,7 +1,28 @@
{
"commands.lib39.calculate": "算二數和",
"commands.lib39.calculate.result": "%d 加 %d 等 %d",
"commands.lib39.config": "示配",
"commands.lib39.debug": "調訊",
"commands.lib39.demo": "演令",
"commands.lib39.demo.message": "此乃展 Lib39 能之演令!",
"commands.lib39.game": "戲控",
"commands.lib39.game.pause": "暫現戲",
"commands.lib39.game.pause.success": "戲已暫!",
"commands.lib39.game.resume": "復暫戲",
"commands.lib39.game.resume.success": "戲已復!",
"commands.lib39.game.start": "啟戲",
"commands.lib39.game.start.success": "戲 '%s' 已啟!",
"commands.lib39.game.stop": "止現戲",
"commands.lib39.game.stop.success": "戲已止!",
"commands.lib39.greet.basic": "問眾安",
"commands.lib39.greet.default": "自 Lib39 問安!",
"commands.lib39.greet.player": "問特者安",
"commands.lib39.greet.received": "%s 問汝安!",
"commands.lib39.help.basic.help": "示助之訊",
"commands.lib39.help.click_expand": "點展",
"commands.lib39.help.command_not_found": "令未見之: %s",
"commands.lib39.help.header": " %s ",
"commands.lib39.help.hover.copy": "點之複刻",
"commands.lib39.help.no_entries": "尚無助之目錄",
"commands.lib39.help.node.expand": "%d 子令已收",
"commands.lib39.help.node.toggle.collapse": "收",
@ -9,6 +30,25 @@
"commands.lib39.help.page_info": "第 %d 卷,凡 %d 卷",
"commands.lib39.help.subcommands_title": "子令:",
"commands.lib39.help.toggle_failed": "變更未果: 無貯存之哈希",
"commands.lib39.info": "示者訊",
"commands.lib39.info.dimension": "界:%s",
"commands.lib39.info.message": " 者訊 ",
"commands.lib39.info.position": "位X=%.1f, Y=%.1f, Z=%.1f",
"commands.lib39.reload": "重載配",
"commands.lib39.root": "Lib39 令系",
"commands.lib39.settings": "示置",
"commands.lib39.team": "隊管",
"commands.lib39.team.create": "創新隊",
"commands.lib39.team.create.success": "隊 '%s' 創新成!",
"commands.lib39.team.join": "入隊",
"commands.lib39.team.join.success": "已入隊 '%s'",
"commands.lib39.team.leave": "離現隊",
"commands.lib39.team.leave.success": "已離隊",
"commands.lib39.teleport": "送至者(唯管)",
"commands.lib39.teleport.success": "已送至 %s",
"commands.lib39.test": "試令",
"commands.lib39.test.success": "試令行成!",
"commands.lib39.test.with_param": "帶參試令:%s",
"item.lib39.fabric": "織",
"item.lib39.forge": "砧",
"item.lib39.neoforge": "狸"

View File

@ -1,7 +1,28 @@
{
"commands.lib39.calculate": "計算兩個數字的和",
"commands.lib39.calculate.result": "%d + %d = %d",
"commands.lib39.config": "顯示配置",
"commands.lib39.debug": "調試信息",
"commands.lib39.demo": "演示命令",
"commands.lib39.demo.message": "這是一個展示 Lib39 功能的演示命令!",
"commands.lib39.game": "遊戲控制",
"commands.lib39.game.pause": "暫停當前遊戲",
"commands.lib39.game.pause.success": "遊戲已暫停!",
"commands.lib39.game.resume": "恢復暫停的遊戲",
"commands.lib39.game.resume.success": "遊戲已恢復!",
"commands.lib39.game.start": "開始遊戲",
"commands.lib39.game.start.success": "遊戲 '%s' 已開始!",
"commands.lib39.game.stop": "停止當前遊戲",
"commands.lib39.game.stop.success": "遊戲已停止!",
"commands.lib39.greet.basic": "向大家問好",
"commands.lib39.greet.default": "來自 Lib39 的問候!",
"commands.lib39.greet.player": "問候特定玩家",
"commands.lib39.greet.received": "%s 向你問好!",
"commands.lib39.help.basic.help": "显示帮助信息",
"commands.lib39.help.click_expand": "點擊展開",
"commands.lib39.help.command_not_found": "命令不存在: %s",
"commands.lib39.help.header": "===== %s 命令帮助 =====",
"commands.lib39.help.hover.copy": "点击复制",
"commands.lib39.help.no_entries": "暂无帮助条目",
"commands.lib39.help.node.expand": "%d 个子命令已折叠",
"commands.lib39.help.node.toggle.collapse": "折叠",
@ -9,6 +30,25 @@
"commands.lib39.help.page_info": "第 %d 页,共 %d 页",
"commands.lib39.help.subcommands_title": "子命令:",
"commands.lib39.help.toggle_failed": "切换失败: 无缓存Hash",
"commands.lib39.info": "顯示玩家信息",
"commands.lib39.info.dimension": "維度:%s",
"commands.lib39.info.message": "=== 玩家信息 ===",
"commands.lib39.info.position": "位置X=%.1f, Y=%.1f, Z=%.1f",
"commands.lib39.reload": "重新加載配置",
"commands.lib39.root": "Lib39 命令系統",
"commands.lib39.settings": "顯示設置",
"commands.lib39.team": "隊伍管理",
"commands.lib39.team.create": "創建新隊伍",
"commands.lib39.team.create.success": "隊伍 '%s' 創建成功!",
"commands.lib39.team.join": "加入隊伍",
"commands.lib39.team.join.success": "已加入隊伍 '%s'",
"commands.lib39.team.leave": "離開當前隊伍",
"commands.lib39.team.leave.success": "已離開隊伍",
"commands.lib39.teleport": "傳送到玩家僅OP",
"commands.lib39.teleport.success": "已傳送至 %s",
"commands.lib39.test": "測試命令",
"commands.lib39.test.success": "測試命令執行成功!",
"commands.lib39.test.with_param": "帶參數的測試命令:%s",
"item.lib39.fabric": "织布",
"item.lib39.forge": "铁砧",
"item.lib39.neoforge": "小狐狸"

View File

@ -1,7 +1,28 @@
{
"commands.lib39.calculate": "計算兩個數字的和",
"commands.lib39.calculate.result": "%d + %d = %d",
"commands.lib39.config": "顯示設定",
"commands.lib39.debug": "除錯資訊",
"commands.lib39.demo": "演示指令",
"commands.lib39.demo.message": "這是一個展示 Lib39 功能的演示指令!",
"commands.lib39.game": "遊戲控制",
"commands.lib39.game.pause": "暫停當前遊戲",
"commands.lib39.game.pause.success": "遊戲已暫停!",
"commands.lib39.game.resume": "恢復暫停的遊戲",
"commands.lib39.game.resume.success": "遊戲已恢復!",
"commands.lib39.game.start": "開始遊戲",
"commands.lib39.game.start.success": "遊戲 '%s' 已開始!",
"commands.lib39.game.stop": "停止當前遊戲",
"commands.lib39.game.stop.success": "遊戲已停止!",
"commands.lib39.greet.basic": "向大家問好",
"commands.lib39.greet.default": "來自 Lib39 的問候!",
"commands.lib39.greet.player": "問候特定玩家",
"commands.lib39.greet.received": "%s 向你問好!",
"commands.lib39.help.basic.help": "顯示幫助信息",
"commands.lib39.help.click_expand": "點擊展開",
"commands.lib39.help.command_not_found": "指令不存在: %s",
"commands.lib39.help.header": "===== %s 命令幫助 =====",
"commands.lib39.help.hover.copy": "點擊複製",
"commands.lib39.help.no_entries": "暫無幫助條目",
"commands.lib39.help.node.expand": "%d 個子指令已折疊",
"commands.lib39.help.node.toggle.collapse": "折疊",
@ -9,6 +30,25 @@
"commands.lib39.help.page_info": "第 %d 頁,共 %d 頁",
"commands.lib39.help.subcommands_title": "子指令:",
"commands.lib39.help.toggle_failed": "切換失敗: 無緩存Hash",
"commands.lib39.info": "顯示玩家資訊",
"commands.lib39.info.dimension": "維度:%s",
"commands.lib39.info.message": "=== 玩家資訊 ===",
"commands.lib39.info.position": "位置X=%.1f, Y=%.1f, Z=%.1f",
"commands.lib39.reload": "重新載入設定",
"commands.lib39.root": "Lib39 指令系統",
"commands.lib39.settings": "顯示設定",
"commands.lib39.team": "隊伍管理",
"commands.lib39.team.create": "創建新隊伍",
"commands.lib39.team.create.success": "隊伍 '%s' 創建成功!",
"commands.lib39.team.join": "加入隊伍",
"commands.lib39.team.join.success": "已加入隊伍 '%s'",
"commands.lib39.team.leave": "離開當前隊伍",
"commands.lib39.team.leave.success": "已離開隊伍",
"commands.lib39.teleport": "傳送到玩家僅OP",
"commands.lib39.teleport.success": "已傳送至 %s",
"commands.lib39.test": "測試指令",
"commands.lib39.test.success": "測試指令執行成功!",
"commands.lib39.test.with_param": "帶參數的測試指令:%s",
"item.lib39.fabric": "織布",
"item.lib39.forge": "铁砧",
"item.lib39.neoforge": "狐狸"

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/acacia_sapling"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/allium"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/amethyst_cluster"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/azure_bluet"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/bamboo_stage0"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/birch_sapling"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/brain_coral"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/brain_coral_fan"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/brown_mushroom"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/bubble_coral"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/bubble_coral_fan"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/cherry_sapling"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/cobweb"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/cornflower"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/crimson_fungus"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/crimson_roots_pot"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/dandelion"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/dark_oak_sapling"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/dead_brain_coral"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/dead_brain_coral_fan"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/dead_bubble_coral"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/dead_bubble_coral_fan"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/dead_bush"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/dead_fire_coral"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/dead_fire_coral_fan"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/dead_horn_coral"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/dead_horn_coral_fan"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/dead_tube_coral"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/dead_tube_coral_fan"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/fire_coral"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/fire_coral_fan"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/horn_coral"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/horn_coral_fan"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/jungle_sapling"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/lily_of_the_valley"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/oak_sapling"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/orange_tulip"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/oxeye_daisy"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/pink_tulip"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/poppy"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/red_mushroom"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/red_tulip"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/redstone_torch"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/spruce_sapling"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/torch"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/tube_coral"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/tube_coral_fan"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/warped_fungus"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/warped_roots_pot"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/white_tulip"
}
}

View File

@ -0,0 +1,7 @@
{
"parent": "lib39:block/base_doll_item",
"ambientocclusion": false,
"textures": {
"item": "minecraft:block/wither_rose"
}
}

View File

@ -0,0 +1,3 @@
{
"parent": "lib39:block/base_doll"
}

View File

@ -42,6 +42,10 @@ public class Lib39 {
*/
public static void initialize() {
LOGGER.info("[Lib39] Initializing Lib39");
// IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
// Lib39Blocks.register(modEventBus);
// Lib39BlockEntities.register(modEventBus);
// Lib39Items.register(modEventBus);
NetworkHandler.register();
if (shouldRegisterExamples()) {
LOGGER.info("[Lib39] Registering Examples");
@ -62,6 +66,15 @@ public class Lib39 {
return new ResourceLocation(MOD_ID, path);
}
@Contract("_, _ -> new")
public static @NotNull ResourceLocation rl(String modId, String path) {
return new ResourceLocation(modId, path);
}
@Contract("_ -> new")
public static @NotNull ResourceLocation mrl(String path) {
return new ResourceLocation(path);
}
/**
* The type Mod info.
*/

View File

@ -0,0 +1,15 @@
package top.r3944realms.lib39.api.event;
import net.minecraft.server.Services;
import net.minecraftforge.eventbus.api.Event;
import java.util.concurrent.Executor;
public class MinecraftSetUpServiceEvent extends Event {
public final Services services;
public final Executor mainThreadExecutor;
public MinecraftSetUpServiceEvent(Services services, Executor mainThreadExecutor) {
this.services = services;
this.mainThreadExecutor = mainThreadExecutor;
}
}

View File

@ -6,10 +6,10 @@ import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.eventbus.api.Event;
import top.r3944realms.lib39.core.command.CommandNode;
import top.r3944realms.lib39.core.command.CommandPath;
import top.r3944realms.lib39.core.command.ICommandHelpManager;
import top.r3944realms.lib39.core.command.ParameterBuilder;
import top.r3944realms.lib39.core.command.model.CommandNode;
import top.r3944realms.lib39.core.command.model.CommandPath;
import top.r3944realms.lib39.core.command.model.Parameter;
public class RegisterCommandHelpEvent extends Event {
private final LiteralArgumentBuilder<CommandSourceStack> builder;
@ -60,7 +60,7 @@ public class RegisterCommandHelpEvent extends Event {
* @param commandPath 命令节点
* @param parametersBuilder 参数列表构造器
*/
public void registerParameters(CommandPath commandPath, ParameterBuilder parametersBuilder) {
public void registerParameters(CommandPath commandPath, Parameter.Builder parametersBuilder) {
this.helpManager.registerCommandParameters(commandPath, parametersBuilder);
}
}

View File

@ -7,9 +7,11 @@ import top.r3944realms.lib39.core.command.SimpleCommandHelpManager;
public class Lib39CommandHelpManager extends SimpleCommandHelpManager {
public static volatile Lib39CommandHelpManager INSTANCE = new Lib39CommandHelpManager();
ResourceLocation ID = Lib39.rl("command_helper");
public Lib39CommandHelpManager() {
initialize();
}
@Override
public ResourceLocation getID() {
return ID;

View File

@ -1,17 +1,396 @@
package top.r3944realms.lib39.base.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.event.RegisterCommandsEvent;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.lib39.Lib39;
import top.r3944realms.lib39.core.command.ICommandHelpManager;
import top.r3944realms.lib39.core.command.SimpleHelpCommand;
import java.util.Map;
public class Lib39HelpCommand extends SimpleHelpCommand {
public Lib39HelpCommand(@NotNull RegisterCommandsEvent event) {
super(event);
if(Lib39.shouldRegisterExamples()) {
// 在這裡註冊測試命令
registerTestCommands(event.getDispatcher());
}
}
@Override
public ICommandHelpManager getCommandHelpManager() {
return Lib39CommandHelpManager.INSTANCE;
}
/**
* 註冊測試命令
*/
private void registerTestCommands(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
// 註冊幫助系統本身
dispatcher.register(
getRoot()
.then(Commands.literal("test")
.executes(this::executeTest)
.then(Commands.argument("param", StringArgumentType.string())
.executes(this::executeTestWithParam))
)
.then(Commands.literal("demo")
.executes(this::executeDemo)
)
);
// 註冊其他測試命令
registerTestCommandTree(dispatcher);
// 在幫助系統中註冊這些命令
registerCommandsInHelpSystem();
}
/**
* 註冊測試命令樹
*/
private void registerTestCommandTree(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
// 基本命令
dispatcher.register(
Commands.literal("lib39")
.then(Commands.literal("greet")
.executes(this::executeGreet)
.then(Commands.argument("player", EntityArgument.player())
.executes(this::executeGreetPlayer))
)
.then(Commands.literal("calculate")
.then(Commands.argument("a", IntegerArgumentType.integer())
.then(Commands.argument("b", IntegerArgumentType.integer())
.executes(this::executeCalculate)))
)
.then(Commands.literal("teleport")
.requires(source -> source.hasPermission(2)) // 需要OP權限
.then(Commands.argument("target", EntityArgument.player())
.executes(this::executeTeleport))
)
.then(Commands.literal("info")
.executes(this::executeInfo)
)
);
// 嵌套命令示例
dispatcher.register(
Commands.literal("lib39")
.then(Commands.literal("team")
.then(Commands.literal("create")
.then(Commands.argument("teamName", StringArgumentType.string())
.executes(this::executeTeamCreate))
)
.then(Commands.literal("join")
.then(Commands.argument("teamName", StringArgumentType.string())
.executes(this::executeTeamJoin))
)
.then(Commands.literal("leave")
.executes(this::executeTeamLeave))
)
.then(Commands.literal("game")
.then(Commands.literal("start")
.then(Commands.argument("map", StringArgumentType.string())
.executes(this::executeGameStart))
)
.then(Commands.literal("stop")
.executes(this::executeGameStop))
.then(Commands.literal("pause")
.executes(this::executeGamePause))
.then(Commands.literal("resume")
.executes(this::executeGameResume))
)
);
}
/**
* 在幫助系統中註冊命令
*/
private void registerCommandsInHelpSystem() {
ICommandHelpManager helpManager = getCommandHelpManager();
// 使用Builder模式註冊完整的命令樹
helpManager.registerCommands(builder -> {
builder.root("lib39", "commands.lib39.root")
.expanded(true) // 根節點默認展開
// 問候命令 - 添加多個子命令
.branch("greet", "commands.lib39.greet.basic", greetBuilder -> {
greetBuilder.expanded(false); // 默認摺疊
greetBuilder.leaf("hello", "commands.lib39.greet.hello");
greetBuilder.leaf("morning", "commands.lib39.greet.morning");
greetBuilder.leaf("evening", "commands.lib39.greet.evening");
greetBuilder.push("player", "commands.lib39.greet.player")
.required("player")
.pop();
})
// 計算命令
.push("calculate", "commands.lib39.calculate")
.required("a")
.required("b")
.pop()
// 傳送命令
.push("teleport", "commands.lib39.teleport")
.required("target")
.pop()
// 信息命令
.leaf("info", "commands.lib39.info")
// 隊伍系統 - 添加多個子命令
.branch("team", "commands.lib39.team", teamBuilder -> {
teamBuilder.expanded(false); // 默認摺疊
teamBuilder.leaf("create", "commands.lib39.team.create")
.required("teamName");
teamBuilder.leaf("join", "commands.lib39.team.join")
.required("teamName");
teamBuilder.leaf("leave", "commands.lib39.team.leave");
teamBuilder.leaf("list", "commands.lib39.team.list");
teamBuilder.leaf("info", "commands.lib39.team.info");
})
// 遊戲系統 - 添加多個子命令
.branch("game", "commands.lib39.game", gameBuilder -> {
gameBuilder.expanded(false); // 默認摺疊
gameBuilder.leaf("start", "commands.lib39.game.start")
.required("map");
gameBuilder.leaf("stop", "commands.lib39.game.stop");
gameBuilder.leaf("pause", "commands.lib39.game.pause");
gameBuilder.leaf("resume", "commands.lib39.game.resume");
gameBuilder.leaf("status", "commands.lib39.game.status");
})
// 設置命令
.leavesT(Map.of(
"settings", "commands.lib39.settings",
"config", "commands.lib39.config",
"reload", "commands.lib39.reload",
"debug", "commands.lib39.debug",
"demo", "commands.lib39.demo",
"test", "commands.lib39.test"
));
});
}
// ==================== 命令執行方法 ====================
private int executeTest(CommandContext<CommandSourceStack> context) {
CommandSourceStack source = context.getSource();
source.sendSuccess(() ->
Component.translatable("commands.lib39.test.success")
.withStyle(net.minecraft.ChatFormatting.GREEN),
false
);
return Command.SINGLE_SUCCESS;
}
private int executeTestWithParam(CommandContext<CommandSourceStack> context) {
String param = StringArgumentType.getString(context, "param");
CommandSourceStack source = context.getSource();
source.sendSuccess(() ->
Component.translatable("commands.lib39.test.with_param", param)
.withStyle(net.minecraft.ChatFormatting.AQUA),
false
);
return Command.SINGLE_SUCCESS;
}
private int executeDemo(CommandContext<CommandSourceStack> context) {
CommandSourceStack source = context.getSource();
source.sendSuccess(() ->
Component.translatable("commands.lib39.demo.message")
.withStyle(net.minecraft.ChatFormatting.GOLD),
false
);
return Command.SINGLE_SUCCESS;
}
private int executeGreet(CommandContext<CommandSourceStack> context) {
CommandSourceStack source = context.getSource();
source.sendSuccess(() ->
Component.translatable("commands.lib39.greet.default")
.withStyle(net.minecraft.ChatFormatting.YELLOW),
false
);
return Command.SINGLE_SUCCESS;
}
private int executeGreetPlayer(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
ServerPlayer player = EntityArgument.getPlayer(context, "player");
CommandSourceStack source = context.getSource();
source.sendSuccess(() ->
Component.translatable("commands.lib39.greet.player", player.getDisplayName())
.withStyle(net.minecraft.ChatFormatting.GREEN),
false
);
player.sendSystemMessage(
Component.translatable("commands.lib39.greet.received", source.getDisplayName())
.withStyle(net.minecraft.ChatFormatting.AQUA)
);
return Command.SINGLE_SUCCESS;
}
private int executeCalculate(CommandContext<CommandSourceStack> context) {
int a = IntegerArgumentType.getInteger(context, "a");
int b = IntegerArgumentType.getInteger(context, "b");
int sum = a + b;
CommandSourceStack source = context.getSource();
source.sendSuccess(() ->
Component.translatable("commands.lib39.calculate.result", a, b, sum)
.withStyle(net.minecraft.ChatFormatting.LIGHT_PURPLE),
false
);
return Command.SINGLE_SUCCESS;
}
private int executeTeleport(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
ServerPlayer target = EntityArgument.getPlayer(context, "target");
CommandSourceStack source = context.getSource();
if (source.getEntity() instanceof ServerPlayer player) {
player.teleportTo(
target.serverLevel(),
target.getX(),
target.getY(),
target.getZ(),
target.getYRot(),
target.getXRot()
);
source.sendSuccess(() ->
Component.translatable("commands.lib39.teleport.success", target.getDisplayName())
.withStyle(net.minecraft.ChatFormatting.GREEN),
false
);
}
return Command.SINGLE_SUCCESS;
}
private int executeInfo(CommandContext<CommandSourceStack> context) {
CommandSourceStack source = context.getSource();
ResourceLocation dimension = source.getLevel().dimension().location();
source.sendSuccess(() ->
Component.translatable("commands.lib39.info.message")
.append("\n")
.append(Component.translatable("commands.lib39.info.dimension", dimension))
.append("\n")
.append(Component.translatable("commands.lib39.info.position",
String.format("%.1f", source.getPosition().x()),
String.format("%.1f", source.getPosition().y()),
String.format("%.1f", source.getPosition().z())))
.withStyle(net.minecraft.ChatFormatting.AQUA),
false
);
return Command.SINGLE_SUCCESS;
}
private int executeTeamCreate(CommandContext<CommandSourceStack> context) {
String teamName = StringArgumentType.getString(context, "teamName");
CommandSourceStack source = context.getSource();
source.sendSuccess(() ->
Component.translatable("commands.lib39.team.create.success", teamName)
.withStyle(net.minecraft.ChatFormatting.GREEN),
false
);
return Command.SINGLE_SUCCESS;
}
private int executeTeamJoin(CommandContext<CommandSourceStack> context) {
String teamName = StringArgumentType.getString(context, "teamName");
CommandSourceStack source = context.getSource();
source.sendSuccess(() ->
Component.translatable("commands.lib39.team.join.success", teamName)
.withStyle(net.minecraft.ChatFormatting.GREEN),
false
);
return Command.SINGLE_SUCCESS;
}
private int executeTeamLeave(CommandContext<CommandSourceStack> context) {
CommandSourceStack source = context.getSource();
source.sendSuccess(() ->
Component.translatable("commands.lib39.team.leave.success")
.withStyle(net.minecraft.ChatFormatting.YELLOW),
false
);
return Command.SINGLE_SUCCESS;
}
private int executeGameStart(CommandContext<CommandSourceStack> context) {
String map = StringArgumentType.getString(context, "map");
CommandSourceStack source = context.getSource();
source.sendSuccess(() ->
Component.translatable("commands.lib39.game.start.success", map)
.withStyle(net.minecraft.ChatFormatting.GREEN),
false
);
return Command.SINGLE_SUCCESS;
}
private int executeGameStop(CommandContext<CommandSourceStack> context) {
CommandSourceStack source = context.getSource();
source.sendSuccess(() ->
Component.translatable("commands.lib39.game.stop.success")
.withStyle(net.minecraft.ChatFormatting.RED),
false
);
return Command.SINGLE_SUCCESS;
}
private int executeGamePause(CommandContext<CommandSourceStack> context) {
CommandSourceStack source = context.getSource();
source.sendSuccess(() ->
Component.translatable("commands.lib39.game.pause.success")
.withStyle(net.minecraft.ChatFormatting.YELLOW),
false
);
return Command.SINGLE_SUCCESS;
}
private int executeGameResume(CommandContext<CommandSourceStack> context) {
CommandSourceStack source = context.getSource();
source.sendSuccess(() ->
Component.translatable("commands.lib39.game.resume.success")
.withStyle(net.minecraft.ChatFormatting.GREEN),
false
);
return Command.SINGLE_SUCCESS;
}
}

View File

@ -6,7 +6,9 @@ import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.r3944realms.lib39.Lib39;
import top.r3944realms.lib39.base.datagen.provider.ExItemModelProvider;
import top.r3944realms.lib39.base.datagen.provider.Lib39BlockModelProvider;
import top.r3944realms.lib39.base.datagen.provider.Lib39BlockStatesProvider;
import top.r3944realms.lib39.base.datagen.provider.Lib39ItemModelProvider;
import top.r3944realms.lib39.base.datagen.value.Lib39LangKey;
import top.r3944realms.lib39.datagen.provider.SimpleLanguageProvider;
import top.r3944realms.lib39.datagen.value.McLocale;
@ -28,9 +30,9 @@ public class Lib39BaseDataGenEvent {
LanguageGenerator(event, McLocale.ZH_CN);
LanguageGenerator(event, McLocale.ZH_TW);
LanguageGenerator(event, McLocale.LZH);
if (Lib39.shouldRegisterExamples()) {
ModelDataGenerate(event);
}
BlockModelDataGenerate(event);
BlockStateDataGenerate(event);
ItemModelDataGenerate(event);
}
private static void LanguageGenerator(@NotNull GatherDataEvent event, McLocale language) {
event.getGenerator().addProvider(
@ -38,10 +40,22 @@ public class Lib39BaseDataGenEvent {
(DataProvider.Factory<SimpleLanguageProvider>) pOutput -> new SimpleLanguageProvider(pOutput, Lib39.MOD_ID ,language , Lib39LangKey.INSTANCE)
);
}
private static void ModelDataGenerate(@NotNull GatherDataEvent event) {
private static void ItemModelDataGenerate(@NotNull GatherDataEvent event) {
event.getGenerator().addProvider(
event.includeClient(),
(DataProvider.Factory<ExItemModelProvider>) pOutput -> new ExItemModelProvider(pOutput, event.getExistingFileHelper())
(DataProvider.Factory<Lib39ItemModelProvider>) pOutput -> new Lib39ItemModelProvider(pOutput, event.getExistingFileHelper())
);
}
private static void BlockModelDataGenerate(@NotNull GatherDataEvent event) {
event.getGenerator().addProvider(
event.includeClient(),
(DataProvider.Factory<Lib39BlockModelProvider>) pOutput -> new Lib39BlockModelProvider(pOutput, event.getExistingFileHelper())
);
}
private static void BlockStateDataGenerate(@NotNull GatherDataEvent event) {
event.getGenerator().addProvider(
event.includeClient(),
(DataProvider.Factory<Lib39BlockStatesProvider>) pOutput -> new Lib39BlockStatesProvider(pOutput, event.getExistingFileHelper())
);
}
}

View File

@ -0,0 +1,31 @@
package top.r3944realms.lib39.base.datagen.provider;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.model.generators.BlockModelProvider;
import net.minecraftforge.common.data.ExistingFileHelper;
import top.r3944realms.lib39.Lib39;
import top.r3944realms.lib39.util.PlantHelper;
public class Lib39BlockModelProvider extends BlockModelProvider {
public Lib39BlockModelProvider(PackOutput output, ExistingFileHelper existingFileHelper) {
super(output, Lib39.MOD_ID, existingFileHelper);
}
@Override
protected void registerModels() {
// registerPlants();
}
protected void registerPlants() {
for (PlantHelper.Plant plant: PlantHelper.Plant.values()) {
createCuffBedHeadModel(plant);
}
}
private void createCuffBedHeadModel(PlantHelper.Plant plant) {
ResourceLocation rl = PlantHelper.getTextureRL(plant);
getBuilder("block/doll_item/" + plant)
.parent(getExistingFile(Lib39.rl("block/base_doll_item")))
.texture("item", rl)
.ao(false);
}
}

View File

@ -0,0 +1,65 @@
package top.r3944realms.lib39.base.datagen.provider;
import net.minecraft.core.Direction;
import net.minecraft.data.PackOutput;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraftforge.client.model.generators.BlockStateProvider;
import net.minecraftforge.client.model.generators.ConfiguredModel;
import net.minecraftforge.client.model.generators.ModelFile;
import net.minecraftforge.common.data.ExistingFileHelper;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.lib39.Lib39;
import top.r3944realms.lib39.content.register.Lib39Blocks;
public class Lib39BlockStatesProvider extends BlockStateProvider {
public Lib39BlockStatesProvider(PackOutput output, ExistingFileHelper exFileHelper) {
super(output, Lib39.MOD_ID, exFileHelper);
}
@Override
protected void registerStatesAndModels() {
// generateDollBlockStatesSimple();
}
private void generateDollBlockStatesSimple() {
Block doll = Lib39Blocks.DOLL.get();
// 创建GeckoLib模型引用
ModelFile modelFile = new ModelFile.ExistingModelFile(
Lib39.rl( "block/base_doll"),
models().existingFileHelper
);
getVariantBuilder(doll).forAllStates(state -> {
Direction direction = state.getValue(BlockStateProperties.HORIZONTAL_FACING);
int rotationY = getMainWestRotationY(direction);
return ConfiguredModel.builder()
.modelFile(modelFile)
.rotationY(rotationY)
.build();
});
}
@Contract(pure = true)
private int getMainWestRotationY(@NotNull Direction direction) {
return switch (direction) {
case WEST -> 0; // 西 - 基准方向0度
case NORTH -> 90; // - 相对于西旋转90度
case EAST -> 180; // - 相对于西旋转180度
case SOUTH -> 270; // - 相对于西旋转270度
default -> 0;
};
}
@Contract(pure = true)
private int getMainNorthRotationY(@NotNull Direction direction) {
return switch (direction) {
case WEST -> 270; // 西 - 基准方向270度
case NORTH -> 0; // - 相对于西旋转0度
case EAST -> 90; // - 相对于西旋转90度
case SOUTH -> 180; // - 相对于西旋转180度
default -> 0;
};
}
}

View File

@ -18,11 +18,9 @@ package top.r3944realms.lib39.base.datagen.provider;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraftforge.client.model.generators.ItemModelProvider;
import net.minecraftforge.common.data.ExistingFileHelper;
import net.minecraftforge.registries.ForgeRegistries;
import top.r3944realms.lib39.Lib39;
import top.r3944realms.lib39.base.datagen.Lib39BaseDataGenEvent;
import top.r3944realms.lib39.base.datagen.value.Lib39LangKey;
import top.r3944realms.lib39.datagen.value.LangKeyValue;
import top.r3944realms.lib39.datagen.value.ModPartEnum;
@ -32,9 +30,9 @@ import java.util.List;
import java.util.Objects;
/**
* The type Slp item model provider.
* The type item model provider.
*/
public class ExItemModelProvider extends ItemModelProvider {
public class Lib39ItemModelProvider extends net.minecraftforge.client.model.generators.ItemModelProvider {
private static List<Item> objectList;
/**
* The constant GENERATED.
@ -46,12 +44,12 @@ public class ExItemModelProvider extends ItemModelProvider {
public static final String HANDHELD = "item/handheld";
/**
* Instantiates a new Slp item model provider.
* Instantiates a new item model provider.
*
* @param output the output
* @param existingFileHelper the existing file helper
*/
public ExItemModelProvider(PackOutput output, ExistingFileHelper existingFileHelper) {
public Lib39ItemModelProvider(PackOutput output, ExistingFileHelper existingFileHelper) {
super(output, Lib39.MOD_ID, existingFileHelper);
objectList = new ArrayList<>();
init();
@ -59,7 +57,8 @@ public class ExItemModelProvider extends ItemModelProvider {
@Override
protected void registerModels() {
DefaultModItemModelRegister();
defaultModItemModelRegister();
// generateDollItemModel();
}
private void init() {
for(LangKeyValue obj : Lib39LangKey.INSTANCE.getValues()) {
@ -70,7 +69,7 @@ public class ExItemModelProvider extends ItemModelProvider {
/**
* @implNote <br/>&nbsp;先有纹理才会成功构建
*/
private void DefaultModItemModelRegister() {
private void defaultModItemModelRegister() {
objectList.forEach(this::basicItem);
}
@ -94,6 +93,11 @@ public class ExItemModelProvider extends ItemModelProvider {
withExistingParent(itemName(item), HANDHELD).texture("layer0", location);
}
protected void generateDollItemModel() {
getBuilder("doll")
.parent(getExistingFile(Lib39.rl("block/base_doll")));
}
/**
* Item name string.
*

View File

@ -31,6 +31,17 @@ public enum Lib39LangKey implements ILangKeyValueCollection {
)
);
public static final LangKeyValue HELP_CLICK_EXPAND = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.help.click_expand", ModPartEnum.MESSAGE,
"Click to expand",
"點擊展開",
"點擊展開",
"點展",
true
)
);
public static final LangKeyValue HELP_PAGE_INFO = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.help.page_info", ModPartEnum.MESSAGE,
@ -129,6 +140,17 @@ public enum Lib39LangKey implements ILangKeyValueCollection {
true
)
);
public static final LangKeyValue HELP_HOVER_COPY_TIP = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.help.hover.copy", ModPartEnum.MESSAGE,
"Copy to clipboard",
"点击复制",
"點擊複製",
"點之複刻", // 文言文點之複刻
true
)
);
private static LangKeyValue addAndRet(LangKeyValue item) {
items.add(item);
return item;
@ -161,6 +183,7 @@ public enum Lib39LangKey implements ILangKeyValueCollection {
ExLib39Items.FORGE, ModPartEnum.ITEM,
"Forge", "铁砧", "铁砧", "", true
));
TestMessage.getItems().forEach(this::addLang);
}
}
@ -187,5 +210,450 @@ public enum Lib39LangKey implements ILangKeyValueCollection {
public @Unmodifiable List<LangKeyValue> getValues() {
return List.copyOf(langKeyValues);
}
@SuppressWarnings("unused")
public static final class TestMessage {
private static final Set<LangKeyValue> items = new HashSet<>();
// ===== lib39 測試命令翻譯 =====
// 根命令
public static final LangKeyValue LIB39_ROOT = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.root", ModPartEnum.MESSAGE,
"Lib39 Command System",
"Lib39 命令系統",
"Lib39 指令系統",
"Lib39 令系",
true
)
);
// 測試命令
public static final LangKeyValue LIB39_TEST = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.test", ModPartEnum.MESSAGE,
"Test command",
"測試命令",
"測試指令",
"試令",
true
)
);
public static final LangKeyValue LIB39_TEST_SUCCESS = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.test.success", ModPartEnum.MESSAGE,
"Test command executed successfully!",
"測試命令執行成功!",
"測試指令執行成功!",
"試令行成!",
true
)
);
public static final LangKeyValue LIB39_TEST_WITH_PARAM = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.test.with_param", ModPartEnum.MESSAGE,
"Test command with parameter: %s",
"帶參數的測試命令:%s",
"帶參數的測試指令:%s",
"帶參試令:%s",
true
)
);
public static final LangKeyValue LIB39_DEMO = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.demo", ModPartEnum.MESSAGE,
"Demo command",
"演示命令",
"演示指令",
"演令",
true
)
);
public static final LangKeyValue LIB39_DEMO_MESSAGE = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.demo.message", ModPartEnum.MESSAGE,
"This is a demo command showing Lib39 features!",
"這是一個展示 Lib39 功能的演示命令!",
"這是一個展示 Lib39 功能的演示指令!",
"此乃展 Lib39 能之演令!",
true
)
);
// 問候命令
public static final LangKeyValue LIB39_GREET_BASIC = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.greet.basic", ModPartEnum.MESSAGE,
"Greet everyone",
"向大家問好",
"向大家問好",
"問眾安",
true
)
);
public static final LangKeyValue LIB39_GREET_DEFAULT = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.greet.default", ModPartEnum.MESSAGE,
"Hello everyone from Lib39!",
"來自 Lib39 的問候!",
"來自 Lib39 的問候!",
"自 Lib39 問安!",
true
)
);
public static final LangKeyValue LIB39_GREET_PLAYER = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.greet.player", ModPartEnum.MESSAGE,
"Greet specific player",
"問候特定玩家",
"問候特定玩家",
"問特者安",
true
)
);
public static final LangKeyValue LIB39_GREET_RECEIVED = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.greet.received", ModPartEnum.MESSAGE,
"%s greeted you!",
"%s 向你問好!",
"%s 向你問好!",
"%s 問汝安!",
true
)
);
// 計算命令
public static final LangKeyValue LIB39_CALCULATE = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.calculate", ModPartEnum.MESSAGE,
"Calculate sum of two numbers",
"計算兩個數字的和",
"計算兩個數字的和",
"算二數和",
true
)
);
public static final LangKeyValue LIB39_CALCULATE_RESULT = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.calculate.result", ModPartEnum.MESSAGE,
"%d + %d = %d",
"%d + %d = %d",
"%d + %d = %d",
"%d 加 %d 等 %d",
true
)
);
// 傳送命令
public static final LangKeyValue LIB39_TELEPORT = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.teleport", ModPartEnum.MESSAGE,
"Teleport to player (OP only)",
"傳送到玩家僅OP",
"傳送到玩家僅OP",
"送至者(唯管)",
true
)
);
public static final LangKeyValue LIB39_TELEPORT_SUCCESS = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.teleport.success", ModPartEnum.MESSAGE,
"Teleported to %s",
"已傳送至 %s",
"已傳送至 %s",
"已送至 %s",
true
)
);
// 信息命令
public static final LangKeyValue LIB39_INFO = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.info", ModPartEnum.MESSAGE,
"Show player information",
"顯示玩家信息",
"顯示玩家資訊",
"示者訊",
true
)
);
public static final LangKeyValue LIB39_INFO_MESSAGE = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.info.message", ModPartEnum.MESSAGE,
"=== Player Information ===",
"=== 玩家信息 ===",
"=== 玩家資訊 ===",
" 者訊 ",
true
)
);
public static final LangKeyValue LIB39_INFO_DIMENSION = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.info.dimension", ModPartEnum.MESSAGE,
"Dimension: %s",
"維度:%s",
"維度:%s",
"界:%s",
true
)
);
public static final LangKeyValue LIB39_INFO_POSITION = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.info.position", ModPartEnum.MESSAGE,
"Position: X=%.1f, Y=%.1f, Z=%.1f",
"位置X=%.1f, Y=%.1f, Z=%.1f",
"位置X=%.1f, Y=%.1f, Z=%.1f",
"X=%.1f, Y=%.1f, Z=%.1f",
true
)
);
// 隊伍系統
public static final LangKeyValue LIB39_TEAM = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.team", ModPartEnum.MESSAGE,
"Team management",
"隊伍管理",
"隊伍管理",
"隊管",
true
)
);
public static final LangKeyValue LIB39_TEAM_CREATE = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.team.create", ModPartEnum.MESSAGE,
"Create a new team",
"創建新隊伍",
"創建新隊伍",
"創新隊",
true
)
);
public static final LangKeyValue LIB39_TEAM_CREATE_SUCCESS = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.team.create.success", ModPartEnum.MESSAGE,
"Team '%s' created successfully!",
"隊伍 '%s' 創建成功!",
"隊伍 '%s' 創建成功!",
"隊 '%s' 創新成!",
true
)
);
public static final LangKeyValue LIB39_TEAM_JOIN = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.team.join", ModPartEnum.MESSAGE,
"Join a team",
"加入隊伍",
"加入隊伍",
"入隊",
true
)
);
public static final LangKeyValue LIB39_TEAM_JOIN_SUCCESS = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.team.join.success", ModPartEnum.MESSAGE,
"Joined team '%s'",
"已加入隊伍 '%s'",
"已加入隊伍 '%s'",
"已入隊 '%s'",
true
)
);
public static final LangKeyValue LIB39_TEAM_LEAVE = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.team.leave", ModPartEnum.MESSAGE,
"Leave current team",
"離開當前隊伍",
"離開當前隊伍",
"離現隊",
true
)
);
public static final LangKeyValue LIB39_TEAM_LEAVE_SUCCESS = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.team.leave.success", ModPartEnum.MESSAGE,
"Left the team",
"已離開隊伍",
"已離開隊伍",
"已離隊",
true
)
);
// 遊戲系統
public static final LangKeyValue LIB39_GAME = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.game", ModPartEnum.MESSAGE,
"Game control",
"遊戲控制",
"遊戲控制",
"戲控",
true
)
);
public static final LangKeyValue LIB39_GAME_START = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.game.start", ModPartEnum.MESSAGE,
"Start a game",
"開始遊戲",
"開始遊戲",
"啟戲",
true
)
);
public static final LangKeyValue LIB39_GAME_START_SUCCESS = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.game.start.success", ModPartEnum.MESSAGE,
"Game '%s' started!",
"遊戲 '%s' 已開始!",
"遊戲 '%s' 已開始!",
"戲 '%s' 已啟!",
true
)
);
public static final LangKeyValue LIB39_GAME_STOP = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.game.stop", ModPartEnum.MESSAGE,
"Stop current game",
"停止當前遊戲",
"停止當前遊戲",
"止現戲",
true
)
);
public static final LangKeyValue LIB39_GAME_STOP_SUCCESS = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.game.stop.success", ModPartEnum.MESSAGE,
"Game stopped!",
"遊戲已停止!",
"遊戲已停止!",
"戲已止!",
true
)
);
public static final LangKeyValue LIB39_GAME_PAUSE = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.game.pause", ModPartEnum.MESSAGE,
"Pause current game",
"暫停當前遊戲",
"暫停當前遊戲",
"暫現戲",
true
)
);
public static final LangKeyValue LIB39_GAME_PAUSE_SUCCESS = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.game.pause.success", ModPartEnum.MESSAGE,
"Game paused!",
"遊戲已暫停!",
"遊戲已暫停!",
"戲已暫!",
true
)
);
public static final LangKeyValue LIB39_GAME_RESUME = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.game.resume", ModPartEnum.MESSAGE,
"Resume paused game",
"恢復暫停的遊戲",
"恢復暫停的遊戲",
"復暫戲",
true
)
);
public static final LangKeyValue LIB39_GAME_RESUME_SUCCESS = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.game.resume.success", ModPartEnum.MESSAGE,
"Game resumed!",
"遊戲已恢復!",
"遊戲已恢復!",
"戲已復!",
true
)
);
// 設置命令
public static final LangKeyValue LIB39_SETTINGS = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.settings", ModPartEnum.MESSAGE,
"Show settings",
"顯示設置",
"顯示設定",
"示置",
true
)
);
public static final LangKeyValue LIB39_CONFIG = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.config", ModPartEnum.MESSAGE,
"Show configuration",
"顯示配置",
"顯示設定",
"示配",
true
)
);
public static final LangKeyValue LIB39_RELOAD = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.reload", ModPartEnum.MESSAGE,
"Reload configuration",
"重新加載配置",
"重新載入設定",
"重載配",
true
)
);
public static final LangKeyValue LIB39_DEBUG = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.debug", ModPartEnum.MESSAGE,
"Debug information",
"調試信息",
"除錯資訊",
"調訊",
true
)
);
// ===== 添加缺失的導入 =====
private static final java.util.function.Consumer<LangKeyValue> addConsumer = items::add;
private static LangKeyValue addAndRet(LangKeyValue item) {
items.add(item);
return item;
}
public static Set<LangKeyValue> getItems() {
return items;
}
}
}

View File

@ -0,0 +1,79 @@
package top.r3944realms.lib39.client.renderer.block;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource;
import net.minecraftforge.client.model.data.ModelData;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.lib39.Lib39;
import top.r3944realms.lib39.client.renderer.item.DollItemRenderer;
import top.r3944realms.lib39.content.block.blockentity.DollBlockEntity;
import top.r3944realms.lib39.util.PlantHelper;
import top.r3944realms.lib39.util.SkinHelper;
import java.util.HashMap;
import java.util.Map;
public class DollBlockEntityRenderer implements BlockEntityRenderer<DollBlockEntity> {
public BakedModel dollWithoutItemModel;
public BakedModel dollNeedItemModel;
public final Map<PlantHelper.Plant, BakedModel> itemModels = new HashMap<>();
@Override
public void render(@NotNull DollBlockEntity blockEntity, float partialTick, @NotNull PoseStack poseStack,
@NotNull MultiBufferSource buffer, int packedLight, int packedOverlay) {
Minecraft minecraft = Minecraft.getInstance();
ModelManager modelManager = minecraft.getModelManager();
if (dollNeedItemModel == null) {
dollNeedItemModel = modelManager.getModel(DollItemRenderer.DOLL_WITHOUT_ITEM_MODEL);
}
if (dollWithoutItemModel == null) {
dollWithoutItemModel = modelManager.getModel(DollItemRenderer.DOLL_NEED_ITEM_MODEL);
}
PlantHelper.Plant holdItem = blockEntity.getHoldItem();
renderModel(blockEntity, poseStack, buffer, packedLight, packedOverlay, holdItem != null ? dollNeedItemModel : dollWithoutItemModel, SkinHelper.getSkinTexture(blockEntity.getOwnerProfile()));
if (holdItem != null) {
BakedModel itemModel = itemModels.get(holdItem);
if (itemModel == null) {
BakedModel model = modelManager.getModel(Lib39.rl( "block/doll_item/" + holdItem));
itemModels.put(holdItem, model);
itemModel = model;
}
renderModel(blockEntity, poseStack, buffer, packedLight, packedOverlay, itemModel, PlantHelper.getTextureRL(holdItem));
}
}
private void renderModel(@NotNull DollBlockEntity blockEntity, @NotNull PoseStack poseStack, @NotNull MultiBufferSource buffer,
int packedLight, int packedOverlay,
BakedModel model, ResourceLocation texture) {
poseStack.pushPose();
poseStack.translate(0.5, 0.5, 0.5);
Minecraft minecraft = Minecraft.getInstance();
RandomSource rand = RandomSource.create();
ModelData data = ModelData.EMPTY;
for (RenderType rt : model.getRenderTypes(blockEntity.getBlockState(), rand, data)) {
VertexConsumer vc = buffer.getBuffer(rt);
// 渲染所有面
minecraft.getBlockRenderer().getModelRenderer().renderModel(
poseStack.last(), vc, null, model,
1.0f, 1.0f, 1.0f,
packedLight, packedOverlay,
data, rt
);
}
poseStack.popPose();
}
}

View File

@ -0,0 +1,422 @@
package top.r3944realms.lib39.client.renderer.item;
import com.mojang.authlib.GameProfile;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite;
import net.minecraft.client.renderer.texture.SimpleTexture;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.resources.DefaultPlayerSkin;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.client.model.data.ModelData;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import top.r3944realms.lib39.Lib39;
import top.r3944realms.lib39.util.PlantHelper;
import top.r3944realms.lib39.util.SkinHelper;
import top.r3944realms.lib39.util.nbt.NBTReader;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
@OnlyIn(Dist.CLIENT)
public class DollItemRenderer extends BlockEntityWithoutLevelRenderer {
private static final DollItemRenderer INSTANCE = new DollItemRenderer();
public static final String BETag = "BlockEntityTag";
// 模型缓存系统
private static class ModelCacheEntry {
private final ResourceLocation modelLocation;
private BakedModel model;
private long lastAccessTime;
private boolean isMissingModel;
ModelCacheEntry(ResourceLocation modelLocation) {
this.modelLocation = modelLocation;
this.lastAccessTime = System.currentTimeMillis();
this.isMissingModel = false;
}
BakedModel getModel() {
if (model == null && !isMissingModel) {
loadModel();
}
this.lastAccessTime = System.currentTimeMillis();
return model;
}
private void loadModel() {
Minecraft minecraft = Minecraft.getInstance();
ModelManager modelManager = minecraft.getModelManager();
this.model = modelManager.getModel(modelLocation);
// 检查是否是缺失模型
BakedModel missingModel = modelManager.getMissingModel();
this.isMissingModel = this.model == missingModel;
if (isMissingModel) {
Lib39.LOGGER.warn("Missing model: {}", modelLocation);
}
}
boolean shouldRefresh() {
return model == null || (!isMissingModel && System.currentTimeMillis() - lastAccessTime > CACHE_DURATION);
}
}
// 缓存管理器
private static class ModelCacheManager {
private final Map<ResourceLocation, ModelCacheEntry> cache = new ConcurrentHashMap<>();
private final Set<ResourceLocation> pendingReloads = Collections.newSetFromMap(new ConcurrentHashMap<>());
private static final long CLEANUP_INTERVAL = 30000; // 30秒清理一次
private long lastCleanupTime = 0;
BakedModel getModel(ResourceLocation location) {
ModelCacheEntry entry = cache.computeIfAbsent(location, ModelCacheEntry::new);
// 定期检查是否需要刷新
if (entry.shouldRefresh() && !pendingReloads.contains(location)) {
scheduleModelReload(location);
}
return entry.getModel();
}
private void scheduleModelReload(ResourceLocation location) {
pendingReloads.add(location);
// 异步重新加载模型
CompletableFuture.runAsync(() -> {
try {
ModelCacheEntry entry = cache.get(location);
if (entry != null) {
entry.loadModel();
}
} finally {
pendingReloads.remove(location);
}
}, Util.backgroundExecutor());
}
void clear() {
cache.clear();
pendingReloads.clear();
}
void cleanup() {
long now = System.currentTimeMillis();
if (now - lastCleanupTime > CLEANUP_INTERVAL) {
cache.entrySet().removeIf(entry ->
entry.getValue().model == null ||
(entry.getValue().isMissingModel &&
now - entry.getValue().lastAccessTime > CACHE_DURATION * 2)
);
lastCleanupTime = now;
}
}
}
// 纹理缓存系统
private static class TextureCacheEntry {
private final ResourceLocation textureLocation;
private ResourceLocation registeredLocation;
private long lastAccessTime;
TextureCacheEntry(ResourceLocation textureLocation) {
this.textureLocation = textureLocation;
this.lastAccessTime = System.currentTimeMillis();
}
ResourceLocation getTexture() {
if (registeredLocation == null) {
registerTexture();
}
this.lastAccessTime = System.currentTimeMillis();
return registeredLocation;
}
private void registerTexture() {
Minecraft minecraft = Minecraft.getInstance();
TextureManager textureManager = minecraft.getTextureManager();
// 检查纹理是否已加载
textureManager.getTexture(textureLocation, MissingTextureAtlasSprite.getTexture());
registeredLocation = textureLocation;
}
private void loadTextureAsync() {
CompletableFuture.runAsync(() -> {
try {
Minecraft minecraft = Minecraft.getInstance();
TextureManager textureManager = minecraft.getTextureManager();
// 加载纹理
textureManager.register(textureLocation,
new SimpleTexture(textureLocation));
registeredLocation = textureLocation;
} catch (Exception e) {
Lib39.LOGGER.error("Failed to load texture: {}", textureLocation, e);
}
});
}
boolean shouldRefresh() {
return registeredLocation == null ||
System.currentTimeMillis() - lastAccessTime > CACHE_DURATION;
}
}
// 常量定义
private static final long CACHE_DURATION = 5000; // 5秒
private static final long ITEM_TEXTURE_CACHE_DURATION = 30000; // 30秒材质不常变
public static final ResourceLocation DOLL_NEED_ITEM_MODEL = Lib39.rl("block/doll_default");
public static final ResourceLocation DOLL_WITHOUT_ITEM_MODEL = Lib39.rl("block/doll_without_item");
public static final ResourceLocation DOLL_ITEM_MODEL = Lib39.rl("block/base_doll_item");
public static final Map<PlantHelper.Plant, ResourceLocation> ITEM_MODELS = new HashMap<>();
static {
initItemModels();
}
public static void initItemModels() {
for (PlantHelper.Plant plant : PlantHelper.Plant.values()) {
ITEM_MODELS.put(plant, Lib39.rl("block/doll_item/" + plant));
}
}
// 缓存实例
private final ModelCacheManager modelCache = new ModelCacheManager();
private final Map<String, TextureCacheEntry> itemTextureCache = new ConcurrentHashMap<>();
private final Map<UUID, TextureCacheEntry> skinTextureCache = new ConcurrentHashMap<>();
private final TextureCacheEntry defaultSkinTexture;
// 花材质映射静态初始化
private static final Map<String, ResourceLocation> ITEM_TEXTURE_MAP = createItemTextureMap();
private static @Unmodifiable Map<String, ResourceLocation> createItemTextureMap() {
Map<String, ResourceLocation> map = new HashMap<>();
for (PlantHelper.Plant value : PlantHelper.Plant.values()) {
map.put(value.toString(), new ResourceLocation(value.name));
}
return Map.copyOf(map);
}
private DollItemRenderer() {
super(Minecraft.getInstance().getBlockEntityRenderDispatcher(),
Minecraft.getInstance().getEntityModels());
// 初始化默认皮肤纹理
this.defaultSkinTexture = new TextureCacheEntry(
DefaultPlayerSkin.getDefaultSkin()
);
}
public static DollItemRenderer getInstance() {
return INSTANCE;
}
@Override
public void renderByItem(@NotNull ItemStack stack, @NotNull ItemDisplayContext displayContext,
@NotNull PoseStack poseStack, @NotNull MultiBufferSource buffer,
int packedLight, int packedOverlay) {
// 1. 从NBT获取数据
DollRenderData renderData = extractRenderData(stack);
// 2. 执行渲染
renderDoll(renderData, displayContext, poseStack, buffer, packedLight, packedOverlay);
// 3. 定期清理缓存
modelCache.cleanup();
cleanupTextureCache();
}
// 提取渲染数据
@Contract("_ -> new")
private @NotNull DollRenderData extractRenderData(@NotNull ItemStack stack) {
AtomicReference<String> itemType = new AtomicReference<>();
AtomicReference<GameProfile> gameProfile = new AtomicReference<>();
CompoundTag tag = stack.getTag();
if (tag != null) {
NBTReader.of(tag)
.compound(BETag, compoundTag -> NBTReader.of(compoundTag)
.string("ItemType", itemType::set)
.compound("ProfileOwner", profile -> gameProfile.set(NbtUtils.readGameProfile(profile)))
);
}
return new DollRenderData(itemType.get(), gameProfile.get());
}
private void renderDoll(@NotNull DollRenderData data, ItemDisplayContext displayContext,
@NotNull PoseStack poseStack, MultiBufferSource buffer,
int packedLight, int packedOverlay) {
// 总是渲染基础人偶模型
BakedModel baseModel = data.hasItem() ? getCachedModel(DOLL_NEED_ITEM_MODEL) : getCachedModel(DOLL_WITHOUT_ITEM_MODEL);
if (baseModel == null || baseModel == Minecraft.getInstance().getModelManager().getMissingModel()) {
return; // 模型不存在不渲染
}
// 获取纹理
ResourceLocation skinTexture = getSkinTexture(data.gameProfile);
// 开始渲染
poseStack.pushPose();
try {
// 应用物品变换
applyItemTransforms(displayContext, poseStack);
// 渲染基础模型皮肤部分
renderModelWithTexture(baseModel, skinTexture, poseStack, buffer,
packedLight, packedOverlay);
// 如果有手持物品渲染物品部分
if (data.hasItem()) {
renderHeldItem(data, poseStack, buffer, packedLight, packedOverlay);
}
} finally {
poseStack.popPose();
}
}
private void renderHeldItem(DollRenderData data, PoseStack poseStack,
MultiBufferSource buffer, int packedLight, int packedOverlay) {
BakedModel itemModel = getCachedModel(ITEM_MODELS.get(PlantHelper.Plant.valueOf(data.itemType)));
if (itemModel == null || itemModel == Minecraft.getInstance().getModelManager().getMissingModel()) {
return;
}
ResourceLocation itemTexture = getItemTexture(data.itemType);
if (itemTexture == null) return;
poseStack.pushPose();
try {
renderModelWithTexture(itemModel, itemTexture, poseStack, buffer,
packedLight, packedOverlay);
} finally {
poseStack.popPose();
}
}
// 获取缓存的模型
private BakedModel getCachedModel(ResourceLocation modelLoc) {
return modelCache.getModel(modelLoc);
}
// 获取皮肤纹理带缓存
public ResourceLocation getSkinTexture(@Nullable GameProfile gameProfile) {
if (gameProfile == null) {
return defaultSkinTexture.getTexture();
}
// 优先使用UUID
UUID cacheKey = gameProfile.getId();
TextureCacheEntry entry = skinTextureCache.get(cacheKey);
if (entry == null || entry.shouldRefresh()) {
ResourceLocation textureLocation = SkinHelper.resolveSkinTexture(gameProfile);
entry = new TextureCacheEntry(textureLocation);
skinTextureCache.put(cacheKey, entry);
}
return entry.getTexture();
}
// 获取花纹理带缓存
private ResourceLocation getItemTexture(String itemType) {
ResourceLocation textureLocation = ITEM_TEXTURE_MAP.getOrDefault(itemType,
PlantHelper.getTextureRL(PlantHelper.Plant.ALLIUM));
TextureCacheEntry entry = itemTextureCache.get(itemType);
if (entry == null || entry.shouldRefresh()) {
entry = new TextureCacheEntry(textureLocation);
itemTextureCache.put(itemType, entry);
}
return entry.getTexture();
}
// 应用物品变换
@SuppressWarnings("deprecation")
private void applyItemTransforms(ItemDisplayContext displayContext, PoseStack poseStack) {
BakedModel baseModel = getCachedModel(DOLL_WITHOUT_ITEM_MODEL);
if (baseModel != null) {
baseModel.getTransforms().getTransform(displayContext)
.apply(false, poseStack);
}
poseStack.translate(-0.5, -0.5, -0.5);
}
// 渲染模型带纹理
private void renderModelWithTexture(BakedModel model, ResourceLocation texture,
@NotNull PoseStack poseStack, @NotNull MultiBufferSource buffer,
int packedLight, int packedOverlay) {
Minecraft minecraft = Minecraft.getInstance();
RenderType renderType = RenderType.entityCutout(texture);
VertexConsumer vertexConsumer = buffer.getBuffer(renderType);
minecraft.getBlockRenderer().getModelRenderer().renderModel(
poseStack.last(),
vertexConsumer,
null,
model,
1.0f, 1.0f, 1.0f,
packedLight,
packedOverlay,
ModelData.EMPTY,
renderType
);
}
// 清理纹理缓存
private void cleanupTextureCache() {
long now = System.currentTimeMillis();
// 清理皮肤缓存超过2分钟未访问
skinTextureCache.entrySet().removeIf(entry ->
now - entry.getValue().lastAccessTime > 120000
);
// 清理花纹理缓存超过5分钟未访问
itemTextureCache.entrySet().removeIf(entry ->
now - entry.getValue().lastAccessTime > ITEM_TEXTURE_CACHE_DURATION
);
}
// 清除所有缓存用于资源包重载
public void clearAllCaches() {
modelCache.clear();
skinTextureCache.clear();
itemTextureCache.clear();
}
// 渲染数据持有类
private record DollRenderData(String itemType, GameProfile gameProfile) {
public boolean hasItem() {
return itemType != null;
}
}
@Override
public void onResourceManagerReload(@NotNull ResourceManager resourceManager) {
super.onResourceManagerReload(resourceManager);
clearAllCaches();
}
}

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