diff --git a/.gitignore b/.gitignore index 31d2550..5c8ba59 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ bin .metadata .classpath .project +cpp/cmake-build-debug # idea out diff --git a/build.gradle b/build.gradle index e898c74..33ba39c 100644 --- a/build.gradle +++ b/build.gradle @@ -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 diff --git a/config/jni-classes.txt b/config/jni-classes.txt new file mode 100644 index 0000000..fc4ab0d --- /dev/null +++ b/config/jni-classes.txt @@ -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.* diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt new file mode 100644 index 0000000..30c97d4 --- /dev/null +++ b/cpp/CMakeLists.txt @@ -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() + # 尝试通过which或where命令查找java + if(WIN32) + execute_process( + COMMAND where java + OUTPUT_VARIABLE JAVA_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + else() + execute_process( + COMMAND which java + OUTPUT_VARIABLE JAVA_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + endif() + + if(JAVA_PATH) + # 从java路径推断JAVA_HOME + get_filename_component(JAVA_HOME "${JAVA_PATH}" DIRECTORY) + get_filename_component(JAVA_HOME "${JAVA_HOME}" DIRECTORY) + message(STATUS "Inferred JAVA_HOME: ${JAVA_HOME}") + else() + message(WARNING "Java not found in PATH") + set(JAVA_HOME "C:/Program Files/Java/jdk-21") # Windows默认路径 + endif() +endif() + +# 设置Java包含目录 +set(JAVA_INCLUDE_DIRS + "${JAVA_HOME}/include" +) + +# 添加平台特定include目录 +if(WIN32) + list(APPEND JAVA_INCLUDE_DIRS "${JAVA_HOME}/include/win32") +elseif(APPLE) + list(APPEND JAVA_INCLUDE_DIRS "${JAVA_HOME}/include/darwin") +else() + list(APPEND JAVA_INCLUDE_DIRS "${JAVA_HOME}/include/linux") +endif() + +# 验证Java包含目录是否存在 +foreach(dir IN LISTS JAVA_INCLUDE_DIRS) + if(NOT EXISTS "${dir}") + message(WARNING "Java include directory not found: ${dir}") + else() + message(STATUS "Found Java include: ${dir}") + endif() +endforeach() + +# ========== 查找源文件和头文件 ========== +# 查找所有的C++源文件 +file(GLOB SOURCE_FILES + "${SOURCE_DIR}/*.cpp" + "${SOURCE_DIR}/*.cxx" +) + +if(NOT SOURCE_FILES) + # 如果没找到,尝试递归查找 + file(GLOB_RECURSE SOURCE_FILES + "${SOURCE_DIR}/*.cpp" + "${SOURCE_DIR}/*.cxx" + "${SOURCE_DIR}/*.cc" + ) +endif() + +if(SOURCE_FILES) + message(STATUS "Found source files:") + foreach(file ${SOURCE_FILES}) + message(STATUS " ${file}") + endforeach() +else() + message(FATAL_ERROR "No source files found in ${SOURCE_DIR}") +endif() + +# 查找头文件 +file(GLOB HEADER_FILES + "${HEADER_DIR}/*.h" + "${HEADER_DIR}/*.hpp" +) + +# ========== 创建库目标 ========== +# 库名称 +if(WIN32) + set(LIBRARY_NAME "ClassEncrypt") +else() + set(LIBRARY_NAME "ClassEncrypt") +endif() + +# 添加库目标 +add_library(${LIBRARY_NAME} SHARED ${SOURCE_FILES} + "src/EnhancedEncryptionMagic.cpp" + src/guard/JByteArrayGuard.cpp) + +# 设置输出名称和位置 +set_target_properties(${LIBRARY_NAME} PROPERTIES + OUTPUT_NAME ${LIBRARY_NAME} + DEBUG_POSTFIX "d" +) + +# 设置输出目录 +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + +# ========== 包含目录 ========== +target_include_directories(${LIBRARY_NAME} PRIVATE + ${JAVA_INCLUDE_DIRS} + ${HEADER_DIR} +) + +# ========== 编译选项 ========== +if(MSVC) + target_compile_options(${LIBRARY_NAME} PRIVATE + /W3 # 警告级别3 + /WX- # 不将警告视为错误 + /EHsc # C++异常处理 + $<$:/MDd> # 调试版本使用MDd + $<$:/MD> # 发布版本使用MD + $<$:/MD> + $<$:/MD> + ) + + # MSVC预处理器定义 + target_compile_definitions(${LIBRARY_NAME} PRIVATE + _CRT_SECURE_NO_WARNINGS + _WINSOCK_DEPRECATED_NO_WARNINGS + BUILDING_DLL + JNIEXPORT=__declspec(dllexport) + ) +else() + target_compile_options(${LIBRARY_NAME} PRIVATE + -Wall + -Wextra + -Wno-unused-parameter + -fPIC + $<$:-g -O0> + $<$:-O2> + $<$:-O2 -g> + $<$:-Os> + ) + + # GCC/Clang预处理器定义 + target_compile_definitions(${LIBRARY_NAME} PRIVATE + BUILDING_DLL + ) + + if(APPLE) + target_compile_options(${LIBRARY_NAME} PRIVATE + -stdlib=libc++ + ) + endif() +endif() + +# ========== 链接库 ========== +if(WIN32) + # Windows链接库 + target_link_libraries(${LIBRARY_NAME} PRIVATE + kernel32.lib + user32.lib + gdi32.lib + winspool.lib + shell32.lib + ole32.lib + oleaut32.lib + uuid.lib + comdlg32.lib + advapi32.lib + ) +else() + # Linux/macOS链接库 + target_link_libraries(${LIBRARY_NAME} PRIVATE + pthread + dl + ) + + if(APPLE) + target_link_options(${LIBRARY_NAME} PRIVATE + -undefined dynamic_lookup + ) + endif() +endif() + +# ========== 安装配置(可选) ========== +if(NOT DEFINED CMAKE_SKIP_INSTALL_RULES) + install(TARGETS ${LIBRARY_NAME} + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin + ARCHIVE DESTINATION lib + ) + + # 安装头文件 + if(HEADER_FILES) + install(FILES ${HEADER_FILES} DESTINATION include) + endif() +endif() + +# ========== 输出总结信息 ========== +message(STATUS "") +message(STATUS "=========================================") +message(STATUS "Project Configuration Summary") +message(STATUS "=========================================") +message(STATUS "Project: ${PROJECT_NAME}") +message(STATUS "Version: ${PROJECT_VERSION}") +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") +message(STATUS "Source directory: ${SOURCE_DIR}") +message(STATUS "Header directory: ${HEADER_DIR}") +message(STATUS "Java include dirs: ${JAVA_INCLUDE_DIRS}") +message(STATUS "Target library: ${LIBRARY_NAME}") +message(STATUS "C++ standard: ${CMAKE_CXX_STANDARD}") +message(STATUS "Output directory: ${CMAKE_BINARY_DIR}") +message(STATUS "=========================================") \ No newline at end of file diff --git a/cpp/Config.cmake.in b/cpp/Config.cmake.in new file mode 100644 index 0000000..d02f9f4 --- /dev/null +++ b/cpp/Config.cmake.in @@ -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@) \ No newline at end of file diff --git a/cpp/header/CMakeLists.txt b/cpp/header/CMakeLists.txt new file mode 100644 index 0000000..38b882e --- /dev/null +++ b/cpp/header/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/cpp/header/top_r3944realms_lib39_core_lang_ClassEncryptor.h b/cpp/header/top_r3944realms_lib39_core_lang_ClassEncryptor.h new file mode 100644 index 0000000..3998ee3 --- /dev/null +++ b/cpp/header/top_r3944realms_lib39_core_lang_ClassEncryptor.h @@ -0,0 +1,37 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class top_r3944realms_lib39_core_lang_ClassEncryptor */ + +#ifndef _Included_top_r3944realms_lib39_core_lang_ClassEncryptor +#define _Included_top_r3944realms_lib39_core_lang_ClassEncryptor +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: top_r3944realms_lib39_core_lang_ClassEncryptor + * Method: encryptClass + * Signature: ([BLjava/lang/String;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_top_r3944realms_lib39_core_lang_ClassEncryptor_encryptClass + (JNIEnv *, jobject, jbyteArray, jstring); + +/* + * Class: top_r3944realms_lib39_core_lang_ClassEncryptor + * Method: decryptClass + * Signature: ([BLjava/lang/String;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_top_r3944realms_lib39_core_lang_ClassEncryptor_decryptClass + (JNIEnv *, jobject, jbyteArray, jstring); + +/* + * Class: top_r3944realms_lib39_core_lang_ClassEncryptor + * Method: isEncryptedFile + * Signature: ([B)Z + */ +JNIEXPORT jboolean JNICALL Java_top_r3944realms_lib39_core_lang_ClassEncryptor_isEncryptedFile + (JNIEnv *, jobject, jbyteArray); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/cpp/header/top_r3944realms_lib39_core_lang_EncryptedClassLoader.h b/cpp/header/top_r3944realms_lib39_core_lang_EncryptedClassLoader.h new file mode 100644 index 0000000..29284f9 --- /dev/null +++ b/cpp/header/top_r3944realms_lib39_core_lang_EncryptedClassLoader.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class top_r3944realms_lib39_core_lang_EncryptedClassLoader */ + +#ifndef _Included_top_r3944realms_lib39_core_lang_EncryptedClassLoader +#define _Included_top_r3944realms_lib39_core_lang_EncryptedClassLoader +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: top_r3944realms_lib39_core_lang_EncryptedClassLoader + * Method: decryptClass + * Signature: ([BLjava/lang/String;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_top_r3944realms_lib39_core_lang_EncryptedClassLoader_decryptClass + (JNIEnv *, jobject, jbyteArray, jstring); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/cpp/lib/CMakeLists.txt b/cpp/lib/CMakeLists.txt new file mode 100644 index 0000000..b55c47b --- /dev/null +++ b/cpp/lib/CMakeLists.txt @@ -0,0 +1 @@ +cmake_minimum_required(VERSION 3.28) diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt new file mode 100644 index 0000000..1b02383 --- /dev/null +++ b/cpp/src/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/cpp/src/EnhancedEncryptionMagic.cpp b/cpp/src/EnhancedEncryptionMagic.cpp new file mode 100644 index 0000000..33c9ef4 --- /dev/null +++ b/cpp/src/EnhancedEncryptionMagic.cpp @@ -0,0 +1,451 @@ +#pragma clang diagnostic push +#pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions" +#pragma once +#include +#include +#include +#ifndef HEADER_JNI_H_ +#define HEADER_JNI_H_ +#include +#endif +#ifndef HEADER_P_H_ +#define HEADER_P_H_ +#include "guard/JByteArrayGuard.cpp" +#endif +#include + +// 字节序转换宏(跨平台) +#pragma clang diagnostic push +#pragma ide diagnostic ignored "UnreachableCallsOfFunction" +#if defined(_WIN32) +#include +#pragma commen+t(lib, "ws2_32.lib") + +#define htobe32(x) htonl(x) +#define be32toh(x) ntohl(x) +#define htobe16(x) htons(x) +#define be16toh(x) ntohs(x) + +// Windows下64位字节序转换需要自己实现 +static inline uint64_t htobe64(uint64_t x) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + return ((uint64_t)htonl(x & 0xFFFFFFFF) << 32) | htonl(x >> 32); +#else + return x; +#endif +} + +static inline uint64_t be64toh(uint64_t x) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + return ((uint64_t)ntohl(x & 0xFFFFFFFF) << 32) | ntohl(x >> 32); +#else + return x; +#endif +} + +#elif defined(__APPLE__) || defined(__FreeBSD__) +#include + #define htobe32(x) OSSwapHostToBigInt32(x) + #define be32toh(x) OSSwapBigToHostInt32(x) + #define htobe16(x) OSSwapHostToBigInt16(x) + #define be16toh(x) OSSwapBigToHostInt16(x) + #define htobe64(x) OSSwapHostToBigInt64(x) + #define be64toh(x) OSSwapBigToHostInt64(x) + +#elif defined(__linux__) || defined(__ANDROID__) + #include + // Linux下endian.h已经定义了这些宏 + +#else + // 通用实现 + #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + // GCC/Clang内置函数 + #define htobe32(x) __builtin_bswap32(x) + #define be32toh(x) __builtin_bswap32(x) + #define htobe16(x) __builtin_bswap16(x) + #define be16toh(x) __builtin_bswap16(x) + #define htobe64(x) __builtin_bswap64(x) + #define be64toh(x) __builtin_bswap64(x) + #else + #define htobe32(x) (x) + #define be32toh(x) (x) + #define htobe16(x) (x) + #define be16toh(x) (x) + #define htobe64(x) (x) + #define be64toh(x) (x) + #endif +#endif + +// 手动字节序转换函数(备用) +static inline uint64_t manual_htobe64(uint64_t x) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + return ((uint64_t)__builtin_bswap32(x & 0xFFFFFFFF) << 32) | __builtin_bswap32(x >> 32); +#else + return x; +#endif +} + +static inline uint64_t manual_be64toh(uint64_t x) { + return manual_htobe64(x); // 对称操作 +} + +namespace EnhancedEncryptionMagic { + // 主魔数:0x4C494233 (ASCII: "LIB3") + static const uint32_t MAGIC = 0x4C494233; // "LIB3" in hex + + // 文件头结构 - 调整为实际大小 + struct EnhancedFileHeader { + uint32_t magic; // 魔数: 0x4C494233 "LIB3" (4字节) + uint16_t version_major; // 主版本号 (2字节) + uint16_t version_minor; // 次版本号 (2字节) + uint32_t flags; // 标志位 (4字节) + uint32_t original_size; // 原始数据大小 (4字节) + uint32_t encrypted_size; // 加密数据大小 (4字节) + uint64_t timestamp; // 时间戳 (8字节) + uint32_t checksum; // 校验和 (4字节) + uint32_t reserved; // 保留字段 (4字节) + // 总计: 4+2+2+4+4+4+8+4+4 = 36字节 + + // 编译器可能添加4字节填充到40字节,但我们应该按36字节处理 + }; + + // 计算实际结构体大小 + static const size_t CALCULATED_HEADER_SIZE = + sizeof(uint32_t) + // magic + sizeof(uint16_t) + // version_major + sizeof(uint16_t) + // version_minor + sizeof(uint32_t) + // flags + sizeof(uint32_t) + // original_size + sizeof(uint32_t) + // encrypted_size + sizeof(uint64_t) + // timestamp + sizeof(uint32_t) + // checksum + sizeof(uint32_t); // reserved + + static const size_t HEADER_SIZE = CALCULATED_HEADER_SIZE; + + // 标志位定义 + namespace Flags { + static const uint32_t COMPRESSED = 0x00000001; // 是否压缩 + static const uint32_t SIGNED = 0x00000002; // 是否签名 + static const uint32_t ENCRYPTED = 0x00000004; // 是否加密 + static const uint32_t VALIDATED = 0x00000008; // 是否验证 + }; + + // 创建文件头 + static inline EnhancedFileHeader createHeader(uint32_t originalSize, uint32_t encryptedSize) { + EnhancedFileHeader header; + memset(&header, 0, sizeof(header)); // 清零初始化 + + header.magic = MAGIC; + header.version_major = 1; + header.version_minor = 0; + header.flags = Flags::ENCRYPTED; + header.original_size = originalSize; + header.encrypted_size = encryptedSize; + header.timestamp = static_cast(time(nullptr)); + header.checksum = 0; // 将在之后计算 + header.reserved = 0; + + return header; + } + + // 字节序安全的内存复制函数 + static inline void writeUint32(jbyte* buffer, uint32_t value, size_t offset) { + uint32_t networkValue = htobe32(value); + memcpy(buffer + offset, &networkValue, sizeof(uint32_t)); + } + + static inline void writeUint16(jbyte* buffer, uint16_t value, size_t offset) { + uint16_t networkValue = htobe16(value); + memcpy(buffer + offset, &networkValue, sizeof(uint16_t)); + } + + static inline void writeUint64(jbyte* buffer, uint64_t value, size_t offset) { + uint64_t networkValue = htobe64(value); + memcpy(buffer + offset, &networkValue, sizeof(uint64_t)); + } + + static inline uint32_t readUint32(const jbyte* buffer, size_t offset) { + uint32_t networkValue; + memcpy(&networkValue, buffer + offset, sizeof(uint32_t)); + return be32toh(networkValue); + } + + static inline uint16_t readUint16(const jbyte* buffer, size_t offset) { + uint16_t networkValue; + memcpy(&networkValue, buffer + offset, sizeof(uint16_t)); + return be16toh(networkValue); + } + + static inline uint64_t readUint64(const jbyte* buffer, size_t offset) { + uint64_t networkValue; + memcpy(&networkValue, buffer + offset, sizeof(uint64_t)); + return be64toh(networkValue); + } + + // 验证文件头 + static inline bool validateHeader(const EnhancedFileHeader& header) { + return header.magic == MAGIC && + header.version_major == 1 && + header.version_minor == 0 && + (header.flags & Flags::ENCRYPTED) != 0; + } + + // 计算校验和(简单的CRC32替代) + static inline uint32_t calculateChecksum(const jbyte* data, jsize length) { + if (!data || length <= 0) { + return 0; + } + + uint32_t crc = 0xFFFFFFFF; + const uint8_t* bytes = reinterpret_cast(data); + + for (jsize i = 0; i < length; i++) { + crc ^= bytes[i]; + for (int j = 0; j < 8; j++) { + if (crc & 1) { + crc = (crc >> 1) ^ 0xEDB88320; + } else { + crc = crc >> 1; + } + } + } + + return ~crc; + } + + // 更新文件头的校验和 + static inline void updateChecksum(EnhancedFileHeader& header, const jbyte* data, jsize length) { + header.checksum = calculateChecksum(data, length); + } + + // 验证数据校验和 + static inline bool verifyChecksum(const EnhancedFileHeader& header, const jbyte* data, jsize length) { + uint32_t calculated = calculateChecksum(data, length); + return header.checksum == calculated; + } + + // 将文件头写入字节数组(使用网络字节序) + static inline void writeHeaderToBytes(const EnhancedFileHeader& header, jbyte* buffer) { + if (!buffer) return; + + size_t offset = 0; + writeUint32(buffer, header.magic, offset); offset += 4; + writeUint16(buffer, header.version_major, offset); offset += 2; + writeUint16(buffer, header.version_minor, offset); offset += 2; + writeUint32(buffer, header.flags, offset); offset += 4; + writeUint32(buffer, header.original_size, offset); offset += 4; + writeUint32(buffer, header.encrypted_size, offset); offset += 4; + writeUint64(buffer, header.timestamp, offset); offset += 8; + writeUint32(buffer, header.checksum, offset); offset += 4; + writeUint32(buffer, header.reserved, offset); offset += 4; + } + + // 从字节数组读取文件头 + static inline EnhancedFileHeader readHeaderFromBytes(const jbyte* buffer) { + EnhancedFileHeader header{}; + memset(&header, 0, sizeof(header)); + + if (!buffer) return header; + + size_t offset = 0; + header.magic = readUint32(buffer, offset); offset += 4; + header.version_major = readUint16(buffer, offset); offset += 2; + header.version_minor = readUint16(buffer, offset); offset += 2; + header.flags = readUint32(buffer, offset); offset += 4; + header.original_size = readUint32(buffer, offset); offset += 4; + header.encrypted_size = readUint32(buffer, offset); offset += 4; + header.timestamp = readUint64(buffer, offset); offset += 8; + header.checksum = readUint32(buffer, offset); offset += 4; + header.reserved = readUint32(buffer, offset); offset += 4; + + return header; + } + + // 将文件头格式化为可读字符串 + static inline std::string headerToString(const EnhancedFileHeader& header) { + char magicStr[5] = {0}; + memcpy(magicStr, &header.magic, 4); + + char buffer[256]; + snprintf(buffer, sizeof(buffer), + "Magic: %s (0x%08X)\n" + "Version: %d.%d\n" + "Flags: 0x%08X\n" + "Original Size: %u bytes\n" + "Encrypted Size: %u bytes\n" + "Timestamp: %llu\n" + "Checksum: 0x%08X\n" + "Reserved: 0x%08X", + magicStr, header.magic, + header.version_major, header.version_minor, + header.flags, + header.original_size, + header.encrypted_size, + (unsigned long long)header.timestamp, + header.checksum, + header.reserved); + + return std::string(buffer); + } + + // 验证文件是否完整 + static inline bool validateFileIntegrity(const EnhancedFileHeader& header, + const jbyte* encryptedData, + jsize encryptedDataSize) { + // 检查大小是否匹配 + if (header.encrypted_size != encryptedDataSize) { + return false; + } + + // 检查校验和 + return verifyChecksum(header, encryptedData, encryptedDataSize); + } + + // 加密函数指针类型 + typedef void (*EncryptFunc)(jbyte*, jsize, const char*, int); + + // 创建完整的加密文件 + static inline jbyteArray createEncryptedFile(JNIEnv* env, + const jbyte* originalData, + jsize originalSize, + const char* key, + size_t keyLen, + EncryptFunc encryptFunc) { + + if (!env || !originalData || originalSize <= 0 || !key || keyLen <= 0 || !encryptFunc) { + return nullptr; + } + + // 1. 创建加密数据数组 + jbyteArray encryptedDataArray = env->NewByteArray(originalSize); + if (!encryptedDataArray) { + return nullptr; + } + + { + // 使用局部作用域确保 encryptedDataGuard 在加密后释放 + JByteArrayGuard encryptedDataGuard(env, encryptedDataArray, false, JNI_ABORT); + if (!encryptedDataGuard.isValid()) { + env->DeleteLocalRef(encryptedDataArray); + return nullptr; + } + + // 复制并加密数据 + memcpy(encryptedDataGuard.get(), originalData, originalSize); + encryptFunc(encryptedDataGuard.get(), originalSize, key, keyLen); + + // encryptedDataGuard 析构函数会自动以 JNI_ABORT 模式释放 + } + + // 注意:这里已经释放了加密数据,需要重新获取 + JByteArrayGuard encryptedDataGuard2(env, encryptedDataArray); + if (!encryptedDataGuard2.isValid()) { + env->DeleteLocalRef(encryptedDataArray); + return nullptr; + } + + // 2. 创建文件头 + EnhancedFileHeader header = createHeader(originalSize, originalSize); + updateChecksum(header, encryptedDataGuard2.get(), originalSize); + + // 3. 创建最终结果 + jsize totalSize = HEADER_SIZE + originalSize; + jbyteArray result = env->NewByteArray(totalSize); + if (!result) { + return nullptr; + } + + JByteArrayGuard resultGuard(env, result); + if (!resultGuard.isValid()) { + env->DeleteLocalRef(result); + return nullptr; + } + + // 4. 写入数据 + writeHeaderToBytes(header, resultGuard.get()); + memcpy(resultGuard.get() + HEADER_SIZE, encryptedDataGuard2.get(), originalSize); + + // 5. 显式提交修改 + resultGuard.commit(); // 提交修改到 Java 端 + // resultGuard 析构时不会再释放 + + // 6. 清理中间数组 + env->DeleteLocalRef(encryptedDataArray); + + return result; + } + + // 从加密文件中提取数据 + static inline jbyteArray extractFromEncryptedFile(JNIEnv* env, + const jbyte* fileData, + jsize fileSize, + const char* key, + size_t keyLen, + EncryptFunc decryptFunc, + bool* isValid) { + + if (isValid) *isValid = false; + + if (!env || !fileData || fileSize < HEADER_SIZE || !key || keyLen <= 0 || !decryptFunc) { + return nullptr; + } + + // 读取文件头 + EnhancedFileHeader header = readHeaderFromBytes(fileData); + + // 验证文件头 + if (!validateHeader(header)) { + return nullptr; + } + + // 检查文件大小 + jsize expectedSize = HEADER_SIZE + header.encrypted_size; + if (fileSize != expectedSize) { + return nullptr; + } + + // 提取加密数据 + const jbyte* encryptedData = fileData + HEADER_SIZE; + + // 验证完整性 + if (!validateFileIntegrity(header, encryptedData, header.encrypted_size)) { + return nullptr; + } + + // 创建结果数组 + jbyteArray result = env->NewByteArray(header.original_size); + if (!result) { + return nullptr; + } + JByteArrayGuard resultDataGuard(env, result); + + if (!resultDataGuard.isValid()) { + env->DeleteLocalRef(result); + return nullptr; + } + + jbyte* resultData = resultDataGuard.get(); + + // 复制加密数据 + memcpy(resultData, encryptedData, header.original_size); + + // 解密数据 + decryptFunc(resultData, header.original_size, key, keyLen); + + if (isValid) *isValid = true; + return result; + } + + // 验证是否为加密文件(不读取整个文件) + static inline bool isEncryptedFile(const jbyte* fileData, jsize fileSize) { + if (fileSize < HEADER_SIZE || !fileData) { + return false; + } + + EnhancedFileHeader header = readHeaderFromBytes(fileData); + return validateHeader(header); + } +} +#pragma clang diagnostic pop +#pragma clang diagnostic pop \ No newline at end of file diff --git a/cpp/src/SimpleClassEncrypt.cpp b/cpp/src/SimpleClassEncrypt.cpp new file mode 100644 index 0000000..22b6ee3 --- /dev/null +++ b/cpp/src/SimpleClassEncrypt.cpp @@ -0,0 +1,234 @@ +#pragma once +#include +#include +#include
+#include
+#include "EnhancedEncryptionMagic.cpp" + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-compare" +#ifdef _WIN32 +#include +#include +#include + +#define JNIEXPORT __declspec(dllexport) +#else +#include + #define JNIEXPORT +#endif + +/** + * 简单的XOR加密/解密 + */ +static void xorEncrypt(jbyte* data, jsize dataLen, const char* key, int keyLen) { + if (!data || !key || keyLen == 0) return; + + for (jsize i = 0; i < dataLen; i++) { + data[i] ^= key[i % keyLen]; + } +} + +static int safeStrlen(const char* str) { + return str ? (int)strlen(str) : 0; +} + +/** + * 验证数据是否为有效的Java类文件 + * @param data 字节码 + * @param dataLen 字节码长度 + * @return 是否有效 + */ +static bool isValidJavaClass(const jbyte* data, jsize dataLen) { + // Java类文件魔数:0xCAFEBABE + return dataLen >= 4 && + data[0] == (jbyte)0xCA && + data[1] == (jbyte)0xFE && + data[2] == (jbyte)0xBA && + data[3] == (jbyte)0xBE; +} + +/** + * 记录错误信息 + */ +void logError(const char* message) { +#ifdef DEBUG + std::cerr << "[JNI Error] " << message << std::endl; +#endif +} + +/** + * 安全的字符串转换 + */ +std::string jstringToString(JNIEnv* env, jstring jstr) { + if (!jstr) return ""; + + const char* chars = env->GetStringUTFChars(jstr, nullptr); + if (!chars) return ""; + + std::string result(chars); + env->ReleaseStringUTFChars(jstr, chars); + return result; +} + +uint32_t calculateChecksum(const jbyte* data, jsize length) { + uint32_t checksum = 0; + for (jsize i = 0; i < length; i++) { + checksum += static_cast(data[i]); + checksum = (checksum << 1) | (checksum >> 31); // 简单旋转 + } + return checksum; +} + + +// ==================== JNI函数实现 ==================== +using namespace EnhancedEncryptionMagic; + +/* + * Class: top_r3944realms_lib39_core_lang_ClassEncryptor + * Method: decryptClass + * Signature: ([BLjava/lang/String;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_top_r3944realms_lib39_core_lang_ClassEncryptor_decryptClass + (JNIEnv *env, jobject obj, jbyteArray encryptedData, jstring key) { + + if (!encryptedData || !key) { + return nullptr; + } + + jsize fileSize = env->GetArrayLength(encryptedData); + if (fileSize == 0) { + return nullptr; + } + JByteArrayGuard fileDataGuard(env, encryptedData); + jbyte* fileData = fileDataGuard.get(); + if (!fileDataGuard.isValid()) { + return nullptr; + } + std::string keyStr = jstringToString(env, key); + size_t keyLen = keyStr.length(); + bool isValid = keyLen > 0; + // 尝试从加密文件中提取数据 + jbyteArray result = EnhancedEncryptionMagic::extractFromEncryptedFile( + env, fileData, fileSize, keyStr.c_str(), keyLen, xorEncrypt, &isValid); + if (!isValid || !result) { + // 如果不是有效的加密文件,返回原始数据 + return encryptedData; + } + + // 验证解密后的数据是否为有效的Java类文件 + JByteArrayGuard resultGuard(env, result); + jsize resultLen = resultGuard.size(); + jbyte* resultData = resultGuard.get(); + + if (!resultGuard.isValid()) { + env->DeleteLocalRef(result); + return nullptr; + } + + bool validClass = isValidJavaClass(resultData, resultLen); + + if (!validClass) { + env->DeleteLocalRef(result); + return nullptr; // 解密后的数据不是有效的Java类 + } + + return result; +} + + +/* + * Class: top_r3944realms_lib39_core_lang_ClassEncryptor + * Method: encryptClass + * Signature: ([BLjava/lang/String;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_top_r3944realms_lib39_core_lang_ClassEncryptor_encryptClass + (JNIEnv *env, jobject obj, jbyteArray classData, jstring key) { + + if (!classData || !key) { + return nullptr; + } + + JByteArrayGuard jGuard(env, classData); + if (jGuard.isValid()) { + return nullptr; + } + + jsize dataLen = jGuard.size(); + jbyte* data = jGuard.get(); + + const char* keyStr = env->GetStringUTFChars(key, nullptr); + if (!keyStr) { + return nullptr; + } + + // 验证输入数据是否为有效的Java类文件 + if (!isValidJavaClass(data, dataLen)) { + env->ReleaseStringUTFChars(key, keyStr); + return nullptr; + } + + int keyLen = safeStrlen(keyStr); + + // 使用增强版创建加密文件 + jbyteArray result = EnhancedEncryptionMagic::createEncryptedFile( + env, data, dataLen, keyStr, keyLen, xorEncrypt); + + env->ReleaseStringUTFChars(key, keyStr); + + return result; +} + +/* + * Class: top_r3944realms_lib39_core_lang_ClassEncryptor + * Method: isEncryptedFile + * Signature: ([B)Z + */ +JNIEXPORT jboolean JNICALL Java_top_r3944realms_lib39_core_lang_ClassEncryptor_isEncryptedFile + (JNIEnv *env, jobject obj, jbyteArray fileData) { + + if (!fileData) { + return JNI_FALSE; + } + JByteArrayGuard dataGuard(env, fileData); + if (!dataGuard.isValid()) { + return JNI_FALSE; + } + + if (dataGuard.size() < EnhancedEncryptionMagic::HEADER_SIZE) { + return JNI_FALSE; + } + + // 验证是否为加密文件 + bool isEncrypted = EnhancedEncryptionMagic::isEncryptedFile(dataGuard.get(), dataGuard.size()); + + return isEncrypted ? JNI_TRUE : JNI_FALSE; +} + +/* + * Class: top_r3944realms_lib39_core_lang_EncryptedClassLoader + * Method: decryptClass + * Signature: ([BLjava/lang/String;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_top_r3944realms_lib39_core_lang_EncryptedClassLoader_decryptClass + (JNIEnv *env, jobject obj, jbyteArray encryptedData, jstring key) { + return Java_top_r3944realms_lib39_core_lang_ClassEncryptor_decryptClass(env, obj, encryptedData, key); +} + +// JNI库初始化和卸载函数 +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { +#ifdef _WIN32 + // Windows下需要初始化Winsock + WSADATA wsaData; + WSAStartup(MAKEWORD(2, 2), &wsaData); +#endif + + return JNI_VERSION_1_8; +} + +JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) { +#ifdef _WIN32 + WSACleanup(); +#endif +} +#pragma clang diagnostic pop \ No newline at end of file diff --git a/cpp/src/guard/CMakeLists.txt b/cpp/src/guard/CMakeLists.txt new file mode 100644 index 0000000..39bdf67 --- /dev/null +++ b/cpp/src/guard/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.28) + +add_library(J_GUARD + JByteArrayGuard.cpp +) \ No newline at end of file diff --git a/cpp/src/guard/JByteArrayGuard.cpp b/cpp/src/guard/JByteArrayGuard.cpp new file mode 100644 index 0000000..13a0413 --- /dev/null +++ b/cpp/src/guard/JByteArrayGuard.cpp @@ -0,0 +1,82 @@ +#pragma once +#ifndef HEADER_JNI_H_ +#define HEADER_JNI_H_ +#include +#endif +class JByteArrayGuard { +private: + JNIEnv* env; + jbyteArray array; + jbyte* data; + jsize length; + bool isCritical; + jint releaseMode; +public: + JByteArrayGuard(JNIEnv* env, jbyteArray array, bool critical = false, jint releaseMode = 0) + : env(env), array(array), data(nullptr), length(0), isCritical(critical), releaseMode(releaseMode) + { + if (array) { + length = env->GetArrayLength(array); + if (isCritical) { + data = (jbyte*) env->GetPrimitiveArrayCritical(array, nullptr); + } else { + data = env->GetByteArrayElements(array, nullptr); + } + } + } + ~JByteArrayGuard() { + release(); + } + // 显式释放方法 + void release() { + if (data && array) { + if (isCritical) { + env->ReleasePrimitiveArrayCritical(array, data, releaseMode); + } else { + env->ReleaseByteArrayElements(array, data, releaseMode); + } + data = nullptr; // 防止重复释放 + array = nullptr; + } + } + + // 提交修改但不释放(用于返回结果的情况) + void commit() { + if (data && array) { + if (isCritical) { + env->ReleasePrimitiveArrayCritical(array, data, 0); + } else { + env->ReleaseByteArrayElements(array, data, 0); + } + data = nullptr; // 标记为已释放,防止析构函数再次释放 + array = nullptr; + } + } + + // 丢弃修改 + void abort() { + if (data && array) { + if (isCritical) { + env->ReleasePrimitiveArrayCritical(array, data, JNI_ABORT); + } else { + env->ReleaseByteArrayElements(array, data, JNI_ABORT); + } + data = nullptr; + array = nullptr; + } + } + jbyte* get() { return data; } + jsize size() const { return length; } + bool isValid() { return data != nullptr; } + + JByteArrayGuard(const JByteArrayGuard&) = delete; + JByteArrayGuard& operator=(const JByteArrayGuard&) = delete; + JByteArrayGuard(JByteArrayGuard&& other) noexcept + : env(other.env), array(other.array), data(other.data), + length(other.length), isCritical(other.isCritical), + releaseMode(other.releaseMode) { + other.array = nullptr; + other.data = nullptr; + other.length = 0; + } +}; \ No newline at end of file diff --git a/gradle/.jni-config.groovy b/gradle/.jni-config.groovy new file mode 100644 index 0000000..68739e6 --- /dev/null +++ b/gradle/.jni-config.groovy @@ -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' // 匹配特定包下的实现类 + ] +} \ No newline at end of file diff --git a/gradle/jni-heads.gradle b/gradle/jni-heads.gradle new file mode 100644 index 0000000..372c629 --- /dev/null +++ b/gradle/jni-heads.gradle @@ -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 查看详细帮助" \ No newline at end of file diff --git a/res/alex.png b/res/alex.png new file mode 100644 index 0000000..b513908 Binary files /dev/null and b/res/alex.png differ diff --git a/res/doll_default.bbmodel b/res/doll_default.bbmodel new file mode 100644 index 0000000..ec1eae1 --- /dev/null +++ b/res/doll_default.bbmodel @@ -0,0 +1 @@ +{"meta":{"format_version":"5.0","model_format":"java_block","box_uv":false},"name":"doll_default","parent":"","java_block_version":"1.9.0","ambientocclusion":true,"front_gui_light":false,"visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"unhandled_root_fields":{"format_version":"1.21.6"},"resolution":{"width":16,"height":16},"elements":[{"name":"Toggle_Helmet","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[3.5,8.8,7.5],"to":[12.5,17.8,16.5],"autouv":0,"color":0,"origin":[8,9.3,12],"faces":{"north":{"uv":[10,2,12,4],"texture":0},"east":{"uv":[8,2,10,4],"texture":0},"south":{"uv":[14,2,16,4],"texture":0},"west":{"uv":[12,2,14,4],"texture":0},"up":{"uv":[10,2,12,0],"texture":0},"down":{"uv":[12,0,14,2],"texture":0}},"type":"cube","uuid":"4034240c-1bb7-3096-56d0-f9d9158bd705"},{"name":"Head","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[4,9.3,8],"to":[12,17.3,16],"autouv":0,"color":0,"origin":[8,9.3,12],"faces":{"north":{"uv":[2,2,4,4],"texture":0},"east":{"uv":[0,2,2,4],"texture":0},"south":{"uv":[6,2,8,4],"texture":0},"west":{"uv":[4,2,6,4],"texture":0},"up":{"uv":[4,2,2,0],"texture":0},"down":{"uv":[6,0,4,2],"texture":0}},"type":"cube","uuid":"5eca2c83-4481-ddf5-fafe-19abeb647c57"},{"name":"Toggle_Chest_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[4.75,2.05,9.75],"to":[11.25,9.55,13.25],"autouv":0,"color":0,"origin":[8,5.8,11.5],"faces":{"north":{"uv":[5,9,7,12],"texture":0},"east":{"uv":[4,9,5,12],"texture":0},"south":{"uv":[8,9,10,12],"texture":0},"west":{"uv":[7,9,8,12],"texture":0},"up":{"uv":[5,8,7,9],"texture":0},"down":{"uv":[7,8,9,9],"texture":0}},"type":"cube","uuid":"8ea34c7a-dcd8-ead4-07d1-ccab4438046e"},{"name":"Body","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[5,2.3,10],"to":[11,9.3,13],"autouv":0,"color":3,"origin":[8,5.8,11.5],"faces":{"north":{"uv":[5,5,7,8],"texture":0},"east":{"uv":[4,5,5,8],"texture":0},"south":{"uv":[8,5,10,8],"texture":0},"west":{"uv":[7,5,8,8],"texture":0},"up":{"uv":[7,5,5,4],"texture":0},"down":{"uv":[9,4,7,5],"texture":0}},"type":"cube","uuid":"fd16d848-4320-9a3f-6b3d-3f4884a92fbe"},{"name":"Toggle_Left_Arm_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[2.75,6.25,3.8],"to":[5.25,9.75,13.3],"autouv":0,"color":3,"rotation":[0,-22.5,0],"origin":[3,8,12.5],"faces":{"north":{"uv":[13.75,12,14.5,13],"rotation":180,"texture":0},"east":{"uv":[12,13,13,16],"rotation":270,"texture":0},"south":{"uv":[13,12,13.75,13],"texture":0},"west":{"uv":[13.75,13,14.75,16],"rotation":90,"texture":0},"up":{"uv":[13,13,13.75,16],"rotation":180,"texture":0},"down":{"uv":[14.75,13,15.5,16],"texture":0}},"type":"cube","uuid":"06ccbe71-f249-9062-076b-ea3f530fa945"},{"name":"Left_arm","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[3,6.5,3.8],"to":[5,9.5,12.8],"autouv":0,"color":6,"rotation":[0,-22.5,0],"origin":[4,8,12.5],"faces":{"north":{"uv":[10.5,12,9.75,13],"rotation":180,"texture":0},"east":{"uv":[8,13,9,16],"rotation":270,"texture":0},"south":{"uv":[9.75,13,9,12],"texture":0},"west":{"uv":[9.75,13,10.5,16],"rotation":90,"texture":0},"up":{"uv":[9,13,9.75,16],"rotation":180,"texture":0},"down":{"uv":[10.5,13,11.5,16],"texture":0}},"type":"cube","uuid":"15980f9e-f82b-d64a-eaa4-38789e0635cc"},{"name":"Toggle_Right_Arm_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[10.75,6.25,3.8],"to":[13.25,9.75,13.3],"autouv":0,"color":6,"rotation":[0,22.5,0],"origin":[12,8,11.5],"faces":{"north":{"uv":[11.75,8,12.5,9],"rotation":180,"texture":0},"east":{"uv":[10,9,11,12],"rotation":270,"texture":0},"south":{"uv":[11,8,11.75,9],"texture":0},"west":{"uv":[11.75,9,12.75,12],"rotation":90,"texture":0},"up":{"uv":[11,9,11.75,12],"rotation":180,"texture":0},"down":{"uv":[12.75,9,13.5,12],"texture":0}},"type":"cube","uuid":"7b6c1629-5993-798e-8a6e-bb9303f0e8da"},{"name":"Right_arm","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[11,6.5,3.8],"to":[13,9.5,12.8],"autouv":0,"color":0,"rotation":[0,22.5,0],"origin":[12,8,11.5],"faces":{"north":{"uv":[12.5,4,11.75,5],"rotation":180,"texture":0},"east":{"uv":[10,5,11,8],"rotation":270,"texture":0},"south":{"uv":[11.75,5,11,4],"texture":0},"west":{"uv":[11.75,5,12.5,8],"rotation":90,"texture":0},"up":{"uv":[11,5,11.75,8],"rotation":180,"texture":0},"down":{"uv":[12.5,5,13.5,8],"texture":0}},"type":"cube","uuid":"e5bd3372-f4e5-9cc0-1f38-726db3f86001"},{"name":"Toggle_Left_Leg_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[5.2,-0.25,3.05],"to":[8.7,3.25,12.55],"autouv":0,"color":1,"rotation":[0,22.5,0],"origin":[5.7,2,13],"faces":{"north":{"uv":[2,12,3,13],"rotation":180,"texture":0},"east":{"uv":[0,13,1,16],"rotation":270,"texture":0},"south":{"uv":[1,12,2,13],"texture":0},"west":{"uv":[2,13,3,16],"rotation":90,"texture":0},"up":{"uv":[1,13,2,16],"rotation":180,"texture":0},"down":{"uv":[3,13,4,16],"texture":0}},"type":"cube","uuid":"859ab26f-304d-1fc2-e695-7c65fa9746c5"},{"name":"Left_leg","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[5.5,0,3.3],"to":[8.5,3,12.3],"autouv":0,"color":5,"rotation":[0,22.5,0],"origin":[6,2,13],"faces":{"north":{"uv":[7,12,6,13],"rotation":180,"texture":0},"east":{"uv":[4,13,5,16],"rotation":270,"texture":0},"south":{"uv":[5.95,13,5,12],"texture":0},"west":{"uv":[6,13,7,16],"rotation":90,"texture":0},"up":{"uv":[5,13,6,16],"rotation":180,"texture":0},"down":{"uv":[7,13,8,16],"texture":0}},"type":"cube","uuid":"1fb83a23-bc95-d6de-1a60-b721c8922407"},{"name":"Toggle_Right_Leg_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[7.2,-0.25,3.05],"to":[10.7,3.25,12.55],"autouv":0,"color":7,"rotation":[0,-22.5,0],"origin":[8.7,2,13],"faces":{"north":{"uv":[2,8,3,9],"rotation":180,"texture":0},"east":{"uv":[0,9,1,12],"rotation":270,"texture":0},"south":{"uv":[1,8,2,9],"texture":0},"west":{"uv":[2,9,3,12],"rotation":90,"texture":0},"up":{"uv":[1,9,2,12],"rotation":180,"texture":0},"down":{"uv":[3,9,4,12],"texture":0}},"type":"cube","uuid":"7fb46a5a-407b-6e68-5395-edf7ec58c151"},{"name":"Right_leg","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[7.5,0,3.3],"to":[10.5,3,12.3],"autouv":0,"color":0,"rotation":[0,-22.5,0],"origin":[9,2,13],"faces":{"north":{"uv":[3,4,2,5],"rotation":180,"texture":0},"east":{"uv":[0,5,1,8],"rotation":270,"texture":0},"south":{"uv":[2,5,1,4],"texture":0},"west":{"uv":[2,5,3,8],"rotation":90,"texture":0},"up":{"uv":[1,5,2,8],"rotation":180,"texture":0},"down":{"uv":[3,5,4,8],"texture":0}},"type":"cube","uuid":"2977e3b7-0d36-5caf-3a69-4c0fb7895a71"}],"groups":[{"uuid":"eda1d026-dfe0-f980-152d-71db37d8a5b3","export":true,"locked":false,"origin":[3,-6.7,6],"rotation":[0,0,0],"color":0,"name":"Player","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"5399b7c3-2eac-47ae-fb88-873e7e24a5d6","export":true,"locked":false,"origin":[8,16,8],"rotation":[0,0,0],"color":0,"name":"Head","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":false,"primary_selected":false},{"uuid":"0c759830-21cb-f6ec-e6d7-af3998e4bbd2","export":true,"locked":false,"origin":[8,11,8],"rotation":[0,0,0],"color":0,"name":"Body","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"b31348f5-2c4e-cfd0-cec3-4c11d4a5bee2","export":true,"locked":false,"origin":[5,15,6],"rotation":[0,0,0],"color":0,"name":"Left_Arm","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"eb158d47-d395-3084-947f-4101fa1eb833","export":true,"locked":false,"origin":[11,15,6],"rotation":[0,0,0],"color":0,"name":"Right_Arm","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"0022c2e4-2add-b938-2d45-0a8b8b95d53a","export":true,"locked":false,"origin":[7,13,7],"rotation":[0,0,0],"color":0,"name":"Left_Leg","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"c752460c-4cd8-aaae-17c7-d45bf46741e4","export":true,"locked":false,"origin":[10,13,7],"rotation":[0,0,0],"color":0,"name":"Right_Leg","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":false,"primary_selected":false}],"outliner":[{"uuid":"eda1d026-dfe0-f980-152d-71db37d8a5b3","isOpen":true,"children":[{"uuid":"5399b7c3-2eac-47ae-fb88-873e7e24a5d6","isOpen":false,"children":["4034240c-1bb7-3096-56d0-f9d9158bd705","5eca2c83-4481-ddf5-fafe-19abeb647c57"]},{"uuid":"0c759830-21cb-f6ec-e6d7-af3998e4bbd2","isOpen":true,"children":["8ea34c7a-dcd8-ead4-07d1-ccab4438046e","fd16d848-4320-9a3f-6b3d-3f4884a92fbe"]},{"uuid":"b31348f5-2c4e-cfd0-cec3-4c11d4a5bee2","isOpen":true,"children":["06ccbe71-f249-9062-076b-ea3f530fa945","15980f9e-f82b-d64a-eaa4-38789e0635cc"]},{"uuid":"eb158d47-d395-3084-947f-4101fa1eb833","isOpen":true,"children":["7b6c1629-5993-798e-8a6e-bb9303f0e8da","e5bd3372-f4e5-9cc0-1f38-726db3f86001"]},{"uuid":"0022c2e4-2add-b938-2d45-0a8b8b95d53a","isOpen":true,"children":["859ab26f-304d-1fc2-e695-7c65fa9746c5","1fb83a23-bc95-d6de-1a60-b721c8922407"]},{"uuid":"c752460c-4cd8-aaae-17c7-d45bf46741e4","isOpen":false,"children":["7fb46a5a-407b-6e68-5395-edf7ec58c151","2977e3b7-0d36-5caf-3a69-4c0fb7895a71"]}]}],"textures":[{"name":"author.png","relative_path":"H:/Download/2d9f724107b509db.png","folder":"H:/Download","namespace":"","id":"0","group":"","width":64,"height":64,"uv_width":16,"uv_height":16,"particle":true,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":true,"uuid":"f5a406a9-0e83-e8e7-427a-4cf3e6991f20","source":"data:image/png;base64,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"},{"name":"allium.png","path":"","folder":"","namespace":"","id":"1","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"b68aca7b-f859-4cdb-5c04-2e5b1fcfe0b6","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAHlBMVEUAAADoz/7Spva4eO1Vqy2mXuFSmi5Kjyh7TqAXfAQSwnSGAAAAAXRSTlMAQObYZgAAADlJREFUeNpjwAqUAyA0k5AFhKGoGNQAZhgZmUIYrMrBUMXtBlAGO0w7G4zBgsFgQzDQdXEyMDAwAACVCARQ+FH48gAAAABJRU5ErkJggg=="},{"name":"acacia_sapling.png","path":"","folder":"","namespace":"","id":"2","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"98bb7bd9-64b8-fac7-1eb5-c24a9483675f","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAr0lEQVQ4y6WTMQrCQBBFt0xtk0YQL+QR0mpnYyk5QbDObTc84YVB0NnFgSGTyf7/5w+bUpJ4LMdaeuI6jzWC5/Vcm0kAAyAleE6Hd00vkn8Fq0oCJv32up9yEg6REPGEQDLemyYR6DS3y7ATp3tQTTCEWqH3U50DcQccRl0b9lKSuAvV0wXG+AQyRTOYiDughkB76bXVgiDr5gkEeYEAu8iuf0L/WiD/Iii94diZ+gZo3N9oOnqLFAAAAABJRU5ErkJggg=="},{"name":"amethyst_cluster.png","path":"","folder":"","namespace":"","id":"3","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"68cf694e-cb45-9f14-68eb-4bc01d190d1d","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAwElEQVQ4y7WSwQ3CMBAE0x1F8OVFCfQAJSB6QFQBEq+0QAO8wWgiDVqcCGEQkS6Ondvx3tld98/nvLuWn8T346XsN19AEAtYL0+lyYniBDRBFJZbPwQlNAFW88MgIhAxB8D4MQBBDVjMttMA7BJpUZEAIgGWSbxMhCjCRQ0Y5fNiNxpm90nOUqzf/+Q+GyuAgOpu6YJ5HufoaF1EYN3pwu9s6uhYs2FZtwA3EDB5Iia5I6Pu0tHbO0CiodVcqy/UAxZem9kikfZjAAAAAElFTkSuQmCC"},{"name":"bamboo_stage0.png","path":"","folder":"","namespace":"","id":"4","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"0428cfd4-3770-fa8b-84d7-1abf7a12826d","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAvUlEQVQ4y7WQLQ7CMBzFewGynoAlmyJoWNIKZggOBdRgEUWDY2cAQTIuwDkffWWgoFlLEP+kou/3PgQA8cuJvwKUzaBtlg5YXgfQe5kOsPcJAcEUQUB7XoEp3MUDnCs2sxzzkQS3UF9SBPtTzBRJAHa/XdbYbYeYFgkACnmsURUS0RvQ1TgxrznqOACd2X0xdht0NaIBLwgrMA3fvQFVKWHq3ENOB+VTsEpvwLu/g9DZdCmiAV5cP4dkqk9/H0aurnV3nkVCAAAAAElFTkSuQmCC"},{"name":"brain_coral.png","path":"","folder":"","namespace":"","id":"5","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"6ff54ec1-7a00-bff3-1847-f6b2a75fcf21","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxklEQVQ4y7VTsQ3DMAzLR/2iF+SCPpGtJ3TIGnTqFZmKPhGge4Z84YAGaNAy00z1YkuWSIpxuu4fa3t80q/4dD0vQ2LTafN6nw8Lv7dXivfv61jXLf2U2bAzpgqcAcJzHKnkkFAQXWBUUMQELQoQUGr0opEs6qzzREaMZjWTd05pTiobvwRHY7NVpQaioMwnJqsnsa9xFn4oSDSOKiojdV6co6n0xLK7ZMUg46mhDYgmlZF53e37V2bOzCbk3Zs4/Jkcu6vdAQWuLZv4B6flAAAAAElFTkSuQmCC"},{"name":"redstone_torch.png","path":"","folder":"","namespace":"","id":"6","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"85835a8f-9619-0277-9e27-12059db4f1f9","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAWUlEQVQ4y2NgGAW0BX8ZGP6DMNma/98A4n4yDQEb8H/6fxCgyBXrydUMAlNL3f6HuuqRb8D8+oD/TqZKlBlAkQso9kJuuNl/WwNZyrxgrSdNmQvMtcTxGgAA/k03zVOyqGEAAAAASUVORK5CYII="}],"display":{"thirdperson_righthand":{"rotation":[75,45,0],"translation":[0,2.5,0],"scale":[0.375,0.375,0.375]},"thirdperson_lefthand":{"rotation":[75,45,0],"translation":[0,2.5,0],"scale":[0.375,0.375,0.375]},"firstperson_righthand":{"rotation":[0,124,0],"translation":[2,3,0],"scale":[0.4,0.4,0.4]},"firstperson_lefthand":{"rotation":[0,120,0],"translation":[1.5,2.75,0],"scale":[0.4,0.4,0.4]},"ground":{"translation":[0,2,0],"scale":[0.5,0.5,0.5]},"gui":{"rotation":[30,-135,0],"translation":[0.75,-1,0],"scale":[0.625,0.625,0.625]},"head":{"translation":[0,14,-0.75]},"fixed":{"translation":[0,0,-2.75],"scale":[0.5,0.5,0.5]},"on_shelf":{"rotation":[0,-180,0],"translation":[0,0,5.25]}}} \ No newline at end of file diff --git a/res/doll_default.json b/res/doll_default.json new file mode 100644 index 0000000..adf40c9 --- /dev/null +++ b/res/doll_default.json @@ -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] + } + ] + } + ] +} \ No newline at end of file diff --git a/res/doll_item.bbmodel b/res/doll_item.bbmodel new file mode 100644 index 0000000..3944b14 --- /dev/null +++ b/res/doll_item.bbmodel @@ -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"}]} \ No newline at end of file diff --git a/res/doll_item.json b/res/doll_item.json new file mode 100644 index 0000000..3d3caae --- /dev/null +++ b/res/doll_item.json @@ -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] + } + ] +} \ No newline at end of file diff --git a/res/doll_without_item.bbmodel b/res/doll_without_item.bbmodel new file mode 100644 index 0000000..6c0fe1e --- /dev/null +++ b/res/doll_without_item.bbmodel @@ -0,0 +1 @@ +{"meta":{"format_version":"5.0","model_format":"java_block","box_uv":false},"name":"doll_without_item","parent":"","java_block_version":"1.9.0","ambientocclusion":true,"front_gui_light":false,"visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"unhandled_root_fields":{"format_version":"1.21.6"},"resolution":{"width":16,"height":16},"elements":[{"name":"Toggle_Helmet","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[3.5,8.8,7.5],"to":[12.5,17.8,16.5],"autouv":0,"color":0,"origin":[8,9.3,12],"faces":{"north":{"uv":[10,2,12,4],"texture":0},"east":{"uv":[8,2,10,4],"texture":0},"south":{"uv":[14,2,16,4],"texture":0},"west":{"uv":[12,2,14,4],"texture":0},"up":{"uv":[10,2,12,0],"texture":0},"down":{"uv":[12,0,14,2],"texture":0}},"type":"cube","uuid":"4034240c-1bb7-3096-56d0-f9d9158bd705"},{"name":"Head","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[4,9.3,8],"to":[12,17.3,16],"autouv":0,"color":0,"origin":[8,9.3,12],"faces":{"north":{"uv":[2,2,4,4],"texture":0},"east":{"uv":[0,2,2,4],"texture":0},"south":{"uv":[6,2,8,4],"texture":0},"west":{"uv":[4,2,6,4],"texture":0},"up":{"uv":[4,2,2,0],"texture":0},"down":{"uv":[6,0,4,2],"texture":0}},"type":"cube","uuid":"5eca2c83-4481-ddf5-fafe-19abeb647c57"},{"name":"Toggle_Chest_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[4.75,2.05,9.75],"to":[11.25,9.55,13.25],"autouv":0,"color":0,"origin":[8,5.8,11.5],"faces":{"north":{"uv":[5,9,7,12],"texture":0},"east":{"uv":[4,9,5,12],"texture":0},"south":{"uv":[8,9,10,12],"texture":0},"west":{"uv":[7,9,8,12],"texture":0},"up":{"uv":[5,8,7,9],"texture":0},"down":{"uv":[7,8,9,9],"texture":0}},"type":"cube","uuid":"8ea34c7a-dcd8-ead4-07d1-ccab4438046e"},{"name":"Body","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[5,2.3,10],"to":[11,9.3,13],"autouv":0,"color":3,"origin":[8,5.8,11.5],"faces":{"north":{"uv":[5,5,7,8],"texture":0},"east":{"uv":[4,5,5,8],"texture":0},"south":{"uv":[8,5,10,8],"texture":0},"west":{"uv":[7,5,8,8],"texture":0},"up":{"uv":[7,5,5,4],"texture":0},"down":{"uv":[9,4,7,5],"texture":0}},"type":"cube","uuid":"fd16d848-4320-9a3f-6b3d-3f4884a92fbe"},{"name":"Toggle_Left_Arm_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[2.75,0.3,9.75],"to":[5.25,9.8,13.25],"autouv":0,"color":3,"origin":[3,8,12.5],"faces":{"north":{"uv":[13,13,13.75,16],"texture":0},"east":{"uv":[12,13,13,16],"texture":0},"south":{"uv":[14.75,13,15.5,16],"texture":0},"west":{"uv":[13.75,13,14.75,16],"texture":0},"up":{"uv":[13,12,13.75,13],"texture":0},"down":{"uv":[13.75,12,14.5,13],"texture":0}},"type":"cube","uuid":"06ccbe71-f249-9062-076b-ea3f530fa945"},{"name":"Left_arm","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[3,0.3,10],"to":[5,9.3,13],"autouv":0,"color":6,"origin":[4,8,12.5],"faces":{"north":{"uv":[9,13,9.75,16],"texture":0},"east":{"uv":[8,13,9,16],"texture":0},"south":{"uv":[10.5,13,11.5,16],"texture":0},"west":{"uv":[9.75,13,10.5,16],"texture":0},"up":{"uv":[9.75,13,9,12],"texture":0},"down":{"uv":[10.5,12,9.75,13],"texture":0}},"type":"cube","uuid":"15980f9e-f82b-d64a-eaa4-38789e0635cc"},{"name":"Toggle_Right_Arm_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[10.75,0.3,9.75],"to":[13.25,9.8,13.25],"autouv":0,"color":6,"origin":[12,8,11.5],"faces":{"north":{"uv":[11,9,11.75,12],"texture":0},"east":{"uv":[10,9,11,12],"texture":0},"south":{"uv":[12.75,9,13.5,12],"texture":0},"west":{"uv":[11.75,9,12.75,12],"texture":0},"up":{"uv":[11,8,11.75,9],"texture":0},"down":{"uv":[11.75,8,12.5,9],"texture":0}},"type":"cube","uuid":"7b6c1629-5993-798e-8a6e-bb9303f0e8da"},{"name":"Right_arm","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[11,0.3,10],"to":[13,9.3,13],"autouv":0,"color":0,"origin":[12,8,11.5],"faces":{"north":{"uv":[11,5,11.75,8],"texture":0},"east":{"uv":[10,5,11,8],"texture":0},"south":{"uv":[12.5,5,13.5,8],"texture":0},"west":{"uv":[11.75,5,12.5,8],"texture":0},"up":{"uv":[11.75,5,11,4],"texture":0},"down":{"uv":[12.5,4,11.75,5],"texture":0}},"type":"cube","uuid":"e5bd3372-f4e5-9cc0-1f38-726db3f86001"},{"name":"Toggle_Left_Leg_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[5.2,-0.25,3.05],"to":[8.7,3.25,12.55],"autouv":0,"color":1,"rotation":[0,22.5,0],"origin":[5.7,2,13],"faces":{"north":{"uv":[2,12,3,13],"rotation":180,"texture":0},"east":{"uv":[0,13,1,16],"rotation":270,"texture":0},"south":{"uv":[1,12,2,13],"texture":0},"west":{"uv":[2,13,3,16],"rotation":90,"texture":0},"up":{"uv":[1,13,2,16],"rotation":180,"texture":0},"down":{"uv":[3,13,4,16],"texture":0}},"type":"cube","uuid":"859ab26f-304d-1fc2-e695-7c65fa9746c5"},{"name":"Left_leg","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[5.5,0,3.3],"to":[8.5,3,12.3],"autouv":0,"color":5,"rotation":[0,22.5,0],"origin":[6,2,13],"faces":{"north":{"uv":[7,12,6,13],"rotation":180,"texture":0},"east":{"uv":[4,13,5,16],"rotation":270,"texture":0},"south":{"uv":[5.95,13,5,12],"texture":0},"west":{"uv":[6,13,7,16],"rotation":90,"texture":0},"up":{"uv":[5,13,6,16],"rotation":180,"texture":0},"down":{"uv":[7,13,8,16],"texture":0}},"type":"cube","uuid":"1fb83a23-bc95-d6de-1a60-b721c8922407"},{"name":"Toggle_Right_Leg_Armor","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[7.2,-0.25,3.05],"to":[10.7,3.25,12.55],"autouv":0,"color":7,"rotation":[0,-22.5,0],"origin":[8.7,2,13],"faces":{"north":{"uv":[2,8,3,9],"rotation":180,"texture":0},"east":{"uv":[0,9,1,12],"rotation":270,"texture":0},"south":{"uv":[1,8,2,9],"texture":0},"west":{"uv":[2,9,3,12],"rotation":90,"texture":0},"up":{"uv":[1,9,2,12],"rotation":180,"texture":0},"down":{"uv":[3,9,4,12],"texture":0}},"type":"cube","uuid":"7fb46a5a-407b-6e68-5395-edf7ec58c151"},{"name":"Right_leg","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[7.5,0,3.3],"to":[10.5,3,12.3],"autouv":0,"color":0,"rotation":[0,-22.5,0],"origin":[9,2,13],"faces":{"north":{"uv":[3,4,2,5],"rotation":180,"texture":0},"east":{"uv":[0,5,1,8],"rotation":270,"texture":0},"south":{"uv":[2,5,1,4],"texture":0},"west":{"uv":[2,5,3,8],"rotation":90,"texture":0},"up":{"uv":[1,5,2,8],"rotation":180,"texture":0},"down":{"uv":[3,5,4,8],"texture":0}},"type":"cube","uuid":"2977e3b7-0d36-5caf-3a69-4c0fb7895a71"},{"name":"cube","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[7,6,1],"to":[7,14,9],"autouv":0,"color":8,"visibility":false,"export":false,"rotation":[0,45,0],"origin":[7,10,3.5],"faces":{"north":{"uv":[0,0,0,0],"texture":null},"east":{"uv":[0,0,16,16],"texture":1},"south":{"uv":[0,0,0,0],"texture":null},"west":{"uv":[0,0,16,16],"texture":1},"up":{"uv":[0,0,0,0],"texture":null},"down":{"uv":[0,0,0,0],"texture":null}},"type":"cube","uuid":"ba8da4eb-b48e-0c2f-8240-95d681f387ff"},{"name":"cube","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[9,6,1],"to":[9,14,9],"autouv":0,"color":0,"visibility":false,"export":false,"rotation":[0,-45,0],"origin":[9,10,3.5],"faces":{"north":{"uv":[0,0,0,0],"texture":null},"east":{"uv":[0,0,16,16],"texture":1},"south":{"uv":[0,0,0,0],"texture":null},"west":{"uv":[0,0,16,16],"texture":1},"up":{"uv":[0,0,0,0],"texture":null},"down":{"uv":[0,0,0,0],"texture":null}},"type":"cube","uuid":"3644a889-b638-1c56-700d-f80c64cc474c"}],"groups":[{"uuid":"eda1d026-dfe0-f980-152d-71db37d8a5b3","export":true,"locked":false,"origin":[3,-6.7,6],"rotation":[0,0,0],"color":0,"name":"Player","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"5399b7c3-2eac-47ae-fb88-873e7e24a5d6","export":true,"locked":false,"origin":[8,16,8],"rotation":[0,0,0],"color":0,"name":"Head","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":false,"primary_selected":false},{"uuid":"0c759830-21cb-f6ec-e6d7-af3998e4bbd2","export":true,"locked":false,"origin":[8,11,8],"rotation":[0,0,0],"color":0,"name":"Body","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":false,"primary_selected":false},{"uuid":"b31348f5-2c4e-cfd0-cec3-4c11d4a5bee2","export":true,"locked":false,"origin":[5,15,6],"rotation":[0,0,0],"color":0,"name":"Left_Arm","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"eb158d47-d395-3084-947f-4101fa1eb833","export":true,"locked":false,"origin":[11,15,6],"rotation":[0,0,0],"color":0,"name":"Right_Arm","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"0022c2e4-2add-b938-2d45-0a8b8b95d53a","export":true,"locked":false,"origin":[7,13,7],"rotation":[0,0,0],"color":0,"name":"Left_Leg","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"c752460c-4cd8-aaae-17c7-d45bf46741e4","export":true,"locked":false,"origin":[10,13,7],"rotation":[0,0,0],"color":0,"name":"Right_Leg","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":false,"primary_selected":false},{"uuid":"6e676a00-56e1-2ec4-ecb9-00710aada58d","export":false,"locked":false,"origin":[0,4,2.5],"rotation":[0,0,0],"color":0,"name":"flower_item","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":false,"autouv":0,"isOpen":true,"primary_selected":false}],"outliner":[{"uuid":"eda1d026-dfe0-f980-152d-71db37d8a5b3","isOpen":true,"children":[{"uuid":"5399b7c3-2eac-47ae-fb88-873e7e24a5d6","isOpen":false,"children":["4034240c-1bb7-3096-56d0-f9d9158bd705","5eca2c83-4481-ddf5-fafe-19abeb647c57"]},{"uuid":"0c759830-21cb-f6ec-e6d7-af3998e4bbd2","isOpen":false,"children":["8ea34c7a-dcd8-ead4-07d1-ccab4438046e","fd16d848-4320-9a3f-6b3d-3f4884a92fbe"]},{"uuid":"b31348f5-2c4e-cfd0-cec3-4c11d4a5bee2","isOpen":true,"children":["06ccbe71-f249-9062-076b-ea3f530fa945","15980f9e-f82b-d64a-eaa4-38789e0635cc"]},{"uuid":"eb158d47-d395-3084-947f-4101fa1eb833","isOpen":true,"children":["7b6c1629-5993-798e-8a6e-bb9303f0e8da","e5bd3372-f4e5-9cc0-1f38-726db3f86001"]},{"uuid":"0022c2e4-2add-b938-2d45-0a8b8b95d53a","isOpen":true,"children":["859ab26f-304d-1fc2-e695-7c65fa9746c5","1fb83a23-bc95-d6de-1a60-b721c8922407"]},{"uuid":"c752460c-4cd8-aaae-17c7-d45bf46741e4","isOpen":false,"children":["7fb46a5a-407b-6e68-5395-edf7ec58c151","2977e3b7-0d36-5caf-3a69-4c0fb7895a71"]}]},{"uuid":"6e676a00-56e1-2ec4-ecb9-00710aada58d","isOpen":true,"children":["ba8da4eb-b48e-0c2f-8240-95d681f387ff","3644a889-b638-1c56-700d-f80c64cc474c"]}],"textures":[{"name":"author.png","relative_path":"H:/Download/2d9f724107b509db.png","folder":"H:/Download","namespace":"","id":"0","group":"","width":64,"height":64,"uv_width":16,"uv_height":16,"particle":true,"use_as_default":false,"layers_enabled":false,"sync_to_project":"db1cee84-e800-465d-d5f1-b3a244a724aa","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":true,"uuid":"f5a406a9-0e83-e8e7-427a-4cf3e6991f20","source":"data:image/png;base64,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"},{"name":"allium.png","path":"","folder":"","namespace":"","id":"1","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"b68aca7b-f859-4cdb-5c04-2e5b1fcfe0b6","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAHlBMVEUAAADoz/7Spva4eO1Vqy2mXuFSmi5Kjyh7TqAXfAQSwnSGAAAAAXRSTlMAQObYZgAAADlJREFUeNpjwAqUAyA0k5AFhKGoGNQAZhgZmUIYrMrBUMXtBlAGO0w7G4zBgsFgQzDQdXEyMDAwAACVCARQ+FH48gAAAABJRU5ErkJggg=="},{"name":"azure_bluet.png","path":"","folder":"","namespace":"","id":"2","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"80bfc883-e993-6956-527a-1979a672b3fa","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAvklEQVQ4y2NgGAWDEGit+81wb9dvhv2lvxmuvXjx/8mrTwxam/4waG74y6C25Dd+zY5v/zJYPf3L8P+N///v37//B9EgQ461/mbYEv2bwfDeX8IugNkcNEvvf+hq3f/3DjcxwAwj6AKYzZ8+zQEbAmKDMMggEJ+g7TDFN+49Y/Dq1wBrAhkKcg1RAQiyGaQRZAhIU/4WXwaYZpg3iDIIphgWDiAxor0BMwCkGKQJhslKEzCvkJ2oQOEBCgd8AAB7qakILgzq0QAAAABJRU5ErkJggg=="}],"display":{"thirdperson_righthand":{"rotation":[75,45,0],"translation":[0,2.5,0],"scale":[0.375,0.375,0.375]},"thirdperson_lefthand":{"rotation":[75,45,0],"translation":[0,2.5,0],"scale":[0.375,0.375,0.375]},"firstperson_righthand":{"rotation":[0,124,0],"translation":[2,3,0],"scale":[0.4,0.4,0.4]},"firstperson_lefthand":{"rotation":[0,120,0],"translation":[1.5,2.75,0],"scale":[0.4,0.4,0.4]},"ground":{"translation":[0,2,0],"scale":[0.5,0.5,0.5]},"gui":{"rotation":[30,-135,0],"translation":[0.75,-1,0],"scale":[0.625,0.625,0.625]},"head":{"translation":[0,14,-0.75]},"fixed":{"translation":[0,0,-2.75],"scale":[0.5,0.5,0.5]},"on_shelf":{"rotation":[0,-180,0],"translation":[0,0,5.25]}}} \ No newline at end of file diff --git a/res/doll_without_item.json b/res/doll_without_item.json new file mode 100644 index 0000000..e4333a5 --- /dev/null +++ b/res/doll_without_item.json @@ -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] + } + ] + } + ] +} \ No newline at end of file diff --git a/res/model.bbmodel b/res/model.bbmodel new file mode 100644 index 0000000..2a4f6cd --- /dev/null +++ b/res/model.bbmodel @@ -0,0 +1 @@ +{"meta":{"format_version":"5.0","model_format":"free","box_uv":true},"name":"model","model_identifier":"","visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"timeline_setups":[],"unhandled_root_fields":{},"resolution":{"width":64,"height":64},"elements":[{"name":"Head","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-4,24,-4],"to":[4,32,4],"autouv":0,"color":0,"origin":[0,0,0],"faces":{"north":{"uv":[8,8,16,16],"texture":0},"east":{"uv":[0,8,8,16],"texture":0},"south":{"uv":[24,8,32,16],"texture":0},"west":{"uv":[16,8,24,16],"texture":0},"up":{"uv":[16,8,8,0],"texture":0},"down":{"uv":[24,0,16,8],"texture":0}},"type":"cube","uuid":"ecbccf02-17d8-13e5-651b-97fd3f72ed50"},{"name":"Hat Layer","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-4,24,-4],"to":[4,32,4],"autouv":0,"color":0,"inflate":0.5,"origin":[0,0,0],"uv_offset":[32,0],"faces":{"north":{"uv":[40,8,48,16],"texture":0},"east":{"uv":[32,8,40,16],"texture":0},"south":{"uv":[56,8,64,16],"texture":0},"west":{"uv":[48,8,56,16],"texture":0},"up":{"uv":[48,8,40,0],"texture":0},"down":{"uv":[56,0,48,8],"texture":0}},"type":"cube","uuid":"354012ee-7f44-9c57-6484-261bb7a9e271"},{"name":"Body","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-4,12,-2],"to":[4,24,2],"autouv":0,"color":0,"origin":[0,0,0],"uv_offset":[16,16],"faces":{"north":{"uv":[20,20,28,32],"texture":0},"east":{"uv":[16,20,20,32],"texture":0},"south":{"uv":[32,20,40,32],"texture":0},"west":{"uv":[28,20,32,32],"texture":0},"up":{"uv":[28,20,20,16],"texture":0},"down":{"uv":[36,16,28,20],"texture":0}},"type":"cube","uuid":"3d9de341-9979-34f0-cbad-9f44b802a10b"},{"name":"Body Layer","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-4,12,-2],"to":[4,24,2],"autouv":0,"color":0,"inflate":0.25,"origin":[0,0,0],"uv_offset":[16,32],"faces":{"north":{"uv":[20,36,28,48],"texture":0},"east":{"uv":[16,36,20,48],"texture":0},"south":{"uv":[32,36,40,48],"texture":0},"west":{"uv":[28,36,32,48],"texture":0},"up":{"uv":[28,36,20,32],"texture":0},"down":{"uv":[36,32,28,36],"texture":0}},"type":"cube","uuid":"c98332cc-c990-4a78-cb50-fd4f797213bb"},{"name":"Right Arm","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[4,12,-2],"to":[7,24,2],"autouv":0,"color":0,"origin":[0,0,0],"uv_offset":[40,16],"faces":{"north":{"uv":[44,20,47,32],"texture":0},"east":{"uv":[40,20,44,32],"texture":0},"south":{"uv":[51,20,54,32],"texture":0},"west":{"uv":[47,20,51,32],"texture":0},"up":{"uv":[47,20,44,16],"texture":0},"down":{"uv":[50,16,47,20],"texture":0}},"type":"cube","uuid":"aaae9fa5-faf3-ee54-9182-5f7f2da53878"},{"name":"Right Arm Layer","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[4,12,-2],"to":[7,24,2],"autouv":0,"color":0,"inflate":0.25,"origin":[0,0,0],"uv_offset":[40,32],"faces":{"north":{"uv":[44,36,47,48],"texture":0},"east":{"uv":[40,36,44,48],"texture":0},"south":{"uv":[51,36,54,48],"texture":0},"west":{"uv":[47,36,51,48],"texture":0},"up":{"uv":[47,36,44,32],"texture":0},"down":{"uv":[50,32,47,36],"texture":0}},"type":"cube","uuid":"1ff0636c-2e37-5ab0-42e6-6d25b317eda4"},{"name":"Left Arm","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-7,12,-2],"to":[-4,24,2],"autouv":0,"color":0,"origin":[0,0,0],"uv_offset":[32,48],"faces":{"north":{"uv":[36,52,39,64],"texture":0},"east":{"uv":[32,52,36,64],"texture":0},"south":{"uv":[43,52,46,64],"texture":0},"west":{"uv":[39,52,43,64],"texture":0},"up":{"uv":[39,52,36,48],"texture":0},"down":{"uv":[42,48,39,52],"texture":0}},"type":"cube","uuid":"862c4ddf-a5e7-0436-7b4f-9b8b6a0ab813"},{"name":"Left Arm Layer","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-7,12,-2],"to":[-4,24,2],"autouv":0,"color":0,"inflate":0.25,"origin":[0,0,0],"uv_offset":[48,48],"faces":{"north":{"uv":[52,52,55,64],"texture":0},"east":{"uv":[48,52,52,64],"texture":0},"south":{"uv":[59,52,62,64],"texture":0},"west":{"uv":[55,52,59,64],"texture":0},"up":{"uv":[55,52,52,48],"texture":0},"down":{"uv":[58,48,55,52],"texture":0}},"type":"cube","uuid":"e17ef606-508e-443e-0070-a4c2f25ca168"},{"name":"Right Leg","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-0.1,0,-2],"to":[3.9,12,2],"autouv":0,"color":0,"origin":[0,0,0],"uv_offset":[0,16],"faces":{"north":{"uv":[4,20,8,32],"texture":0},"east":{"uv":[0,20,4,32],"texture":0},"south":{"uv":[12,20,16,32],"texture":0},"west":{"uv":[8,20,12,32],"texture":0},"up":{"uv":[8,20,4,16],"texture":0},"down":{"uv":[12,16,8,20],"texture":0}},"type":"cube","uuid":"12a1223d-2d3e-ad8e-d713-ff7b590e9190"},{"name":"Right Leg Layer","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-0.1,0,-2],"to":[3.9,12,2],"autouv":0,"color":0,"inflate":0.25,"origin":[0,0,0],"uv_offset":[0,32],"faces":{"north":{"uv":[4,36,8,48],"texture":0},"east":{"uv":[0,36,4,48],"texture":0},"south":{"uv":[12,36,16,48],"texture":0},"west":{"uv":[8,36,12,48],"texture":0},"up":{"uv":[8,36,4,32],"texture":0},"down":{"uv":[12,32,8,36],"texture":0}},"type":"cube","uuid":"86c41d76-bd1e-c435-ebf2-8738e7b52c4b"},{"name":"Left Leg","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-3.9,0,-2],"to":[0.1,12,2],"autouv":0,"color":0,"origin":[0,0,0],"uv_offset":[16,48],"faces":{"north":{"uv":[20,52,24,64],"texture":0},"east":{"uv":[16,52,20,64],"texture":0},"south":{"uv":[28,52,32,64],"texture":0},"west":{"uv":[24,52,28,64],"texture":0},"up":{"uv":[24,52,20,48],"texture":0},"down":{"uv":[28,48,24,52],"texture":0}},"type":"cube","uuid":"6232e1ad-27b0-6116-94ff-405cf1479a71"},{"name":"Left Leg Layer","box_uv":true,"render_order":"default","locked":false,"allow_mirror_modeling":true,"from":[-3.9,0,-2],"to":[0.1,12,2],"autouv":0,"color":0,"inflate":0.25,"origin":[0,0,0],"uv_offset":[0,48],"faces":{"north":{"uv":[4,52,8,64],"texture":0},"east":{"uv":[0,52,4,64],"texture":0},"south":{"uv":[12,52,16,64],"texture":0},"west":{"uv":[8,52,12,64],"texture":0},"up":{"uv":[8,52,4,48],"texture":0},"down":{"uv":[12,48,8,52],"texture":0}},"type":"cube","uuid":"ec99bc3f-8466-3550-174c-a4d2e97a4c60"}],"groups":[{"uuid":"bff4eb5d-5156-45ba-55a0-e606ac5ee407","export":true,"locked":false,"origin":[0,12,0],"rotation":[0,0,0],"color":0,"name":"Waist","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"d3a4765a-9e70-2832-b06c-f83e1e86751f","export":true,"locked":false,"origin":[0,24,0],"rotation":[0,0,0],"color":0,"name":"Head","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"67cfc0fc-3f6d-0b85-c627-dd87f8aa31fc","export":true,"locked":false,"origin":[0,24,0],"rotation":[0,0,0],"color":0,"name":"Body","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"031da658-7419-e3d1-ce01-1f31a531da71","export":true,"locked":false,"origin":[5,22,0],"rotation":[0,0,0],"color":0,"name":"Right Arm","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":true},{"uuid":"f9046dbf-effe-caae-f779-52fb1f9f1641","export":true,"locked":false,"origin":[-5,22,0],"rotation":[0,0,0],"color":0,"name":"Left Arm","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"f95be5e7-1abd-a6d9-0a8b-4a358bd90bf1","export":true,"locked":false,"origin":[1.9,12,0],"rotation":[0,0,0],"color":0,"name":"Right Leg","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false},{"uuid":"1ca1fd5f-01f4-830b-edaa-8b64993d94c2","export":true,"locked":false,"origin":[-1.9,12,0],"rotation":[0,0,0],"color":0,"name":"Left Leg","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":false}],"outliner":[{"uuid":"bff4eb5d-5156-45ba-55a0-e606ac5ee407","isOpen":true,"children":[{"uuid":"d3a4765a-9e70-2832-b06c-f83e1e86751f","isOpen":true,"children":["ecbccf02-17d8-13e5-651b-97fd3f72ed50","354012ee-7f44-9c57-6484-261bb7a9e271"]},{"uuid":"67cfc0fc-3f6d-0b85-c627-dd87f8aa31fc","isOpen":true,"children":["3d9de341-9979-34f0-cbad-9f44b802a10b","c98332cc-c990-4a78-cb50-fd4f797213bb"]},{"uuid":"f9046dbf-effe-caae-f779-52fb1f9f1641","isOpen":true,"children":["862c4ddf-a5e7-0436-7b4f-9b8b6a0ab813","e17ef606-508e-443e-0070-a4c2f25ca168"]},{"uuid":"031da658-7419-e3d1-ce01-1f31a531da71","isOpen":true,"children":["aaae9fa5-faf3-ee54-9182-5f7f2da53878","1ff0636c-2e37-5ab0-42e6-6d25b317eda4"]}]},{"uuid":"f95be5e7-1abd-a6d9-0a8b-4a358bd90bf1","isOpen":true,"children":["12a1223d-2d3e-ad8e-d713-ff7b590e9190","86c41d76-bd1e-c435-ebf2-8738e7b52c4b"]},{"uuid":"1ca1fd5f-01f4-830b-edaa-8b64993d94c2","isOpen":true,"children":["6232e1ad-27b0-6116-94ff-405cf1479a71","ec99bc3f-8466-3550-174c-a4d2e97a4c60"]}],"textures":[{"name":"alex.png","relative_path":"alex.png","folder":"","namespace":"","id":"0","group":"","width":64,"height":64,"uv_width":64,"uv_height":64,"particle":false,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":true,"uuid":"977536e4-9ca7-ad3c-7604-f4bbce003c18","source":"data:image/png;base64,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"}]} \ No newline at end of file diff --git a/src/generated/resources/.cache/1de3d2ee724999f84a11b20b51c37030049be277 b/src/generated/resources/.cache/1de3d2ee724999f84a11b20b51c37030049be277 index 11d4f78..12bd838 100644 --- a/src/generated/resources/.cache/1de3d2ee724999f84a11b20b51c37030049be277 +++ b/src/generated/resources/.cache/1de3d2ee724999f84a11b20b51c37030049be277 @@ -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 diff --git a/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac b/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac index b49706b..9dd270f 100644 --- a/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac +++ b/src/generated/resources/.cache/2a65ee2815744be1ef1ffdae1c9a37f2a9cbe2ac @@ -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 diff --git a/src/generated/resources/.cache/2dbf84d84cf6f7b7a95fea9038e192dbf226e5f5 b/src/generated/resources/.cache/2dbf84d84cf6f7b7a95fea9038e192dbf226e5f5 index 5a2f35d..3475ed2 100644 --- a/src/generated/resources/.cache/2dbf84d84cf6f7b7a95fea9038e192dbf226e5f5 +++ b/src/generated/resources/.cache/2dbf84d84cf6f7b7a95fea9038e192dbf226e5f5 @@ -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 diff --git a/src/generated/resources/.cache/3473f48ad2b0675418886c7a50b38f1c5da41e76 b/src/generated/resources/.cache/3473f48ad2b0675418886c7a50b38f1c5da41e76 new file mode 100644 index 0000000..03a667e --- /dev/null +++ b/src/generated/resources/.cache/3473f48ad2b0675418886c7a50b38f1c5da41e76 @@ -0,0 +1,2 @@ +// 1.20.1 2025-12-22T20:31:52.804071 Block States: lib39 +1dda476533f87cc377e800d537c22b48509a25cf assets/lib39/blockstates/doll.json diff --git a/src/generated/resources/.cache/82018c5420b46ddbb7071e62df09fdecd98133e6 b/src/generated/resources/.cache/82018c5420b46ddbb7071e62df09fdecd98133e6 index ae31860..1ba2e45 100644 --- a/src/generated/resources/.cache/82018c5420b46ddbb7071e62df09fdecd98133e6 +++ b/src/generated/resources/.cache/82018c5420b46ddbb7071e62df09fdecd98133e6 @@ -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 diff --git a/src/generated/resources/.cache/bc76051cc963abe6835a2599f9f7cc4f1a1b0fb5 b/src/generated/resources/.cache/bc76051cc963abe6835a2599f9f7cc4f1a1b0fb5 new file mode 100644 index 0000000..a356d87 --- /dev/null +++ b/src/generated/resources/.cache/bc76051cc963abe6835a2599f9f7cc4f1a1b0fb5 @@ -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 diff --git a/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 b/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 index 851a125..2537aba 100644 --- a/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 +++ b/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 @@ -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 diff --git a/src/generated/resources/assets/lib39/blockstates/doll.json b/src/generated/resources/assets/lib39/blockstates/doll.json new file mode 100644 index 0000000..b51ecad --- /dev/null +++ b/src/generated/resources/assets/lib39/blockstates/doll.json @@ -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" + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/lang/en_us.json b/src/generated/resources/assets/lib39/lang/en_us.json index 4b76986..ecda3c4 100644 --- a/src/generated/resources/assets/lib39/lang/en_us.json +++ b/src/generated/resources/assets/lib39/lang/en_us.json @@ -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" diff --git a/src/generated/resources/assets/lib39/lang/lzh.json b/src/generated/resources/assets/lib39/lang/lzh.json index 6af15d1..378e4c5 100644 --- a/src/generated/resources/assets/lib39/lang/lzh.json +++ b/src/generated/resources/assets/lib39/lang/lzh.json @@ -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": "狸" diff --git a/src/generated/resources/assets/lib39/lang/zh_cn.json b/src/generated/resources/assets/lib39/lang/zh_cn.json index ce5d7ed..e9d69dd 100644 --- a/src/generated/resources/assets/lib39/lang/zh_cn.json +++ b/src/generated/resources/assets/lib39/lang/zh_cn.json @@ -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": "小狐狸" diff --git a/src/generated/resources/assets/lib39/lang/zh_tw.json b/src/generated/resources/assets/lib39/lang/zh_tw.json index 6c8a25d..dbc836b 100644 --- a/src/generated/resources/assets/lib39/lang/zh_tw.json +++ b/src/generated/resources/assets/lib39/lang/zh_tw.json @@ -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": "狐狸" diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/acacia_sapling.json b/src/generated/resources/assets/lib39/models/block/doll_item/acacia_sapling.json new file mode 100644 index 0000000..0b228e2 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/acacia_sapling.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/acacia_sapling" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/allium.json b/src/generated/resources/assets/lib39/models/block/doll_item/allium.json new file mode 100644 index 0000000..3908a81 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/allium.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/allium" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/amethyst_cluster.json b/src/generated/resources/assets/lib39/models/block/doll_item/amethyst_cluster.json new file mode 100644 index 0000000..aca29a7 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/amethyst_cluster.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/amethyst_cluster" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/azure_bluet.json b/src/generated/resources/assets/lib39/models/block/doll_item/azure_bluet.json new file mode 100644 index 0000000..18af821 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/azure_bluet.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/azure_bluet" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/bamboo.json b/src/generated/resources/assets/lib39/models/block/doll_item/bamboo.json new file mode 100644 index 0000000..98f205a --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/bamboo.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/bamboo_stage0" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/birch_sapling.json b/src/generated/resources/assets/lib39/models/block/doll_item/birch_sapling.json new file mode 100644 index 0000000..44b393a --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/birch_sapling.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/birch_sapling" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/brain_coral.json b/src/generated/resources/assets/lib39/models/block/doll_item/brain_coral.json new file mode 100644 index 0000000..1b2fdf4 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/brain_coral.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/brain_coral" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/brain_coral_fan.json b/src/generated/resources/assets/lib39/models/block/doll_item/brain_coral_fan.json new file mode 100644 index 0000000..93a0edb --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/brain_coral_fan.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/brain_coral_fan" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/brown_mushroom.json b/src/generated/resources/assets/lib39/models/block/doll_item/brown_mushroom.json new file mode 100644 index 0000000..44f9b25 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/brown_mushroom.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/brown_mushroom" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/bubble_coral.json b/src/generated/resources/assets/lib39/models/block/doll_item/bubble_coral.json new file mode 100644 index 0000000..08deddc --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/bubble_coral.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/bubble_coral" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/bubble_coral_fan.json b/src/generated/resources/assets/lib39/models/block/doll_item/bubble_coral_fan.json new file mode 100644 index 0000000..fb03a93 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/bubble_coral_fan.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/bubble_coral_fan" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/cherry_sapling.json b/src/generated/resources/assets/lib39/models/block/doll_item/cherry_sapling.json new file mode 100644 index 0000000..bf523a1 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/cherry_sapling.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/cherry_sapling" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/cobweb.json b/src/generated/resources/assets/lib39/models/block/doll_item/cobweb.json new file mode 100644 index 0000000..85338b2 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/cobweb.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/cobweb" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/cornflower.json b/src/generated/resources/assets/lib39/models/block/doll_item/cornflower.json new file mode 100644 index 0000000..375321d --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/cornflower.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/cornflower" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/crimson_fungus.json b/src/generated/resources/assets/lib39/models/block/doll_item/crimson_fungus.json new file mode 100644 index 0000000..38a07c4 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/crimson_fungus.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/crimson_fungus" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/crimson_roots_pot.json b/src/generated/resources/assets/lib39/models/block/doll_item/crimson_roots_pot.json new file mode 100644 index 0000000..cacb194 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/crimson_roots_pot.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/crimson_roots_pot" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/dandelion.json b/src/generated/resources/assets/lib39/models/block/doll_item/dandelion.json new file mode 100644 index 0000000..1e0c4a3 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/dandelion.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/dandelion" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/dark_oak_sapling.json b/src/generated/resources/assets/lib39/models/block/doll_item/dark_oak_sapling.json new file mode 100644 index 0000000..890cadd --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/dark_oak_sapling.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/dark_oak_sapling" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/dead_brain_coral.json b/src/generated/resources/assets/lib39/models/block/doll_item/dead_brain_coral.json new file mode 100644 index 0000000..a386a74 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/dead_brain_coral.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/dead_brain_coral" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/dead_brain_coral_fan.json b/src/generated/resources/assets/lib39/models/block/doll_item/dead_brain_coral_fan.json new file mode 100644 index 0000000..23eb1fa --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/dead_brain_coral_fan.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/dead_brain_coral_fan" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/dead_bubble_coral.json b/src/generated/resources/assets/lib39/models/block/doll_item/dead_bubble_coral.json new file mode 100644 index 0000000..909c606 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/dead_bubble_coral.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/dead_bubble_coral" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/dead_bubble_coral_fan.json b/src/generated/resources/assets/lib39/models/block/doll_item/dead_bubble_coral_fan.json new file mode 100644 index 0000000..4210e2d --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/dead_bubble_coral_fan.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/dead_bubble_coral_fan" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/dead_bush.json b/src/generated/resources/assets/lib39/models/block/doll_item/dead_bush.json new file mode 100644 index 0000000..2076266 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/dead_bush.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/dead_bush" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/dead_fire_coral.json b/src/generated/resources/assets/lib39/models/block/doll_item/dead_fire_coral.json new file mode 100644 index 0000000..245115b --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/dead_fire_coral.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/dead_fire_coral" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/dead_fire_coral_fan.json b/src/generated/resources/assets/lib39/models/block/doll_item/dead_fire_coral_fan.json new file mode 100644 index 0000000..b23332e --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/dead_fire_coral_fan.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/dead_fire_coral_fan" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/dead_horn_coral.json b/src/generated/resources/assets/lib39/models/block/doll_item/dead_horn_coral.json new file mode 100644 index 0000000..1ead7ba --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/dead_horn_coral.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/dead_horn_coral" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/dead_horn_coral_fan.json b/src/generated/resources/assets/lib39/models/block/doll_item/dead_horn_coral_fan.json new file mode 100644 index 0000000..0ac11b1 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/dead_horn_coral_fan.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/dead_horn_coral_fan" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/dead_tube_coral.json b/src/generated/resources/assets/lib39/models/block/doll_item/dead_tube_coral.json new file mode 100644 index 0000000..a5063d4 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/dead_tube_coral.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/dead_tube_coral" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/dead_tube_coral_fan.json b/src/generated/resources/assets/lib39/models/block/doll_item/dead_tube_coral_fan.json new file mode 100644 index 0000000..f855c16 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/dead_tube_coral_fan.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/dead_tube_coral_fan" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/fire_coral.json b/src/generated/resources/assets/lib39/models/block/doll_item/fire_coral.json new file mode 100644 index 0000000..1523857 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/fire_coral.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/fire_coral" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/fire_coral_fan.json b/src/generated/resources/assets/lib39/models/block/doll_item/fire_coral_fan.json new file mode 100644 index 0000000..353cbbd --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/fire_coral_fan.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/fire_coral_fan" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/horn_coral.json b/src/generated/resources/assets/lib39/models/block/doll_item/horn_coral.json new file mode 100644 index 0000000..7dd2a78 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/horn_coral.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/horn_coral" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/horn_coral_fan.json b/src/generated/resources/assets/lib39/models/block/doll_item/horn_coral_fan.json new file mode 100644 index 0000000..b14dce7 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/horn_coral_fan.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/horn_coral_fan" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/jungle_sapling.json b/src/generated/resources/assets/lib39/models/block/doll_item/jungle_sapling.json new file mode 100644 index 0000000..f72ea68 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/jungle_sapling.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/jungle_sapling" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/lily_of_the_valley.json b/src/generated/resources/assets/lib39/models/block/doll_item/lily_of_the_valley.json new file mode 100644 index 0000000..9c4ec89 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/lily_of_the_valley.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/lily_of_the_valley" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/oak_sapling.json b/src/generated/resources/assets/lib39/models/block/doll_item/oak_sapling.json new file mode 100644 index 0000000..df4e752 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/oak_sapling.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/oak_sapling" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/orange_tulip.json b/src/generated/resources/assets/lib39/models/block/doll_item/orange_tulip.json new file mode 100644 index 0000000..400ca0c --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/orange_tulip.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/orange_tulip" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/oxeye_daisy.json b/src/generated/resources/assets/lib39/models/block/doll_item/oxeye_daisy.json new file mode 100644 index 0000000..0764ab5 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/oxeye_daisy.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/oxeye_daisy" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/pink_tulip.json b/src/generated/resources/assets/lib39/models/block/doll_item/pink_tulip.json new file mode 100644 index 0000000..60bff59 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/pink_tulip.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/pink_tulip" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/poppy.json b/src/generated/resources/assets/lib39/models/block/doll_item/poppy.json new file mode 100644 index 0000000..0f1653d --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/poppy.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/poppy" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/red_mushroom.json b/src/generated/resources/assets/lib39/models/block/doll_item/red_mushroom.json new file mode 100644 index 0000000..520fc77 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/red_mushroom.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/red_mushroom" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/red_tulip.json b/src/generated/resources/assets/lib39/models/block/doll_item/red_tulip.json new file mode 100644 index 0000000..67b20fc --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/red_tulip.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/red_tulip" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/redstone_torch.json b/src/generated/resources/assets/lib39/models/block/doll_item/redstone_torch.json new file mode 100644 index 0000000..4c69b2e --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/redstone_torch.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/redstone_torch" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/spruce_sapling.json b/src/generated/resources/assets/lib39/models/block/doll_item/spruce_sapling.json new file mode 100644 index 0000000..cec0ad5 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/spruce_sapling.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/spruce_sapling" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/torch.json b/src/generated/resources/assets/lib39/models/block/doll_item/torch.json new file mode 100644 index 0000000..eb3d62a --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/torch.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/torch" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/tube_coral.json b/src/generated/resources/assets/lib39/models/block/doll_item/tube_coral.json new file mode 100644 index 0000000..5aca852 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/tube_coral.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/tube_coral" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/tube_coral_fan.json b/src/generated/resources/assets/lib39/models/block/doll_item/tube_coral_fan.json new file mode 100644 index 0000000..445b436 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/tube_coral_fan.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/tube_coral_fan" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/warped_fungus.json b/src/generated/resources/assets/lib39/models/block/doll_item/warped_fungus.json new file mode 100644 index 0000000..4959ad1 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/warped_fungus.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/warped_fungus" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/warped_roots_pot.json b/src/generated/resources/assets/lib39/models/block/doll_item/warped_roots_pot.json new file mode 100644 index 0000000..a566b5c --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/warped_roots_pot.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/warped_roots_pot" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/white_tulip.json b/src/generated/resources/assets/lib39/models/block/doll_item/white_tulip.json new file mode 100644 index 0000000..8864aba --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/white_tulip.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/white_tulip" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/block/doll_item/wither_rose.json b/src/generated/resources/assets/lib39/models/block/doll_item/wither_rose.json new file mode 100644 index 0000000..87e7611 --- /dev/null +++ b/src/generated/resources/assets/lib39/models/block/doll_item/wither_rose.json @@ -0,0 +1,7 @@ +{ + "parent": "lib39:block/base_doll_item", + "ambientocclusion": false, + "textures": { + "item": "minecraft:block/wither_rose" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/lib39/models/item/doll.json b/src/generated/resources/assets/lib39/models/item/doll.json new file mode 100644 index 0000000..7f6e87a --- /dev/null +++ b/src/generated/resources/assets/lib39/models/item/doll.json @@ -0,0 +1,3 @@ +{ + "parent": "lib39:block/base_doll" +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/lib39/Lib39.java b/src/main/java/top/r3944realms/lib39/Lib39.java index 238d4fa..91842ef 100644 --- a/src/main/java/top/r3944realms/lib39/Lib39.java +++ b/src/main/java/top/r3944realms/lib39/Lib39.java @@ -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. */ diff --git a/src/main/java/top/r3944realms/lib39/api/event/MinecraftSetUpServiceEvent.java b/src/main/java/top/r3944realms/lib39/api/event/MinecraftSetUpServiceEvent.java new file mode 100644 index 0000000..cf2936c --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/api/event/MinecraftSetUpServiceEvent.java @@ -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; + } +} diff --git a/src/main/java/top/r3944realms/lib39/api/event/RegisterCommandHelpEvent.java b/src/main/java/top/r3944realms/lib39/api/event/RegisterCommandHelpEvent.java index e2286d6..248eb31 100644 --- a/src/main/java/top/r3944realms/lib39/api/event/RegisterCommandHelpEvent.java +++ b/src/main/java/top/r3944realms/lib39/api/event/RegisterCommandHelpEvent.java @@ -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 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); } } diff --git a/src/main/java/top/r3944realms/lib39/base/command/Lib39CommandHelpManager.java b/src/main/java/top/r3944realms/lib39/base/command/Lib39CommandHelpManager.java index 06779de..bbc6fc4 100644 --- a/src/main/java/top/r3944realms/lib39/base/command/Lib39CommandHelpManager.java +++ b/src/main/java/top/r3944realms/lib39/base/command/Lib39CommandHelpManager.java @@ -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; diff --git a/src/main/java/top/r3944realms/lib39/base/command/Lib39HelpCommand.java b/src/main/java/top/r3944realms/lib39/base/command/Lib39HelpCommand.java index 4cd2b89..b8cc218 100644 --- a/src/main/java/top/r3944realms/lib39/base/command/Lib39HelpCommand.java +++ b/src/main/java/top/r3944realms/lib39/base/command/Lib39HelpCommand.java @@ -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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 context) { + CommandSourceStack source = context.getSource(); + + source.sendSuccess(() -> + Component.translatable("commands.lib39.game.resume.success") + .withStyle(net.minecraft.ChatFormatting.GREEN), + false + ); + + return Command.SINGLE_SUCCESS; + } } diff --git a/src/main/java/top/r3944realms/lib39/base/datagen/Lib39BaseDataGenEvent.java b/src/main/java/top/r3944realms/lib39/base/datagen/Lib39BaseDataGenEvent.java index 756f6ec..a4eb05b 100644 --- a/src/main/java/top/r3944realms/lib39/base/datagen/Lib39BaseDataGenEvent.java +++ b/src/main/java/top/r3944realms/lib39/base/datagen/Lib39BaseDataGenEvent.java @@ -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) 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) pOutput -> new ExItemModelProvider(pOutput, event.getExistingFileHelper()) + (DataProvider.Factory) pOutput -> new Lib39ItemModelProvider(pOutput, event.getExistingFileHelper()) + ); + } + private static void BlockModelDataGenerate(@NotNull GatherDataEvent event) { + event.getGenerator().addProvider( + event.includeClient(), + (DataProvider.Factory) pOutput -> new Lib39BlockModelProvider(pOutput, event.getExistingFileHelper()) + ); + } + private static void BlockStateDataGenerate(@NotNull GatherDataEvent event) { + event.getGenerator().addProvider( + event.includeClient(), + (DataProvider.Factory) pOutput -> new Lib39BlockStatesProvider(pOutput, event.getExistingFileHelper()) ); } } diff --git a/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39BlockModelProvider.java b/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39BlockModelProvider.java new file mode 100644 index 0000000..fcd0572 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39BlockModelProvider.java @@ -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); + } +} diff --git a/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39BlockStatesProvider.java b/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39BlockStatesProvider.java new file mode 100644 index 0000000..c08dc03 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39BlockStatesProvider.java @@ -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; + }; + } +} diff --git a/src/main/java/top/r3944realms/lib39/base/datagen/provider/ExItemModelProvider.java b/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39ItemModelProvider.java similarity index 85% rename from src/main/java/top/r3944realms/lib39/base/datagen/provider/ExItemModelProvider.java rename to src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39ItemModelProvider.java index b4a07ac..0692f71 100644 --- a/src/main/java/top/r3944realms/lib39/base/datagen/provider/ExItemModelProvider.java +++ b/src/main/java/top/r3944realms/lib39/base/datagen/provider/Lib39ItemModelProvider.java @@ -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 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
 先有纹理才会成功构建 */ - 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. * diff --git a/src/main/java/top/r3944realms/lib39/base/datagen/value/Lib39LangKey.java b/src/main/java/top/r3944realms/lib39/base/datagen/value/Lib39LangKey.java index 5648840..7f5db0d 100644 --- a/src/main/java/top/r3944realms/lib39/base/datagen/value/Lib39LangKey.java +++ b/src/main/java/top/r3944realms/lib39/base/datagen/value/Lib39LangKey.java @@ -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 getValues() { return List.copyOf(langKeyValues); } + @SuppressWarnings("unused") + public static final class TestMessage { + private static final Set 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 addConsumer = items::add; + + private static LangKeyValue addAndRet(LangKeyValue item) { + items.add(item); + return item; + } + + public static Set getItems() { + return items; + } + } } diff --git a/src/main/java/top/r3944realms/lib39/client/renderer/block/DollBlockEntityRenderer.java b/src/main/java/top/r3944realms/lib39/client/renderer/block/DollBlockEntityRenderer.java new file mode 100644 index 0000000..cd428d3 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/client/renderer/block/DollBlockEntityRenderer.java @@ -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 { + public BakedModel dollWithoutItemModel; + public BakedModel dollNeedItemModel; + public final Map 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(); + } +} diff --git a/src/main/java/top/r3944realms/lib39/client/renderer/item/DollItemRenderer.java b/src/main/java/top/r3944realms/lib39/client/renderer/item/DollItemRenderer.java new file mode 100644 index 0000000..0e25200 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/client/renderer/item/DollItemRenderer.java @@ -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 cache = new ConcurrentHashMap<>(); + private final Set 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 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 itemTextureCache = new ConcurrentHashMap<>(); + private final Map skinTextureCache = new ConcurrentHashMap<>(); + private final TextureCacheEntry defaultSkinTexture; + + // 花材质映射(静态初始化) + private static final Map ITEM_TEXTURE_MAP = createItemTextureMap(); + + private static @Unmodifiable Map createItemTextureMap() { + Map 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 itemType = new AtomicReference<>(); + AtomicReference 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(); + } +} diff --git a/src/main/java/top/r3944realms/lib39/content/block/DollBlock.java b/src/main/java/top/r3944realms/lib39/content/block/DollBlock.java new file mode 100644 index 0000000..e2edbba --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/content/block/DollBlock.java @@ -0,0 +1,161 @@ +package top.r3944realms.lib39.content.block; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.level.block.state.properties.NoteBlockInstrument; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.lib39.content.block.property.DollPose; +import top.r3944realms.lib39.content.register.Lib39BlockEntities; + +@SuppressWarnings("deprecation") +public class DollBlock extends HorizontalDirectionalBlock implements SimpleWaterloggedBlock, EntityBlock { + private static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; + public static final EnumProperty POSE = EnumProperty.create("pose", DollPose.class); + + private static final VoxelShape DOLL_SHAPE = Block.box(2.0d, 0.0d, 2.0d, 14.0d, 12.0d, 14.0d); + private static final Properties properties = Properties.of() + .instrument(NoteBlockInstrument.BASEDRUM) + .sound(SoundType.WOOL) + .strength(0f, 10f) + .noOcclusion(); + + private static final double PARTICLE_OFFSET_RANGE = 0.25; + private static final double PARTICLE_HEIGHT_OFFSET = 1.0; + private static final double PARTICLE_HEIGHT_VARIANCE = 0.2; + private static final float NOTE_COLOR_DIVISOR = 24.0F; + private static final int MAX_NOTE_COLORS = 4; + + private static final float BASE_VOLUME = 1.0f; + private static final float PITCH_VARIANCE = 0.5f; + private static final float BASE_PITCH = 0.75f; + + public DollBlock() { + super(properties); + this.registerDefaultState(this.stateDefinition.any().setValue(FACING, Direction.SOUTH) + .setValue(POSE, DollPose.DEFAULT) + .setValue(WATERLOGGED, false)); + } + + @Override + public @NotNull BlockState updateShape(@NotNull BlockState currentState, @NotNull Direction direction, @NotNull BlockState neighborState, + @NotNull LevelAccessor level, @NotNull BlockPos currentPos, @NotNull BlockPos neighborPos) { + if (currentState.getValue(WATERLOGGED)) { + level.scheduleTick(currentPos, Fluids.WATER, Fluids.WATER.getTickDelay(level)); + } + return super.updateShape(currentState, direction, neighborState, level, currentPos, neighborPos); + } + + @Override + public @NotNull FluidState getFluidState(@NotNull BlockState blockState) { + return blockState.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(blockState); + } + + @Override + public @NotNull InteractionResult use(@NotNull BlockState blockState, @NotNull Level level, @NotNull BlockPos blockPos, @NotNull Player player, + @NotNull InteractionHand hand, @NotNull BlockHitResult hitResult) { + if (level instanceof ServerLevel serverLevel) { + // 播放粒子效果 + spawnNoteParticles(serverLevel, blockPos); + // 播放音效 + playDollSound(serverLevel, blockPos); + } + return InteractionResult.SUCCESS; + } + + /** + * 在玩偶位置生成音符粒子效果 + */ + private void spawnNoteParticles(ServerLevel serverLevel, BlockPos blockPos) { + Vec3 particlePosition = calculateParticlePosition(serverLevel, blockPos); + float noteColor = calculateNoteColor(serverLevel); + + serverLevel.sendParticles(ParticleTypes.NOTE, + particlePosition.x(), particlePosition.y(), particlePosition.z(), + 0, noteColor, 0, 0, 1); + } + + /** + * 计算粒子生成位置,添加随机偏移 + */ + private @NotNull Vec3 calculateParticlePosition(@NotNull ServerLevel serverLevel, BlockPos blockPos) { + return Vec3.atBottomCenterOf(blockPos).add( + (serverLevel.getRandom().nextFloat() - 0.5) * PARTICLE_OFFSET_RANGE * 2, + PARTICLE_HEIGHT_OFFSET + serverLevel.getRandom().nextFloat() * PARTICLE_HEIGHT_VARIANCE, + (serverLevel.getRandom().nextFloat() - 0.5) * PARTICLE_OFFSET_RANGE * 2 + ); + } + + /** + * 计算音符粒子的颜色 + */ + private float calculateNoteColor(@NotNull ServerLevel serverLevel) { + return serverLevel.getRandom().nextInt(MAX_NOTE_COLORS) / NOTE_COLOR_DIVISOR; + } + + /** + * 播放玩偶音效 + */ + private void playDollSound(@NotNull ServerLevel serverLevel, BlockPos blockPos) { + float pitch = BASE_PITCH + serverLevel.random.nextFloat() * PITCH_VARIANCE; + serverLevel.playSound(null, blockPos, SoundEvents.NOTE_BLOCK_BASEDRUM.get(), + SoundSource.BLOCKS, BASE_VOLUME, pitch); + } + + @Override + public @Nullable BlockState getStateForPlacement(@NotNull BlockPlaceContext context) { + FluidState fluidState = context.getLevel().getFluidState(context.getClickedPos()); + boolean isWaterlogged = fluidState.getType() == Fluids.WATER; + + return this.defaultBlockState() + .setValue(FACING, context.getHorizontalDirection().getOpposite()) + .setValue(WATERLOGGED, isWaterlogged) + .setValue(POSE, DollPose.DEFAULT); + } + + @Override + public @NotNull VoxelShape getShape(@NotNull BlockState blockState, @NotNull BlockGetter level, @NotNull BlockPos blockPos, @NotNull CollisionContext context) { + return DOLL_SHAPE; + } + + @Override + protected void createBlockStateDefinition(StateDefinition.@NotNull Builder builder) { + builder.add(FACING, WATERLOGGED, POSE); + } + + @Nullable + @Override + public BlockEntity newBlockEntity(@NotNull BlockPos blockPos, @NotNull BlockState blockState) { + return Lib39BlockEntities.DOLL_BLOCK_ENTITY.get().create(blockPos, blockState); + } + + @SuppressWarnings("deprecation") + @Override + public @NotNull RenderShape getRenderShape(@NotNull BlockState state) { + return RenderShape.ENTITYBLOCK_ANIMATED; + } +} diff --git a/src/main/java/top/r3944realms/lib39/content/block/blockentity/DollBlockEntity.java b/src/main/java/top/r3944realms/lib39/content/block/blockentity/DollBlockEntity.java new file mode 100644 index 0000000..22fca41 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/content/block/blockentity/DollBlockEntity.java @@ -0,0 +1,84 @@ +package top.r3944realms.lib39.content.block.blockentity; + +import com.mojang.authlib.GameProfile; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.SkullBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.content.register.Lib39BlockEntities; +import top.r3944realms.lib39.util.PlantHelper; +import top.r3944realms.lib39.util.nbt.NBTReader; +import top.r3944realms.lib39.util.nbt.NBTWriter; + +import javax.annotation.Nullable; + +public class DollBlockEntity extends BlockEntity { + public static final String TAG_OWNER = "ProfileOwner"; + public static final String TAG_HOLD_ITEM = "ItemType"; + + @Nullable + private GameProfile owner; + @Nullable + private PlantHelper.Plant holdItem; + + public DollBlockEntity(BlockPos pos, BlockState blockState) { + super(Lib39BlockEntities.DOLL_BLOCK_ENTITY.get(), pos, blockState); + } + + protected void saveAdditional(@NotNull CompoundTag tag) { + super.saveAdditional(tag); + //noinspection DataFlowIssue + NBTWriter.of(tag) + .compoundIf(TAG_OWNER, owner != null, () -> NbtUtils.writeGameProfile(new CompoundTag(), this.owner)) + .stringIf(TAG_HOLD_ITEM, () -> holdItem.name,holdItem != null); + } + + public void load(@NotNull CompoundTag tag) { + super.load(tag); + NBTReader.of(tag) + .compound(TAG_OWNER, compoundTag -> setOwner(NbtUtils.readGameProfile(compoundTag))) + .string(TAG_HOLD_ITEM, holdItem -> setHoldItem(PlantHelper.Plant.valueOf(holdItem))); + } + + @Nullable + public GameProfile getOwnerProfile() { + return this.owner; + } + + @Nullable + public PlantHelper.Plant getHoldItem() { + return this.holdItem; + } + + public ClientboundBlockEntityDataPacket getUpdatePacket() { + return ClientboundBlockEntityDataPacket.create(this); + } + + public @NotNull CompoundTag getUpdateTag() { + return this.saveWithoutMetadata(); + } + + public void setOwner(@Nullable GameProfile owner) { + synchronized(this) { + this.owner = owner; + } + + this.updateOwnerProfile(); + } + + public void setHoldItem(@Nullable PlantHelper.Plant holdItem) { + this.holdItem = holdItem; + } + + private void updateOwnerProfile() { + SkullBlockEntity.updateGameprofile(this.owner, gameProfile -> { + this.owner = gameProfile; + this.setChanged(); + }); + } + +} diff --git a/src/main/java/top/r3944realms/lib39/content/block/property/DollPose.java b/src/main/java/top/r3944realms/lib39/content/block/property/DollPose.java new file mode 100644 index 0000000..e0cde50 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/content/block/property/DollPose.java @@ -0,0 +1,19 @@ +package top.r3944realms.lib39.content.block.property; + +import net.minecraft.util.StringRepresentable; +import org.jetbrains.annotations.NotNull; + +public enum DollPose implements StringRepresentable { + DEFAULT("default"), + WITHOUT_ITEM("without_item"), + ; + private final String name; + DollPose(String name) { + this.name = name; + } + + @Override + public @NotNull String getSerializedName() { + return name; + } +} diff --git a/src/main/java/top/r3944realms/lib39/content/item/DollItem.java b/src/main/java/top/r3944realms/lib39/content/item/DollItem.java new file mode 100644 index 0000000..bc351b6 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/content/item/DollItem.java @@ -0,0 +1,26 @@ +package top.r3944realms.lib39.content.item; + +import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer; +import net.minecraft.world.item.BlockItem; +import net.minecraftforge.client.extensions.common.IClientItemExtensions; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.client.renderer.item.DollItemRenderer; +import top.r3944realms.lib39.content.register.Lib39Blocks; + +import java.util.function.Consumer; + +public class DollItem extends BlockItem { + public DollItem(Properties properties) { + super(Lib39Blocks.DOLL.get(), properties); + } + + @Override + public void initializeClient(@NotNull Consumer consumer) { + consumer.accept(new IClientItemExtensions() { + @Override + public BlockEntityWithoutLevelRenderer getCustomRenderer() { + return DollItemRenderer.getInstance(); + } + }); + } +} diff --git a/src/main/java/top/r3944realms/lib39/content/register/Lib39BlockEntities.java b/src/main/java/top/r3944realms/lib39/content/register/Lib39BlockEntities.java new file mode 100644 index 0000000..dd77f82 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/content/register/Lib39BlockEntities.java @@ -0,0 +1,30 @@ +package top.r3944realms.lib39.content.register; + +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.content.block.blockentity.DollBlockEntity; + +public class Lib39BlockEntities { + /** + * The constant BLOCK_ENTITY_TYPES. + */ + public static final DeferredRegister> BLOCK_ENTITY_TYPES = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, Lib39.MOD_ID); + @SuppressWarnings("DataFlowIssue") + public static final RegistryObject> DOLL_BLOCK_ENTITY = BLOCK_ENTITY_TYPES.register("doll", + () -> BlockEntityType.Builder + .of(DollBlockEntity::new, Lib39Blocks.DOLL.get()) + .build(null) + ); + /** + * Register. + * + * @param bus the bus + */ + public static void register(IEventBus bus) { + BLOCK_ENTITY_TYPES.register(bus); + } +} diff --git a/src/main/java/top/r3944realms/lib39/content/register/Lib39Blocks.java b/src/main/java/top/r3944realms/lib39/content/register/Lib39Blocks.java new file mode 100644 index 0000000..f59f542 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/content/register/Lib39Blocks.java @@ -0,0 +1,31 @@ +package top.r3944realms.lib39.content.register; + +import net.minecraft.world.level.block.Block; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.content.block.DollBlock; +import top.r3944realms.lib39.util.block.BlockRegistryBuilder; + +public class Lib39Blocks { + /** + * The constant BLOCKS. + */ + public static final DeferredRegister BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, Lib39.MOD_ID); + + public static final RegistryObject DOLL = BlockRegistryBuilder + .create() + .withName("doll") + .registerBlock(BLOCKS, DollBlock::new) + .build(); + /** + * Register. + * + * @param bus the bus + */ + public static void register(IEventBus bus) { + BLOCKS.register(bus); + } +} diff --git a/src/main/java/top/r3944realms/lib39/content/register/Lib39Items.java b/src/main/java/top/r3944realms/lib39/content/register/Lib39Items.java new file mode 100644 index 0000000..eccbe92 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/content/register/Lib39Items.java @@ -0,0 +1,31 @@ +package top.r3944realms.lib39.content.register; + +import net.minecraft.world.item.Item; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; +import top.r3944realms.lib39.Lib39; +import top.r3944realms.lib39.content.item.DollItem; + +/** + * The type Ex lib 39 items. + */ +public class Lib39Items { + /** + * The constant ITEMS. + */ + public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, Lib39.MOD_ID); + + public static final RegistryObject DOLL = ITEMS.register("doll", () -> new DollItem(new Item.Properties())); + + /** + * Register. + * + * @param bus the bus + */ + public static void register(IEventBus bus) { + ITEMS.register(bus); + } + +} diff --git a/src/main/java/top/r3944realms/lib39/core/command/CommandNode.java b/src/main/java/top/r3944realms/lib39/core/command/CommandNode.java deleted file mode 100644 index 7452651..0000000 --- a/src/main/java/top/r3944realms/lib39/core/command/CommandNode.java +++ /dev/null @@ -1,140 +0,0 @@ -package top.r3944realms.lib39.core.command; - -import net.minecraft.network.chat.MutableComponent; - -import java.util.*; - -public class CommandNode { - private boolean isRoot = false; - private int hashCache = 0; - private boolean hashValid = false; - private final ICommandHelpManager helpManager; - private final String name; - private final MutableComponent description; - private final Map children; - - // 参数列表,用于存储命令参数 - private final List parameters = new ArrayList<>(); - - // 展开/闭合状态 - private boolean expanded = true; - - public CommandNode(ICommandHelpManager helpManager, String name, MutableComponent description, boolean isRoot) { - this.name = name; - this.helpManager = helpManager; - this.description = description; - this.children = new TreeMap<>(); - this.isRoot = isRoot; - invalidateHash(); - } - - public CommandNode(ICommandHelpManager helpManager, String name, MutableComponent description) { - this.name = name; - this.helpManager = helpManager; - this.description = description; - this.children = new TreeMap<>(); - invalidateHash(); - } - - public void addChild(CommandNode child) { - children.put(child.name, child); - invalidateHash(); - } - - public CommandNode getChild(String name) { - return children.get(name); - } - - public String getName() { - return name; - } - - public MutableComponent getDescription() { - return description; - } - - public Collection getChildren() { - return children.values(); - } - - // 添加参数 - public void addParameter(String parameter, boolean required) { - parameters.add(new Parameter(parameter, required)); - invalidateHash(); - } - - // 获取参数列表 - public List getParameters() { - return parameters; - } - - // 获取展开状态 - public boolean isExpanded() { - return expanded; - } - - // 设置展开状态 - public void setExpanded(boolean expanded) { - this.expanded = expanded; - } - - // 切换展开状态 - public void toggleExpanded() { - this.expanded = !this.expanded; - } - - @Override - public int hashCode() { - return computeHash(); - } - - // 计算节点的Hash值 - public int computeHash() { - if (!hashValid) { - if (hashCache != 0) { - helpManager.getCache().remove(hashCache); - } - int result = 31; - result = 31 * result + name.hashCode(); - result = 31 * result + description.getString().hashCode(); - - // 计算参数的Hash - for (Parameter param : parameters) { - result = 31 * result + param.name().hashCode(); - result = 31 * result + (param.required() ? 1 : 0); - } - - // 计算子节点的Hash - for (CommandNode child : children.values()) { - result = 31 * result + child.computeHash(); - } - - if (!isRoot && children.size() > 1) { - expanded = false; - } - - hashCache = result; - helpManager.getCache().put(result, this); - hashValid = true; - } - return hashCache; - } - - // 使Hash缓存失效 - private void invalidateHash() { - hashValid = false; - computeHash(); - } - - //递归构建完整路径 - public String getFullPath() { - StringBuilder sb = new StringBuilder(); - if (sb.isEmpty()) { - sb.append(name); - } else { - sb.insert(0, name + " "); - } - return sb.toString(); - } - -} diff --git a/src/main/java/top/r3944realms/lib39/core/command/ICommandHelpManager.java b/src/main/java/top/r3944realms/lib39/core/command/ICommandHelpManager.java index 7e11bb1..c7f7a09 100644 --- a/src/main/java/top/r3944realms/lib39/core/command/ICommandHelpManager.java +++ b/src/main/java/top/r3944realms/lib39/core/command/ICommandHelpManager.java @@ -1,20 +1,28 @@ package top.r3944realms.lib39.core.command; import net.minecraft.ChatFormatting; -import net.minecraft.network.chat.ClickEvent; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.network.chat.Style; +import net.minecraft.network.chat.*; import net.minecraft.resources.ResourceLocation; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import top.r3944realms.lib39.base.datagen.value.Lib39LangKey; +import top.r3944realms.lib39.core.command.model.CommandNode; +import top.r3944realms.lib39.core.command.model.CommandPath; +import top.r3944realms.lib39.core.command.model.Parameter; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; public interface ICommandHelpManager { String NEWLINE = "\n"; + ResourceLocation getID(); + String getHeadKey(); + default String getCommandHead() { return getID().getNamespace(); } @@ -27,20 +35,22 @@ public interface ICommandHelpManager { } Map getCache(); + CommandNode getRootNode(); + default void registerCommandHelp(@NotNull CommandNode commandNode, MutableComponent description) { registerCommandHelp(commandNode.getFullPath(), description); } default void registerCommandHelp(@NotNull CommandNode commandNode, String descriptionKey) { - registerCommandHelp(commandNode.getFullPath(), Component.translatable(descriptionKey)); + registerCommandHelp(commandNode.getFullPath(), Component.translatable(descriptionKey)); } default void registerCommandHelp(@NotNull CommandPath commandPath) { registerCommandHelp(commandPath.fullPath(), Component.literal("")); } - default void registerCommandParameters(@NotNull CommandPath commandPath, @NotNull ParameterBuilder parameters) { + default void registerCommandParameters(@NotNull CommandPath commandPath, @NotNull Parameter.Builder parameters) { registerCommandParameters(commandPath.fullPath(), parameters.build()); } @@ -63,6 +73,33 @@ public interface ICommandHelpManager { currentNode = child; } } + + /** + * 註冊命令幫助節點 + */ + default void registerCommand(@NotNull CommandNode.Builder builder) { + CommandNode newRoot = builder.build(); + mergeTree(getRootNode(), newRoot); + } + + void mergeTree(@NotNull CommandNode target, @NotNull CommandNode source); + + /** + * 使用Builder模式註冊命令樹 + */ + default void registerCommandTree(@NotNull CommandNode.Builder builder) { + registerCommand(builder); + } + + /** + * 使用Builder模式快速註冊命令 + */ + default void registerCommands(@NotNull Consumer configurator) { + CommandNode.Builder builder = CommandNode.Builder.of(this); + configurator.accept(builder); + registerCommand(builder); + } + /** * 根据路径查找节点 */ @@ -100,10 +137,11 @@ public interface ICommandHelpManager { /** * 注册命令参数(支持单个参数可选标记,使用*前缀表示必选参数) + * * @param commandPath 命令路径,如 "fpsm tacz dummy" - * @param parameters 参数列表,如 "*requiredParam", "optionalParam" + * @param parameters 参数列表,如 "*requiredParam", "optionalParam" */ - private void registerCommandParameters(@NotNull String commandPath, String... parameters) { + private void registerCommandParameters(@NotNull String commandPath, Parameter... parameters) { String[] pathParts = commandPath.split(" "); CommandNode currentNode = getRootNode(); @@ -124,17 +162,16 @@ public interface ICommandHelpManager { } // 添加参数,处理可选标记 - for (String param : parameters) { - boolean required = param.startsWith("*"); - String paramName = required ? param.substring(1) : param; - currentNode.addParameter(paramName, required); + for (Parameter param : parameters) { + currentNode.addParameter(param.name(), param.required()); } } /** * 动态添加子指令到指定命令路径 + * * @param commandPath 命令路径,如 "fpsm map modify" - * @param childName 子指令名称 + * @param childName 子指令名称 * @param description 子指令描述 * @return 是否添加成功 */ @@ -163,78 +200,208 @@ public interface ICommandHelpManager { /** * 构建单个命令节点的显示格式 - * @param node 当前命令节点 + * + * @param node 当前命令节点 * @param indent 当前缩进 * @param isRoot 是否为根节点 * @return 格式化后的命令节点组件 */ - private @NotNull MutableComponent buildCommandLine(CommandNode node, String indent, boolean isRoot) { + private @NotNull MutableComponent buildCommandLine(CommandNode node, String indent, boolean isRoot, @Nullable String currentFullPath) { if (isRoot) { // 根节点特殊处理 - return Component.literal("/" + node.getName()).withStyle(ChatFormatting.AQUA); + String rootCommand = "/" + node.getName(); + return Component.literal(rootCommand) + .withStyle(ChatFormatting.AQUA) + .withStyle(Style.EMPTY + .withClickEvent(new ClickEvent( + ClickEvent.Action.SUGGEST_COMMAND, + rootCommand + " " + )) + .withHoverEvent(new HoverEvent( + HoverEvent.Action.SHOW_TEXT, + Component.translatable(Lib39LangKey.Message.HELP_HOVER_COPY_TIP.getKey()) + )) + ); } else { + // 构建完整命令路径 + String fullCommand = (currentFullPath != null && !currentFullPath.isEmpty()) + ? currentFullPath + " " + node.getName() + : "/" + getRootNode().getName() + " " + node.getFullPath(); + + // 构建建议的命令(带参数占位符) + String suggestedCommand = buildSuggestedCommand(node, fullCommand); + // 非根节点:显示命令和描述 MutableComponent prefix = Component.literal(indent + "└─ ").withStyle(ChatFormatting.GRAY); - MutableComponent commandName = Component.literal(node.getName()).withStyle(ChatFormatting.DARK_AQUA); + + // 命令名称(可点击) + MutableComponent commandName = Component.literal(node.getName()) + .withStyle(ChatFormatting.DARK_AQUA) + .withStyle(Style.EMPTY + .withClickEvent(new ClickEvent( + ClickEvent.Action.SUGGEST_COMMAND, + suggestedCommand + )) + .withHoverEvent(new HoverEvent( + HoverEvent.Action.SHOW_TEXT, + Component.translatable(Lib39LangKey.Message.HELP_HOVER_COPY_TIP.getKey(), suggestedCommand) + )) + ); + MutableComponent displayLine = prefix.append(commandName); - // 添加参数显示 + // 添加参数显示(只显示,不添加额外空格) if (!node.getParameters().isEmpty()) { - for (Parameter param : node.getParameters()) { + displayLine.append(Component.literal(" ")); // 命令名和参数之间的空格 + + for (int i = 0; i < node.getParameters().size(); i++) { + Parameter param = node.getParameters().get(i); if (param.required()) { - displayLine.append(Component.literal(" <").withStyle(ChatFormatting.GRAY)) + displayLine.append(Component.literal("<").withStyle(ChatFormatting.GRAY)) .append(Component.literal(param.name()).withStyle(ChatFormatting.WHITE)) - .append(Component.literal("> ").withStyle(ChatFormatting.GRAY)); + .append(Component.literal(">").withStyle(ChatFormatting.GRAY)); } else { - displayLine.append(Component.literal(" [").withStyle(ChatFormatting.GRAY)) + displayLine.append(Component.literal("[").withStyle(ChatFormatting.GRAY)) .append(Component.literal(param.name()).withStyle(ChatFormatting.WHITE)) - .append(Component.literal("] ").withStyle(ChatFormatting.GRAY)); + .append(Component.literal("]").withStyle(ChatFormatting.GRAY)); + } + + // 参数之间添加空格(除了最后一个) + if (i < node.getParameters().size() - 1) { + displayLine.append(Component.literal(" ")); } } } + // 添加分隔符和描述 displayLine.append(Component.literal(" - ").withStyle(ChatFormatting.DARK_GRAY)) - .append(node.getDescription().withStyle(ChatFormatting.GRAY)); - if (node.getChildren().size() > 1) { - String toggleKey = node.isExpanded() ? Lib39LangKey.Message.HELP_NODE_TOGGLE_COLLAPSE.getKey() : Lib39LangKey.Message.HELP_NODE_TOGGLE_EXPAND.getKey(); - displayLine.append(Component.literal(" [").withStyle(ChatFormatting.GRAY)) + .append(node.getDescription().copy().withStyle(ChatFormatting.GRAY)); + + // 如果有子节点,添加展开/折叠按钮 + boolean shouldShowToggle = node.hasChildren() && !node.isLeaf(); + + if (shouldShowToggle) { + String toggleKey = node.isExpanded() + ? Lib39LangKey.Message.HELP_NODE_TOGGLE_COLLAPSE.getKey() + : Lib39LangKey.Message.HELP_NODE_TOGGLE_EXPAND.getKey(); + + MutableComponent toggleButton = Component.literal(" [") + .withStyle(ChatFormatting.GRAY) .append(Component.translatable(toggleKey).withStyle(ChatFormatting.YELLOW)) .append(Component.literal("]").withStyle(ChatFormatting.GRAY)); + + // 为按钮添加点击事件 + toggleButton.withStyle(Style.EMPTY + .withClickEvent(new ClickEvent( + ClickEvent.Action.RUN_COMMAND, + "/" + getCommandHead() + " help toggle " + node.hashCode() + )) + .withHoverEvent(new HoverEvent( + HoverEvent.Action.SHOW_TEXT, + Component.translatable(Lib39LangKey.Message.HELP_CLICK_EXPAND.getKey()) + .withStyle(ChatFormatting.GRAY) + )) + ); + + displayLine.append(toggleButton); } - return displayLine.withStyle(Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/"+ getCommandHead() +" help toggle " + node.hashCode()))); + return displayLine; } } /** - * 递归构建命令树的字符串表示 - * @param node 当前命令节点 - * @param indent 当前缩进 - * @param commandPath 当前命令路径 - * @param result 结果列表 + * 构建建议的命令(包含参数占位符) */ - private void buildCommandTreeString(CommandNode node, @NotNull String indent, String commandPath, @NotNull List result) { - boolean isRoot = indent.isEmpty(); - MutableComponent commandLine = buildCommandLine(node, indent, isRoot); - result.add(commandLine.append(NEWLINE)); + private @NotNull String buildSuggestedCommand(@NotNull CommandNode node, @NotNull String baseCommand) { + StringBuilder sb = new StringBuilder(baseCommand); - // 递归处理子节点 + // 如果有参数,添加参数占位符 + if (!node.getParameters().isEmpty()) { + for (Parameter param : node.getParameters()) { + sb.append(" "); + if (param.required()) { + sb.append("<").append(param.name()).append(">"); + } else { + sb.append("[").append(param.name()).append("]"); + } + } + } + + // 如果是叶子节点且没有参数,添加空格以便继续输入 + if (node.isLeaf() && node.getParameters().isEmpty()) { + sb.append(" "); + } + + return sb.toString(); + } + + /** + * 檢查節點是否應該顯示摺疊信息 + */ + private boolean shouldShowCollapsedInfo(@NotNull CommandNode node) { + return node.hasChildren() && !node.isExpanded() && !node.getChildren().isEmpty(); + } + + /** + * 獲取有效的子命令數量(過濾掉空描述的命令) + */ + private long getValidChildCount(@NotNull CommandNode node) { + return node.getChildren().stream() + .filter(child -> !child.getDescription().getString().isEmpty()) + .count(); + } + + /** + * 遞歸構建命令樹 + */ + private void buildCommandTreeString(@NotNull CommandNode node, + @NotNull String indent, + @Nullable String currentFullPath, + @NotNull List result) { + boolean isRoot = indent.isEmpty(); + MutableComponent commandLine = buildCommandLine(node, indent, isRoot, currentFullPath); + result.add(commandLine.append(Component.literal(NEWLINE))); + + // 遞歸處理子節點 String childIndent = indent + "| "; if (node.isExpanded()) { - String fullCommandPath = commandPath.isEmpty() ? "/" + node.getName() : commandPath + " " + node.getName(); + String newFullPath = (currentFullPath != null && !currentFullPath.isEmpty()) + ? currentFullPath + " " + node.getName() + : "/" + node.getName(); + for (CommandNode child : node.getChildren()) { - buildCommandTreeString(child, childIndent, fullCommandPath, result); + // 只顯示有描述的子命令 + if (!child.getDescription().getString().isEmpty()) { + buildCommandTreeString(child, childIndent, newFullPath, result); + } + } + } else if (shouldShowCollapsedInfo(node)) { + long childCount = getValidChildCount(node); + if (childCount > 0) { + MutableComponent collapsedInfo = Component.literal(indent + "| " + "└─ ") + .withStyle(ChatFormatting.GRAY) + .append(Component.translatable( + Lib39LangKey.Message.HELP_NODE_EXPAND.getKey(), + childCount + ).withStyle(ChatFormatting.GRAY)); + + collapsedInfo.withStyle(Style.EMPTY + .withClickEvent(new ClickEvent( + ClickEvent.Action.RUN_COMMAND, + "/" + getCommandHead() + " help toggle " + node.hashCode() + )) + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable(Lib39LangKey.Message.HELP_CLICK_EXPAND.getKey()))) + ); + + result.add(collapsedInfo.append(Component.literal(NEWLINE))); } - } else { - result.add(Component.literal(indent + "| " + "└─ ").withStyle(ChatFormatting.GRAY) - .append(Component.translatable(Lib39LangKey.Message.HELP_NODE_EXPAND.getKey(), node.getChildren().size()) - .withStyle(ChatFormatting.GRAY)) - .append(NEWLINE)); } } /** * 获取命令树的字符串表示 + * * @return 命令树列表 */ default List getCommandTree() { @@ -245,6 +412,7 @@ public interface ICommandHelpManager { /** * 切换指定节点的展开/闭合状态 + * * @param hashCode 节点哈希值 * @return 是否成功切换 */ @@ -264,7 +432,8 @@ public interface ICommandHelpManager { /** * 构建帮助消息 - * @param header 帮助头部 + * + * @param header 帮助头部 * @param entries 帮助条目列表 */ default MutableComponent buildHelpMessage(@NotNull Component header, @NotNull List entries) { diff --git a/src/main/java/top/r3944realms/lib39/core/command/Parameter.java b/src/main/java/top/r3944realms/lib39/core/command/Parameter.java deleted file mode 100644 index 914300f..0000000 --- a/src/main/java/top/r3944realms/lib39/core/command/Parameter.java +++ /dev/null @@ -1,3 +0,0 @@ -package top.r3944realms.lib39.core.command; - -public record Parameter(String name, boolean required) {} diff --git a/src/main/java/top/r3944realms/lib39/core/command/ParameterBuilder.java b/src/main/java/top/r3944realms/lib39/core/command/ParameterBuilder.java deleted file mode 100644 index f407df3..0000000 --- a/src/main/java/top/r3944realms/lib39/core/command/ParameterBuilder.java +++ /dev/null @@ -1,31 +0,0 @@ -package top.r3944realms.lib39.core.command; - -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; - -public class ParameterBuilder { - private final List parameters = new ArrayList<>(); - - public ParameterBuilder required(String name) { - parameters.add("*" + name); - return this; - } - - public ParameterBuilder optional(String name) { - parameters.add(name); - return this; - } - - public String[] build() { - return parameters.toArray(new String[0]); - } - - // 链式调用的便利方法 - @Contract(" -> new") - public static @NotNull ParameterBuilder builder() { - return new ParameterBuilder(); - } -} diff --git a/src/main/java/top/r3944realms/lib39/core/command/SimpleCommandHelpManager.java b/src/main/java/top/r3944realms/lib39/core/command/SimpleCommandHelpManager.java index 4ae7245..e76a5e8 100644 --- a/src/main/java/top/r3944realms/lib39/core/command/SimpleCommandHelpManager.java +++ b/src/main/java/top/r3944realms/lib39/core/command/SimpleCommandHelpManager.java @@ -2,6 +2,8 @@ package top.r3944realms.lib39.core.command; import net.minecraft.resources.ResourceLocation; import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.core.command.model.CommandNode; +import top.r3944realms.lib39.core.command.model.Parameter; import java.util.HashMap; import java.util.Map; @@ -36,7 +38,25 @@ public abstract class SimpleCommandHelpManager implements ICommandHelpManager { public final Map getCache() { return nodeCache; } + @Override + public void mergeTree(@NotNull CommandNode target, @NotNull CommandNode source) { + // 合併參數 + for (Parameter param : source.getParameters()) { + if (!target.getParameters().contains(param)) { + target.addParameter(param.name(), param.required()); + } + } + // 合併子節點 + for (CommandNode sourceChild : source.getChildren()) { + CommandNode targetChild = target.getChild(sourceChild.getName()); + if (targetChild == null) { + target.addChild(sourceChild.deepCopy()); + } else { + mergeTree(targetChild, sourceChild); + } + } + } /** * 獲取根節點(如果未初始化則初始化) diff --git a/src/main/java/top/r3944realms/lib39/core/command/SimpleHelpCommand.java b/src/main/java/top/r3944realms/lib39/core/command/SimpleHelpCommand.java index 90f7e16..4a475b0 100644 --- a/src/main/java/top/r3944realms/lib39/core/command/SimpleHelpCommand.java +++ b/src/main/java/top/r3944realms/lib39/core/command/SimpleHelpCommand.java @@ -16,4 +16,7 @@ public abstract class SimpleHelpCommand implements IHelpCommand { CommandBuildContext context) { root = buildCommand(dispatcher, context); } + public LiteralArgumentBuilder getRoot() { + return root; + } } diff --git a/src/main/java/top/r3944realms/lib39/core/command/model/CommandNode.java b/src/main/java/top/r3944realms/lib39/core/command/model/CommandNode.java new file mode 100644 index 0000000..3e90560 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/core/command/model/CommandNode.java @@ -0,0 +1,747 @@ +package top.r3944realms.lib39.core.command.model; + +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.r3944realms.lib39.core.command.ICommandHelpManager; + +import java.util.*; + +public class CommandNode { + private boolean isRoot = false; + private int hashCache = 0; + private boolean hashValid = false; + private final ICommandHelpManager helpManager; + private final String name; + private final MutableComponent description; + private final Map children; + + // 父節點引用,用於構建完整路徑 + @Nullable + private CommandNode parent; + + // 参数列表,用于存储命令参数 + private final List parameters = new ArrayList<>(); + + // 展开/闭合状态 + private boolean expanded = true; + + public CommandNode(ICommandHelpManager helpManager, String name, MutableComponent description, boolean isRoot) { + this.name = name; + this.helpManager = helpManager; + this.description = description; + this.children = new TreeMap<>(); + this.isRoot = isRoot; + invalidateHash(); + } + + public CommandNode(ICommandHelpManager helpManager, String name, MutableComponent description) { + this.name = name; + this.helpManager = helpManager; + this.description = description; + this.children = new TreeMap<>(); + invalidateHash(); + } + + public void addChild(CommandNode child) { + children.put(child.name, child); + child.parent = this; // 設置父節點引用 + + // 如果不是根節點,確保展開狀態正確 + if (!isRoot) { + this.expanded = false; + } + + invalidateHash(); + } + + public CommandNode getChild(String name) { + return children.get(name); + } + + @Nullable + public CommandNode getParent() { + return parent; + } + + public boolean hasParent() { + return parent != null; + } + + + public String getName() { + return name; + } + + public MutableComponent getDescription() { + return description; + } + + public Collection getChildren() { + return children.values(); + } + + // 添加参数 + public void addParameter(String parameter, boolean required) { + parameters.add(new Parameter(parameter, required)); + invalidateHash(); + } + + // 获取参数列表 + public List getParameters() { + return parameters; + } + + // 获取展开状态 + public boolean isExpanded() { + return expanded; + } + + // 设置展开状态 + public void setExpanded(boolean expanded) { + this.expanded = expanded; + } + + // 切换展开状态 + public void toggleExpanded() { + this.expanded = !this.expanded; + } + + @Override + public int hashCode() { + return computeHash(); + } + + // 计算节点的Hash值 + public int computeHash() { + if (!hashValid) { + if (hashCache != 0) { + helpManager.getCache().remove(hashCache); + } + int result = 31; + result = 31 * result + name.hashCode(); + result = 31 * result + description.getString().hashCode(); + + // 计算参数的Hash + for (Parameter param : parameters) { + result = 31 * result + param.name().hashCode(); + result = 31 * result + (param.required() ? 1 : 0); + } + + // 计算子节点的Hash + for (CommandNode child : children.values()) { + result = 31 * result + child.computeHash(); + } + + if (!isRoot && children.size() > 1) { + expanded = false; + } + + hashCache = result; + helpManager.getCache().put(result, this); + hashValid = true; + } + return hashCache; + } + + // 使Hash缓存失效 + private void invalidateHash() { + hashValid = false; + computeHash(); + } + + /** + * 递归构建完整路径 + */ + public String getFullPath() { + if (parent == null) { + return name; + } + return parent.getFullPath() + " " + name; + } + + /** + * 獲取完整的命令(以 / 開頭) + */ + public String getFullCommand() { + if (isRoot) { + return "/" + name; + } + return "/" + getFullPath(); + } + /** + * 檢查是否為葉子節點(沒有子節點) + */ + public boolean isLeaf() { + return children.isEmpty(); + } + + /** + * 獲取所有葉子節點(可執行的命令) + */ + public List getAllLeafNodes() { + List leaves = new ArrayList<>(); + collectLeafNodes(this, leaves); + return leaves; + } + + private void collectLeafNodes(@NotNull CommandNode node, List leaves) { + if (node.isLeaf()) { + leaves.add(node); + } else { + for (CommandNode child : node.getChildren()) { + collectLeafNodes(child, leaves); + } + } + } + + /** + * 獲取節點路徑(從根節點到當前節點的路徑列表) + */ + public List getPathSegments() { + List segments = new ArrayList<>(); + collectPathSegments(this, segments); + Collections.reverse(segments); // 從根到當前 + return segments; + } + + private void collectPathSegments(@NotNull CommandNode node, @NotNull List segments) { + segments.add(node.name); + if (node.parent != null) { + collectPathSegments(node.parent, segments); + } + } + + /** + * 比較兩個節點是否在同一路徑上 + */ + public boolean isAncestorOf(CommandNode other) { + CommandNode current = other; + while (current != null) { + if (current == this) { + return true; + } + current = current.parent; + } + return false; + } + + /** + * 獲取最近的共同祖先 + */ + @Nullable + public CommandNode getCommonAncestor(CommandNode other) { + Set ancestors = new HashSet<>(); + CommandNode current = this; + + // 收集當前節點的所有祖先 + while (current != null) { + ancestors.add(current); + current = current.parent; + } + + // 從另一個節點向上查找 + current = other; + while (current != null) { + if (ancestors.contains(current)) { + return current; + } + current = current.parent; + } + + return null; + } + + /** + * 複製節點(不包括子節點) + */ + public CommandNode shallowCopy() { + CommandNode copy = new CommandNode(helpManager, name, description.copy(), isRoot); + for (Parameter param : parameters) { + copy.addParameter(param.name(), param.required()); + } + copy.expanded = expanded; + return copy; + } + /** + * 深複製節點(包括所有子節點) + */ + public CommandNode deepCopy() { + return deepCopy(this, null); + } + + /** + * 深複製節點(遞歸複製所有子節點) + */ + private @NotNull CommandNode deepCopy(@NotNull CommandNode original, @Nullable CommandNode newParent) { + // 複製當前節點 + CommandNode copy = new CommandNode(helpManager, original.name, original.description.copy(), original.isRoot); + copy.parent = newParent; + copy.expanded = original.expanded; + + // 複製參數 + for (Parameter param : original.parameters) { + copy.addParameter(param.name(), param.required()); + } + + // 遞歸複製子節點 + for (CommandNode child : original.children.values()) { + CommandNode childCopy = deepCopy(child, copy); + copy.children.put(childCopy.name, childCopy); + } + + return copy; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CommandNode that = (CommandNode) o; + return hashCache == that.hashCache && + Objects.equals(name, that.name) && + Objects.equals(helpManager, that.helpManager); + } + + @Override + public String toString() { + return String.format("CommandNode{name='%s', path='%s', children=%d, params=%d}", + name, getFullPath(), children.size(), parameters.size()); + } + + public static class Builder { + private final ICommandHelpManager helpManager; + private final Deque nodeStack = new ArrayDeque<>(); + private CommandNode root; + private boolean built = false; + + private Builder(@NotNull final ICommandHelpManager helpManager) { + this.helpManager = helpManager; + } + + @Contract(value = "_ -> new", pure = true) + public static @NotNull Builder of(@NotNull final ICommandHelpManager helpManager) { + return new Builder(helpManager); + } + + /** + * 添加根節點 + */ + public Builder root(@NotNull String name, @NotNull MutableComponent description) { + if (root != null) { + throw new IllegalStateException("Root node already set"); + } + root = new CommandNode(helpManager, name, description, true); + nodeStack.push(root); + return this; + } + + /** + * 添加根節點(使用翻譯鍵) + */ + public Builder root(@NotNull String name, @NotNull String translationKey) { + return root(name, Component.translatable(translationKey)); + } + + /** + * 進入子節點(向下移動) + */ + public Builder push(@NotNull String name, @NotNull MutableComponent description) { + checkBuilt(); + if (nodeStack.isEmpty()) { + throw new IllegalStateException("No parent node available. Call root() first."); + } + + CommandNode parent = nodeStack.peek(); + CommandNode child = new CommandNode(helpManager, name, description); + + if (!child.isLeaf()) { + child.setExpanded(false); + } + + parent.addChild(child); + nodeStack.push(child); + return this; + } + + /** + * 進入子節點(使用翻譯鍵) + */ + public Builder push(@NotNull String name, @NotNull String translationKey) { + return push(name, Component.translatable(translationKey)); + } + + /** + * 進入子節點並添加參數 + */ + public Builder pushWithParams(@NotNull String name, @NotNull MutableComponent description, + @NotNull Parameter... parameters) { + push(name, description); + current().addParameters(parameters); + return this; + } + + /** + * 進入子節點並添加必填參數 + */ + public Builder pushWithRequiredParams(@NotNull String name, @NotNull MutableComponent description, + @NotNull String @NotNull ... paramNames) { + push(name, description); + for (String paramName : paramNames) { + current().addParameter(paramName, true); + } + return this; + } + + /** + * 進入子節點並添加可選參數 + */ + public Builder pushWithOptionalParams(@NotNull String name, @NotNull MutableComponent description, + @NotNull String @NotNull ... paramNames) { + push(name, description); + for (String paramName : paramNames) { + current().addParameter(paramName, false); + } + return this; + } + + /** + * 返回上一級節點(向上移動) + */ + public Builder pop() { + checkBuilt(); + if (nodeStack.size() <= 1) { + throw new IllegalStateException("Cannot pop root node"); + } + nodeStack.pop(); + return this; + } + + /** + * 返回到根節點 + */ + public Builder popToRoot() { + checkBuilt(); + while (nodeStack.size() > 1) { + nodeStack.pop(); + } + return this; + } + + /** + * 返回到指定深度的節點 + */ + public Builder popToDepth(int depth) { + checkBuilt(); + if (depth < 0 || depth >= nodeStack.size()) { + throw new IllegalArgumentException("Invalid depth: " + depth); + } + while (nodeStack.size() > depth + 1) { + nodeStack.pop(); + } + return this; + } + + /** + * 獲取當前節點 + */ + public @NotNull CommandNode current() { + checkBuilt(); + if (nodeStack.isEmpty()) { + throw new IllegalStateException("No current node"); + } + return nodeStack.peek(); + } + + /** + * 獲取當前深度 + */ + public int depth() { + return nodeStack.size() - 1; + } + + /** + * 為當前節點添加參數 + */ + public Builder param(@NotNull String name, boolean required) { + checkBuilt(); + current().addParameter(name, required); + return this; + } + + /** + * 為當前節點添加必填參數 + */ + public Builder required(@NotNull String name) { + return param(name, true); + } + + /** + * 為當前節點添加可選參數 + */ + public Builder optional(@NotNull String name) { + return param(name, false); + } + + /** + * 為當前節點添加多個參數 + */ + public Builder params(@NotNull Parameter... parameters) { + checkBuilt(); + current().addParameters(parameters); + return this; + } + + /** + * 設置當前節點的展開狀態 + */ + public Builder expanded(boolean expanded) { + checkBuilt(); + current().setExpanded(expanded); + // 如果設置為展開,確保父節點知道 + if (expanded && current().hasParent()) { + CommandNode parent = current().getParent(); + if (parent != null && !parent.isExpanded()) { + parent.setExpanded(true); + } + } + return this; + } + + /** + * 添加一個完整的命令分支(鏈式調用) + */ + public Builder branch(@NotNull String name, @NotNull MutableComponent description, + @NotNull BranchConfigurator configurator) { + push(name, description); + configurator.configure(this); + pop(); + return this; + } + + /** + * 添加一個完整的命令分支(使用翻譯鍵) + */ + public Builder branch(@NotNull String name, @NotNull String translationKey, + @NotNull BranchConfigurator configurator) { + return branch(name, Component.translatable(translationKey), configurator); + } + + /** + * 快速添加葉子節點(沒有子節點的節點) + */ + public Builder leaf(@NotNull String name, @NotNull MutableComponent description, + @NotNull Parameter @NotNull ... parameters) { + push(name, description); + if (parameters.length > 0) { + params(parameters); + } + pop(); + return this; + } + + /** + * 快速添加葉子節點(使用翻譯鍵) + */ + public Builder leaf(@NotNull String name, @NotNull String translationKey, + @NotNull Parameter... parameters) { + return leaf(name, Component.translatable(translationKey), parameters); + } + + /** + * 批量添加多個葉子節點 + */ + public Builder leaves(@NotNull Map commands) { + for (Map.Entry entry : commands.entrySet()) { + leaf(entry.getKey(), entry.getValue()); + } + return this; + } + /** + * 批量添加多個葉子節點 + */ + public Builder leavesT(@NotNull Map commands) { + for (Map.Entry entry : commands.entrySet()) { + leaf(entry.getKey(), entry.getValue()); + } + return this; + } + + /** + * 批量添加多個帶參數的葉子節點 + */ + public Builder leavesWithParams(@NotNull Map commands) { + for (Map.Entry entry : commands.entrySet()) { + LeafConfig config = entry.getValue(); + push(entry.getKey(), config.description()); + if (config.parameters() != null && !config.parameters().isEmpty()) { + for (Parameter param : config.parameters()) { + param(param.name(), param.required()); + } + } + pop(); + } + return this; + } + + /** + * 構建命令樹 + */ + public @NotNull CommandNode build() { + checkBuilt(); + if (root == null) { + throw new IllegalStateException("Root node not set"); + } + built = true; + return root; + } + + /** + * 重置構建器(重用) + */ + public Builder reset() { + nodeStack.clear(); + root = null; + built = false; + return this; + } + + private void checkBuilt() { + if (built) { + throw new IllegalStateException("Builder has already been built. Call reset() to reuse."); + } + } + + /** + * 分支配置器接口(用於lambda表達式) + */ + @FunctionalInterface + public interface BranchConfigurator { + void configure(@NotNull Builder builder); + } + + /** + * 葉子節點配置記錄 + */ + public record LeafConfig(@NotNull MutableComponent description, + @Nullable List parameters) { + + @Contract("_ -> new") + public static @NotNull LeafConfig of(@NotNull MutableComponent description) { + return new LeafConfig(description, null); + } + + @Contract("_, _ -> new") + public static @NotNull LeafConfig of(@NotNull MutableComponent description, + @NotNull Parameter... parameters) { + return new LeafConfig(description, Arrays.asList(parameters)); + } + + @Contract("_ -> new") + public static @NotNull LeafConfig of(@NotNull String translationKey) { + return new LeafConfig(Component.translatable(translationKey), null); + } + + @Contract("_, _ -> new") + public static @NotNull LeafConfig of(@NotNull String translationKey, + @NotNull Parameter... parameters) { + return new LeafConfig(Component.translatable(translationKey), Arrays.asList(parameters)); + } + } + } + + // ==================== 新增的輔助方法 ==================== + + /** + * 添加多個參數 + */ + public void addParameters(@NotNull Parameter @NotNull ... parameters) { + for (Parameter param : parameters) { + addParameter(param.name(), param.required()); + } + } + + /** + * 檢查是否有子節點 + */ + public boolean hasChildren() { + return !children.isEmpty(); + } + + /** + * 檢查是否有參數 + */ + public boolean hasParameters() { + return !parameters.isEmpty(); + } + + /** + * 獲取節點深度(根節點為0) + */ + public int getDepth() { + if (isRoot || parent == null) { + return 0; + } + return parent.getDepth() + 1; + } + + /** + * 查找子節點(支持遞歸查找) + */ + @Nullable + public CommandNode findChild(@NotNull String @NotNull ... path) { + if (path.length == 0) { + return this; + } + + CommandNode current = this; + for (String segment : path) { + current = current.getChild(segment); + if (current == null) { + return null; + } + } + + return current; + } + + /** + * 獲取完整的命令(用於點擊建議) + */ + public String getSuggestedCommand() { + StringBuilder sb = new StringBuilder(getFullCommand()); + + // 如果有參數,添加參數佔位符 + for (Parameter param : parameters) { + sb.append(" "); + if (param.required()) { + sb.append("<").append(param.name()).append(">"); + } else { + sb.append("[").append(param.name()).append("]"); + } + } + + // 如果沒有子節點且沒有參數,添加空格以便繼續輸入 + if (children.isEmpty() && parameters.isEmpty()) { + sb.append(" "); + } + + return sb.toString(); + } + + /** + * 獲取根節點 + */ + public CommandNode getRoot() { + if (isRoot) { + return this; + } + if (parent == null) { + throw new IllegalStateException("Node has no parent but is not marked as root"); + } + return parent.getRoot(); + } + +} diff --git a/src/main/java/top/r3944realms/lib39/core/command/CommandPath.java b/src/main/java/top/r3944realms/lib39/core/command/model/CommandPath.java similarity index 94% rename from src/main/java/top/r3944realms/lib39/core/command/CommandPath.java rename to src/main/java/top/r3944realms/lib39/core/command/model/CommandPath.java index dc70497..0a76878 100644 --- a/src/main/java/top/r3944realms/lib39/core/command/CommandPath.java +++ b/src/main/java/top/r3944realms/lib39/core/command/model/CommandPath.java @@ -1,4 +1,4 @@ -package top.r3944realms.lib39.core.command; +package top.r3944realms.lib39.core.command.model; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -28,6 +28,9 @@ public final class CommandPath { @Contract("_ -> new") public static @NotNull CommandPath fromString(@NotNull String path) { + if (path.charAt(0) == '/') { + path = path.substring(1); + } return of(path.split(" ")); } diff --git a/src/main/java/top/r3944realms/lib39/core/command/model/Parameter.java b/src/main/java/top/r3944realms/lib39/core/command/model/Parameter.java new file mode 100644 index 0000000..338c7dd --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/core/command/model/Parameter.java @@ -0,0 +1,48 @@ +package top.r3944realms.lib39.core.command.model; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public record Parameter(String name, boolean required) { + + /** + * 獲取參數類型標識 + */ + @Contract(pure = true) + public @NotNull String getTypeIndicator() { + return required ? "required" : "optional"; + } + + @Override + public String toString() { + return String.format("Parameter{name='%s', required=%s}", name, required); + } + + public static class Builder { + private final List parameters = new ArrayList<>(); + + public Builder required(String name) { + parameters.add(new Parameter(name, true)); + return this; + } + + public Builder optional(String name) { + parameters.add(new Parameter(name, false)); + return this; + } + + + public Parameter[] build() { + return parameters.toArray(new Parameter[0]); + } + + // 链式调用的便利方法 + @Contract(" -> new") + public static @NotNull Parameter.Builder builder() { + return new Builder(); + } + } +} diff --git a/src/main/java/top/r3944realms/lib39/core/event/ClientEventHandler.java b/src/main/java/top/r3944realms/lib39/core/event/ClientEventHandler.java index bcb43b5..b1b2c12 100644 --- a/src/main/java/top/r3944realms/lib39/core/event/ClientEventHandler.java +++ b/src/main/java/top/r3944realms/lib39/core/event/ClientEventHandler.java @@ -1,11 +1,19 @@ package top.r3944realms.lib39.core.event; +import net.minecraft.client.Minecraft; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.resources.ResourceLocation; import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.EntityRenderersEvent; +import net.minecraftforge.client.event.ModelEvent; import net.minecraftforge.client.event.RegisterShadersEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; +import org.jetbrains.annotations.NotNull; import top.r3944realms.lib39.Lib39; import top.r3944realms.lib39.client.shader.Lib39Shaders; +import java.util.Map; + /** * The type Client handler. */ @@ -24,6 +32,38 @@ public class ClientEventHandler { public static void onRegisterShaders(RegisterShadersEvent event) { Lib39Shaders.register(event); } + @SubscribeEvent + public static void onRegisterRenderer (EntityRenderersEvent.RegisterRenderers event) { +// event.registerBlockEntityRenderer(Lib39BlockEntities.DOLL_BLOCK_ENTITY.get(), context -> new DollBlockEntityRenderer()); + } + @SubscribeEvent + public static void onModelBaking(ModelEvent.RegisterAdditional event) { +// event.register(DollItemRenderer.DOLL_ITEM_MODEL); +// DollItemRenderer.ITEM_MODELS.values().forEach(event::register); +// event.register(DollItemRenderer.DOLL_WITHOUT_ITEM_MODEL); +// event.register(DollItemRenderer.DOLL_NEED_ITEM_MODEL); + } + @SubscribeEvent + public static void onModelBaking(ModelEvent.ModifyBakingResult event) { +// Map modelRegistry = event.getModels(); +// +// // 检查模型是否被正确加载 +// checkModelLoaded(modelRegistry, DollItemRenderer.DOLL_NEED_ITEM_MODEL); +// checkModelLoaded(modelRegistry, DollItemRenderer.DOLL_WITHOUT_ITEM_MODEL); +// checkModelLoaded(modelRegistry, DollItemRenderer.DOLL_ITEM_MODEL); + + } + + private static void checkModelLoaded(@NotNull Map registry, ResourceLocation location) { + BakedModel model = registry.get(location); + if (model == null) { + Lib39.LOGGER.warn("Model not found in registry: {}", location); + } else if (model == Minecraft.getInstance().getModelManager().getMissingModel()) { + Lib39.LOGGER.warn("Model is missing: {}", location); + } else { + Lib39.LOGGER.debug("Model loaded successfully: {}", location); + } + } } /** diff --git a/src/main/java/top/r3944realms/lib39/core/event/CommonEventHandler.java b/src/main/java/top/r3944realms/lib39/core/event/CommonEventHandler.java index dc4807a..0d41495 100644 --- a/src/main/java/top/r3944realms/lib39/core/event/CommonEventHandler.java +++ b/src/main/java/top/r3944realms/lib39/core/event/CommonEventHandler.java @@ -23,8 +23,8 @@ import net.minecraftforge.registries.RegistryObject; import top.r3944realms.lib39.Lib39; import top.r3944realms.lib39.api.event.RegisterCompatEvent; import top.r3944realms.lib39.api.event.SyncManagerRegisterEvent; -import top.r3944realms.lib39.base.datagen.Lib39BaseDataGenEvent; import top.r3944realms.lib39.base.command.Lib39HelpCommand; +import top.r3944realms.lib39.base.datagen.Lib39BaseDataGenEvent; import top.r3944realms.lib39.core.compat.CompatManager; import top.r3944realms.lib39.core.sync.ISyncData; import top.r3944realms.lib39.core.sync.SyncData2Manager; diff --git a/src/main/java/top/r3944realms/lib39/core/lang/ClassEncryptor.java b/src/main/java/top/r3944realms/lib39/core/lang/ClassEncryptor.java new file mode 100644 index 0000000..5093f64 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/core/lang/ClassEncryptor.java @@ -0,0 +1,52 @@ +package top.r3944realms.lib39.core.lang; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Stream; + +public class ClassEncryptor { + static { + System.loadLibrary("ClassEncrypt"); + } + + public native byte[] encryptClass(byte[] classData, String key); + public native byte[] decryptClass(byte[] encryptedData, String key); + + public native boolean isEncryptedFile(byte[] fileData); + + public void encryptClassFile(String inputPath, String outputPath, String key) + throws IOException { + byte[] classData = Files.readAllBytes(Paths.get(inputPath)); + byte[] encryptedData = encryptClass(classData, key); + Files.write(Paths.get(outputPath), encryptedData); + System.out.println("Encrypted: " + inputPath + " -> " + outputPath); + } + + + public void encryptDirectory(String inputDir, String outputDir, String key) + throws IOException { + try (Stream walk = Files.walk(Paths.get(inputDir))) { + walk + .filter(Files::isRegularFile) + .filter(p -> p.toString().endsWith(".class")) + .forEach(p -> { + try { + String relativePath = inputDir.equals(p.getParent().toString()) + ? p.getFileName().toString() + : inputDir.equals(p.getParent().getParent().toString()) + ? p.getParent().getFileName() + "/" + p.getFileName() + : p.toString().substring(inputDir.length() + 1); + + Path outputPath = Paths.get(outputDir, relativePath); + Files.createDirectories(outputPath.getParent()); + + encryptClassFile(p.toString(), outputPath.toString(), key); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } + } +} diff --git a/src/main/java/top/r3944realms/lib39/core/lang/EncryptedClassLoader.java b/src/main/java/top/r3944realms/lib39/core/lang/EncryptedClassLoader.java new file mode 100644 index 0000000..d8caa48 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/core/lang/EncryptedClassLoader.java @@ -0,0 +1,196 @@ +package top.r3944realms.lib39.core.lang; + +import java.io.*; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +public class EncryptedClassLoader extends ClassLoader { + static { + System.loadLibrary("ClassEncrypt"); + } + + private native byte[] decryptClass(byte[] encryptedData, String key); + + private final String encryptedClassPath; + private final String decryptionKey; + + // 缓存已加载的类字节码,避免重复加载 + private final Map classBytesCache = new HashMap<>(); + + public EncryptedClassLoader(String encryptedClassPath, String key) { + this.encryptedClassPath = encryptedClassPath; + this.decryptionKey = key; + } + + public EncryptedClassLoader(String encryptedClassPath, String key, ClassLoader parent) { + super(parent); + this.encryptedClassPath = encryptedClassPath; + this.decryptionKey = key; + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + try { + // 从缓存获取或加载类字节码 + byte[] classData; + synchronized (classBytesCache) { + classData = classBytesCache.get(name); + if (classData == null) { + // 1. 读取加密的class文件 + byte[] encryptedData = loadEncryptedClass(name); + + // 2. 使用JNI解密 + classData = decryptClass(encryptedData, decryptionKey); + + // 3. 验证解密后的数据是否是有效的class文件 + if (!isValidClass(classData)) { + throw new ClassNotFoundException("Invalid class data after decryption"); + } + + // 缓存类字节码 + classBytesCache.put(name, classData); + } + } + + // 4. 定义类 + return defineClass(name, classData, 0, classData.length); + + } catch (Exception e) { + throw new ClassNotFoundException("Failed to load encrypted class: " + name, e); + } + } + + private byte [] loadEncryptedClass(String className) throws IOException { + String path = className.replace('.', File.separatorChar) + ".class"; + Path fullPath = Paths.get(encryptedClassPath, path); + + if (!Files.exists(fullPath)) { + // 尝试寻找内部类 + int dollarIndex = className.lastIndexOf('$'); + if (dollarIndex != -1) { + String outerClass = className.substring(0, dollarIndex); + path = outerClass.replace('.', File.separatorChar) + + "$" + className.substring(dollarIndex + 1) + ".class"; + fullPath = Paths.get(encryptedClassPath, path); + } + } + + if (!Files.exists(fullPath)) { + // 尝试其他可能的文件扩展名 + fullPath = Paths.get(encryptedClassPath, className.replace('.', File.separatorChar) + ".enc"); + if (!Files.exists(fullPath)) { + throw new FileNotFoundException("Encrypted class not found: " + className); + } + } + + return Files.readAllBytes(fullPath); + } + + private boolean isValidClass(byte [] data) { + // Java class文件的魔数是0xCAFEBABE + return data.length >= 4 && + data[0] == (byte)0xCA && + data[1] == (byte)0xFE && + data[2] == (byte)0xBA && + data[3] == (byte)0xBE; + } + + @Override + protected Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + // 优先检查是否已加载 + Class clazz = findLoadedClass(name); + if (clazz != null) { + return clazz; + } + + // Java核心类库使用父加载器 + if (name.startsWith("java.") || name.startsWith("javax.") || + name.startsWith("sun.") || name.startsWith("jdk.")) { + return super.loadClass(name, resolve); + } + + try { + // 尝试用自定义ClassLoader加载 + clazz = findClass(name); + } catch (ClassNotFoundException e) { + // 如果找不到,委托给父加载器 + clazz = super.loadClass(name, resolve); + } + + if (resolve) { + resolveClass(clazz); + } + + return clazz; + } + + // 添加获取类字节码的方法 + public byte[] getClassBytes(String className) throws ClassNotFoundException { + synchronized (classBytesCache) { + byte[] bytes = classBytesCache.get(className); + if (bytes == null) { + // 触发类加载以填充缓存 + loadClass(className); + bytes = classBytesCache.get(className); + } + return bytes != null ? bytes.clone() : null; // 返回副本 + } + } + + @Override + public InputStream getResourceAsStream(String name) { + try { + // 处理.class资源请求 + if (name.endsWith(".class")) { + String className = name.substring(0, name.length() - 6) + .replace('/', '.'); + byte[] classData = getClassBytes(className); + if (classData != null) { + return new ByteArrayInputStream(classData); + } + } + + // 处理其他资源文件 + Path resourcePath = Paths.get(encryptedClassPath, name); + if (Files.exists(resourcePath)) { + return Files.newInputStream(resourcePath); + } + + } catch (Exception e) { + // 忽略异常,返回null让父加载器处理 + } + + // 委托给父加载器 + return super.getResourceAsStream(name); + } + + @Override + public URL getResource(String name) { + try { + Path resourcePath = Paths.get(encryptedClassPath, name); + if (Files.exists(resourcePath)) { + return resourcePath.toUri().toURL(); + } + } catch (Exception e) { + // 忽略异常 + } + return super.getResource(name); + } + + public void clearCache() { + synchronized (classBytesCache) { + classBytesCache.clear(); + } + } + + public void clearCache(String className) { + synchronized (classBytesCache) { + classBytesCache.remove(className); + } + } +} \ No newline at end of file diff --git a/src/main/java/top/r3944realms/lib39/mixin/Lib39MixinPlugin.java b/src/main/java/top/r3944realms/lib39/mixin/Lib39MixinPlugin.java new file mode 100644 index 0000000..a6d579a --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/mixin/Lib39MixinPlugin.java @@ -0,0 +1,57 @@ +/* + * * + * * Copyright (c) 2025 R3944Realms. All rights reserved. + * * + * * This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. + * * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ + * * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + * * + * * 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 + * + */ + +package top.r3944realms.lib39.mixin; + +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; + +import java.util.List; +import java.util.Set; + +public class Lib39MixinPlugin implements IMixinConfigPlugin { + @Override + public void onLoad(String mixinPackage) { + + } + + @Override + public String getRefMapperConfig() { + return null; + } + + @Override + public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { + return true; // 改为 true 允许所有 mixin + } + + @Override + public void acceptTargets(Set myTargets, Set otherTargets) { + + } + + @Override + public List getMixins() { + return null; // 改为 null 表示不使用动态注册 + } + + @Override + public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + + } + + @Override + public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + + } +} diff --git a/src/main/java/top/r3944realms/lib39/mixin/init/MixinDedicateServer.java b/src/main/java/top/r3944realms/lib39/mixin/init/MixinDedicateServer.java new file mode 100644 index 0000000..9df41ff --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/mixin/init/MixinDedicateServer.java @@ -0,0 +1,38 @@ +package top.r3944realms.lib39.mixin.init; + +import com.mojang.datafixers.DataFixer; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.ServerInterface; +import net.minecraft.server.Services; +import net.minecraft.server.WorldStem; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.level.progress.ChunkProgressListenerFactory; +import net.minecraft.server.packs.repository.PackRepository; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraftforge.common.MinecraftForge; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import top.r3944realms.lib39.api.event.MinecraftSetUpServiceEvent; + +import java.net.Proxy; + +@Mixin(DedicatedServer.class) +public abstract class MixinDedicateServer extends MinecraftServer implements ServerInterface { + public MixinDedicateServer(Thread serverThread, LevelStorageSource.LevelStorageAccess storageSource, PackRepository packRepository, WorldStem worldStem, Proxy proxy, DataFixer fixerUpper, Services services, ChunkProgressListenerFactory progressListenerFactory) { + super(serverThread, storageSource, packRepository, worldStem, proxy, fixerUpper, services, progressListenerFactory); + } + + @Inject( + method = "initServer", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/players/GameProfileCache;setUsesAuthentication(Z)V", + shift = At.Shift.AFTER + ) + ) + private void initServer$setup(CallbackInfoReturnable cir) { + MinecraftForge.EVENT_BUS.post(new MinecraftSetUpServiceEvent(this.services,this)); + } +} diff --git a/src/main/java/top/r3944realms/lib39/mixin/init/MixinMinecraft.java b/src/main/java/top/r3944realms/lib39/mixin/init/MixinMinecraft.java new file mode 100644 index 0000000..8a39d8d --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/mixin/init/MixinMinecraft.java @@ -0,0 +1,50 @@ +package top.r3944realms.lib39.mixin.init; + +import com.llamalad7.mixinextras.sugar.Local; +import com.mojang.blaze3d.platform.WindowEventHandler; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.server.Services; +import net.minecraft.server.WorldStem; +import net.minecraft.server.packs.repository.PackRepository; +import net.minecraft.util.thread.ReentrantBlockableEventLoop; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraftforge.common.MinecraftForge; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import top.r3944realms.lib39.api.event.MinecraftSetUpServiceEvent; + +@Mixin(Minecraft.class) +public abstract class MixinMinecraft extends ReentrantBlockableEventLoop implements WindowEventHandler, net.minecraftforge.client.extensions.IForgeMinecraft { + public MixinMinecraft(String name) { + super(name); + } + @Inject( + method = "setLevel", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/players/GameProfileCache;setUsesAuthentication(Z)V", + shift = At.Shift.AFTER + ) + ) + public void setLevel$setup(ClientLevel levelClient, CallbackInfo ci, + @Local(ordinal = 0) Services services) { + MinecraftForge.EVENT_BUS.post(new MinecraftSetUpServiceEvent(services,this)); + } + + @Inject( + method = "doWorldLoad", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/server/players/GameProfileCache;setUsesAuthentication(Z)V", + shift = At.Shift.AFTER + ) + ) + public void doWorldLoad$setup(String levelId, LevelStorageSource.LevelStorageAccess level, PackRepository packRepository, WorldStem worldStem, boolean newWorld, CallbackInfo ci, + @Local(ordinal = 0) Services services) { + MinecraftForge.EVENT_BUS.post(new MinecraftSetUpServiceEvent(services,this)); + } +} + diff --git a/src/main/java/top/r3944realms/lib39/util/PlantHelper.java b/src/main/java/top/r3944realms/lib39/util/PlantHelper.java new file mode 100644 index 0000000..e7e54c3 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/PlantHelper.java @@ -0,0 +1,106 @@ +package top.r3944realms.lib39.util; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.StringRepresentable; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import top.r3944realms.lib39.Lib39; + +public class PlantHelper { + public enum Plant implements StringRepresentable { + // 树苗 + ACACIA_SAPLING("acacia_sapling", Items.ACACIA_SAPLING), + BAMBOO("bamboo_stage0", Items.BAMBOO), + BIRCH_SAPLING("birch_sapling", Items.BIRCH_SAPLING), + CHERRY_SAPLING("cherry_sapling", Items.CHERRY_SAPLING), + DARK_OAK_SAPLING("dark_oak_sapling", Items.DARK_OAK_SAPLING), + DEAD_BUSH("dead_bush", Items.DEAD_BUSH), + JUNGLE_SAPLING("jungle_sapling", Items.JUNGLE_SAPLING), + OAK_SAPLING("oak_sapling", Items.OAK_SAPLING), + SPRUCE_SAPLING("spruce_sapling", Items.SPRUCE_SAPLING), + + // 花 + ALLIUM("allium", Items.ALLIUM), + AZURE_BLUET("azure_bluet", Items.AZURE_BLUET), + CORNFLOWER("cornflower", Items.CORNFLOWER), + DANDELION("dandelion", Items.DANDELION), + LILY_OF_THE_VALLEY("lily_of_the_valley", Items.LILY_OF_THE_VALLEY), + OXEYE_DAISY("oxeye_daisy", Items.OXEYE_DAISY), + ORANGE_TULIP("orange_tulip", Items.ORANGE_TULIP), + PINK_TULIP("pink_tulip", Items.PINK_TULIP), + RED_TULIP("red_tulip", Items.RED_TULIP), + WHITE_TULIP("white_tulip", Items.WHITE_TULIP), + WITHER_ROSE("wither_rose", Items.WITHER_ROSE), + POPPY("poppy", Items.POPPY), + + + // 晶体 + AMETHYST_CLUSTER("amethyst_cluster", Items.AMETHYST_CLUSTER), + + // 珊瑚 + BRAIN_CORAL("brain_coral", Items.BRAIN_CORAL), + BRAIN_CORAL_FAN("brain_coral_fan", Items.BRAIN_CORAL_FAN), + BUBBLE_CORAL("bubble_coral", Items.BUBBLE_CORAL), + BUBBLE_CORAL_FAN("bubble_coral_fan", Items.BUBBLE_CORAL_FAN), + FIRE_CORAL("fire_coral", Items.FIRE_CORAL), + FIRE_CORAL_FAN("fire_coral_fan", Items.FIRE_CORAL_FAN), + HORN_CORAL("horn_coral", Items.HORN_CORAL), + HORN_CORAL_FAN("horn_coral_fan", Items.HORN_CORAL_FAN), + TUBE_CORAL("tube_coral", Items.TUBE_CORAL), + TUBE_CORAL_FAN("tube_coral_fan", Items.TUBE_CORAL_FAN), + DEAD_FIRE_CORAL("dead_fire_coral", Items.DEAD_FIRE_CORAL), + DEAD_FIRE_CORAL_FAN("dead_fire_coral_fan", Items.DEAD_FIRE_CORAL_FAN), + DEAD_HORN_CORAL("dead_horn_coral", Items.DEAD_HORN_CORAL), + DEAD_HORN_CORAL_FAN("dead_horn_coral_fan", Items.DEAD_HORN_CORAL_FAN), + DEAD_TUBE_CORAL("dead_tube_coral", Items.DEAD_TUBE_CORAL), + DEAD_TUBE_CORAL_FAN("dead_tube_coral_fan", Items.DEAD_TUBE_CORAL_FAN), + DEAD_BRAIN_CORAL("dead_brain_coral", Items.DEAD_BRAIN_CORAL), + DEAD_BRAIN_CORAL_FAN("dead_brain_coral_fan", Items.DEAD_BRAIN_CORAL_FAN), + DEAD_BUBBLE_CORAL("dead_bubble_coral", Items.DEAD_BUBBLE_CORAL), + DEAD_BUBBLE_CORAL_FAN("dead_bubble_coral_fan", Items.DEAD_BUBBLE_CORAL_FAN), + + // 蘑菇 + CRIMSON_FUNGUS("crimson_fungus", Items.CRIMSON_FUNGUS), + RED_MUSHROOM("red_mushroom", Items.RED_MUSHROOM), + BROWN_MUSHROOM("brown_mushroom", Items.BROWN_MUSHROOM), + WARPED_FUNGUS("warped_fungus", Items.WARPED_FUNGUS), + + + // Grass +// CRIMSON_ROOTS("crimson_roots"), + CRIMSON_ROOTS_POT("crimson_roots_pot", Items.CRIMSON_ROOTS), +// WARPED_ROOTS("warped_roots"), + WARPED_ROOTS_POT("warped_roots_pot", Items.WARPED_ROOTS), + + // Other + COBWEB("cobweb", Items.COBWEB), + REDSTONE_TORCH("redstone_torch", Items.REDSTONE_TORCH), + TORCH("torch", Items.TORCH), + ; + public final String name; + public final Item item; + Plant(String name, Item item) { + this.name = name; + this.item = item; + } + @Override + public @NotNull String toString() { + return super.toString().toLowerCase(); + } + + @Override + public @NotNull String getSerializedName() { + return name; + } + } + @Contract("_ -> new") + public static @NotNull ResourceLocation getTextureRL(@NotNull Plant plant) { + return Lib39.mrl("block/" + plant.name); + } + @Contract("_ -> new") + public static @NotNull ResourceLocation getDirectlyTextureRL(@NotNull Plant plant) { + return Lib39.mrl("textures/block/" + plant.name + ".png"); + } +} diff --git a/src/main/java/top/r3944realms/lib39/util/SkinHelper.java b/src/main/java/top/r3944realms/lib39/util/SkinHelper.java new file mode 100644 index 0000000..8081ba4 --- /dev/null +++ b/src/main/java/top/r3944realms/lib39/util/SkinHelper.java @@ -0,0 +1,23 @@ +package top.r3944realms.lib39.util; + +import com.mojang.authlib.GameProfile; +import net.minecraft.client.Minecraft; +import net.minecraft.client.resources.DefaultPlayerSkin; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class SkinHelper { + public static ResourceLocation DEFAULT = DefaultPlayerSkin.getDefaultSkin(); + public static ResourceLocation getSkinTexture(@Nullable GameProfile gameProfile) { + if (gameProfile == null) { + return DEFAULT; + } + return resolveSkinTexture(gameProfile); + } + public static @NotNull ResourceLocation resolveSkinTexture(@NotNull GameProfile gameProfile) { + Minecraft minecraft = Minecraft.getInstance(); + return minecraft.getSkinManager() + .getInsecureSkinLocation(gameProfile); + } +} diff --git a/src/main/java/top/r3944realms/lib39/util/nbt/NBTWriter.java b/src/main/java/top/r3944realms/lib39/util/nbt/NBTWriter.java index d70cdc8..d176e20 100644 --- a/src/main/java/top/r3944realms/lib39/util/nbt/NBTWriter.java +++ b/src/main/java/top/r3944realms/lib39/util/nbt/NBTWriter.java @@ -8,6 +8,7 @@ import org.jetbrains.annotations.Nullable; import java.util.UUID; import java.util.function.Consumer; +import java.util.function.Supplier; /** * The type Nbt writer. @@ -475,6 +476,21 @@ public class NBTWriter { return this; } + /** + * Compound if nbt writer. + * + * @param key the key + * @param condition the condition + * @param compoundTag the compoundTag + * @return the nbt writer + */ + public NBTWriter compoundIf(String key, boolean condition, Supplier compoundTag) { + if (condition && compoundTag != null) { + return compound(key, compoundTag.get()); + } + return this; + } + /** * List nbt writer. * @@ -524,6 +540,21 @@ public class NBTWriter { return this; } + /** + * List if nbt writer. + * + * @param key the key + * @param condition the condition + * @param listTag the listTag + * @return the nbt writer + */ + public NBTWriter listIf(String key, boolean condition, Supplier listTag) { + if (condition && listTag != null) { + return list(key, listTag.get()); + } + return this; + } + /** * Tag nbt writer. * @@ -548,9 +579,9 @@ public class NBTWriter { * @return the nbt writer */ // 条件添加方法 - public NBTWriter stringIf(String key, String value, boolean condition) { + public NBTWriter stringIf(String key, Supplier value, boolean condition) { if (condition && value != null) { - root.putString(key, value); + root.putString(key, value.get()); } return this; } @@ -563,9 +594,9 @@ public class NBTWriter { * @param condition the condition * @return the nbt writer */ - public NBTWriter intValueIf(String key, Integer value, boolean condition) { + public NBTWriter intValueIf(String key, Supplier value, boolean condition) { if (condition && value != null) { - root.putInt(key, value); + root.putInt(key, value.get()); } return this; } @@ -578,9 +609,9 @@ public class NBTWriter { * @param condition the condition * @return the nbt writer */ - public NBTWriter longValueIf(String key, Long value, boolean condition) { + public NBTWriter longValueIf(String key, Supplier value, boolean condition) { if (condition && value != null) { - root.putLong(key, value); + root.putLong(key, value.get()); } return this; } @@ -593,9 +624,9 @@ public class NBTWriter { * @param condition the condition * @return the nbt writer */ - public NBTWriter uuidValueIf(String key, UUID value, boolean condition) { + public NBTWriter uuidValueIf(String key, Supplier value, boolean condition) { if (condition && value != null) { - root.putUUID(key, value); + root.putUUID(key, value.get()); } return this; } @@ -608,9 +639,9 @@ public class NBTWriter { * @param condition the condition * @return the nbt writer */ - public NBTWriter booleanValueIf(String key, Boolean value, boolean condition) { + public NBTWriter booleanValueIf(String key, Supplier value, boolean condition) { if (condition && value != null) { - root.putBoolean(key, value); + root.putBoolean(key, value.get()); } return this; } diff --git a/src/main/resources/assets/lib39/models/block/base_doll.json b/src/main/resources/assets/lib39/models/block/base_doll.json new file mode 100644 index 0000000..c8574d2 --- /dev/null +++ b/src/main/resources/assets/lib39/models/block/base_doll.json @@ -0,0 +1,50 @@ +{ + "format_version": "1.9.0", + "credit": "3D Model © 2025 LeisureTimeDock", + "parent": "builtin/entity", + "textures": { + "particle": "minecraft:block/white_wool" + }, + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 124, 0], + "translation": [2, 3, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 120, 0], + "translation": [1.5, 2.75, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 2, 0], + "scale": [0.5, 0.5, 0.5] + }, + "gui": { + "rotation": [30, -135, 0], + "translation": [0.75, -1, 0], + "scale": [0.625, 0.625, 0.625] + }, + "head": { + "translation": [0, 14, -0.75] + }, + "fixed": { + "translation": [0, 0, -2.75], + "scale": [0.5, 0.5, 0.5] + }, + "on_shelf": { + "rotation": [0, -180, 0], + "translation": [0, 0, 5.25] + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/lib39/models/block/base_doll_item.json b/src/main/resources/assets/lib39/models/block/base_doll_item.json new file mode 100644 index 0000000..316f35d --- /dev/null +++ b/src/main/resources/assets/lib39/models/block/base_doll_item.json @@ -0,0 +1,36 @@ +{ + "format_version": "1.9.0", + "credit": "3D Model © 2025 LeisureTimeDock", + "textures": { + "3": "#item" + }, + "elements": [ + { + "from": [7, 6, 1], + "to": [7, 14, 9], + "rotation": {"angle": 45, "axis": "y", "origin": [7, 10, 3.5]}, + "faces": { + "east": {"uv": [0, 0, 16, 16], "texture": "#3"}, + "west": {"uv": [0, 0, 16, 16], "texture": "#3"} + } + }, + { + "from": [9, 6, 1], + "to": [9, 14, 9], + "rotation": {"angle": -45, "axis": "y", "origin": [9, 10, 3.5]}, + "faces": { + "east": {"uv": [0, 0, 16, 16], "texture": "#3"}, + "west": {"uv": [0, 0, 16, 16], "texture": "#3"} + } + } + ], + "display": {}, + "groups": [ + { + "name": "item", + "origin": [0, 4, 2.5], + "color": 0, + "children": [0, 1] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/lib39/models/block/doll_default.json b/src/main/resources/assets/lib39/models/block/doll_default.json new file mode 100644 index 0000000..1afca42 --- /dev/null +++ b/src/main/resources/assets/lib39/models/block/doll_default.json @@ -0,0 +1,264 @@ +{ + "format_version": "1.9.0", + "credit": "3D Model © 2025 LeisureTimeDock", + "textures": { + "0": "#skin" + }, + "elements": [ + { + "name": "Toggle_Helmet", + "from": [3.5, 8.8, 7.5], + "to": [12.5, 17.8, 16.5], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 9.3, 12]}, + "faces": { + "north": {"uv": [10, 2, 12, 4], "texture": "#0"}, + "east": {"uv": [8, 2, 10, 4], "texture": "#0"}, + "south": {"uv": [14, 2, 16, 4], "texture": "#0"}, + "west": {"uv": [12, 2, 14, 4], "texture": "#0"}, + "up": {"uv": [10, 2, 12, 0], "texture": "#0"}, + "down": {"uv": [12, 0, 14, 2], "texture": "#0"} + } + }, + { + "name": "Head", + "from": [4, 9.3, 8], + "to": [12, 17.3, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 9.3, 12]}, + "faces": { + "north": {"uv": [2, 2, 4, 4], "texture": "#0"}, + "east": {"uv": [0, 2, 2, 4], "texture": "#0"}, + "south": {"uv": [6, 2, 8, 4], "texture": "#0"}, + "west": {"uv": [4, 2, 6, 4], "texture": "#0"}, + "up": {"uv": [4, 2, 2, 0], "texture": "#0"}, + "down": {"uv": [6, 0, 4, 2], "texture": "#0"} + } + }, + { + "name": "Toggle_Chest_Armor", + "from": [4.75, 2.05, 9.75], + "to": [11.25, 9.55, 13.25], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 5.8, 11.5]}, + "faces": { + "north": {"uv": [5, 9, 7, 12], "texture": "#0"}, + "east": {"uv": [4, 9, 5, 12], "texture": "#0"}, + "south": {"uv": [8, 9, 10, 12], "texture": "#0"}, + "west": {"uv": [7, 9, 8, 12], "texture": "#0"}, + "up": {"uv": [5, 8, 7, 9], "texture": "#0"}, + "down": {"uv": [7, 8, 9, 9], "texture": "#0"} + } + }, + { + "name": "Body", + "from": [5, 2.3, 10], + "to": [11, 9.3, 13], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 5.8, 11.5]}, + "faces": { + "north": {"uv": [5, 5, 7, 8], "texture": "#0"}, + "east": {"uv": [4, 5, 5, 8], "texture": "#0"}, + "south": {"uv": [8, 5, 10, 8], "texture": "#0"}, + "west": {"uv": [7, 5, 8, 8], "texture": "#0"}, + "up": {"uv": [7, 5, 5, 4], "texture": "#0"}, + "down": {"uv": [9, 4, 7, 5], "texture": "#0"} + } + }, + { + "name": "Toggle_Left_Arm_Armor", + "from": [2.75, 6.25, 3.8], + "to": [5.25, 9.75, 13.3], + "rotation": {"angle": -22.5, "axis": "y", "origin": [3, 8, 12.5]}, + "faces": { + "north": {"uv": [13.75, 12, 14.5, 13], "rotation": 180, "texture": "#0"}, + "east": {"uv": [12, 13, 13, 16], "rotation": 270, "texture": "#0"}, + "south": {"uv": [13, 12, 13.75, 13], "texture": "#0"}, + "west": {"uv": [13.75, 13, 14.75, 16], "rotation": 90, "texture": "#0"}, + "up": {"uv": [13, 13, 13.75, 16], "rotation": 180, "texture": "#0"}, + "down": {"uv": [14.75, 13, 15.5, 16], "texture": "#0"} + } + }, + { + "name": "Left_arm", + "from": [3, 6.5, 3.8], + "to": [5, 9.5, 12.8], + "rotation": {"angle": -22.5, "axis": "y", "origin": [4, 8, 12.5]}, + "faces": { + "north": {"uv": [10.5, 12, 9.75, 13], "rotation": 180, "texture": "#0"}, + "east": {"uv": [8, 13, 9, 16], "rotation": 270, "texture": "#0"}, + "south": {"uv": [9.75, 13, 9, 12], "texture": "#0"}, + "west": {"uv": [9.75, 13, 10.5, 16], "rotation": 90, "texture": "#0"}, + "up": {"uv": [9, 13, 9.75, 16], "rotation": 180, "texture": "#0"}, + "down": {"uv": [10.5, 13, 11.5, 16], "texture": "#0"} + } + }, + { + "name": "Toggle_Right_Arm_Armor", + "from": [10.75, 6.25, 3.8], + "to": [13.25, 9.75, 13.3], + "rotation": {"angle": 22.5, "axis": "y", "origin": [12, 8, 11.5]}, + "faces": { + "north": {"uv": [11.75, 8, 12.5, 9], "rotation": 180, "texture": "#0"}, + "east": {"uv": [10, 9, 11, 12], "rotation": 270, "texture": "#0"}, + "south": {"uv": [11, 8, 11.75, 9], "texture": "#0"}, + "west": {"uv": [11.75, 9, 12.75, 12], "rotation": 90, "texture": "#0"}, + "up": {"uv": [11, 9, 11.75, 12], "rotation": 180, "texture": "#0"}, + "down": {"uv": [12.75, 9, 13.5, 12], "texture": "#0"} + } + }, + { + "name": "Right_arm", + "from": [11, 6.5, 3.8], + "to": [13, 9.5, 12.8], + "rotation": {"angle": 22.5, "axis": "y", "origin": [12, 8, 11.5]}, + "faces": { + "north": {"uv": [12.5, 4, 11.75, 5], "rotation": 180, "texture": "#0"}, + "east": {"uv": [10, 5, 11, 8], "rotation": 270, "texture": "#0"}, + "south": {"uv": [11.75, 5, 11, 4], "texture": "#0"}, + "west": {"uv": [11.75, 5, 12.5, 8], "rotation": 90, "texture": "#0"}, + "up": {"uv": [11, 5, 11.75, 8], "rotation": 180, "texture": "#0"}, + "down": {"uv": [12.5, 5, 13.5, 8], "texture": "#0"} + } + }, + { + "name": "Toggle_Left_Leg_Armor", + "from": [5.2, -0.25, 3.05], + "to": [8.7, 3.25, 12.55], + "rotation": {"angle": 22.5, "axis": "y", "origin": [5.7, 2, 13]}, + "faces": { + "north": {"uv": [2, 12, 3, 13], "rotation": 180, "texture": "#0"}, + "east": {"uv": [0, 13, 1, 16], "rotation": 270, "texture": "#0"}, + "south": {"uv": [1, 12, 2, 13], "texture": "#0"}, + "west": {"uv": [2, 13, 3, 16], "rotation": 90, "texture": "#0"}, + "up": {"uv": [1, 13, 2, 16], "rotation": 180, "texture": "#0"}, + "down": {"uv": [3, 13, 4, 16], "texture": "#0"} + } + }, + { + "name": "Left_leg", + "from": [5.5, 0, 3.3], + "to": [8.5, 3, 12.3], + "rotation": {"angle": 22.5, "axis": "y", "origin": [6, 2, 13]}, + "faces": { + "north": {"uv": [7, 12, 6, 13], "rotation": 180, "texture": "#0"}, + "east": {"uv": [4, 13, 5, 16], "rotation": 270, "texture": "#0"}, + "south": {"uv": [5.95, 13, 5, 12], "texture": "#0"}, + "west": {"uv": [6, 13, 7, 16], "rotation": 90, "texture": "#0"}, + "up": {"uv": [5, 13, 6, 16], "rotation": 180, "texture": "#0"}, + "down": {"uv": [7, 13, 8, 16], "texture": "#0"} + } + }, + { + "name": "Toggle_Right_Leg_Armor", + "from": [7.2, -0.25, 3.05], + "to": [10.7, 3.25, 12.55], + "rotation": {"angle": -22.5, "axis": "y", "origin": [8.7, 2, 13]}, + "faces": { + "north": {"uv": [2, 8, 3, 9], "rotation": 180, "texture": "#0"}, + "east": {"uv": [0, 9, 1, 12], "rotation": 270, "texture": "#0"}, + "south": {"uv": [1, 8, 2, 9], "texture": "#0"}, + "west": {"uv": [2, 9, 3, 12], "rotation": 90, "texture": "#0"}, + "up": {"uv": [1, 9, 2, 12], "rotation": 180, "texture": "#0"}, + "down": {"uv": [3, 9, 4, 12], "texture": "#0"} + } + }, + { + "name": "Right_leg", + "from": [7.5, 0, 3.3], + "to": [10.5, 3, 12.3], + "rotation": {"angle": -22.5, "axis": "y", "origin": [9, 2, 13]}, + "faces": { + "north": {"uv": [3, 4, 2, 5], "rotation": 180, "texture": "#0"}, + "east": {"uv": [0, 5, 1, 8], "rotation": 270, "texture": "#0"}, + "south": {"uv": [2, 5, 1, 4], "texture": "#0"}, + "west": {"uv": [2, 5, 3, 8], "rotation": 90, "texture": "#0"}, + "up": {"uv": [1, 5, 2, 8], "rotation": 180, "texture": "#0"}, + "down": {"uv": [3, 5, 4, 8], "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 124, 0], + "translation": [2, 3, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 120, 0], + "translation": [1.5, 2.75, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 2, 0], + "scale": [0.5, 0.5, 0.5] + }, + "gui": { + "rotation": [30, -135, 0], + "translation": [0.75, -1, 0], + "scale": [0.625, 0.625, 0.625] + }, + "head": { + "translation": [0, 14, -0.75] + }, + "fixed": { + "translation": [0, 0, -2.75], + "scale": [0.5, 0.5, 0.5] + }, + "on_shelf": { + "rotation": [0, -180, 0], + "translation": [0, 0, 5.25] + } + }, + "groups": [ + { + "name": "Player", + "origin": [3, -6.7, 6], + "color": 0, + "children": [ + { + "name": "Head", + "origin": [8, 16, 8], + "color": 0, + "children": [0, 1] + }, + { + "name": "Body", + "origin": [8, 11, 8], + "color": 0, + "children": [2, 3] + }, + { + "name": "Left_Arm", + "origin": [5, 15, 6], + "color": 0, + "children": [4, 5] + }, + { + "name": "Right_Arm", + "origin": [11, 15, 6], + "color": 0, + "children": [6, 7] + }, + { + "name": "Left_Leg", + "origin": [7, 13, 7], + "color": 0, + "children": [8, 9] + }, + { + "name": "Right_Leg", + "origin": [10, 13, 7], + "color": 0, + "children": [10, 11] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/lib39/models/block/doll_without_item.json b/src/main/resources/assets/lib39/models/block/doll_without_item.json new file mode 100644 index 0000000..a810153 --- /dev/null +++ b/src/main/resources/assets/lib39/models/block/doll_without_item.json @@ -0,0 +1,264 @@ +{ + "format_version": "1.9.0", + "credit": "3D Model © 2025 LeisureTimeDock", + "textures": { + "0": "#skin" + }, + "elements": [ + { + "name": "Toggle_Helmet", + "from": [3.5, 8.8, 7.5], + "to": [12.5, 17.8, 16.5], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 9.3, 12]}, + "faces": { + "north": {"uv": [10, 2, 12, 4], "texture": "#0"}, + "east": {"uv": [8, 2, 10, 4], "texture": "#0"}, + "south": {"uv": [14, 2, 16, 4], "texture": "#0"}, + "west": {"uv": [12, 2, 14, 4], "texture": "#0"}, + "up": {"uv": [10, 2, 12, 0], "texture": "#0"}, + "down": {"uv": [12, 0, 14, 2], "texture": "#0"} + } + }, + { + "name": "Head", + "from": [4, 9.3, 8], + "to": [12, 17.3, 16], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 9.3, 12]}, + "faces": { + "north": {"uv": [2, 2, 4, 4], "texture": "#0"}, + "east": {"uv": [0, 2, 2, 4], "texture": "#0"}, + "south": {"uv": [6, 2, 8, 4], "texture": "#0"}, + "west": {"uv": [4, 2, 6, 4], "texture": "#0"}, + "up": {"uv": [4, 2, 2, 0], "texture": "#0"}, + "down": {"uv": [6, 0, 4, 2], "texture": "#0"} + } + }, + { + "name": "Toggle_Chest_Armor", + "from": [4.75, 2.05, 9.75], + "to": [11.25, 9.55, 13.25], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 5.8, 11.5]}, + "faces": { + "north": {"uv": [5, 9, 7, 12], "texture": "#0"}, + "east": {"uv": [4, 9, 5, 12], "texture": "#0"}, + "south": {"uv": [8, 9, 10, 12], "texture": "#0"}, + "west": {"uv": [7, 9, 8, 12], "texture": "#0"}, + "up": {"uv": [5, 8, 7, 9], "texture": "#0"}, + "down": {"uv": [7, 8, 9, 9], "texture": "#0"} + } + }, + { + "name": "Body", + "from": [5, 2.3, 10], + "to": [11, 9.3, 13], + "rotation": {"angle": 0, "axis": "y", "origin": [8, 5.8, 11.5]}, + "faces": { + "north": {"uv": [5, 5, 7, 8], "texture": "#0"}, + "east": {"uv": [4, 5, 5, 8], "texture": "#0"}, + "south": {"uv": [8, 5, 10, 8], "texture": "#0"}, + "west": {"uv": [7, 5, 8, 8], "texture": "#0"}, + "up": {"uv": [7, 5, 5, 4], "texture": "#0"}, + "down": {"uv": [9, 4, 7, 5], "texture": "#0"} + } + }, + { + "name": "Toggle_Left_Arm_Armor", + "from": [2.75, 0.3, 9.75], + "to": [5.25, 9.8, 13.25], + "rotation": {"angle": 0, "axis": "x", "origin": [3, 8, 12.5]}, + "faces": { + "north": {"uv": [13, 13, 13.75, 16], "texture": "#0"}, + "east": {"uv": [12, 13, 13, 16], "texture": "#0"}, + "south": {"uv": [14.75, 13, 15.5, 16], "texture": "#0"}, + "west": {"uv": [13.75, 13, 14.75, 16], "texture": "#0"}, + "up": {"uv": [13, 12, 13.75, 13], "texture": "#0"}, + "down": {"uv": [13.75, 12, 14.5, 13], "texture": "#0"} + } + }, + { + "name": "Left_arm", + "from": [3, 0.3, 10], + "to": [5, 9.3, 13], + "rotation": {"angle": 0, "axis": "x", "origin": [4, 8, 12.5]}, + "faces": { + "north": {"uv": [9, 13, 9.75, 16], "texture": "#0"}, + "east": {"uv": [8, 13, 9, 16], "texture": "#0"}, + "south": {"uv": [10.5, 13, 11.5, 16], "texture": "#0"}, + "west": {"uv": [9.75, 13, 10.5, 16], "texture": "#0"}, + "up": {"uv": [9.75, 13, 9, 12], "texture": "#0"}, + "down": {"uv": [10.5, 12, 9.75, 13], "texture": "#0"} + } + }, + { + "name": "Toggle_Right_Arm_Armor", + "from": [10.75, 0.3, 9.75], + "to": [13.25, 9.8, 13.25], + "rotation": {"angle": 0, "axis": "x", "origin": [12, 8, 11.5]}, + "faces": { + "north": {"uv": [11, 9, 11.75, 12], "texture": "#0"}, + "east": {"uv": [10, 9, 11, 12], "texture": "#0"}, + "south": {"uv": [12.75, 9, 13.5, 12], "texture": "#0"}, + "west": {"uv": [11.75, 9, 12.75, 12], "texture": "#0"}, + "up": {"uv": [11, 8, 11.75, 9], "texture": "#0"}, + "down": {"uv": [11.75, 8, 12.5, 9], "texture": "#0"} + } + }, + { + "name": "Right_arm", + "from": [11, 0.3, 10], + "to": [13, 9.3, 13], + "rotation": {"angle": 0, "axis": "x", "origin": [12, 8, 11.5]}, + "faces": { + "north": {"uv": [11, 5, 11.75, 8], "texture": "#0"}, + "east": {"uv": [10, 5, 11, 8], "texture": "#0"}, + "south": {"uv": [12.5, 5, 13.5, 8], "texture": "#0"}, + "west": {"uv": [11.75, 5, 12.5, 8], "texture": "#0"}, + "up": {"uv": [11.75, 5, 11, 4], "texture": "#0"}, + "down": {"uv": [12.5, 4, 11.75, 5], "texture": "#0"} + } + }, + { + "name": "Toggle_Left_Leg_Armor", + "from": [5.2, -0.25, 3.05], + "to": [8.7, 3.25, 12.55], + "rotation": {"angle": 22.5, "axis": "y", "origin": [5.7, 2, 13]}, + "faces": { + "north": {"uv": [2, 12, 3, 13], "rotation": 180, "texture": "#0"}, + "east": {"uv": [0, 13, 1, 16], "rotation": 270, "texture": "#0"}, + "south": {"uv": [1, 12, 2, 13], "texture": "#0"}, + "west": {"uv": [2, 13, 3, 16], "rotation": 90, "texture": "#0"}, + "up": {"uv": [1, 13, 2, 16], "rotation": 180, "texture": "#0"}, + "down": {"uv": [3, 13, 4, 16], "texture": "#0"} + } + }, + { + "name": "Left_leg", + "from": [5.5, 0, 3.3], + "to": [8.5, 3, 12.3], + "rotation": {"angle": 22.5, "axis": "y", "origin": [6, 2, 13]}, + "faces": { + "north": {"uv": [7, 12, 6, 13], "rotation": 180, "texture": "#0"}, + "east": {"uv": [4, 13, 5, 16], "rotation": 270, "texture": "#0"}, + "south": {"uv": [5.95, 13, 5, 12], "texture": "#0"}, + "west": {"uv": [6, 13, 7, 16], "rotation": 90, "texture": "#0"}, + "up": {"uv": [5, 13, 6, 16], "rotation": 180, "texture": "#0"}, + "down": {"uv": [7, 13, 8, 16], "texture": "#0"} + } + }, + { + "name": "Toggle_Right_Leg_Armor", + "from": [7.2, -0.25, 3.05], + "to": [10.7, 3.25, 12.55], + "rotation": {"angle": -22.5, "axis": "y", "origin": [8.7, 2, 13]}, + "faces": { + "north": {"uv": [2, 8, 3, 9], "rotation": 180, "texture": "#0"}, + "east": {"uv": [0, 9, 1, 12], "rotation": 270, "texture": "#0"}, + "south": {"uv": [1, 8, 2, 9], "texture": "#0"}, + "west": {"uv": [2, 9, 3, 12], "rotation": 90, "texture": "#0"}, + "up": {"uv": [1, 9, 2, 12], "rotation": 180, "texture": "#0"}, + "down": {"uv": [3, 9, 4, 12], "texture": "#0"} + } + }, + { + "name": "Right_leg", + "from": [7.5, 0, 3.3], + "to": [10.5, 3, 12.3], + "rotation": {"angle": -22.5, "axis": "y", "origin": [9, 2, 13]}, + "faces": { + "north": {"uv": [3, 4, 2, 5], "rotation": 180, "texture": "#0"}, + "east": {"uv": [0, 5, 1, 8], "rotation": 270, "texture": "#0"}, + "south": {"uv": [2, 5, 1, 4], "texture": "#0"}, + "west": {"uv": [2, 5, 3, 8], "rotation": 90, "texture": "#0"}, + "up": {"uv": [1, 5, 2, 8], "rotation": 180, "texture": "#0"}, + "down": {"uv": [3, 5, 4, 8], "texture": "#0"} + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "thirdperson_lefthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + }, + "firstperson_righthand": { + "rotation": [0, 124, 0], + "translation": [2, 3, 0], + "scale": [0.4, 0.4, 0.4] + }, + "firstperson_lefthand": { + "rotation": [0, 120, 0], + "translation": [1.5, 2.75, 0], + "scale": [0.4, 0.4, 0.4] + }, + "ground": { + "translation": [0, 2, 0], + "scale": [0.5, 0.5, 0.5] + }, + "gui": { + "rotation": [30, -135, 0], + "translation": [0.75, -1, 0], + "scale": [0.625, 0.625, 0.625] + }, + "head": { + "translation": [0, 14, -0.75] + }, + "fixed": { + "translation": [0, 0, -2.75], + "scale": [0.5, 0.5, 0.5] + }, + "on_shelf": { + "rotation": [0, -180, 0], + "translation": [0, 0, 5.25] + } + }, + "groups": [ + { + "name": "Player", + "origin": [3, -6.7, 6], + "color": 0, + "children": [ + { + "name": "Head", + "origin": [8, 16, 8], + "color": 0, + "children": [0, 1] + }, + { + "name": "Body", + "origin": [8, 11, 8], + "color": 0, + "children": [2, 3] + }, + { + "name": "Left_Arm", + "origin": [5, 15, 6], + "color": 0, + "children": [4, 5] + }, + { + "name": "Right_Arm", + "origin": [11, 15, 6], + "color": 0, + "children": [6, 7] + }, + { + "name": "Left_Leg", + "origin": [7, 13, 7], + "color": 0, + "children": [8, 9] + }, + { + "name": "Right_Leg", + "origin": [10, 13, 7], + "color": 0, + "children": [10, 11] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/main/resources/lib39.mixins.json b/src/main/resources/lib39.mixins.json new file mode 100644 index 0000000..e1ebdfd --- /dev/null +++ b/src/main/resources/lib39.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "top.r3944realms.lib39.mixin", + "compatibilityLevel": "JAVA_17", + "refmap": "lib39.refmap.json", + "mixins": [ + "init.MixinDedicateServer" + ], + "client": [ + "init.MixinMinecraft" + ] +}