更新内容
1. 加解密ClassLoader 2. 帮助指令管理器
This commit is contained in:
parent
2b0a14d0e9
commit
f062be7f51
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -5,6 +5,7 @@ bin
|
|||
.metadata
|
||||
.classpath
|
||||
.project
|
||||
cpp/cmake-build-debug
|
||||
|
||||
# idea
|
||||
out
|
||||
|
|
|
|||
18
build.gradle
18
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
|
||||
|
|
|
|||
8
config/jni-classes.txt
Normal file
8
config/jni-classes.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# JNI 头文件生成配置
|
||||
# 每行一个类全限定名,例如:
|
||||
# com.example.MyNativeClass
|
||||
# com.example.NativeUtils
|
||||
top.r3944realms.lib39.core.lang.ClassEncryptor
|
||||
top.r3944realms.lib39.core.lang.EncryptedClassLoader
|
||||
# 或者使用正则表达式自动匹配:
|
||||
# #auto:.*Native.*
|
||||
241
cpp/CMakeLists.txt
Normal file
241
cpp/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
cmake_minimum_required(VERSION 3.28)
|
||||
project(ENCRYPTED_CPP VERSION 1.0.0)
|
||||
|
||||
# 设置C++标准
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
# ========== 目录设置 ==========
|
||||
# 项目根目录
|
||||
set(PROJECT_ROOT "${CMAKE_SOURCE_DIR}")
|
||||
|
||||
# 源代码目录 - 使用cpp-src目录
|
||||
if(EXISTS "${PROJECT_ROOT}/cpp-src")
|
||||
set(SOURCE_DIR "${PROJECT_ROOT}/cpp-src")
|
||||
set(HEADER_DIR "${PROJECT_ROOT}/cpp-src")
|
||||
message(STATUS "Using cpp-src directory: ${CPP_SRC_DIR}")
|
||||
else()
|
||||
# 如果没有cpp-src目录,使用当前目录
|
||||
set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
set(HEADER_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
message(STATUS "Using current directory as source directory")
|
||||
endif()
|
||||
|
||||
# ========== 手动设置Java路径 ==========
|
||||
# 查找Java HOME
|
||||
if(DEFINED ENV{JAVA_HOME})
|
||||
set(JAVA_HOME $ENV{JAVA_HOME})
|
||||
message(STATUS "Found JAVA_HOME: ${JAVA_HOME}")
|
||||
else()
|
||||
# 尝试通过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++异常处理
|
||||
$<$<CONFIG:Debug>:/MDd> # 调试版本使用MDd
|
||||
$<$<CONFIG:Release>:/MD> # 发布版本使用MD
|
||||
$<$<CONFIG:RelWithDebInfo>:/MD>
|
||||
$<$<CONFIG:MinSizeRel>:/MD>
|
||||
)
|
||||
|
||||
# MSVC预处理器定义
|
||||
target_compile_definitions(${LIBRARY_NAME} PRIVATE
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
_WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
BUILDING_DLL
|
||||
JNIEXPORT=__declspec(dllexport)
|
||||
)
|
||||
else()
|
||||
target_compile_options(${LIBRARY_NAME} PRIVATE
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wno-unused-parameter
|
||||
-fPIC
|
||||
$<$<CONFIG:Debug>:-g -O0>
|
||||
$<$<CONFIG:Release>:-O2>
|
||||
$<$<CONFIG:RelWithDebInfo>:-O2 -g>
|
||||
$<$<CONFIG:MinSizeRel>:-Os>
|
||||
)
|
||||
|
||||
# GCC/Clang预处理器定义
|
||||
target_compile_definitions(${LIBRARY_NAME} PRIVATE
|
||||
BUILDING_DLL
|
||||
)
|
||||
|
||||
if(APPLE)
|
||||
target_compile_options(${LIBRARY_NAME} PRIVATE
|
||||
-stdlib=libc++
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# ========== 链接库 ==========
|
||||
if(WIN32)
|
||||
# Windows链接库
|
||||
target_link_libraries(${LIBRARY_NAME} PRIVATE
|
||||
kernel32.lib
|
||||
user32.lib
|
||||
gdi32.lib
|
||||
winspool.lib
|
||||
shell32.lib
|
||||
ole32.lib
|
||||
oleaut32.lib
|
||||
uuid.lib
|
||||
comdlg32.lib
|
||||
advapi32.lib
|
||||
)
|
||||
else()
|
||||
# Linux/macOS链接库
|
||||
target_link_libraries(${LIBRARY_NAME} PRIVATE
|
||||
pthread
|
||||
dl
|
||||
)
|
||||
|
||||
if(APPLE)
|
||||
target_link_options(${LIBRARY_NAME} PRIVATE
|
||||
-undefined dynamic_lookup
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# ========== 安装配置(可选) ==========
|
||||
if(NOT DEFINED CMAKE_SKIP_INSTALL_RULES)
|
||||
install(TARGETS ${LIBRARY_NAME}
|
||||
LIBRARY DESTINATION lib
|
||||
RUNTIME DESTINATION bin
|
||||
ARCHIVE DESTINATION lib
|
||||
)
|
||||
|
||||
# 安装头文件
|
||||
if(HEADER_FILES)
|
||||
install(FILES ${HEADER_FILES} DESTINATION include)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# ========== 输出总结信息 ==========
|
||||
message(STATUS "")
|
||||
message(STATUS "=========================================")
|
||||
message(STATUS "Project Configuration Summary")
|
||||
message(STATUS "=========================================")
|
||||
message(STATUS "Project: ${PROJECT_NAME}")
|
||||
message(STATUS "Version: ${PROJECT_VERSION}")
|
||||
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
||||
message(STATUS "Source directory: ${SOURCE_DIR}")
|
||||
message(STATUS "Header directory: ${HEADER_DIR}")
|
||||
message(STATUS "Java include dirs: ${JAVA_INCLUDE_DIRS}")
|
||||
message(STATUS "Target library: ${LIBRARY_NAME}")
|
||||
message(STATUS "C++ standard: ${CMAKE_CXX_STANDARD}")
|
||||
message(STATUS "Output directory: ${CMAKE_BINARY_DIR}")
|
||||
message(STATUS "=========================================")
|
||||
17
cpp/Config.cmake.in
Normal file
17
cpp/Config.cmake.in
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Config.cmake.in
|
||||
@PACKAGE_INIT@
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
|
||||
# 查找依赖
|
||||
find_dependency(Java COMPONENTS Development)
|
||||
|
||||
if(@USE_OPENSSL@)
|
||||
find_dependency(OpenSSL REQUIRED)
|
||||
endif()
|
||||
|
||||
# 导入目标
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
|
||||
|
||||
# 检查目标是否存在
|
||||
check_required_components(@PROJECT_NAME@)
|
||||
6
cpp/header/CMakeLists.txt
Normal file
6
cpp/header/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
cmake_minimum_required(VERSION 3.28)
|
||||
|
||||
add_library( HEADER
|
||||
top_r3944realms_lib39_core_lang_ClassEncryptor.h
|
||||
top_r3944realms_lib39_core_lang_EncryptedClassLoader.h
|
||||
)
|
||||
37
cpp/header/top_r3944realms_lib39_core_lang_ClassEncryptor.h
Normal file
37
cpp/header/top_r3944realms_lib39_core_lang_ClassEncryptor.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/* DO NOT EDIT THIS FILE - it is machine generated */
|
||||
#include <jni.h>
|
||||
/* Header for class top_r3944realms_lib39_core_lang_ClassEncryptor */
|
||||
|
||||
#ifndef _Included_top_r3944realms_lib39_core_lang_ClassEncryptor
|
||||
#define _Included_top_r3944realms_lib39_core_lang_ClassEncryptor
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
/*
|
||||
* Class: top_r3944realms_lib39_core_lang_ClassEncryptor
|
||||
* Method: encryptClass
|
||||
* Signature: ([BLjava/lang/String;)[B
|
||||
*/
|
||||
JNIEXPORT jbyteArray JNICALL Java_top_r3944realms_lib39_core_lang_ClassEncryptor_encryptClass
|
||||
(JNIEnv *, jobject, jbyteArray, jstring);
|
||||
|
||||
/*
|
||||
* Class: top_r3944realms_lib39_core_lang_ClassEncryptor
|
||||
* Method: decryptClass
|
||||
* Signature: ([BLjava/lang/String;)[B
|
||||
*/
|
||||
JNIEXPORT jbyteArray JNICALL Java_top_r3944realms_lib39_core_lang_ClassEncryptor_decryptClass
|
||||
(JNIEnv *, jobject, jbyteArray, jstring);
|
||||
|
||||
/*
|
||||
* Class: top_r3944realms_lib39_core_lang_ClassEncryptor
|
||||
* Method: isEncryptedFile
|
||||
* Signature: ([B)Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL Java_top_r3944realms_lib39_core_lang_ClassEncryptor_isEncryptedFile
|
||||
(JNIEnv *, jobject, jbyteArray);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/* DO NOT EDIT THIS FILE - it is machine generated */
|
||||
#include <jni.h>
|
||||
/* Header for class top_r3944realms_lib39_core_lang_EncryptedClassLoader */
|
||||
|
||||
#ifndef _Included_top_r3944realms_lib39_core_lang_EncryptedClassLoader
|
||||
#define _Included_top_r3944realms_lib39_core_lang_EncryptedClassLoader
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
/*
|
||||
* Class: top_r3944realms_lib39_core_lang_EncryptedClassLoader
|
||||
* Method: decryptClass
|
||||
* Signature: ([BLjava/lang/String;)[B
|
||||
*/
|
||||
JNIEXPORT jbyteArray JNICALL Java_top_r3944realms_lib39_core_lang_EncryptedClassLoader_decryptClass
|
||||
(JNIEnv *, jobject, jbyteArray, jstring);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
1
cpp/lib/CMakeLists.txt
Normal file
1
cpp/lib/CMakeLists.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
cmake_minimum_required(VERSION 3.28)
|
||||
7
cpp/src/CMakeLists.txt
Normal file
7
cpp/src/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
cmake_minimum_required(VERSION 3.28)
|
||||
|
||||
|
||||
add_subdirectory(header)
|
||||
add_subdirectory(lib)
|
||||
|
||||
set_target_properties(CONST_LIB PROPERTIES LINKER_LANGUAGE CXX)
|
||||
451
cpp/src/EnhancedEncryptionMagic.cpp
Normal file
451
cpp/src/EnhancedEncryptionMagic.cpp
Normal file
|
|
@ -0,0 +1,451 @@
|
|||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions"
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <cstring>
|
||||
#ifndef HEADER_JNI_H_
|
||||
#define HEADER_JNI_H_
|
||||
#include <jni.h>
|
||||
#endif
|
||||
#ifndef HEADER_P_H_
|
||||
#define HEADER_P_H_
|
||||
#include "guard/JByteArrayGuard.cpp"
|
||||
#endif
|
||||
#include <string>
|
||||
|
||||
// 字节序转换宏(跨平台)
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "UnreachableCallsOfFunction"
|
||||
#if defined(_WIN32)
|
||||
#include <winsock2.h>
|
||||
#pragma commen+t(lib, "ws2_32.lib")
|
||||
|
||||
#define htobe32(x) htonl(x)
|
||||
#define be32toh(x) ntohl(x)
|
||||
#define htobe16(x) htons(x)
|
||||
#define be16toh(x) ntohs(x)
|
||||
|
||||
// Windows下64位字节序转换需要自己实现
|
||||
static inline uint64_t htobe64(uint64_t x) {
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
return ((uint64_t)htonl(x & 0xFFFFFFFF) << 32) | htonl(x >> 32);
|
||||
#else
|
||||
return x;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint64_t be64toh(uint64_t x) {
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
return ((uint64_t)ntohl(x & 0xFFFFFFFF) << 32) | ntohl(x >> 32);
|
||||
#else
|
||||
return x;
|
||||
#endif
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__) || defined(__FreeBSD__)
|
||||
#include <libkern/OSByteOrder.h>
|
||||
#define htobe32(x) OSSwapHostToBigInt32(x)
|
||||
#define be32toh(x) OSSwapBigToHostInt32(x)
|
||||
#define htobe16(x) OSSwapHostToBigInt16(x)
|
||||
#define be16toh(x) OSSwapBigToHostInt16(x)
|
||||
#define htobe64(x) OSSwapHostToBigInt64(x)
|
||||
#define be64toh(x) OSSwapBigToHostInt64(x)
|
||||
|
||||
#elif defined(__linux__) || defined(__ANDROID__)
|
||||
#include <endian.h>
|
||||
// Linux下endian.h已经定义了这些宏
|
||||
|
||||
#else
|
||||
// 通用实现
|
||||
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
// GCC/Clang内置函数
|
||||
#define htobe32(x) __builtin_bswap32(x)
|
||||
#define be32toh(x) __builtin_bswap32(x)
|
||||
#define htobe16(x) __builtin_bswap16(x)
|
||||
#define be16toh(x) __builtin_bswap16(x)
|
||||
#define htobe64(x) __builtin_bswap64(x)
|
||||
#define be64toh(x) __builtin_bswap64(x)
|
||||
#else
|
||||
#define htobe32(x) (x)
|
||||
#define be32toh(x) (x)
|
||||
#define htobe16(x) (x)
|
||||
#define be16toh(x) (x)
|
||||
#define htobe64(x) (x)
|
||||
#define be64toh(x) (x)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// 手动字节序转换函数(备用)
|
||||
static inline uint64_t manual_htobe64(uint64_t x) {
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
return ((uint64_t)__builtin_bswap32(x & 0xFFFFFFFF) << 32) | __builtin_bswap32(x >> 32);
|
||||
#else
|
||||
return x;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint64_t manual_be64toh(uint64_t x) {
|
||||
return manual_htobe64(x); // 对称操作
|
||||
}
|
||||
|
||||
namespace EnhancedEncryptionMagic {
|
||||
// 主魔数:0x4C494233 (ASCII: "LIB3")
|
||||
static const uint32_t MAGIC = 0x4C494233; // "LIB3" in hex
|
||||
|
||||
// 文件头结构 - 调整为实际大小
|
||||
struct EnhancedFileHeader {
|
||||
uint32_t magic; // 魔数: 0x4C494233 "LIB3" (4字节)
|
||||
uint16_t version_major; // 主版本号 (2字节)
|
||||
uint16_t version_minor; // 次版本号 (2字节)
|
||||
uint32_t flags; // 标志位 (4字节)
|
||||
uint32_t original_size; // 原始数据大小 (4字节)
|
||||
uint32_t encrypted_size; // 加密数据大小 (4字节)
|
||||
uint64_t timestamp; // 时间戳 (8字节)
|
||||
uint32_t checksum; // 校验和 (4字节)
|
||||
uint32_t reserved; // 保留字段 (4字节)
|
||||
// 总计: 4+2+2+4+4+4+8+4+4 = 36字节
|
||||
|
||||
// 编译器可能添加4字节填充到40字节,但我们应该按36字节处理
|
||||
};
|
||||
|
||||
// 计算实际结构体大小
|
||||
static const size_t CALCULATED_HEADER_SIZE =
|
||||
sizeof(uint32_t) + // magic
|
||||
sizeof(uint16_t) + // version_major
|
||||
sizeof(uint16_t) + // version_minor
|
||||
sizeof(uint32_t) + // flags
|
||||
sizeof(uint32_t) + // original_size
|
||||
sizeof(uint32_t) + // encrypted_size
|
||||
sizeof(uint64_t) + // timestamp
|
||||
sizeof(uint32_t) + // checksum
|
||||
sizeof(uint32_t); // reserved
|
||||
|
||||
static const size_t HEADER_SIZE = CALCULATED_HEADER_SIZE;
|
||||
|
||||
// 标志位定义
|
||||
namespace Flags {
|
||||
static const uint32_t COMPRESSED = 0x00000001; // 是否压缩
|
||||
static const uint32_t SIGNED = 0x00000002; // 是否签名
|
||||
static const uint32_t ENCRYPTED = 0x00000004; // 是否加密
|
||||
static const uint32_t VALIDATED = 0x00000008; // 是否验证
|
||||
};
|
||||
|
||||
// 创建文件头
|
||||
static inline EnhancedFileHeader createHeader(uint32_t originalSize, uint32_t encryptedSize) {
|
||||
EnhancedFileHeader header;
|
||||
memset(&header, 0, sizeof(header)); // 清零初始化
|
||||
|
||||
header.magic = MAGIC;
|
||||
header.version_major = 1;
|
||||
header.version_minor = 0;
|
||||
header.flags = Flags::ENCRYPTED;
|
||||
header.original_size = originalSize;
|
||||
header.encrypted_size = encryptedSize;
|
||||
header.timestamp = static_cast<uint64_t>(time(nullptr));
|
||||
header.checksum = 0; // 将在之后计算
|
||||
header.reserved = 0;
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
// 字节序安全的内存复制函数
|
||||
static inline void writeUint32(jbyte* buffer, uint32_t value, size_t offset) {
|
||||
uint32_t networkValue = htobe32(value);
|
||||
memcpy(buffer + offset, &networkValue, sizeof(uint32_t));
|
||||
}
|
||||
|
||||
static inline void writeUint16(jbyte* buffer, uint16_t value, size_t offset) {
|
||||
uint16_t networkValue = htobe16(value);
|
||||
memcpy(buffer + offset, &networkValue, sizeof(uint16_t));
|
||||
}
|
||||
|
||||
static inline void writeUint64(jbyte* buffer, uint64_t value, size_t offset) {
|
||||
uint64_t networkValue = htobe64(value);
|
||||
memcpy(buffer + offset, &networkValue, sizeof(uint64_t));
|
||||
}
|
||||
|
||||
static inline uint32_t readUint32(const jbyte* buffer, size_t offset) {
|
||||
uint32_t networkValue;
|
||||
memcpy(&networkValue, buffer + offset, sizeof(uint32_t));
|
||||
return be32toh(networkValue);
|
||||
}
|
||||
|
||||
static inline uint16_t readUint16(const jbyte* buffer, size_t offset) {
|
||||
uint16_t networkValue;
|
||||
memcpy(&networkValue, buffer + offset, sizeof(uint16_t));
|
||||
return be16toh(networkValue);
|
||||
}
|
||||
|
||||
static inline uint64_t readUint64(const jbyte* buffer, size_t offset) {
|
||||
uint64_t networkValue;
|
||||
memcpy(&networkValue, buffer + offset, sizeof(uint64_t));
|
||||
return be64toh(networkValue);
|
||||
}
|
||||
|
||||
// 验证文件头
|
||||
static inline bool validateHeader(const EnhancedFileHeader& header) {
|
||||
return header.magic == MAGIC &&
|
||||
header.version_major == 1 &&
|
||||
header.version_minor == 0 &&
|
||||
(header.flags & Flags::ENCRYPTED) != 0;
|
||||
}
|
||||
|
||||
// 计算校验和(简单的CRC32替代)
|
||||
static inline uint32_t calculateChecksum(const jbyte* data, jsize length) {
|
||||
if (!data || length <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t crc = 0xFFFFFFFF;
|
||||
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(data);
|
||||
|
||||
for (jsize i = 0; i < length; i++) {
|
||||
crc ^= bytes[i];
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (crc & 1) {
|
||||
crc = (crc >> 1) ^ 0xEDB88320;
|
||||
} else {
|
||||
crc = crc >> 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
// 更新文件头的校验和
|
||||
static inline void updateChecksum(EnhancedFileHeader& header, const jbyte* data, jsize length) {
|
||||
header.checksum = calculateChecksum(data, length);
|
||||
}
|
||||
|
||||
// 验证数据校验和
|
||||
static inline bool verifyChecksum(const EnhancedFileHeader& header, const jbyte* data, jsize length) {
|
||||
uint32_t calculated = calculateChecksum(data, length);
|
||||
return header.checksum == calculated;
|
||||
}
|
||||
|
||||
// 将文件头写入字节数组(使用网络字节序)
|
||||
static inline void writeHeaderToBytes(const EnhancedFileHeader& header, jbyte* buffer) {
|
||||
if (!buffer) return;
|
||||
|
||||
size_t offset = 0;
|
||||
writeUint32(buffer, header.magic, offset); offset += 4;
|
||||
writeUint16(buffer, header.version_major, offset); offset += 2;
|
||||
writeUint16(buffer, header.version_minor, offset); offset += 2;
|
||||
writeUint32(buffer, header.flags, offset); offset += 4;
|
||||
writeUint32(buffer, header.original_size, offset); offset += 4;
|
||||
writeUint32(buffer, header.encrypted_size, offset); offset += 4;
|
||||
writeUint64(buffer, header.timestamp, offset); offset += 8;
|
||||
writeUint32(buffer, header.checksum, offset); offset += 4;
|
||||
writeUint32(buffer, header.reserved, offset); offset += 4;
|
||||
}
|
||||
|
||||
// 从字节数组读取文件头
|
||||
static inline EnhancedFileHeader readHeaderFromBytes(const jbyte* buffer) {
|
||||
EnhancedFileHeader header{};
|
||||
memset(&header, 0, sizeof(header));
|
||||
|
||||
if (!buffer) return header;
|
||||
|
||||
size_t offset = 0;
|
||||
header.magic = readUint32(buffer, offset); offset += 4;
|
||||
header.version_major = readUint16(buffer, offset); offset += 2;
|
||||
header.version_minor = readUint16(buffer, offset); offset += 2;
|
||||
header.flags = readUint32(buffer, offset); offset += 4;
|
||||
header.original_size = readUint32(buffer, offset); offset += 4;
|
||||
header.encrypted_size = readUint32(buffer, offset); offset += 4;
|
||||
header.timestamp = readUint64(buffer, offset); offset += 8;
|
||||
header.checksum = readUint32(buffer, offset); offset += 4;
|
||||
header.reserved = readUint32(buffer, offset); offset += 4;
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
// 将文件头格式化为可读字符串
|
||||
static inline std::string headerToString(const EnhancedFileHeader& header) {
|
||||
char magicStr[5] = {0};
|
||||
memcpy(magicStr, &header.magic, 4);
|
||||
|
||||
char buffer[256];
|
||||
snprintf(buffer, sizeof(buffer),
|
||||
"Magic: %s (0x%08X)\n"
|
||||
"Version: %d.%d\n"
|
||||
"Flags: 0x%08X\n"
|
||||
"Original Size: %u bytes\n"
|
||||
"Encrypted Size: %u bytes\n"
|
||||
"Timestamp: %llu\n"
|
||||
"Checksum: 0x%08X\n"
|
||||
"Reserved: 0x%08X",
|
||||
magicStr, header.magic,
|
||||
header.version_major, header.version_minor,
|
||||
header.flags,
|
||||
header.original_size,
|
||||
header.encrypted_size,
|
||||
(unsigned long long)header.timestamp,
|
||||
header.checksum,
|
||||
header.reserved);
|
||||
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
// 验证文件是否完整
|
||||
static inline bool validateFileIntegrity(const EnhancedFileHeader& header,
|
||||
const jbyte* encryptedData,
|
||||
jsize encryptedDataSize) {
|
||||
// 检查大小是否匹配
|
||||
if (header.encrypted_size != encryptedDataSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查校验和
|
||||
return verifyChecksum(header, encryptedData, encryptedDataSize);
|
||||
}
|
||||
|
||||
// 加密函数指针类型
|
||||
typedef void (*EncryptFunc)(jbyte*, jsize, const char*, int);
|
||||
|
||||
// 创建完整的加密文件
|
||||
static inline jbyteArray createEncryptedFile(JNIEnv* env,
|
||||
const jbyte* originalData,
|
||||
jsize originalSize,
|
||||
const char* key,
|
||||
size_t keyLen,
|
||||
EncryptFunc encryptFunc) {
|
||||
|
||||
if (!env || !originalData || originalSize <= 0 || !key || keyLen <= 0 || !encryptFunc) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 1. 创建加密数据数组
|
||||
jbyteArray encryptedDataArray = env->NewByteArray(originalSize);
|
||||
if (!encryptedDataArray) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
{
|
||||
// 使用局部作用域确保 encryptedDataGuard 在加密后释放
|
||||
JByteArrayGuard encryptedDataGuard(env, encryptedDataArray, false, JNI_ABORT);
|
||||
if (!encryptedDataGuard.isValid()) {
|
||||
env->DeleteLocalRef(encryptedDataArray);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 复制并加密数据
|
||||
memcpy(encryptedDataGuard.get(), originalData, originalSize);
|
||||
encryptFunc(encryptedDataGuard.get(), originalSize, key, keyLen);
|
||||
|
||||
// encryptedDataGuard 析构函数会自动以 JNI_ABORT 模式释放
|
||||
}
|
||||
|
||||
// 注意:这里已经释放了加密数据,需要重新获取
|
||||
JByteArrayGuard encryptedDataGuard2(env, encryptedDataArray);
|
||||
if (!encryptedDataGuard2.isValid()) {
|
||||
env->DeleteLocalRef(encryptedDataArray);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 2. 创建文件头
|
||||
EnhancedFileHeader header = createHeader(originalSize, originalSize);
|
||||
updateChecksum(header, encryptedDataGuard2.get(), originalSize);
|
||||
|
||||
// 3. 创建最终结果
|
||||
jsize totalSize = HEADER_SIZE + originalSize;
|
||||
jbyteArray result = env->NewByteArray(totalSize);
|
||||
if (!result) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JByteArrayGuard resultGuard(env, result);
|
||||
if (!resultGuard.isValid()) {
|
||||
env->DeleteLocalRef(result);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 4. 写入数据
|
||||
writeHeaderToBytes(header, resultGuard.get());
|
||||
memcpy(resultGuard.get() + HEADER_SIZE, encryptedDataGuard2.get(), originalSize);
|
||||
|
||||
// 5. 显式提交修改
|
||||
resultGuard.commit(); // 提交修改到 Java 端
|
||||
// resultGuard 析构时不会再释放
|
||||
|
||||
// 6. 清理中间数组
|
||||
env->DeleteLocalRef(encryptedDataArray);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 从加密文件中提取数据
|
||||
static inline jbyteArray extractFromEncryptedFile(JNIEnv* env,
|
||||
const jbyte* fileData,
|
||||
jsize fileSize,
|
||||
const char* key,
|
||||
size_t keyLen,
|
||||
EncryptFunc decryptFunc,
|
||||
bool* isValid) {
|
||||
|
||||
if (isValid) *isValid = false;
|
||||
|
||||
if (!env || !fileData || fileSize < HEADER_SIZE || !key || keyLen <= 0 || !decryptFunc) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 读取文件头
|
||||
EnhancedFileHeader header = readHeaderFromBytes(fileData);
|
||||
|
||||
// 验证文件头
|
||||
if (!validateHeader(header)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 检查文件大小
|
||||
jsize expectedSize = HEADER_SIZE + header.encrypted_size;
|
||||
if (fileSize != expectedSize) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 提取加密数据
|
||||
const jbyte* encryptedData = fileData + HEADER_SIZE;
|
||||
|
||||
// 验证完整性
|
||||
if (!validateFileIntegrity(header, encryptedData, header.encrypted_size)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 创建结果数组
|
||||
jbyteArray result = env->NewByteArray(header.original_size);
|
||||
if (!result) {
|
||||
return nullptr;
|
||||
}
|
||||
JByteArrayGuard resultDataGuard(env, result);
|
||||
|
||||
if (!resultDataGuard.isValid()) {
|
||||
env->DeleteLocalRef(result);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
jbyte* resultData = resultDataGuard.get();
|
||||
|
||||
// 复制加密数据
|
||||
memcpy(resultData, encryptedData, header.original_size);
|
||||
|
||||
// 解密数据
|
||||
decryptFunc(resultData, header.original_size, key, keyLen);
|
||||
|
||||
if (isValid) *isValid = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 验证是否为加密文件(不读取整个文件)
|
||||
static inline bool isEncryptedFile(const jbyte* fileData, jsize fileSize) {
|
||||
if (fileSize < HEADER_SIZE || !fileData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EnhancedFileHeader header = readHeaderFromBytes(fileData);
|
||||
return validateHeader(header);
|
||||
}
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
#pragma clang diagnostic pop
|
||||
234
cpp/src/SimpleClassEncrypt.cpp
Normal file
234
cpp/src/SimpleClassEncrypt.cpp
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
#pragma once
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <header/top_r3944realms_lib39_core_lang_ClassEncryptor.h>
|
||||
#include <header/top_r3944realms_lib39_core_lang_EncryptedClassLoader.h>
|
||||
#include "EnhancedEncryptionMagic.cpp"
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wsign-compare"
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <wincrypt.h>
|
||||
#include <cstdint>
|
||||
|
||||
#define JNIEXPORT __declspec(dllexport)
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#define JNIEXPORT
|
||||
#endif
|
||||
|
||||
/**
|
||||
* 简单的XOR加密/解密
|
||||
*/
|
||||
static void xorEncrypt(jbyte* data, jsize dataLen, const char* key, int keyLen) {
|
||||
if (!data || !key || keyLen == 0) return;
|
||||
|
||||
for (jsize i = 0; i < dataLen; i++) {
|
||||
data[i] ^= key[i % keyLen];
|
||||
}
|
||||
}
|
||||
|
||||
static int safeStrlen(const char* str) {
|
||||
return str ? (int)strlen(str) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证数据是否为有效的Java类文件
|
||||
* @param data 字节码
|
||||
* @param dataLen 字节码长度
|
||||
* @return 是否有效
|
||||
*/
|
||||
static bool isValidJavaClass(const jbyte* data, jsize dataLen) {
|
||||
// Java类文件魔数:0xCAFEBABE
|
||||
return dataLen >= 4 &&
|
||||
data[0] == (jbyte)0xCA &&
|
||||
data[1] == (jbyte)0xFE &&
|
||||
data[2] == (jbyte)0xBA &&
|
||||
data[3] == (jbyte)0xBE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录错误信息
|
||||
*/
|
||||
void logError(const char* message) {
|
||||
#ifdef DEBUG
|
||||
std::cerr << "[JNI Error] " << message << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全的字符串转换
|
||||
*/
|
||||
std::string jstringToString(JNIEnv* env, jstring jstr) {
|
||||
if (!jstr) return "";
|
||||
|
||||
const char* chars = env->GetStringUTFChars(jstr, nullptr);
|
||||
if (!chars) return "";
|
||||
|
||||
std::string result(chars);
|
||||
env->ReleaseStringUTFChars(jstr, chars);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t calculateChecksum(const jbyte* data, jsize length) {
|
||||
uint32_t checksum = 0;
|
||||
for (jsize i = 0; i < length; i++) {
|
||||
checksum += static_cast<uint8_t>(data[i]);
|
||||
checksum = (checksum << 1) | (checksum >> 31); // 简单旋转
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
|
||||
// ==================== JNI函数实现 ====================
|
||||
using namespace EnhancedEncryptionMagic;
|
||||
|
||||
/*
|
||||
* Class: top_r3944realms_lib39_core_lang_ClassEncryptor
|
||||
* Method: decryptClass
|
||||
* Signature: ([BLjava/lang/String;)[B
|
||||
*/
|
||||
JNIEXPORT jbyteArray JNICALL Java_top_r3944realms_lib39_core_lang_ClassEncryptor_decryptClass
|
||||
(JNIEnv *env, jobject obj, jbyteArray encryptedData, jstring key) {
|
||||
|
||||
if (!encryptedData || !key) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
jsize fileSize = env->GetArrayLength(encryptedData);
|
||||
if (fileSize == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
JByteArrayGuard fileDataGuard(env, encryptedData);
|
||||
jbyte* fileData = fileDataGuard.get();
|
||||
if (!fileDataGuard.isValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
std::string keyStr = jstringToString(env, key);
|
||||
size_t keyLen = keyStr.length();
|
||||
bool isValid = keyLen > 0;
|
||||
// 尝试从加密文件中提取数据
|
||||
jbyteArray result = EnhancedEncryptionMagic::extractFromEncryptedFile(
|
||||
env, fileData, fileSize, keyStr.c_str(), keyLen, xorEncrypt, &isValid);
|
||||
if (!isValid || !result) {
|
||||
// 如果不是有效的加密文件,返回原始数据
|
||||
return encryptedData;
|
||||
}
|
||||
|
||||
// 验证解密后的数据是否为有效的Java类文件
|
||||
JByteArrayGuard resultGuard(env, result);
|
||||
jsize resultLen = resultGuard.size();
|
||||
jbyte* resultData = resultGuard.get();
|
||||
|
||||
if (!resultGuard.isValid()) {
|
||||
env->DeleteLocalRef(result);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool validClass = isValidJavaClass(resultData, resultLen);
|
||||
|
||||
if (!validClass) {
|
||||
env->DeleteLocalRef(result);
|
||||
return nullptr; // 解密后的数据不是有效的Java类
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Class: top_r3944realms_lib39_core_lang_ClassEncryptor
|
||||
* Method: encryptClass
|
||||
* Signature: ([BLjava/lang/String;)[B
|
||||
*/
|
||||
JNIEXPORT jbyteArray JNICALL Java_top_r3944realms_lib39_core_lang_ClassEncryptor_encryptClass
|
||||
(JNIEnv *env, jobject obj, jbyteArray classData, jstring key) {
|
||||
|
||||
if (!classData || !key) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JByteArrayGuard jGuard(env, classData);
|
||||
if (jGuard.isValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
jsize dataLen = jGuard.size();
|
||||
jbyte* data = jGuard.get();
|
||||
|
||||
const char* keyStr = env->GetStringUTFChars(key, nullptr);
|
||||
if (!keyStr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 验证输入数据是否为有效的Java类文件
|
||||
if (!isValidJavaClass(data, dataLen)) {
|
||||
env->ReleaseStringUTFChars(key, keyStr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int keyLen = safeStrlen(keyStr);
|
||||
|
||||
// 使用增强版创建加密文件
|
||||
jbyteArray result = EnhancedEncryptionMagic::createEncryptedFile(
|
||||
env, data, dataLen, keyStr, keyLen, xorEncrypt);
|
||||
|
||||
env->ReleaseStringUTFChars(key, keyStr);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: top_r3944realms_lib39_core_lang_ClassEncryptor
|
||||
* Method: isEncryptedFile
|
||||
* Signature: ([B)Z
|
||||
*/
|
||||
JNIEXPORT jboolean JNICALL Java_top_r3944realms_lib39_core_lang_ClassEncryptor_isEncryptedFile
|
||||
(JNIEnv *env, jobject obj, jbyteArray fileData) {
|
||||
|
||||
if (!fileData) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
JByteArrayGuard dataGuard(env, fileData);
|
||||
if (!dataGuard.isValid()) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
if (dataGuard.size() < EnhancedEncryptionMagic::HEADER_SIZE) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
// 验证是否为加密文件
|
||||
bool isEncrypted = EnhancedEncryptionMagic::isEncryptedFile(dataGuard.get(), dataGuard.size());
|
||||
|
||||
return isEncrypted ? JNI_TRUE : JNI_FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: top_r3944realms_lib39_core_lang_EncryptedClassLoader
|
||||
* Method: decryptClass
|
||||
* Signature: ([BLjava/lang/String;)[B
|
||||
*/
|
||||
JNIEXPORT jbyteArray JNICALL Java_top_r3944realms_lib39_core_lang_EncryptedClassLoader_decryptClass
|
||||
(JNIEnv *env, jobject obj, jbyteArray encryptedData, jstring key) {
|
||||
return Java_top_r3944realms_lib39_core_lang_ClassEncryptor_decryptClass(env, obj, encryptedData, key);
|
||||
}
|
||||
|
||||
// JNI库初始化和卸载函数
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||
#ifdef _WIN32
|
||||
// Windows下需要初始化Winsock
|
||||
WSADATA wsaData;
|
||||
WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||
#endif
|
||||
|
||||
return JNI_VERSION_1_8;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
5
cpp/src/guard/CMakeLists.txt
Normal file
5
cpp/src/guard/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.28)
|
||||
|
||||
add_library(J_GUARD
|
||||
JByteArrayGuard.cpp
|
||||
)
|
||||
82
cpp/src/guard/JByteArrayGuard.cpp
Normal file
82
cpp/src/guard/JByteArrayGuard.cpp
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#pragma once
|
||||
#ifndef HEADER_JNI_H_
|
||||
#define HEADER_JNI_H_
|
||||
#include <jni.h>
|
||||
#endif
|
||||
class JByteArrayGuard {
|
||||
private:
|
||||
JNIEnv* env;
|
||||
jbyteArray array;
|
||||
jbyte* data;
|
||||
jsize length;
|
||||
bool isCritical;
|
||||
jint releaseMode;
|
||||
public:
|
||||
JByteArrayGuard(JNIEnv* env, jbyteArray array, bool critical = false, jint releaseMode = 0)
|
||||
: env(env), array(array), data(nullptr), length(0), isCritical(critical), releaseMode(releaseMode)
|
||||
{
|
||||
if (array) {
|
||||
length = env->GetArrayLength(array);
|
||||
if (isCritical) {
|
||||
data = (jbyte*) env->GetPrimitiveArrayCritical(array, nullptr);
|
||||
} else {
|
||||
data = env->GetByteArrayElements(array, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
~JByteArrayGuard() {
|
||||
release();
|
||||
}
|
||||
// 显式释放方法
|
||||
void release() {
|
||||
if (data && array) {
|
||||
if (isCritical) {
|
||||
env->ReleasePrimitiveArrayCritical(array, data, releaseMode);
|
||||
} else {
|
||||
env->ReleaseByteArrayElements(array, data, releaseMode);
|
||||
}
|
||||
data = nullptr; // 防止重复释放
|
||||
array = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// 提交修改但不释放(用于返回结果的情况)
|
||||
void commit() {
|
||||
if (data && array) {
|
||||
if (isCritical) {
|
||||
env->ReleasePrimitiveArrayCritical(array, data, 0);
|
||||
} else {
|
||||
env->ReleaseByteArrayElements(array, data, 0);
|
||||
}
|
||||
data = nullptr; // 标记为已释放,防止析构函数再次释放
|
||||
array = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// 丢弃修改
|
||||
void abort() {
|
||||
if (data && array) {
|
||||
if (isCritical) {
|
||||
env->ReleasePrimitiveArrayCritical(array, data, JNI_ABORT);
|
||||
} else {
|
||||
env->ReleaseByteArrayElements(array, data, JNI_ABORT);
|
||||
}
|
||||
data = nullptr;
|
||||
array = nullptr;
|
||||
}
|
||||
}
|
||||
jbyte* get() { return data; }
|
||||
jsize size() const { return length; }
|
||||
bool isValid() { return data != nullptr; }
|
||||
|
||||
JByteArrayGuard(const JByteArrayGuard&) = delete;
|
||||
JByteArrayGuard& operator=(const JByteArrayGuard&) = delete;
|
||||
JByteArrayGuard(JByteArrayGuard&& other) noexcept
|
||||
: env(other.env), array(other.array), data(other.data),
|
||||
length(other.length), isCritical(other.isCritical),
|
||||
releaseMode(other.releaseMode) {
|
||||
other.array = nullptr;
|
||||
other.data = nullptr;
|
||||
other.length = 0;
|
||||
}
|
||||
};
|
||||
21
gradle/.jni-config.groovy
Normal file
21
gradle/.jni-config.groovy
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
ext.jniConfig = {
|
||||
// 输出目录
|
||||
outputDir = project.file("native/include")
|
||||
|
||||
// 配置文件路径
|
||||
configFile = project.file("jni/jni-classes.txt")
|
||||
|
||||
// 是否在构建时自动生成
|
||||
autoGenerateOnBuild = true
|
||||
|
||||
// 是否启用详细日志
|
||||
verbose = true
|
||||
|
||||
// 自定义匹配模式
|
||||
defaultPatterns = [
|
||||
'.*Native.*',
|
||||
'.*JNI.*',
|
||||
'.*native.*',
|
||||
'com\\.mymod\\..*Impl' // 匹配特定包下的实现类
|
||||
]
|
||||
}
|
||||
458
gradle/jni-heads.gradle
Normal file
458
gradle/jni-heads.gradle
Normal file
|
|
@ -0,0 +1,458 @@
|
|||
// 配置
|
||||
def outputDir = project.file("cpp/header")
|
||||
def configFile = project.file("config/jni-classes.txt")
|
||||
|
||||
// 日志函数
|
||||
def log(msg) {
|
||||
println "[JNI] $msg"
|
||||
}
|
||||
|
||||
def logError(msg) {
|
||||
println "[JNI ERROR] $msg"
|
||||
}
|
||||
|
||||
def logWarn(msg) {
|
||||
println "[JNI WARN] $msg"
|
||||
}
|
||||
|
||||
// 创建配置任务
|
||||
tasks.register('createJniConfig') {
|
||||
group = 'jni'
|
||||
description = '创建 JNI 配置文件模板'
|
||||
|
||||
doLast {
|
||||
configFile.parentFile.mkdirs()
|
||||
if (!configFile.exists()) {
|
||||
configFile.text = """# JNI 头文件生成配置
|
||||
# 每行一个类全限定名,例如:
|
||||
# com.example.MyNativeClass
|
||||
# com.example.NativeUtils
|
||||
|
||||
# 或者使用正则表达式自动匹配:
|
||||
# #auto:.*Native.*
|
||||
"""
|
||||
log "配置文件已创建: ${configFile.absolutePath}"
|
||||
log "请编辑此文件并添加包含 native 方法的类"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 生成头文件任务
|
||||
tasks.register('generateJniHeaders') {
|
||||
group = 'jni'
|
||||
description = '生成 JNI 头文件'
|
||||
|
||||
dependsOn 'compileJava'
|
||||
|
||||
doLast {
|
||||
// 确保目录存在
|
||||
outputDir.mkdirs()
|
||||
|
||||
// 读取配置
|
||||
def targetClasses = []
|
||||
if (configFile.exists()) {
|
||||
configFile.eachLine { line ->
|
||||
def trimmed = line.trim()
|
||||
if (trimmed && !trimmed.startsWith('#')) {
|
||||
targetClasses.add(trimmed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetClasses.isEmpty()) {
|
||||
logError "没有配置任何 JNI 类"
|
||||
logError "请先运行: ./gradlew createJniConfig"
|
||||
logError "然后编辑 ${configFile.absolutePath} 添加类名"
|
||||
return
|
||||
}
|
||||
|
||||
log "开始生成 JNI 头文件..."
|
||||
log "目标类 (${targetClasses.size()} 个):"
|
||||
targetClasses.eachWithIndex { className, i ->
|
||||
log " ${i + 1}. $className"
|
||||
}
|
||||
|
||||
// 准备类路径
|
||||
def classesDir = project.sourceSets.main.output.classesDirs.singleFile
|
||||
def classpath = project.configurations.runtimeClasspath.asPath +
|
||||
File.pathSeparator +
|
||||
classesDir.absolutePath
|
||||
|
||||
// 查找对应的 Java 源文件
|
||||
def sourceFiles = []
|
||||
def sourceDirs = project.sourceSets.main.java.srcDirs
|
||||
|
||||
targetClasses.each { className ->
|
||||
def found = false
|
||||
def relativePath = className.replace('.', '/') + '.java'
|
||||
|
||||
sourceDirs.each { srcDir ->
|
||||
def sourceFile = new File(srcDir, relativePath)
|
||||
if (sourceFile.exists()) {
|
||||
sourceFiles.add(sourceFile)
|
||||
found = true
|
||||
log "找到源文件: ${sourceFile.absolutePath}"
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
logWarn "警告: 未找到类 $className 的源文件"
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceFiles.isEmpty()) {
|
||||
logError "错误: 未找到任何源文件"
|
||||
logError "请确保源文件存在于 src/main/java/ 目录中"
|
||||
return
|
||||
}
|
||||
|
||||
// 使用 javac -h 命令(正确的方式)
|
||||
def javaHome = System.getProperty('java.home')
|
||||
def javacPath = "${javaHome}/bin/javac"
|
||||
|
||||
if (!new File(javacPath).exists()) {
|
||||
javacPath = "${javaHome}/bin/javac"
|
||||
}
|
||||
|
||||
log "使用 javac -h 生成头文件..."
|
||||
|
||||
// 构建临时目录用于编译输出
|
||||
def tempOutputDir = new File(project.buildDir, "tmp/jni-headers")
|
||||
tempOutputDir.mkdirs()
|
||||
|
||||
try {
|
||||
// 方式1:逐个类生成(更可靠)
|
||||
def successCount = 0
|
||||
def failCount = 0
|
||||
|
||||
sourceFiles.each { sourceFile ->
|
||||
try {
|
||||
// 构建 javac 命令
|
||||
def processArgs = [
|
||||
javacPath,
|
||||
'-h', outputDir.absolutePath, // 头文件输出目录
|
||||
'-cp', classpath, // 类路径
|
||||
'-d', tempOutputDir.absolutePath, // 类文件输出目录
|
||||
sourceFile.absolutePath // 源文件
|
||||
]
|
||||
|
||||
log "处理: ${sourceFile.name}"
|
||||
|
||||
def process = processArgs.execute()
|
||||
def stdout = new StringBuilder()
|
||||
def stderr = new StringBuilder()
|
||||
|
||||
process.consumeProcessOutput(stdout, stderr)
|
||||
def exitCode = process.waitFor()
|
||||
|
||||
if (exitCode == 0) {
|
||||
successCount++
|
||||
if (stdout.length() > 0) {
|
||||
log " 输出: ${stdout.toString().trim()}"
|
||||
}
|
||||
} else {
|
||||
failCount++
|
||||
logError " 处理失败: ${sourceFile.name}"
|
||||
if (stderr.length() > 0) {
|
||||
logError " 错误: ${stderr.toString().trim()}"
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
failCount++
|
||||
logError " 处理异常: ${e.message}"
|
||||
}
|
||||
}
|
||||
|
||||
log "处理完成: 成功 ${successCount} 个, 失败 ${failCount} 个"
|
||||
|
||||
if (successCount > 0) {
|
||||
// 检查生成了哪些头文件
|
||||
def headerFiles = outputDir.listFiles({ dir, name -> name.endsWith('.h') } as FilenameFilter)
|
||||
if (headerFiles && headerFiles.size() > 0) {
|
||||
log "=" * 60
|
||||
log "JNI 头文件生成成功!"
|
||||
log "=" * 60
|
||||
log "输出目录: ${outputDir.absolutePath}"
|
||||
log "生成的头文件 (${headerFiles.size()} 个):"
|
||||
|
||||
headerFiles.sort { it.name }.each { file ->
|
||||
def size = file.length()
|
||||
def sizeStr = size < 1024 ? "${size} B" : "${String.format("%.1f", size / 1024.0)} KB"
|
||||
log " ✓ ${file.name} ($sizeStr)"
|
||||
|
||||
// 显示文件开头几行
|
||||
try {
|
||||
def lines = file.readLines()
|
||||
if (lines.size() > 0) {
|
||||
def headerGuard = lines.find { it.contains('#ifndef') }
|
||||
if (headerGuard) {
|
||||
log " 头文件保护: ${headerGuard.trim()}"
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 忽略读取错误
|
||||
}
|
||||
}
|
||||
|
||||
log "=" * 60
|
||||
log "🎉 头文件已成功生成!"
|
||||
log ""
|
||||
log "使用建议:"
|
||||
log " 1. 将生成的头文件复制到你的 C/C++ 项目中"
|
||||
log " 2. 在 C/C++ 源文件中包含这些头文件"
|
||||
log " 3. 实现头文件中声明的 JNI 函数"
|
||||
log ""
|
||||
log "示例 C++ 代码:"
|
||||
log " #include \"${headerFiles[0].name}\""
|
||||
log " JNIEXPORT void JNICALL Java_com_example_MyClass_nativeMethod(JNIEnv* env, jobject obj) {"
|
||||
log " // 你的实现代码"
|
||||
log " }"
|
||||
|
||||
} else {
|
||||
logWarn "警告: 未生成任何 .h 头文件"
|
||||
logWarn "可能的原因:"
|
||||
logWarn " 1. 源文件中没有 native 方法声明"
|
||||
logWarn " 2. javac 版本不支持 -h 选项"
|
||||
logWarn " 3. 类路径配置不正确"
|
||||
}
|
||||
} else {
|
||||
logError "所有处理都失败,未生成任何头文件"
|
||||
}
|
||||
|
||||
} finally {
|
||||
// 清理临时目录
|
||||
tempOutputDir.deleteDir()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 备选方案:使用传统 javah(如果 javac -h 失败)
|
||||
tasks.register('generateJniHeadersLegacy') {
|
||||
group = 'jni'
|
||||
description = '使用传统 javah 生成 JNI 头文件'
|
||||
|
||||
dependsOn 'compileJava'
|
||||
|
||||
doLast {
|
||||
outputDir.mkdirs()
|
||||
|
||||
def targetClasses = []
|
||||
if (configFile.exists()) {
|
||||
configFile.eachLine { line ->
|
||||
def trimmed = line.trim()
|
||||
if (trimmed && !trimmed.startsWith('#')) {
|
||||
targetClasses.add(trimmed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetClasses.isEmpty()) {
|
||||
logError "没有配置任何 JNI 类"
|
||||
return
|
||||
}
|
||||
|
||||
log "使用传统 javah 生成头文件..."
|
||||
|
||||
def classesDir = project.sourceSets.main.output.classesDirs.singleFile
|
||||
def classpath = project.configurations.runtimeClasspath.asPath +
|
||||
File.pathSeparator +
|
||||
classesDir.absolutePath
|
||||
|
||||
def javaHome = System.getProperty('java.home')
|
||||
def javahPath = "${javaHome}/bin/javah"
|
||||
|
||||
if (!new File(javahPath).exists()) {
|
||||
javahPath = "${javaHome}/../bin/javah"
|
||||
}
|
||||
|
||||
if (!new File(javahPath).exists()) {
|
||||
logError "找不到 javah 工具"
|
||||
logError "请使用 Java 8-9 或使用 generateJniHeaders 任务"
|
||||
return
|
||||
}
|
||||
|
||||
def processArgs = [javahPath, '-classpath', classpath, '-d', outputDir.absolutePath]
|
||||
processArgs.addAll(targetClasses)
|
||||
|
||||
log "执行命令: ${processArgs.join(' ')}"
|
||||
|
||||
def process = processArgs.execute()
|
||||
def exitCode = process.waitFor()
|
||||
|
||||
if (exitCode == 0) {
|
||||
def files = outputDir.listFiles({ dir, name -> name.endsWith('.h') } as FilenameFilter)
|
||||
log "生成成功!创建了 ${files?.size() ?: 0} 个头文件"
|
||||
if (files) {
|
||||
files.each { log " - ${it.name}" }
|
||||
}
|
||||
} else {
|
||||
logError "生成失败"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 扫描 native 方法任务
|
||||
tasks.register('scanForNativeMethods') {
|
||||
group = 'jni'
|
||||
description = '扫描项目中的 native 方法'
|
||||
|
||||
doLast {
|
||||
log "扫描项目中可能包含 native 方法的类..."
|
||||
|
||||
def sourceDirs = project.sourceSets.main.java.srcDirs
|
||||
def foundClasses = []
|
||||
|
||||
sourceDirs.each { srcDir ->
|
||||
if (srcDir.exists()) {
|
||||
srcDir.eachFileRecurse(groovy.io.FileType.FILES) { file ->
|
||||
if (file.name.endsWith('.java')) {
|
||||
def content = file.text
|
||||
if (content.contains('native ') || content.contains(' native')) {
|
||||
// 提取类名
|
||||
def packageMatch = content =~ /package\s+([\w.]+)\s*;/
|
||||
def classMatch = content =~ /class\s+(\w+)/
|
||||
|
||||
if (packageMatch.find() && classMatch.find()) {
|
||||
def packageName = packageMatch.group(1)
|
||||
def className = classMatch.group(1)
|
||||
def fullClassName = "${packageName}.${className}"
|
||||
|
||||
if (!foundClasses.contains(fullClassName)) {
|
||||
foundClasses.add(fullClassName)
|
||||
log "发现: $fullClassName"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundClasses.isEmpty()) {
|
||||
log "未发现包含 native 方法的类"
|
||||
} else {
|
||||
log "=" * 60
|
||||
log "发现 ${foundClasses.size()} 个可能包含 native 方法的类:"
|
||||
foundClasses.sort().eachWithIndex { cls, i ->
|
||||
log " ${i + 1}. $cls"
|
||||
}
|
||||
log ""
|
||||
log "你可以将这些类添加到 ${configFile.name} 中"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清理任务
|
||||
tasks.register('cleanJniHeaders', Delete) {
|
||||
group = 'jni'
|
||||
description = '清理 JNI 头文件'
|
||||
delete outputDir
|
||||
|
||||
doLast {
|
||||
log "已清理 JNI 头文件目录: ${outputDir.absolutePath}"
|
||||
}
|
||||
}
|
||||
|
||||
// 验证任务
|
||||
tasks.register('verifyJniSetup') {
|
||||
group = 'jni'
|
||||
description = '验证 JNI 配置'
|
||||
|
||||
doLast {
|
||||
log "验证 JNI 配置..."
|
||||
log "Java 版本: ${System.getProperty('java.version')}"
|
||||
log "Java Home: ${System.getProperty('java.home')}"
|
||||
|
||||
// 检查 javac
|
||||
def javaHome = System.getProperty('java.home')
|
||||
def javacPath = "${javaHome}/bin/javac"
|
||||
def javacExists = new File(javacPath).exists() || new File("${javaHome}/../bin/javac").exists()
|
||||
log "javac 工具: ${javacExists ? '找到 ✓' : '未找到 ✗'}"
|
||||
|
||||
// 检查 javah(传统方式)
|
||||
def javahPath = "${javaHome}/bin/javah"
|
||||
def javahExists = new File(javahPath).exists() || new File("${javaHome}/../bin/javah").exists()
|
||||
log "javah 工具: ${javahExists ? '找到 ✓' : '未找到 ✗'}"
|
||||
|
||||
// 检查配置文件
|
||||
if (configFile.exists()) {
|
||||
def classes = configFile.readLines()
|
||||
.findAll { it.trim() && !it.trim().startsWith('#') }
|
||||
log "配置文件: 已找到 (${classes.size()} 个类)"
|
||||
if (classes.size() > 0) {
|
||||
classes.each { log " - $it" }
|
||||
}
|
||||
} else {
|
||||
log "配置文件: 未找到 ✗"
|
||||
}
|
||||
|
||||
// 检查输出目录
|
||||
log "输出目录: ${outputDir.absolutePath}"
|
||||
}
|
||||
}
|
||||
|
||||
// 帮助任务
|
||||
tasks.register('jniHelp') {
|
||||
group = 'help'
|
||||
description = 'JNI 帮助'
|
||||
|
||||
doLast {
|
||||
println """
|
||||
${'=' * 70}
|
||||
JNI 头文件生成系统
|
||||
${'=' * 70}
|
||||
|
||||
📋 概述:
|
||||
为包含 native 方法的 Java 类生成 C/C++ 头文件。
|
||||
|
||||
🚀 快速开始:
|
||||
1. ./gradlew createJniConfig # 创建配置文件
|
||||
2. 编辑 config/jni-classes.txt # 添加你的 JNI 类
|
||||
3. ./gradlew generateJniHeaders # 生成头文件(推荐)
|
||||
4. 头文件输出到: cpp/header/
|
||||
|
||||
🔧 可用任务:
|
||||
jniHelp - 显示此帮助
|
||||
createJniConfig - 创建配置文件
|
||||
generateJniHeaders - 生成头文件(现代方式)
|
||||
generateJniHeadersLegacy - 传统方式(Java 8-9)
|
||||
scanForNativeMethods - 扫描 native 方法
|
||||
verifyJniSetup - 验证配置
|
||||
cleanJniHeaders - 清理生成的文件
|
||||
|
||||
📝 配置文件格式 (config/jni-classes.txt):
|
||||
# 注释
|
||||
com.example.MyNativeClass # 直接指定类名
|
||||
#auto:.*Native.* # 自动匹配(以 #auto: 开头)
|
||||
|
||||
⚠️ 注意事项:
|
||||
• 确保类中包含 native 方法声明
|
||||
• 先编译项目再生成头文件
|
||||
• 对于 Java 10+ 使用 generateJniHeaders
|
||||
• 对于 Java 8-9 使用 generateJniHeadersLegacy
|
||||
|
||||
🔍 调试:
|
||||
• 运行 verifyJniSetup 检查环境
|
||||
• 运行 scanForNativeMethods 发现 native 类
|
||||
• 确保源文件中有 native 关键字
|
||||
|
||||
${'=' * 70}
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
// 可选:自动集成到构建
|
||||
// tasks.named('build') {
|
||||
// dependsOn tasks.named('generateJniHeaders')
|
||||
// }
|
||||
|
||||
// 清理时包含 JNI 头文件
|
||||
tasks.named('clean') {
|
||||
dependsOn tasks.named('cleanJniHeaders')
|
||||
}
|
||||
|
||||
log "JNI 模块已加载"
|
||||
log "输出目录: ${outputDir.absolutePath}"
|
||||
log "配置文件: ${configFile.absolutePath}"
|
||||
log "使用 ./gradlew jniHelp 查看详细帮助"
|
||||
BIN
res/alex.png
Normal file
BIN
res/alex.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
1
res/doll_default.bbmodel
Normal file
1
res/doll_default.bbmodel
Normal file
File diff suppressed because one or more lines are too long
265
res/doll_default.json
Normal file
265
res/doll_default.json
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
{
|
||||
"format_version": "1.9.0",
|
||||
"credit": "3D Model © 2025 LeisureTimeDock",
|
||||
"textures": {
|
||||
"0": "#skin",
|
||||
"particle": "minecraft:block/white_wool"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"name": "Toggle_Helmet",
|
||||
"from": [3.5, 8.8, 7.5],
|
||||
"to": [12.5, 17.8, 16.5],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [8, 9.3, 12]},
|
||||
"faces": {
|
||||
"north": {"uv": [10, 2, 12, 4], "texture": "#0"},
|
||||
"east": {"uv": [8, 2, 10, 4], "texture": "#0"},
|
||||
"south": {"uv": [14, 2, 16, 4], "texture": "#0"},
|
||||
"west": {"uv": [12, 2, 14, 4], "texture": "#0"},
|
||||
"up": {"uv": [10, 2, 12, 0], "texture": "#0"},
|
||||
"down": {"uv": [12, 0, 14, 2], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Head",
|
||||
"from": [4, 9.3, 8],
|
||||
"to": [12, 17.3, 16],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [8, 9.3, 12]},
|
||||
"faces": {
|
||||
"north": {"uv": [2, 2, 4, 4], "texture": "#0"},
|
||||
"east": {"uv": [0, 2, 2, 4], "texture": "#0"},
|
||||
"south": {"uv": [6, 2, 8, 4], "texture": "#0"},
|
||||
"west": {"uv": [4, 2, 6, 4], "texture": "#0"},
|
||||
"up": {"uv": [4, 2, 2, 0], "texture": "#0"},
|
||||
"down": {"uv": [6, 0, 4, 2], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Toggle_Chest_Armor",
|
||||
"from": [4.75, 2.05, 9.75],
|
||||
"to": [11.25, 9.55, 13.25],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [8, 5.8, 11.5]},
|
||||
"faces": {
|
||||
"north": {"uv": [5, 9, 7, 12], "texture": "#0"},
|
||||
"east": {"uv": [4, 9, 5, 12], "texture": "#0"},
|
||||
"south": {"uv": [8, 9, 10, 12], "texture": "#0"},
|
||||
"west": {"uv": [7, 9, 8, 12], "texture": "#0"},
|
||||
"up": {"uv": [5, 8, 7, 9], "texture": "#0"},
|
||||
"down": {"uv": [7, 8, 9, 9], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Body",
|
||||
"from": [5, 2.3, 10],
|
||||
"to": [11, 9.3, 13],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [8, 5.8, 11.5]},
|
||||
"faces": {
|
||||
"north": {"uv": [5, 5, 7, 8], "texture": "#0"},
|
||||
"east": {"uv": [4, 5, 5, 8], "texture": "#0"},
|
||||
"south": {"uv": [8, 5, 10, 8], "texture": "#0"},
|
||||
"west": {"uv": [7, 5, 8, 8], "texture": "#0"},
|
||||
"up": {"uv": [7, 5, 5, 4], "texture": "#0"},
|
||||
"down": {"uv": [9, 4, 7, 5], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Toggle_Left_Arm_Armor",
|
||||
"from": [2.75, 6.25, 3.8],
|
||||
"to": [5.25, 9.75, 13.3],
|
||||
"rotation": {"angle": -22.5, "axis": "y", "origin": [3, 8, 12.5]},
|
||||
"faces": {
|
||||
"north": {"uv": [13.75, 12, 14.5, 13], "rotation": 180, "texture": "#0"},
|
||||
"east": {"uv": [12, 13, 13, 16], "rotation": 270, "texture": "#0"},
|
||||
"south": {"uv": [13, 12, 13.75, 13], "texture": "#0"},
|
||||
"west": {"uv": [13.75, 13, 14.75, 16], "rotation": 90, "texture": "#0"},
|
||||
"up": {"uv": [13, 13, 13.75, 16], "rotation": 180, "texture": "#0"},
|
||||
"down": {"uv": [14.75, 13, 15.5, 16], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Left_arm",
|
||||
"from": [3, 6.5, 3.8],
|
||||
"to": [5, 9.5, 12.8],
|
||||
"rotation": {"angle": -22.5, "axis": "y", "origin": [4, 8, 12.5]},
|
||||
"faces": {
|
||||
"north": {"uv": [10.5, 12, 9.75, 13], "rotation": 180, "texture": "#0"},
|
||||
"east": {"uv": [8, 13, 9, 16], "rotation": 270, "texture": "#0"},
|
||||
"south": {"uv": [9.75, 13, 9, 12], "texture": "#0"},
|
||||
"west": {"uv": [9.75, 13, 10.5, 16], "rotation": 90, "texture": "#0"},
|
||||
"up": {"uv": [9, 13, 9.75, 16], "rotation": 180, "texture": "#0"},
|
||||
"down": {"uv": [10.5, 13, 11.5, 16], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Toggle_Right_Arm_Armor",
|
||||
"from": [10.75, 6.25, 3.8],
|
||||
"to": [13.25, 9.75, 13.3],
|
||||
"rotation": {"angle": 22.5, "axis": "y", "origin": [12, 8, 11.5]},
|
||||
"faces": {
|
||||
"north": {"uv": [11.75, 8, 12.5, 9], "rotation": 180, "texture": "#0"},
|
||||
"east": {"uv": [10, 9, 11, 12], "rotation": 270, "texture": "#0"},
|
||||
"south": {"uv": [11, 8, 11.75, 9], "texture": "#0"},
|
||||
"west": {"uv": [11.75, 9, 12.75, 12], "rotation": 90, "texture": "#0"},
|
||||
"up": {"uv": [11, 9, 11.75, 12], "rotation": 180, "texture": "#0"},
|
||||
"down": {"uv": [12.75, 9, 13.5, 12], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Right_arm",
|
||||
"from": [11, 6.5, 3.8],
|
||||
"to": [13, 9.5, 12.8],
|
||||
"rotation": {"angle": 22.5, "axis": "y", "origin": [12, 8, 11.5]},
|
||||
"faces": {
|
||||
"north": {"uv": [12.5, 4, 11.75, 5], "rotation": 180, "texture": "#0"},
|
||||
"east": {"uv": [10, 5, 11, 8], "rotation": 270, "texture": "#0"},
|
||||
"south": {"uv": [11.75, 5, 11, 4], "texture": "#0"},
|
||||
"west": {"uv": [11.75, 5, 12.5, 8], "rotation": 90, "texture": "#0"},
|
||||
"up": {"uv": [11, 5, 11.75, 8], "rotation": 180, "texture": "#0"},
|
||||
"down": {"uv": [12.5, 5, 13.5, 8], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Toggle_Left_Leg_Armor",
|
||||
"from": [5.2, -0.25, 3.05],
|
||||
"to": [8.7, 3.25, 12.55],
|
||||
"rotation": {"angle": 22.5, "axis": "y", "origin": [5.7, 2, 13]},
|
||||
"faces": {
|
||||
"north": {"uv": [2, 12, 3, 13], "rotation": 180, "texture": "#0"},
|
||||
"east": {"uv": [0, 13, 1, 16], "rotation": 270, "texture": "#0"},
|
||||
"south": {"uv": [1, 12, 2, 13], "texture": "#0"},
|
||||
"west": {"uv": [2, 13, 3, 16], "rotation": 90, "texture": "#0"},
|
||||
"up": {"uv": [1, 13, 2, 16], "rotation": 180, "texture": "#0"},
|
||||
"down": {"uv": [3, 13, 4, 16], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Left_leg",
|
||||
"from": [5.5, 0, 3.3],
|
||||
"to": [8.5, 3, 12.3],
|
||||
"rotation": {"angle": 22.5, "axis": "y", "origin": [6, 2, 13]},
|
||||
"faces": {
|
||||
"north": {"uv": [7, 12, 6, 13], "rotation": 180, "texture": "#0"},
|
||||
"east": {"uv": [4, 13, 5, 16], "rotation": 270, "texture": "#0"},
|
||||
"south": {"uv": [5.95, 13, 5, 12], "texture": "#0"},
|
||||
"west": {"uv": [6, 13, 7, 16], "rotation": 90, "texture": "#0"},
|
||||
"up": {"uv": [5, 13, 6, 16], "rotation": 180, "texture": "#0"},
|
||||
"down": {"uv": [7, 13, 8, 16], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Toggle_Right_Leg_Armor",
|
||||
"from": [7.2, -0.25, 3.05],
|
||||
"to": [10.7, 3.25, 12.55],
|
||||
"rotation": {"angle": -22.5, "axis": "y", "origin": [8.7, 2, 13]},
|
||||
"faces": {
|
||||
"north": {"uv": [2, 8, 3, 9], "rotation": 180, "texture": "#0"},
|
||||
"east": {"uv": [0, 9, 1, 12], "rotation": 270, "texture": "#0"},
|
||||
"south": {"uv": [1, 8, 2, 9], "texture": "#0"},
|
||||
"west": {"uv": [2, 9, 3, 12], "rotation": 90, "texture": "#0"},
|
||||
"up": {"uv": [1, 9, 2, 12], "rotation": 180, "texture": "#0"},
|
||||
"down": {"uv": [3, 9, 4, 12], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Right_leg",
|
||||
"from": [7.5, 0, 3.3],
|
||||
"to": [10.5, 3, 12.3],
|
||||
"rotation": {"angle": -22.5, "axis": "y", "origin": [9, 2, 13]},
|
||||
"faces": {
|
||||
"north": {"uv": [3, 4, 2, 5], "rotation": 180, "texture": "#0"},
|
||||
"east": {"uv": [0, 5, 1, 8], "rotation": 270, "texture": "#0"},
|
||||
"south": {"uv": [2, 5, 1, 4], "texture": "#0"},
|
||||
"west": {"uv": [2, 5, 3, 8], "rotation": 90, "texture": "#0"},
|
||||
"up": {"uv": [1, 5, 2, 8], "rotation": 180, "texture": "#0"},
|
||||
"down": {"uv": [3, 5, 4, 8], "texture": "#0"}
|
||||
}
|
||||
}
|
||||
],
|
||||
"display": {
|
||||
"thirdperson_righthand": {
|
||||
"rotation": [75, 45, 0],
|
||||
"translation": [0, 2.5, 0],
|
||||
"scale": [0.375, 0.375, 0.375]
|
||||
},
|
||||
"thirdperson_lefthand": {
|
||||
"rotation": [75, 45, 0],
|
||||
"translation": [0, 2.5, 0],
|
||||
"scale": [0.375, 0.375, 0.375]
|
||||
},
|
||||
"firstperson_righthand": {
|
||||
"rotation": [0, 124, 0],
|
||||
"translation": [2, 3, 0],
|
||||
"scale": [0.4, 0.4, 0.4]
|
||||
},
|
||||
"firstperson_lefthand": {
|
||||
"rotation": [0, 120, 0],
|
||||
"translation": [1.5, 2.75, 0],
|
||||
"scale": [0.4, 0.4, 0.4]
|
||||
},
|
||||
"ground": {
|
||||
"translation": [0, 2, 0],
|
||||
"scale": [0.5, 0.5, 0.5]
|
||||
},
|
||||
"gui": {
|
||||
"rotation": [30, -135, 0],
|
||||
"translation": [0.75, -1, 0],
|
||||
"scale": [0.625, 0.625, 0.625]
|
||||
},
|
||||
"head": {
|
||||
"translation": [0, 14, -0.75]
|
||||
},
|
||||
"fixed": {
|
||||
"translation": [0, 0, -2.75],
|
||||
"scale": [0.5, 0.5, 0.5]
|
||||
},
|
||||
"on_shelf": {
|
||||
"rotation": [0, -180, 0],
|
||||
"translation": [0, 0, 5.25]
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
{
|
||||
"name": "Player",
|
||||
"origin": [3, -6.7, 6],
|
||||
"color": 0,
|
||||
"children": [
|
||||
{
|
||||
"name": "Head",
|
||||
"origin": [8, 16, 8],
|
||||
"color": 0,
|
||||
"children": [0, 1]
|
||||
},
|
||||
{
|
||||
"name": "Body",
|
||||
"origin": [8, 11, 8],
|
||||
"color": 0,
|
||||
"children": [2, 3]
|
||||
},
|
||||
{
|
||||
"name": "Left_Arm",
|
||||
"origin": [5, 15, 6],
|
||||
"color": 0,
|
||||
"children": [4, 5]
|
||||
},
|
||||
{
|
||||
"name": "Right_Arm",
|
||||
"origin": [11, 15, 6],
|
||||
"color": 0,
|
||||
"children": [6, 7]
|
||||
},
|
||||
{
|
||||
"name": "Left_Leg",
|
||||
"origin": [7, 13, 7],
|
||||
"color": 0,
|
||||
"children": [8, 9]
|
||||
},
|
||||
{
|
||||
"name": "Right_Leg",
|
||||
"origin": [10, 13, 7],
|
||||
"color": 0,
|
||||
"children": [10, 11]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
1
res/doll_item.bbmodel
Normal file
1
res/doll_item.bbmodel
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"meta":{"format_version":"5.0","model_format":"java_block","box_uv":false},"name":"doll_item","parent":"","java_block_version":"1.9.0","ambientocclusion":true,"front_gui_light":false,"visible_box":[1,1,0],"variable_placeholders":"","variable_placeholder_buttons":[],"unhandled_root_fields":{},"resolution":{"width":16,"height":16},"elements":[{"name":"cube","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[7,6,1],"to":[7,14,9],"autouv":0,"color":8,"rotation":[0,45,0],"origin":[7,10,3.5],"faces":{"north":{"uv":[0,0,0,0],"texture":null},"east":{"uv":[0,0,16,16],"texture":0},"south":{"uv":[0,0,0,0],"texture":null},"west":{"uv":[0,0,16,16],"texture":0},"up":{"uv":[0,0,0,0],"texture":null},"down":{"uv":[0,0,0,0],"texture":null}},"type":"cube","uuid":"f9f45372-1441-1b47-bbb1-278cfff92370"},{"name":"cube","box_uv":false,"render_order":"default","rescale":false,"locked":false,"shade":true,"light_emission":0,"allow_mirror_modeling":true,"from":[9,6,1],"to":[9,14,9],"autouv":0,"color":0,"rotation":[0,-45,0],"origin":[9,10,3.5],"faces":{"north":{"uv":[0,0,0,0],"texture":null},"east":{"uv":[0,0,16,16],"texture":0},"south":{"uv":[0,0,0,0],"texture":null},"west":{"uv":[0,0,16,16],"texture":0},"up":{"uv":[0,0,0,0],"texture":null},"down":{"uv":[0,0,0,0],"texture":null}},"type":"cube","uuid":"04196842-4494-bee4-1b44-b302f4a2475a"}],"groups":[{"uuid":"2b55d183-11b7-4e36-1f45-11cc7c5d7445","export":true,"locked":false,"origin":[0,4,2.5],"rotation":[0,0,0],"color":0,"name":"flower_item","children":[],"reset":false,"shade":true,"mirror_uv":false,"selected":false,"visibility":true,"autouv":0,"isOpen":true,"primary_selected":true}],"outliner":[{"uuid":"2b55d183-11b7-4e36-1f45-11cc7c5d7445","isOpen":true,"children":["f9f45372-1441-1b47-bbb1-278cfff92370","04196842-4494-bee4-1b44-b302f4a2475a"]}],"textures":[{"name":"amethyst_cluster.png","path":"","folder":"","namespace":"","id":"3","group":"","width":16,"height":16,"uv_width":16,"uv_height":16,"particle":true,"use_as_default":false,"layers_enabled":false,"sync_to_project":"","render_mode":"default","render_sides":"auto","pbr_channel":"color","frame_time":1,"frame_order_type":"loop","frame_order":"","frame_interpolate":false,"visible":true,"internal":true,"saved":false,"uuid":"65a7492d-58ab-bfad-b419-fd46962220fb","source":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAwElEQVQ4y7WSwQ3CMBAE0x1F8OVFCfQAJSB6QFQBEq+0QAO8wWgiDVqcCGEQkS6Ondvx3tld98/nvLuWn8T346XsN19AEAtYL0+lyYniBDRBFJZbPwQlNAFW88MgIhAxB8D4MQBBDVjMttMA7BJpUZEAIgGWSbxMhCjCRQ0Y5fNiNxpm90nOUqzf/+Q+GyuAgOpu6YJ5HufoaF1EYN3pwu9s6uhYs2FZtwA3EDB5Iia5I6Pu0tHbO0CiodVcqy/UAxZem9kikfZjAAAAAElFTkSuQmCC"}]}
|
||||
37
res/doll_item.json
Normal file
37
res/doll_item.json
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"format_version": "1.9.0",
|
||||
"credit": "3D Model © 2025 LeisureTimeDock",
|
||||
"textures": {
|
||||
"3": "#item",
|
||||
"particle": "#item"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [7, 6, 1],
|
||||
"to": [7, 14, 9],
|
||||
"rotation": {"angle": 45, "axis": "y", "origin": [7, 10, 3.5]},
|
||||
"faces": {
|
||||
"east": {"uv": [0, 0, 16, 16], "texture": "#3"},
|
||||
"west": {"uv": [0, 0, 16, 16], "texture": "#3"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [9, 6, 1],
|
||||
"to": [9, 14, 9],
|
||||
"rotation": {"angle": -45, "axis": "y", "origin": [9, 10, 3.5]},
|
||||
"faces": {
|
||||
"east": {"uv": [0, 0, 16, 16], "texture": "#3"},
|
||||
"west": {"uv": [0, 0, 16, 16], "texture": "#3"}
|
||||
}
|
||||
}
|
||||
],
|
||||
"display": {},
|
||||
"groups": [
|
||||
{
|
||||
"name": "item",
|
||||
"origin": [0, 4, 2.5],
|
||||
"color": 0,
|
||||
"children": [0, 1]
|
||||
}
|
||||
]
|
||||
}
|
||||
1
res/doll_without_item.bbmodel
Normal file
1
res/doll_without_item.bbmodel
Normal file
File diff suppressed because one or more lines are too long
265
res/doll_without_item.json
Normal file
265
res/doll_without_item.json
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
{
|
||||
"format_version": "1.9.0",
|
||||
"credit": "3D Model © 2025 LeisureTimeDock",
|
||||
"textures": {
|
||||
"0": "#skin",
|
||||
"particle": "minecraft:block/white_wool"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"name": "Toggle_Helmet",
|
||||
"from": [3.5, 8.8, 7.5],
|
||||
"to": [12.5, 17.8, 16.5],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [8, 9.3, 12]},
|
||||
"faces": {
|
||||
"north": {"uv": [10, 2, 12, 4], "texture": "#0"},
|
||||
"east": {"uv": [8, 2, 10, 4], "texture": "#0"},
|
||||
"south": {"uv": [14, 2, 16, 4], "texture": "#0"},
|
||||
"west": {"uv": [12, 2, 14, 4], "texture": "#0"},
|
||||
"up": {"uv": [10, 2, 12, 0], "texture": "#0"},
|
||||
"down": {"uv": [12, 0, 14, 2], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Head",
|
||||
"from": [4, 9.3, 8],
|
||||
"to": [12, 17.3, 16],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [8, 9.3, 12]},
|
||||
"faces": {
|
||||
"north": {"uv": [2, 2, 4, 4], "texture": "#0"},
|
||||
"east": {"uv": [0, 2, 2, 4], "texture": "#0"},
|
||||
"south": {"uv": [6, 2, 8, 4], "texture": "#0"},
|
||||
"west": {"uv": [4, 2, 6, 4], "texture": "#0"},
|
||||
"up": {"uv": [4, 2, 2, 0], "texture": "#0"},
|
||||
"down": {"uv": [6, 0, 4, 2], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Toggle_Chest_Armor",
|
||||
"from": [4.75, 2.05, 9.75],
|
||||
"to": [11.25, 9.55, 13.25],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [8, 5.8, 11.5]},
|
||||
"faces": {
|
||||
"north": {"uv": [5, 9, 7, 12], "texture": "#0"},
|
||||
"east": {"uv": [4, 9, 5, 12], "texture": "#0"},
|
||||
"south": {"uv": [8, 9, 10, 12], "texture": "#0"},
|
||||
"west": {"uv": [7, 9, 8, 12], "texture": "#0"},
|
||||
"up": {"uv": [5, 8, 7, 9], "texture": "#0"},
|
||||
"down": {"uv": [7, 8, 9, 9], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Body",
|
||||
"from": [5, 2.3, 10],
|
||||
"to": [11, 9.3, 13],
|
||||
"rotation": {"angle": 0, "axis": "y", "origin": [8, 5.8, 11.5]},
|
||||
"faces": {
|
||||
"north": {"uv": [5, 5, 7, 8], "texture": "#0"},
|
||||
"east": {"uv": [4, 5, 5, 8], "texture": "#0"},
|
||||
"south": {"uv": [8, 5, 10, 8], "texture": "#0"},
|
||||
"west": {"uv": [7, 5, 8, 8], "texture": "#0"},
|
||||
"up": {"uv": [7, 5, 5, 4], "texture": "#0"},
|
||||
"down": {"uv": [9, 4, 7, 5], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Toggle_Left_Arm_Armor",
|
||||
"from": [2.75, 0.3, 9.75],
|
||||
"to": [5.25, 9.8, 13.25],
|
||||
"rotation": {"angle": 0, "axis": "x", "origin": [3, 8, 12.5]},
|
||||
"faces": {
|
||||
"north": {"uv": [13, 13, 13.75, 16], "texture": "#0"},
|
||||
"east": {"uv": [12, 13, 13, 16], "texture": "#0"},
|
||||
"south": {"uv": [14.75, 13, 15.5, 16], "texture": "#0"},
|
||||
"west": {"uv": [13.75, 13, 14.75, 16], "texture": "#0"},
|
||||
"up": {"uv": [13, 12, 13.75, 13], "texture": "#0"},
|
||||
"down": {"uv": [13.75, 12, 14.5, 13], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Left_arm",
|
||||
"from": [3, 0.3, 10],
|
||||
"to": [5, 9.3, 13],
|
||||
"rotation": {"angle": 0, "axis": "x", "origin": [4, 8, 12.5]},
|
||||
"faces": {
|
||||
"north": {"uv": [9, 13, 9.75, 16], "texture": "#0"},
|
||||
"east": {"uv": [8, 13, 9, 16], "texture": "#0"},
|
||||
"south": {"uv": [10.5, 13, 11.5, 16], "texture": "#0"},
|
||||
"west": {"uv": [9.75, 13, 10.5, 16], "texture": "#0"},
|
||||
"up": {"uv": [9.75, 13, 9, 12], "texture": "#0"},
|
||||
"down": {"uv": [10.5, 12, 9.75, 13], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Toggle_Right_Arm_Armor",
|
||||
"from": [10.75, 0.3, 9.75],
|
||||
"to": [13.25, 9.8, 13.25],
|
||||
"rotation": {"angle": 0, "axis": "x", "origin": [12, 8, 11.5]},
|
||||
"faces": {
|
||||
"north": {"uv": [11, 9, 11.75, 12], "texture": "#0"},
|
||||
"east": {"uv": [10, 9, 11, 12], "texture": "#0"},
|
||||
"south": {"uv": [12.75, 9, 13.5, 12], "texture": "#0"},
|
||||
"west": {"uv": [11.75, 9, 12.75, 12], "texture": "#0"},
|
||||
"up": {"uv": [11, 8, 11.75, 9], "texture": "#0"},
|
||||
"down": {"uv": [11.75, 8, 12.5, 9], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Right_arm",
|
||||
"from": [11, 0.3, 10],
|
||||
"to": [13, 9.3, 13],
|
||||
"rotation": {"angle": 0, "axis": "x", "origin": [12, 8, 11.5]},
|
||||
"faces": {
|
||||
"north": {"uv": [11, 5, 11.75, 8], "texture": "#0"},
|
||||
"east": {"uv": [10, 5, 11, 8], "texture": "#0"},
|
||||
"south": {"uv": [12.5, 5, 13.5, 8], "texture": "#0"},
|
||||
"west": {"uv": [11.75, 5, 12.5, 8], "texture": "#0"},
|
||||
"up": {"uv": [11.75, 5, 11, 4], "texture": "#0"},
|
||||
"down": {"uv": [12.5, 4, 11.75, 5], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Toggle_Left_Leg_Armor",
|
||||
"from": [5.2, -0.25, 3.05],
|
||||
"to": [8.7, 3.25, 12.55],
|
||||
"rotation": {"angle": 22.5, "axis": "y", "origin": [5.7, 2, 13]},
|
||||
"faces": {
|
||||
"north": {"uv": [2, 12, 3, 13], "rotation": 180, "texture": "#0"},
|
||||
"east": {"uv": [0, 13, 1, 16], "rotation": 270, "texture": "#0"},
|
||||
"south": {"uv": [1, 12, 2, 13], "texture": "#0"},
|
||||
"west": {"uv": [2, 13, 3, 16], "rotation": 90, "texture": "#0"},
|
||||
"up": {"uv": [1, 13, 2, 16], "rotation": 180, "texture": "#0"},
|
||||
"down": {"uv": [3, 13, 4, 16], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Left_leg",
|
||||
"from": [5.5, 0, 3.3],
|
||||
"to": [8.5, 3, 12.3],
|
||||
"rotation": {"angle": 22.5, "axis": "y", "origin": [6, 2, 13]},
|
||||
"faces": {
|
||||
"north": {"uv": [7, 12, 6, 13], "rotation": 180, "texture": "#0"},
|
||||
"east": {"uv": [4, 13, 5, 16], "rotation": 270, "texture": "#0"},
|
||||
"south": {"uv": [5.95, 13, 5, 12], "texture": "#0"},
|
||||
"west": {"uv": [6, 13, 7, 16], "rotation": 90, "texture": "#0"},
|
||||
"up": {"uv": [5, 13, 6, 16], "rotation": 180, "texture": "#0"},
|
||||
"down": {"uv": [7, 13, 8, 16], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Toggle_Right_Leg_Armor",
|
||||
"from": [7.2, -0.25, 3.05],
|
||||
"to": [10.7, 3.25, 12.55],
|
||||
"rotation": {"angle": -22.5, "axis": "y", "origin": [8.7, 2, 13]},
|
||||
"faces": {
|
||||
"north": {"uv": [2, 8, 3, 9], "rotation": 180, "texture": "#0"},
|
||||
"east": {"uv": [0, 9, 1, 12], "rotation": 270, "texture": "#0"},
|
||||
"south": {"uv": [1, 8, 2, 9], "texture": "#0"},
|
||||
"west": {"uv": [2, 9, 3, 12], "rotation": 90, "texture": "#0"},
|
||||
"up": {"uv": [1, 9, 2, 12], "rotation": 180, "texture": "#0"},
|
||||
"down": {"uv": [3, 9, 4, 12], "texture": "#0"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Right_leg",
|
||||
"from": [7.5, 0, 3.3],
|
||||
"to": [10.5, 3, 12.3],
|
||||
"rotation": {"angle": -22.5, "axis": "y", "origin": [9, 2, 13]},
|
||||
"faces": {
|
||||
"north": {"uv": [3, 4, 2, 5], "rotation": 180, "texture": "#0"},
|
||||
"east": {"uv": [0, 5, 1, 8], "rotation": 270, "texture": "#0"},
|
||||
"south": {"uv": [2, 5, 1, 4], "texture": "#0"},
|
||||
"west": {"uv": [2, 5, 3, 8], "rotation": 90, "texture": "#0"},
|
||||
"up": {"uv": [1, 5, 2, 8], "rotation": 180, "texture": "#0"},
|
||||
"down": {"uv": [3, 5, 4, 8], "texture": "#0"}
|
||||
}
|
||||
}
|
||||
],
|
||||
"display": {
|
||||
"thirdperson_righthand": {
|
||||
"rotation": [75, 45, 0],
|
||||
"translation": [0, 2.5, 0],
|
||||
"scale": [0.375, 0.375, 0.375]
|
||||
},
|
||||
"thirdperson_lefthand": {
|
||||
"rotation": [75, 45, 0],
|
||||
"translation": [0, 2.5, 0],
|
||||
"scale": [0.375, 0.375, 0.375]
|
||||
},
|
||||
"firstperson_righthand": {
|
||||
"rotation": [0, 124, 0],
|
||||
"translation": [2, 3, 0],
|
||||
"scale": [0.4, 0.4, 0.4]
|
||||
},
|
||||
"firstperson_lefthand": {
|
||||
"rotation": [0, 120, 0],
|
||||
"translation": [1.5, 2.75, 0],
|
||||
"scale": [0.4, 0.4, 0.4]
|
||||
},
|
||||
"ground": {
|
||||
"translation": [0, 2, 0],
|
||||
"scale": [0.5, 0.5, 0.5]
|
||||
},
|
||||
"gui": {
|
||||
"rotation": [30, -135, 0],
|
||||
"translation": [0.75, -1, 0],
|
||||
"scale": [0.625, 0.625, 0.625]
|
||||
},
|
||||
"head": {
|
||||
"translation": [0, 14, -0.75]
|
||||
},
|
||||
"fixed": {
|
||||
"translation": [0, 0, -2.75],
|
||||
"scale": [0.5, 0.5, 0.5]
|
||||
},
|
||||
"on_shelf": {
|
||||
"rotation": [0, -180, 0],
|
||||
"translation": [0, 0, 5.25]
|
||||
}
|
||||
},
|
||||
"groups": [
|
||||
{
|
||||
"name": "Player",
|
||||
"origin": [3, -6.7, 6],
|
||||
"color": 0,
|
||||
"children": [
|
||||
{
|
||||
"name": "Head",
|
||||
"origin": [8, 16, 8],
|
||||
"color": 0,
|
||||
"children": [0, 1]
|
||||
},
|
||||
{
|
||||
"name": "Body",
|
||||
"origin": [8, 11, 8],
|
||||
"color": 0,
|
||||
"children": [2, 3]
|
||||
},
|
||||
{
|
||||
"name": "Left_Arm",
|
||||
"origin": [5, 15, 6],
|
||||
"color": 0,
|
||||
"children": [4, 5]
|
||||
},
|
||||
{
|
||||
"name": "Right_Arm",
|
||||
"origin": [11, 15, 6],
|
||||
"color": 0,
|
||||
"children": [6, 7]
|
||||
},
|
||||
{
|
||||
"name": "Left_Leg",
|
||||
"origin": [7, 13, 7],
|
||||
"color": 0,
|
||||
"children": [8, 9]
|
||||
},
|
||||
{
|
||||
"name": "Right_Leg",
|
||||
"origin": [10, 13, 7],
|
||||
"color": 0,
|
||||
"children": [10, 11]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
1
res/model.bbmodel
Normal file
1
res/model.bbmodel
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
// 1.20.1 2025-12-22T20:31:52.804071 Block States: lib39
|
||||
1dda476533f87cc377e800d537c22b48509a25cf assets/lib39/blockstates/doll.json
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
64
src/generated/resources/assets/lib39/blockstates/doll.json
Normal file
64
src/generated/resources/assets/lib39/blockstates/doll.json
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"variants": {
|
||||
"facing=east,pose=default,waterlogged=false": {
|
||||
"model": "lib39:block/base_doll",
|
||||
"y": 180
|
||||
},
|
||||
"facing=east,pose=default,waterlogged=true": {
|
||||
"model": "lib39:block/base_doll",
|
||||
"y": 180
|
||||
},
|
||||
"facing=east,pose=without_item,waterlogged=false": {
|
||||
"model": "lib39:block/base_doll",
|
||||
"y": 180
|
||||
},
|
||||
"facing=east,pose=without_item,waterlogged=true": {
|
||||
"model": "lib39:block/base_doll",
|
||||
"y": 180
|
||||
},
|
||||
"facing=north,pose=default,waterlogged=false": {
|
||||
"model": "lib39:block/base_doll",
|
||||
"y": 90
|
||||
},
|
||||
"facing=north,pose=default,waterlogged=true": {
|
||||
"model": "lib39:block/base_doll",
|
||||
"y": 90
|
||||
},
|
||||
"facing=north,pose=without_item,waterlogged=false": {
|
||||
"model": "lib39:block/base_doll",
|
||||
"y": 90
|
||||
},
|
||||
"facing=north,pose=without_item,waterlogged=true": {
|
||||
"model": "lib39:block/base_doll",
|
||||
"y": 90
|
||||
},
|
||||
"facing=south,pose=default,waterlogged=false": {
|
||||
"model": "lib39:block/base_doll",
|
||||
"y": 270
|
||||
},
|
||||
"facing=south,pose=default,waterlogged=true": {
|
||||
"model": "lib39:block/base_doll",
|
||||
"y": 270
|
||||
},
|
||||
"facing=south,pose=without_item,waterlogged=false": {
|
||||
"model": "lib39:block/base_doll",
|
||||
"y": 270
|
||||
},
|
||||
"facing=south,pose=without_item,waterlogged=true": {
|
||||
"model": "lib39:block/base_doll",
|
||||
"y": 270
|
||||
},
|
||||
"facing=west,pose=default,waterlogged=false": {
|
||||
"model": "lib39:block/base_doll"
|
||||
},
|
||||
"facing=west,pose=default,waterlogged=true": {
|
||||
"model": "lib39:block/base_doll"
|
||||
},
|
||||
"facing=west,pose=without_item,waterlogged=false": {
|
||||
"model": "lib39:block/base_doll"
|
||||
},
|
||||
"facing=west,pose=without_item,waterlogged=true": {
|
||||
"model": "lib39:block/base_doll"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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": "狸"
|
||||
|
|
|
|||
|
|
@ -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": "小狐狸"
|
||||
|
|
|
|||
|
|
@ -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": "狐狸"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/acacia_sapling"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/allium"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/amethyst_cluster"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/azure_bluet"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/bamboo_stage0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/birch_sapling"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/brain_coral"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/brain_coral_fan"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/brown_mushroom"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/bubble_coral"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/bubble_coral_fan"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/cherry_sapling"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/cobweb"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/cornflower"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/crimson_fungus"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/crimson_roots_pot"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/dandelion"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/dark_oak_sapling"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/dead_brain_coral"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/dead_brain_coral_fan"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/dead_bubble_coral"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/dead_bubble_coral_fan"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/dead_bush"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/dead_fire_coral"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/dead_fire_coral_fan"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/dead_horn_coral"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/dead_horn_coral_fan"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/dead_tube_coral"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/dead_tube_coral_fan"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/fire_coral"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/fire_coral_fan"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/horn_coral"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/horn_coral_fan"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/jungle_sapling"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/lily_of_the_valley"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/oak_sapling"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/orange_tulip"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/oxeye_daisy"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/pink_tulip"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/poppy"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/red_mushroom"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/red_tulip"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/redstone_torch"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/spruce_sapling"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/torch"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/tube_coral"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/tube_coral_fan"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/warped_fungus"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/warped_roots_pot"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/white_tulip"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll_item",
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"item": "minecraft:block/wither_rose"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"parent": "lib39:block/base_doll"
|
||||
}
|
||||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,10 @@ import net.minecraft.commands.CommandSourceStack;
|
|||
import net.minecraft.network.chat.MutableComponent;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import top.r3944realms.lib39.core.command.CommandNode;
|
||||
import top.r3944realms.lib39.core.command.CommandPath;
|
||||
import top.r3944realms.lib39.core.command.ICommandHelpManager;
|
||||
import top.r3944realms.lib39.core.command.ParameterBuilder;
|
||||
import top.r3944realms.lib39.core.command.model.CommandNode;
|
||||
import top.r3944realms.lib39.core.command.model.CommandPath;
|
||||
import top.r3944realms.lib39.core.command.model.Parameter;
|
||||
|
||||
public class RegisterCommandHelpEvent extends Event {
|
||||
private final LiteralArgumentBuilder<CommandSourceStack> builder;
|
||||
|
|
@ -60,7 +60,7 @@ public class RegisterCommandHelpEvent extends Event {
|
|||
* @param commandPath 命令节点
|
||||
* @param parametersBuilder 参数列表构造器
|
||||
*/
|
||||
public void registerParameters(CommandPath commandPath, ParameterBuilder parametersBuilder) {
|
||||
public void registerParameters(CommandPath commandPath, Parameter.Builder parametersBuilder) {
|
||||
this.helpManager.registerCommandParameters(commandPath, parametersBuilder);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,396 @@
|
|||
package top.r3944realms.lib39.base.command;
|
||||
|
||||
import com.mojang.brigadier.Command;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.commands.arguments.EntityArgument;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.event.RegisterCommandsEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import top.r3944realms.lib39.Lib39;
|
||||
import top.r3944realms.lib39.core.command.ICommandHelpManager;
|
||||
import top.r3944realms.lib39.core.command.SimpleHelpCommand;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class Lib39HelpCommand extends SimpleHelpCommand {
|
||||
|
||||
public Lib39HelpCommand(@NotNull RegisterCommandsEvent event) {
|
||||
super(event);
|
||||
if(Lib39.shouldRegisterExamples()) {
|
||||
// 在這裡註冊測試命令
|
||||
registerTestCommands(event.getDispatcher());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICommandHelpManager getCommandHelpManager() {
|
||||
return Lib39CommandHelpManager.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 註冊測試命令
|
||||
*/
|
||||
private void registerTestCommands(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
// 註冊幫助系統本身
|
||||
dispatcher.register(
|
||||
getRoot()
|
||||
.then(Commands.literal("test")
|
||||
.executes(this::executeTest)
|
||||
.then(Commands.argument("param", StringArgumentType.string())
|
||||
.executes(this::executeTestWithParam))
|
||||
)
|
||||
.then(Commands.literal("demo")
|
||||
.executes(this::executeDemo)
|
||||
)
|
||||
);
|
||||
|
||||
// 註冊其他測試命令
|
||||
registerTestCommandTree(dispatcher);
|
||||
|
||||
// 在幫助系統中註冊這些命令
|
||||
registerCommandsInHelpSystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* 註冊測試命令樹
|
||||
*/
|
||||
private void registerTestCommandTree(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
// 基本命令
|
||||
dispatcher.register(
|
||||
Commands.literal("lib39")
|
||||
.then(Commands.literal("greet")
|
||||
.executes(this::executeGreet)
|
||||
.then(Commands.argument("player", EntityArgument.player())
|
||||
.executes(this::executeGreetPlayer))
|
||||
)
|
||||
.then(Commands.literal("calculate")
|
||||
.then(Commands.argument("a", IntegerArgumentType.integer())
|
||||
.then(Commands.argument("b", IntegerArgumentType.integer())
|
||||
.executes(this::executeCalculate)))
|
||||
)
|
||||
.then(Commands.literal("teleport")
|
||||
.requires(source -> source.hasPermission(2)) // 需要OP權限
|
||||
.then(Commands.argument("target", EntityArgument.player())
|
||||
.executes(this::executeTeleport))
|
||||
)
|
||||
.then(Commands.literal("info")
|
||||
.executes(this::executeInfo)
|
||||
)
|
||||
);
|
||||
|
||||
// 嵌套命令示例
|
||||
dispatcher.register(
|
||||
Commands.literal("lib39")
|
||||
.then(Commands.literal("team")
|
||||
.then(Commands.literal("create")
|
||||
.then(Commands.argument("teamName", StringArgumentType.string())
|
||||
.executes(this::executeTeamCreate))
|
||||
)
|
||||
.then(Commands.literal("join")
|
||||
.then(Commands.argument("teamName", StringArgumentType.string())
|
||||
.executes(this::executeTeamJoin))
|
||||
)
|
||||
.then(Commands.literal("leave")
|
||||
.executes(this::executeTeamLeave))
|
||||
)
|
||||
.then(Commands.literal("game")
|
||||
.then(Commands.literal("start")
|
||||
.then(Commands.argument("map", StringArgumentType.string())
|
||||
.executes(this::executeGameStart))
|
||||
)
|
||||
.then(Commands.literal("stop")
|
||||
.executes(this::executeGameStop))
|
||||
.then(Commands.literal("pause")
|
||||
.executes(this::executeGamePause))
|
||||
.then(Commands.literal("resume")
|
||||
.executes(this::executeGameResume))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在幫助系統中註冊命令
|
||||
*/
|
||||
private void registerCommandsInHelpSystem() {
|
||||
ICommandHelpManager helpManager = getCommandHelpManager();
|
||||
|
||||
// 使用Builder模式註冊完整的命令樹
|
||||
helpManager.registerCommands(builder -> {
|
||||
builder.root("lib39", "commands.lib39.root")
|
||||
.expanded(true) // 根節點默認展開
|
||||
|
||||
// 問候命令 - 添加多個子命令
|
||||
.branch("greet", "commands.lib39.greet.basic", greetBuilder -> {
|
||||
greetBuilder.expanded(false); // 默認摺疊
|
||||
greetBuilder.leaf("hello", "commands.lib39.greet.hello");
|
||||
greetBuilder.leaf("morning", "commands.lib39.greet.morning");
|
||||
greetBuilder.leaf("evening", "commands.lib39.greet.evening");
|
||||
greetBuilder.push("player", "commands.lib39.greet.player")
|
||||
.required("player")
|
||||
.pop();
|
||||
})
|
||||
|
||||
// 計算命令
|
||||
.push("calculate", "commands.lib39.calculate")
|
||||
.required("a")
|
||||
.required("b")
|
||||
.pop()
|
||||
|
||||
// 傳送命令
|
||||
.push("teleport", "commands.lib39.teleport")
|
||||
.required("target")
|
||||
.pop()
|
||||
|
||||
// 信息命令
|
||||
.leaf("info", "commands.lib39.info")
|
||||
|
||||
// 隊伍系統 - 添加多個子命令
|
||||
.branch("team", "commands.lib39.team", teamBuilder -> {
|
||||
teamBuilder.expanded(false); // 默認摺疊
|
||||
teamBuilder.leaf("create", "commands.lib39.team.create")
|
||||
.required("teamName");
|
||||
teamBuilder.leaf("join", "commands.lib39.team.join")
|
||||
.required("teamName");
|
||||
teamBuilder.leaf("leave", "commands.lib39.team.leave");
|
||||
teamBuilder.leaf("list", "commands.lib39.team.list");
|
||||
teamBuilder.leaf("info", "commands.lib39.team.info");
|
||||
})
|
||||
|
||||
// 遊戲系統 - 添加多個子命令
|
||||
.branch("game", "commands.lib39.game", gameBuilder -> {
|
||||
gameBuilder.expanded(false); // 默認摺疊
|
||||
gameBuilder.leaf("start", "commands.lib39.game.start")
|
||||
.required("map");
|
||||
gameBuilder.leaf("stop", "commands.lib39.game.stop");
|
||||
gameBuilder.leaf("pause", "commands.lib39.game.pause");
|
||||
gameBuilder.leaf("resume", "commands.lib39.game.resume");
|
||||
gameBuilder.leaf("status", "commands.lib39.game.status");
|
||||
})
|
||||
|
||||
// 設置命令
|
||||
.leavesT(Map.of(
|
||||
"settings", "commands.lib39.settings",
|
||||
"config", "commands.lib39.config",
|
||||
"reload", "commands.lib39.reload",
|
||||
"debug", "commands.lib39.debug",
|
||||
"demo", "commands.lib39.demo",
|
||||
"test", "commands.lib39.test"
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 命令執行方法 ====================
|
||||
|
||||
private int executeTest(CommandContext<CommandSourceStack> context) {
|
||||
CommandSourceStack source = context.getSource();
|
||||
source.sendSuccess(() ->
|
||||
Component.translatable("commands.lib39.test.success")
|
||||
.withStyle(net.minecraft.ChatFormatting.GREEN),
|
||||
false
|
||||
);
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private int executeTestWithParam(CommandContext<CommandSourceStack> context) {
|
||||
String param = StringArgumentType.getString(context, "param");
|
||||
CommandSourceStack source = context.getSource();
|
||||
source.sendSuccess(() ->
|
||||
Component.translatable("commands.lib39.test.with_param", param)
|
||||
.withStyle(net.minecraft.ChatFormatting.AQUA),
|
||||
false
|
||||
);
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private int executeDemo(CommandContext<CommandSourceStack> context) {
|
||||
CommandSourceStack source = context.getSource();
|
||||
source.sendSuccess(() ->
|
||||
Component.translatable("commands.lib39.demo.message")
|
||||
.withStyle(net.minecraft.ChatFormatting.GOLD),
|
||||
false
|
||||
);
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private int executeGreet(CommandContext<CommandSourceStack> context) {
|
||||
CommandSourceStack source = context.getSource();
|
||||
source.sendSuccess(() ->
|
||||
Component.translatable("commands.lib39.greet.default")
|
||||
.withStyle(net.minecraft.ChatFormatting.YELLOW),
|
||||
false
|
||||
);
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private int executeGreetPlayer(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
|
||||
ServerPlayer player = EntityArgument.getPlayer(context, "player");
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
source.sendSuccess(() ->
|
||||
Component.translatable("commands.lib39.greet.player", player.getDisplayName())
|
||||
.withStyle(net.minecraft.ChatFormatting.GREEN),
|
||||
false
|
||||
);
|
||||
|
||||
player.sendSystemMessage(
|
||||
Component.translatable("commands.lib39.greet.received", source.getDisplayName())
|
||||
.withStyle(net.minecraft.ChatFormatting.AQUA)
|
||||
);
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private int executeCalculate(CommandContext<CommandSourceStack> context) {
|
||||
int a = IntegerArgumentType.getInteger(context, "a");
|
||||
int b = IntegerArgumentType.getInteger(context, "b");
|
||||
int sum = a + b;
|
||||
|
||||
CommandSourceStack source = context.getSource();
|
||||
source.sendSuccess(() ->
|
||||
Component.translatable("commands.lib39.calculate.result", a, b, sum)
|
||||
.withStyle(net.minecraft.ChatFormatting.LIGHT_PURPLE),
|
||||
false
|
||||
);
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private int executeTeleport(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
|
||||
ServerPlayer target = EntityArgument.getPlayer(context, "target");
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
if (source.getEntity() instanceof ServerPlayer player) {
|
||||
player.teleportTo(
|
||||
target.serverLevel(),
|
||||
target.getX(),
|
||||
target.getY(),
|
||||
target.getZ(),
|
||||
target.getYRot(),
|
||||
target.getXRot()
|
||||
);
|
||||
|
||||
source.sendSuccess(() ->
|
||||
Component.translatable("commands.lib39.teleport.success", target.getDisplayName())
|
||||
.withStyle(net.minecraft.ChatFormatting.GREEN),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private int executeInfo(CommandContext<CommandSourceStack> context) {
|
||||
CommandSourceStack source = context.getSource();
|
||||
ResourceLocation dimension = source.getLevel().dimension().location();
|
||||
|
||||
source.sendSuccess(() ->
|
||||
Component.translatable("commands.lib39.info.message")
|
||||
.append("\n")
|
||||
.append(Component.translatable("commands.lib39.info.dimension", dimension))
|
||||
.append("\n")
|
||||
.append(Component.translatable("commands.lib39.info.position",
|
||||
String.format("%.1f", source.getPosition().x()),
|
||||
String.format("%.1f", source.getPosition().y()),
|
||||
String.format("%.1f", source.getPosition().z())))
|
||||
.withStyle(net.minecraft.ChatFormatting.AQUA),
|
||||
false
|
||||
);
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private int executeTeamCreate(CommandContext<CommandSourceStack> context) {
|
||||
String teamName = StringArgumentType.getString(context, "teamName");
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
source.sendSuccess(() ->
|
||||
Component.translatable("commands.lib39.team.create.success", teamName)
|
||||
.withStyle(net.minecraft.ChatFormatting.GREEN),
|
||||
false
|
||||
);
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private int executeTeamJoin(CommandContext<CommandSourceStack> context) {
|
||||
String teamName = StringArgumentType.getString(context, "teamName");
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
source.sendSuccess(() ->
|
||||
Component.translatable("commands.lib39.team.join.success", teamName)
|
||||
.withStyle(net.minecraft.ChatFormatting.GREEN),
|
||||
false
|
||||
);
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private int executeTeamLeave(CommandContext<CommandSourceStack> context) {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
source.sendSuccess(() ->
|
||||
Component.translatable("commands.lib39.team.leave.success")
|
||||
.withStyle(net.minecraft.ChatFormatting.YELLOW),
|
||||
false
|
||||
);
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private int executeGameStart(CommandContext<CommandSourceStack> context) {
|
||||
String map = StringArgumentType.getString(context, "map");
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
source.sendSuccess(() ->
|
||||
Component.translatable("commands.lib39.game.start.success", map)
|
||||
.withStyle(net.minecraft.ChatFormatting.GREEN),
|
||||
false
|
||||
);
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private int executeGameStop(CommandContext<CommandSourceStack> context) {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
source.sendSuccess(() ->
|
||||
Component.translatable("commands.lib39.game.stop.success")
|
||||
.withStyle(net.minecraft.ChatFormatting.RED),
|
||||
false
|
||||
);
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private int executeGamePause(CommandContext<CommandSourceStack> context) {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
source.sendSuccess(() ->
|
||||
Component.translatable("commands.lib39.game.pause.success")
|
||||
.withStyle(net.minecraft.ChatFormatting.YELLOW),
|
||||
false
|
||||
);
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private int executeGameResume(CommandContext<CommandSourceStack> context) {
|
||||
CommandSourceStack source = context.getSource();
|
||||
|
||||
source.sendSuccess(() ->
|
||||
Component.translatable("commands.lib39.game.resume.success")
|
||||
.withStyle(net.minecraft.ChatFormatting.GREEN),
|
||||
false
|
||||
);
|
||||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import org.jetbrains.annotations.NotNull;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.r3944realms.lib39.Lib39;
|
||||
import top.r3944realms.lib39.base.datagen.provider.ExItemModelProvider;
|
||||
import top.r3944realms.lib39.base.datagen.provider.Lib39BlockModelProvider;
|
||||
import top.r3944realms.lib39.base.datagen.provider.Lib39BlockStatesProvider;
|
||||
import top.r3944realms.lib39.base.datagen.provider.Lib39ItemModelProvider;
|
||||
import top.r3944realms.lib39.base.datagen.value.Lib39LangKey;
|
||||
import top.r3944realms.lib39.datagen.provider.SimpleLanguageProvider;
|
||||
import top.r3944realms.lib39.datagen.value.McLocale;
|
||||
|
|
@ -28,9 +30,9 @@ public class Lib39BaseDataGenEvent {
|
|||
LanguageGenerator(event, McLocale.ZH_CN);
|
||||
LanguageGenerator(event, McLocale.ZH_TW);
|
||||
LanguageGenerator(event, McLocale.LZH);
|
||||
if (Lib39.shouldRegisterExamples()) {
|
||||
ModelDataGenerate(event);
|
||||
}
|
||||
BlockModelDataGenerate(event);
|
||||
BlockStateDataGenerate(event);
|
||||
ItemModelDataGenerate(event);
|
||||
}
|
||||
private static void LanguageGenerator(@NotNull GatherDataEvent event, McLocale language) {
|
||||
event.getGenerator().addProvider(
|
||||
|
|
@ -38,10 +40,22 @@ public class Lib39BaseDataGenEvent {
|
|||
(DataProvider.Factory<SimpleLanguageProvider>) pOutput -> new SimpleLanguageProvider(pOutput, Lib39.MOD_ID ,language , Lib39LangKey.INSTANCE)
|
||||
);
|
||||
}
|
||||
private static void ModelDataGenerate(@NotNull GatherDataEvent event) {
|
||||
private static void ItemModelDataGenerate(@NotNull GatherDataEvent event) {
|
||||
event.getGenerator().addProvider(
|
||||
event.includeClient(),
|
||||
(DataProvider.Factory<ExItemModelProvider>) pOutput -> new ExItemModelProvider(pOutput, event.getExistingFileHelper())
|
||||
(DataProvider.Factory<Lib39ItemModelProvider>) pOutput -> new Lib39ItemModelProvider(pOutput, event.getExistingFileHelper())
|
||||
);
|
||||
}
|
||||
private static void BlockModelDataGenerate(@NotNull GatherDataEvent event) {
|
||||
event.getGenerator().addProvider(
|
||||
event.includeClient(),
|
||||
(DataProvider.Factory<Lib39BlockModelProvider>) pOutput -> new Lib39BlockModelProvider(pOutput, event.getExistingFileHelper())
|
||||
);
|
||||
}
|
||||
private static void BlockStateDataGenerate(@NotNull GatherDataEvent event) {
|
||||
event.getGenerator().addProvider(
|
||||
event.includeClient(),
|
||||
(DataProvider.Factory<Lib39BlockStatesProvider>) pOutput -> new Lib39BlockStatesProvider(pOutput, event.getExistingFileHelper())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -18,11 +18,9 @@ package top.r3944realms.lib39.base.datagen.provider;
|
|||
import net.minecraft.data.PackOutput;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraftforge.client.model.generators.ItemModelProvider;
|
||||
import net.minecraftforge.common.data.ExistingFileHelper;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
import top.r3944realms.lib39.Lib39;
|
||||
import top.r3944realms.lib39.base.datagen.Lib39BaseDataGenEvent;
|
||||
import top.r3944realms.lib39.base.datagen.value.Lib39LangKey;
|
||||
import top.r3944realms.lib39.datagen.value.LangKeyValue;
|
||||
import top.r3944realms.lib39.datagen.value.ModPartEnum;
|
||||
|
|
@ -32,9 +30,9 @@ import java.util.List;
|
|||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* The type Slp item model provider.
|
||||
* The type item model provider.
|
||||
*/
|
||||
public class ExItemModelProvider extends ItemModelProvider {
|
||||
public class Lib39ItemModelProvider extends net.minecraftforge.client.model.generators.ItemModelProvider {
|
||||
private static List<Item> objectList;
|
||||
/**
|
||||
* The constant GENERATED.
|
||||
|
|
@ -46,12 +44,12 @@ public class ExItemModelProvider extends ItemModelProvider {
|
|||
public static final String HANDHELD = "item/handheld";
|
||||
|
||||
/**
|
||||
* Instantiates a new Slp item model provider.
|
||||
* Instantiates a new item model provider.
|
||||
*
|
||||
* @param output the output
|
||||
* @param existingFileHelper the existing file helper
|
||||
*/
|
||||
public ExItemModelProvider(PackOutput output, ExistingFileHelper existingFileHelper) {
|
||||
public Lib39ItemModelProvider(PackOutput output, ExistingFileHelper existingFileHelper) {
|
||||
super(output, Lib39.MOD_ID, existingFileHelper);
|
||||
objectList = new ArrayList<>();
|
||||
init();
|
||||
|
|
@ -59,7 +57,8 @@ public class ExItemModelProvider extends ItemModelProvider {
|
|||
|
||||
@Override
|
||||
protected void registerModels() {
|
||||
DefaultModItemModelRegister();
|
||||
defaultModItemModelRegister();
|
||||
// generateDollItemModel();
|
||||
}
|
||||
private void init() {
|
||||
for(LangKeyValue obj : Lib39LangKey.INSTANCE.getValues()) {
|
||||
|
|
@ -70,7 +69,7 @@ public class ExItemModelProvider extends ItemModelProvider {
|
|||
/**
|
||||
* @implNote <br/> 先有纹理才会成功构建
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
|
@ -31,6 +31,17 @@ public enum Lib39LangKey implements ILangKeyValueCollection {
|
|||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue HELP_CLICK_EXPAND = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.help.click_expand", ModPartEnum.MESSAGE,
|
||||
"Click to expand",
|
||||
"點擊展開",
|
||||
"點擊展開",
|
||||
"點展",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue HELP_PAGE_INFO = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.help.page_info", ModPartEnum.MESSAGE,
|
||||
|
|
@ -129,6 +140,17 @@ public enum Lib39LangKey implements ILangKeyValueCollection {
|
|||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue HELP_HOVER_COPY_TIP = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.help.hover.copy", ModPartEnum.MESSAGE,
|
||||
"Copy to clipboard",
|
||||
"点击复制",
|
||||
"點擊複製",
|
||||
"點之複刻", // 文言文:點之複刻
|
||||
true
|
||||
)
|
||||
);
|
||||
private static LangKeyValue addAndRet(LangKeyValue item) {
|
||||
items.add(item);
|
||||
return item;
|
||||
|
|
@ -161,6 +183,7 @@ public enum Lib39LangKey implements ILangKeyValueCollection {
|
|||
ExLib39Items.FORGE, ModPartEnum.ITEM,
|
||||
"Forge", "铁砧", "铁砧", "砧", true
|
||||
));
|
||||
TestMessage.getItems().forEach(this::addLang);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -187,5 +210,450 @@ public enum Lib39LangKey implements ILangKeyValueCollection {
|
|||
public @Unmodifiable List<LangKeyValue> getValues() {
|
||||
return List.copyOf(langKeyValues);
|
||||
}
|
||||
@SuppressWarnings("unused")
|
||||
public static final class TestMessage {
|
||||
private static final Set<LangKeyValue> items = new HashSet<>();
|
||||
|
||||
// ===== lib39 測試命令翻譯 =====
|
||||
|
||||
// 根命令
|
||||
public static final LangKeyValue LIB39_ROOT = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.root", ModPartEnum.MESSAGE,
|
||||
"Lib39 Command System",
|
||||
"Lib39 命令系統",
|
||||
"Lib39 指令系統",
|
||||
"Lib39 令系",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
// 測試命令
|
||||
public static final LangKeyValue LIB39_TEST = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.test", ModPartEnum.MESSAGE,
|
||||
"Test command",
|
||||
"測試命令",
|
||||
"測試指令",
|
||||
"試令",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_TEST_SUCCESS = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.test.success", ModPartEnum.MESSAGE,
|
||||
"Test command executed successfully!",
|
||||
"測試命令執行成功!",
|
||||
"測試指令執行成功!",
|
||||
"試令行成!",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_TEST_WITH_PARAM = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.test.with_param", ModPartEnum.MESSAGE,
|
||||
"Test command with parameter: %s",
|
||||
"帶參數的測試命令:%s",
|
||||
"帶參數的測試指令:%s",
|
||||
"帶參試令:%s",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_DEMO = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.demo", ModPartEnum.MESSAGE,
|
||||
"Demo command",
|
||||
"演示命令",
|
||||
"演示指令",
|
||||
"演令",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_DEMO_MESSAGE = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.demo.message", ModPartEnum.MESSAGE,
|
||||
"This is a demo command showing Lib39 features!",
|
||||
"這是一個展示 Lib39 功能的演示命令!",
|
||||
"這是一個展示 Lib39 功能的演示指令!",
|
||||
"此乃展 Lib39 能之演令!",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
// 問候命令
|
||||
public static final LangKeyValue LIB39_GREET_BASIC = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.greet.basic", ModPartEnum.MESSAGE,
|
||||
"Greet everyone",
|
||||
"向大家問好",
|
||||
"向大家問好",
|
||||
"問眾安",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_GREET_DEFAULT = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.greet.default", ModPartEnum.MESSAGE,
|
||||
"Hello everyone from Lib39!",
|
||||
"來自 Lib39 的問候!",
|
||||
"來自 Lib39 的問候!",
|
||||
"自 Lib39 問安!",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_GREET_PLAYER = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.greet.player", ModPartEnum.MESSAGE,
|
||||
"Greet specific player",
|
||||
"問候特定玩家",
|
||||
"問候特定玩家",
|
||||
"問特者安",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_GREET_RECEIVED = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.greet.received", ModPartEnum.MESSAGE,
|
||||
"%s greeted you!",
|
||||
"%s 向你問好!",
|
||||
"%s 向你問好!",
|
||||
"%s 問汝安!",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
// 計算命令
|
||||
public static final LangKeyValue LIB39_CALCULATE = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.calculate", ModPartEnum.MESSAGE,
|
||||
"Calculate sum of two numbers",
|
||||
"計算兩個數字的和",
|
||||
"計算兩個數字的和",
|
||||
"算二數和",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_CALCULATE_RESULT = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.calculate.result", ModPartEnum.MESSAGE,
|
||||
"%d + %d = %d",
|
||||
"%d + %d = %d",
|
||||
"%d + %d = %d",
|
||||
"%d 加 %d 等 %d",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
// 傳送命令
|
||||
public static final LangKeyValue LIB39_TELEPORT = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.teleport", ModPartEnum.MESSAGE,
|
||||
"Teleport to player (OP only)",
|
||||
"傳送到玩家(僅OP)",
|
||||
"傳送到玩家(僅OP)",
|
||||
"送至者(唯管)",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_TELEPORT_SUCCESS = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.teleport.success", ModPartEnum.MESSAGE,
|
||||
"Teleported to %s",
|
||||
"已傳送至 %s",
|
||||
"已傳送至 %s",
|
||||
"已送至 %s",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
// 信息命令
|
||||
public static final LangKeyValue LIB39_INFO = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.info", ModPartEnum.MESSAGE,
|
||||
"Show player information",
|
||||
"顯示玩家信息",
|
||||
"顯示玩家資訊",
|
||||
"示者訊",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_INFO_MESSAGE = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.info.message", ModPartEnum.MESSAGE,
|
||||
"=== Player Information ===",
|
||||
"=== 玩家信息 ===",
|
||||
"=== 玩家資訊 ===",
|
||||
"〇〇 者訊 〇〇",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_INFO_DIMENSION = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.info.dimension", ModPartEnum.MESSAGE,
|
||||
"Dimension: %s",
|
||||
"維度:%s",
|
||||
"維度:%s",
|
||||
"界:%s",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_INFO_POSITION = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.info.position", ModPartEnum.MESSAGE,
|
||||
"Position: X=%.1f, Y=%.1f, Z=%.1f",
|
||||
"位置:X=%.1f, Y=%.1f, Z=%.1f",
|
||||
"位置:X=%.1f, Y=%.1f, Z=%.1f",
|
||||
"位:X=%.1f, Y=%.1f, Z=%.1f",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
// 隊伍系統
|
||||
public static final LangKeyValue LIB39_TEAM = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.team", ModPartEnum.MESSAGE,
|
||||
"Team management",
|
||||
"隊伍管理",
|
||||
"隊伍管理",
|
||||
"隊管",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_TEAM_CREATE = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.team.create", ModPartEnum.MESSAGE,
|
||||
"Create a new team",
|
||||
"創建新隊伍",
|
||||
"創建新隊伍",
|
||||
"創新隊",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_TEAM_CREATE_SUCCESS = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.team.create.success", ModPartEnum.MESSAGE,
|
||||
"Team '%s' created successfully!",
|
||||
"隊伍 '%s' 創建成功!",
|
||||
"隊伍 '%s' 創建成功!",
|
||||
"隊 '%s' 創新成!",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_TEAM_JOIN = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.team.join", ModPartEnum.MESSAGE,
|
||||
"Join a team",
|
||||
"加入隊伍",
|
||||
"加入隊伍",
|
||||
"入隊",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_TEAM_JOIN_SUCCESS = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.team.join.success", ModPartEnum.MESSAGE,
|
||||
"Joined team '%s'",
|
||||
"已加入隊伍 '%s'",
|
||||
"已加入隊伍 '%s'",
|
||||
"已入隊 '%s'",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_TEAM_LEAVE = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.team.leave", ModPartEnum.MESSAGE,
|
||||
"Leave current team",
|
||||
"離開當前隊伍",
|
||||
"離開當前隊伍",
|
||||
"離現隊",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_TEAM_LEAVE_SUCCESS = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.team.leave.success", ModPartEnum.MESSAGE,
|
||||
"Left the team",
|
||||
"已離開隊伍",
|
||||
"已離開隊伍",
|
||||
"已離隊",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
// 遊戲系統
|
||||
public static final LangKeyValue LIB39_GAME = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.game", ModPartEnum.MESSAGE,
|
||||
"Game control",
|
||||
"遊戲控制",
|
||||
"遊戲控制",
|
||||
"戲控",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_GAME_START = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.game.start", ModPartEnum.MESSAGE,
|
||||
"Start a game",
|
||||
"開始遊戲",
|
||||
"開始遊戲",
|
||||
"啟戲",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_GAME_START_SUCCESS = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.game.start.success", ModPartEnum.MESSAGE,
|
||||
"Game '%s' started!",
|
||||
"遊戲 '%s' 已開始!",
|
||||
"遊戲 '%s' 已開始!",
|
||||
"戲 '%s' 已啟!",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_GAME_STOP = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.game.stop", ModPartEnum.MESSAGE,
|
||||
"Stop current game",
|
||||
"停止當前遊戲",
|
||||
"停止當前遊戲",
|
||||
"止現戲",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_GAME_STOP_SUCCESS = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.game.stop.success", ModPartEnum.MESSAGE,
|
||||
"Game stopped!",
|
||||
"遊戲已停止!",
|
||||
"遊戲已停止!",
|
||||
"戲已止!",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_GAME_PAUSE = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.game.pause", ModPartEnum.MESSAGE,
|
||||
"Pause current game",
|
||||
"暫停當前遊戲",
|
||||
"暫停當前遊戲",
|
||||
"暫現戲",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_GAME_PAUSE_SUCCESS = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.game.pause.success", ModPartEnum.MESSAGE,
|
||||
"Game paused!",
|
||||
"遊戲已暫停!",
|
||||
"遊戲已暫停!",
|
||||
"戲已暫!",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_GAME_RESUME = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.game.resume", ModPartEnum.MESSAGE,
|
||||
"Resume paused game",
|
||||
"恢復暫停的遊戲",
|
||||
"恢復暫停的遊戲",
|
||||
"復暫戲",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_GAME_RESUME_SUCCESS = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.game.resume.success", ModPartEnum.MESSAGE,
|
||||
"Game resumed!",
|
||||
"遊戲已恢復!",
|
||||
"遊戲已恢復!",
|
||||
"戲已復!",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
// 設置命令
|
||||
public static final LangKeyValue LIB39_SETTINGS = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.settings", ModPartEnum.MESSAGE,
|
||||
"Show settings",
|
||||
"顯示設置",
|
||||
"顯示設定",
|
||||
"示置",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_CONFIG = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.config", ModPartEnum.MESSAGE,
|
||||
"Show configuration",
|
||||
"顯示配置",
|
||||
"顯示設定",
|
||||
"示配",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_RELOAD = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.reload", ModPartEnum.MESSAGE,
|
||||
"Reload configuration",
|
||||
"重新加載配置",
|
||||
"重新載入設定",
|
||||
"重載配",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
public static final LangKeyValue LIB39_DEBUG = addAndRet(
|
||||
LangKeyValue.ofKey(
|
||||
"commands.lib39.debug", ModPartEnum.MESSAGE,
|
||||
"Debug information",
|
||||
"調試信息",
|
||||
"除錯資訊",
|
||||
"調訊",
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
// ===== 添加缺失的導入 =====
|
||||
private static final java.util.function.Consumer<LangKeyValue> addConsumer = items::add;
|
||||
|
||||
private static LangKeyValue addAndRet(LangKeyValue item) {
|
||||
items.add(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
public static Set<LangKeyValue> getItems() {
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
package top.r3944realms.lib39.client.renderer.block;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraftforge.client.model.data.ModelData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import top.r3944realms.lib39.Lib39;
|
||||
import top.r3944realms.lib39.client.renderer.item.DollItemRenderer;
|
||||
import top.r3944realms.lib39.content.block.blockentity.DollBlockEntity;
|
||||
import top.r3944realms.lib39.util.PlantHelper;
|
||||
import top.r3944realms.lib39.util.SkinHelper;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class DollBlockEntityRenderer implements BlockEntityRenderer<DollBlockEntity> {
|
||||
public BakedModel dollWithoutItemModel;
|
||||
public BakedModel dollNeedItemModel;
|
||||
public final Map<PlantHelper.Plant, BakedModel> itemModels = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void render(@NotNull DollBlockEntity blockEntity, float partialTick, @NotNull PoseStack poseStack,
|
||||
@NotNull MultiBufferSource buffer, int packedLight, int packedOverlay) {
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
ModelManager modelManager = minecraft.getModelManager();
|
||||
if (dollNeedItemModel == null) {
|
||||
dollNeedItemModel = modelManager.getModel(DollItemRenderer.DOLL_WITHOUT_ITEM_MODEL);
|
||||
}
|
||||
if (dollWithoutItemModel == null) {
|
||||
dollWithoutItemModel = modelManager.getModel(DollItemRenderer.DOLL_NEED_ITEM_MODEL);
|
||||
}
|
||||
|
||||
PlantHelper.Plant holdItem = blockEntity.getHoldItem();
|
||||
renderModel(blockEntity, poseStack, buffer, packedLight, packedOverlay, holdItem != null ? dollNeedItemModel : dollWithoutItemModel, SkinHelper.getSkinTexture(blockEntity.getOwnerProfile()));
|
||||
if (holdItem != null) {
|
||||
BakedModel itemModel = itemModels.get(holdItem);
|
||||
if (itemModel == null) {
|
||||
BakedModel model = modelManager.getModel(Lib39.rl( "block/doll_item/" + holdItem));
|
||||
itemModels.put(holdItem, model);
|
||||
itemModel = model;
|
||||
}
|
||||
renderModel(blockEntity, poseStack, buffer, packedLight, packedOverlay, itemModel, PlantHelper.getTextureRL(holdItem));
|
||||
}
|
||||
}
|
||||
|
||||
private void renderModel(@NotNull DollBlockEntity blockEntity, @NotNull PoseStack poseStack, @NotNull MultiBufferSource buffer,
|
||||
int packedLight, int packedOverlay,
|
||||
BakedModel model, ResourceLocation texture) {
|
||||
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(0.5, 0.5, 0.5);
|
||||
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
RandomSource rand = RandomSource.create();
|
||||
ModelData data = ModelData.EMPTY;
|
||||
|
||||
for (RenderType rt : model.getRenderTypes(blockEntity.getBlockState(), rand, data)) {
|
||||
VertexConsumer vc = buffer.getBuffer(rt);
|
||||
// 渲染所有面
|
||||
minecraft.getBlockRenderer().getModelRenderer().renderModel(
|
||||
poseStack.last(), vc, null, model,
|
||||
1.0f, 1.0f, 1.0f,
|
||||
packedLight, packedOverlay,
|
||||
data, rt
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
poseStack.popPose();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,422 @@
|
|||
package top.r3944realms.lib39.client.renderer.item;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite;
|
||||
import net.minecraft.client.renderer.texture.SimpleTexture;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.client.resources.DefaultPlayerSkin;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtUtils;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.client.model.data.ModelData;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
import top.r3944realms.lib39.Lib39;
|
||||
import top.r3944realms.lib39.util.PlantHelper;
|
||||
import top.r3944realms.lib39.util.SkinHelper;
|
||||
import top.r3944realms.lib39.util.nbt.NBTReader;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class DollItemRenderer extends BlockEntityWithoutLevelRenderer {
|
||||
private static final DollItemRenderer INSTANCE = new DollItemRenderer();
|
||||
public static final String BETag = "BlockEntityTag";
|
||||
// 模型缓存系统
|
||||
private static class ModelCacheEntry {
|
||||
private final ResourceLocation modelLocation;
|
||||
private BakedModel model;
|
||||
private long lastAccessTime;
|
||||
private boolean isMissingModel;
|
||||
|
||||
ModelCacheEntry(ResourceLocation modelLocation) {
|
||||
this.modelLocation = modelLocation;
|
||||
this.lastAccessTime = System.currentTimeMillis();
|
||||
this.isMissingModel = false;
|
||||
}
|
||||
|
||||
BakedModel getModel() {
|
||||
if (model == null && !isMissingModel) {
|
||||
loadModel();
|
||||
}
|
||||
this.lastAccessTime = System.currentTimeMillis();
|
||||
return model;
|
||||
}
|
||||
|
||||
private void loadModel() {
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
ModelManager modelManager = minecraft.getModelManager();
|
||||
this.model = modelManager.getModel(modelLocation);
|
||||
|
||||
// 检查是否是缺失模型
|
||||
BakedModel missingModel = modelManager.getMissingModel();
|
||||
this.isMissingModel = this.model == missingModel;
|
||||
|
||||
if (isMissingModel) {
|
||||
Lib39.LOGGER.warn("Missing model: {}", modelLocation);
|
||||
}
|
||||
}
|
||||
|
||||
boolean shouldRefresh() {
|
||||
return model == null || (!isMissingModel && System.currentTimeMillis() - lastAccessTime > CACHE_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存管理器
|
||||
private static class ModelCacheManager {
|
||||
private final Map<ResourceLocation, ModelCacheEntry> cache = new ConcurrentHashMap<>();
|
||||
private final Set<ResourceLocation> pendingReloads = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
private static final long CLEANUP_INTERVAL = 30000; // 30秒清理一次
|
||||
private long lastCleanupTime = 0;
|
||||
|
||||
BakedModel getModel(ResourceLocation location) {
|
||||
ModelCacheEntry entry = cache.computeIfAbsent(location, ModelCacheEntry::new);
|
||||
|
||||
// 定期检查是否需要刷新
|
||||
if (entry.shouldRefresh() && !pendingReloads.contains(location)) {
|
||||
scheduleModelReload(location);
|
||||
}
|
||||
|
||||
return entry.getModel();
|
||||
}
|
||||
|
||||
private void scheduleModelReload(ResourceLocation location) {
|
||||
pendingReloads.add(location);
|
||||
|
||||
// 异步重新加载模型
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
ModelCacheEntry entry = cache.get(location);
|
||||
if (entry != null) {
|
||||
entry.loadModel();
|
||||
}
|
||||
} finally {
|
||||
pendingReloads.remove(location);
|
||||
}
|
||||
}, Util.backgroundExecutor());
|
||||
}
|
||||
|
||||
void clear() {
|
||||
cache.clear();
|
||||
pendingReloads.clear();
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - lastCleanupTime > CLEANUP_INTERVAL) {
|
||||
cache.entrySet().removeIf(entry ->
|
||||
entry.getValue().model == null ||
|
||||
(entry.getValue().isMissingModel &&
|
||||
now - entry.getValue().lastAccessTime > CACHE_DURATION * 2)
|
||||
);
|
||||
lastCleanupTime = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 纹理缓存系统
|
||||
private static class TextureCacheEntry {
|
||||
private final ResourceLocation textureLocation;
|
||||
private ResourceLocation registeredLocation;
|
||||
private long lastAccessTime;
|
||||
|
||||
TextureCacheEntry(ResourceLocation textureLocation) {
|
||||
this.textureLocation = textureLocation;
|
||||
this.lastAccessTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
ResourceLocation getTexture() {
|
||||
if (registeredLocation == null) {
|
||||
registerTexture();
|
||||
}
|
||||
this.lastAccessTime = System.currentTimeMillis();
|
||||
return registeredLocation;
|
||||
}
|
||||
|
||||
private void registerTexture() {
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
TextureManager textureManager = minecraft.getTextureManager();
|
||||
|
||||
// 检查纹理是否已加载
|
||||
textureManager.getTexture(textureLocation, MissingTextureAtlasSprite.getTexture());
|
||||
registeredLocation = textureLocation;
|
||||
}
|
||||
|
||||
private void loadTextureAsync() {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
TextureManager textureManager = minecraft.getTextureManager();
|
||||
|
||||
// 加载纹理
|
||||
textureManager.register(textureLocation,
|
||||
new SimpleTexture(textureLocation));
|
||||
registeredLocation = textureLocation;
|
||||
} catch (Exception e) {
|
||||
Lib39.LOGGER.error("Failed to load texture: {}", textureLocation, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
boolean shouldRefresh() {
|
||||
return registeredLocation == null ||
|
||||
System.currentTimeMillis() - lastAccessTime > CACHE_DURATION;
|
||||
}
|
||||
}
|
||||
|
||||
// 常量定义
|
||||
private static final long CACHE_DURATION = 5000; // 5秒
|
||||
private static final long ITEM_TEXTURE_CACHE_DURATION = 30000; // 30秒(材质不常变)
|
||||
public static final ResourceLocation DOLL_NEED_ITEM_MODEL = Lib39.rl("block/doll_default");
|
||||
public static final ResourceLocation DOLL_WITHOUT_ITEM_MODEL = Lib39.rl("block/doll_without_item");
|
||||
public static final ResourceLocation DOLL_ITEM_MODEL = Lib39.rl("block/base_doll_item");
|
||||
public static final Map<PlantHelper.Plant, ResourceLocation> ITEM_MODELS = new HashMap<>();
|
||||
static {
|
||||
initItemModels();
|
||||
}
|
||||
public static void initItemModels() {
|
||||
for (PlantHelper.Plant plant : PlantHelper.Plant.values()) {
|
||||
ITEM_MODELS.put(plant, Lib39.rl("block/doll_item/" + plant));
|
||||
}
|
||||
}
|
||||
// 缓存实例
|
||||
private final ModelCacheManager modelCache = new ModelCacheManager();
|
||||
private final Map<String, TextureCacheEntry> itemTextureCache = new ConcurrentHashMap<>();
|
||||
private final Map<UUID, TextureCacheEntry> skinTextureCache = new ConcurrentHashMap<>();
|
||||
private final TextureCacheEntry defaultSkinTexture;
|
||||
|
||||
// 花材质映射(静态初始化)
|
||||
private static final Map<String, ResourceLocation> ITEM_TEXTURE_MAP = createItemTextureMap();
|
||||
|
||||
private static @Unmodifiable Map<String, ResourceLocation> createItemTextureMap() {
|
||||
Map<String, ResourceLocation> map = new HashMap<>();
|
||||
for (PlantHelper.Plant value : PlantHelper.Plant.values()) {
|
||||
map.put(value.toString(), new ResourceLocation(value.name));
|
||||
}
|
||||
return Map.copyOf(map);
|
||||
}
|
||||
|
||||
private DollItemRenderer() {
|
||||
super(Minecraft.getInstance().getBlockEntityRenderDispatcher(),
|
||||
Minecraft.getInstance().getEntityModels());
|
||||
|
||||
// 初始化默认皮肤纹理
|
||||
this.defaultSkinTexture = new TextureCacheEntry(
|
||||
DefaultPlayerSkin.getDefaultSkin()
|
||||
);
|
||||
}
|
||||
|
||||
public static DollItemRenderer getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderByItem(@NotNull ItemStack stack, @NotNull ItemDisplayContext displayContext,
|
||||
@NotNull PoseStack poseStack, @NotNull MultiBufferSource buffer,
|
||||
int packedLight, int packedOverlay) {
|
||||
|
||||
// 1. 从NBT获取数据
|
||||
DollRenderData renderData = extractRenderData(stack);
|
||||
|
||||
// 2. 执行渲染
|
||||
renderDoll(renderData, displayContext, poseStack, buffer, packedLight, packedOverlay);
|
||||
|
||||
// 3. 定期清理缓存
|
||||
modelCache.cleanup();
|
||||
cleanupTextureCache();
|
||||
}
|
||||
|
||||
// 提取渲染数据
|
||||
@Contract("_ -> new")
|
||||
private @NotNull DollRenderData extractRenderData(@NotNull ItemStack stack) {
|
||||
AtomicReference<String> itemType = new AtomicReference<>();
|
||||
AtomicReference<GameProfile> gameProfile = new AtomicReference<>();
|
||||
CompoundTag tag = stack.getTag();
|
||||
if (tag != null) {
|
||||
NBTReader.of(tag)
|
||||
.compound(BETag, compoundTag -> NBTReader.of(compoundTag)
|
||||
.string("ItemType", itemType::set)
|
||||
.compound("ProfileOwner", profile -> gameProfile.set(NbtUtils.readGameProfile(profile)))
|
||||
);
|
||||
}
|
||||
return new DollRenderData(itemType.get(), gameProfile.get());
|
||||
}
|
||||
|
||||
private void renderDoll(@NotNull DollRenderData data, ItemDisplayContext displayContext,
|
||||
@NotNull PoseStack poseStack, MultiBufferSource buffer,
|
||||
int packedLight, int packedOverlay) {
|
||||
|
||||
// 总是渲染基础人偶模型
|
||||
BakedModel baseModel = data.hasItem() ? getCachedModel(DOLL_NEED_ITEM_MODEL) : getCachedModel(DOLL_WITHOUT_ITEM_MODEL);
|
||||
|
||||
if (baseModel == null || baseModel == Minecraft.getInstance().getModelManager().getMissingModel()) {
|
||||
return; // 模型不存在,不渲染
|
||||
}
|
||||
|
||||
// 获取纹理
|
||||
ResourceLocation skinTexture = getSkinTexture(data.gameProfile);
|
||||
|
||||
// 开始渲染
|
||||
poseStack.pushPose();
|
||||
|
||||
try {
|
||||
// 应用物品变换
|
||||
applyItemTransforms(displayContext, poseStack);
|
||||
|
||||
// 渲染基础模型(皮肤部分)
|
||||
renderModelWithTexture(baseModel, skinTexture, poseStack, buffer,
|
||||
packedLight, packedOverlay);
|
||||
|
||||
// 如果有手持物品,渲染物品部分
|
||||
if (data.hasItem()) {
|
||||
renderHeldItem(data, poseStack, buffer, packedLight, packedOverlay);
|
||||
}
|
||||
} finally {
|
||||
poseStack.popPose();
|
||||
}
|
||||
}
|
||||
|
||||
private void renderHeldItem(DollRenderData data, PoseStack poseStack,
|
||||
MultiBufferSource buffer, int packedLight, int packedOverlay) {
|
||||
BakedModel itemModel = getCachedModel(ITEM_MODELS.get(PlantHelper.Plant.valueOf(data.itemType)));
|
||||
if (itemModel == null || itemModel == Minecraft.getInstance().getModelManager().getMissingModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ResourceLocation itemTexture = getItemTexture(data.itemType);
|
||||
if (itemTexture == null) return;
|
||||
poseStack.pushPose();
|
||||
try {
|
||||
renderModelWithTexture(itemModel, itemTexture, poseStack, buffer,
|
||||
packedLight, packedOverlay);
|
||||
} finally {
|
||||
poseStack.popPose();
|
||||
}
|
||||
}
|
||||
|
||||
// 获取缓存的模型
|
||||
private BakedModel getCachedModel(ResourceLocation modelLoc) {
|
||||
return modelCache.getModel(modelLoc);
|
||||
}
|
||||
|
||||
// 获取皮肤纹理(带缓存)
|
||||
public ResourceLocation getSkinTexture(@Nullable GameProfile gameProfile) {
|
||||
if (gameProfile == null) {
|
||||
return defaultSkinTexture.getTexture();
|
||||
}
|
||||
|
||||
// 优先使用UUID
|
||||
UUID cacheKey = gameProfile.getId();
|
||||
TextureCacheEntry entry = skinTextureCache.get(cacheKey);
|
||||
|
||||
if (entry == null || entry.shouldRefresh()) {
|
||||
ResourceLocation textureLocation = SkinHelper.resolveSkinTexture(gameProfile);
|
||||
entry = new TextureCacheEntry(textureLocation);
|
||||
skinTextureCache.put(cacheKey, entry);
|
||||
}
|
||||
|
||||
return entry.getTexture();
|
||||
}
|
||||
|
||||
// 获取花纹理(带缓存)
|
||||
private ResourceLocation getItemTexture(String itemType) {
|
||||
ResourceLocation textureLocation = ITEM_TEXTURE_MAP.getOrDefault(itemType,
|
||||
PlantHelper.getTextureRL(PlantHelper.Plant.ALLIUM));
|
||||
|
||||
TextureCacheEntry entry = itemTextureCache.get(itemType);
|
||||
if (entry == null || entry.shouldRefresh()) {
|
||||
entry = new TextureCacheEntry(textureLocation);
|
||||
itemTextureCache.put(itemType, entry);
|
||||
}
|
||||
|
||||
return entry.getTexture();
|
||||
}
|
||||
|
||||
// 应用物品变换
|
||||
@SuppressWarnings("deprecation")
|
||||
private void applyItemTransforms(ItemDisplayContext displayContext, PoseStack poseStack) {
|
||||
BakedModel baseModel = getCachedModel(DOLL_WITHOUT_ITEM_MODEL);
|
||||
if (baseModel != null) {
|
||||
baseModel.getTransforms().getTransform(displayContext)
|
||||
.apply(false, poseStack);
|
||||
}
|
||||
poseStack.translate(-0.5, -0.5, -0.5);
|
||||
}
|
||||
|
||||
// 渲染模型带纹理
|
||||
private void renderModelWithTexture(BakedModel model, ResourceLocation texture,
|
||||
@NotNull PoseStack poseStack, @NotNull MultiBufferSource buffer,
|
||||
int packedLight, int packedOverlay) {
|
||||
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
RenderType renderType = RenderType.entityCutout(texture);
|
||||
VertexConsumer vertexConsumer = buffer.getBuffer(renderType);
|
||||
|
||||
minecraft.getBlockRenderer().getModelRenderer().renderModel(
|
||||
poseStack.last(),
|
||||
vertexConsumer,
|
||||
null,
|
||||
model,
|
||||
1.0f, 1.0f, 1.0f,
|
||||
packedLight,
|
||||
packedOverlay,
|
||||
ModelData.EMPTY,
|
||||
renderType
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// 清理纹理缓存
|
||||
private void cleanupTextureCache() {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
// 清理皮肤缓存(超过2分钟未访问)
|
||||
skinTextureCache.entrySet().removeIf(entry ->
|
||||
now - entry.getValue().lastAccessTime > 120000
|
||||
);
|
||||
|
||||
// 清理花纹理缓存(超过5分钟未访问)
|
||||
itemTextureCache.entrySet().removeIf(entry ->
|
||||
now - entry.getValue().lastAccessTime > ITEM_TEXTURE_CACHE_DURATION
|
||||
);
|
||||
}
|
||||
|
||||
// 清除所有缓存(用于资源包重载)
|
||||
public void clearAllCaches() {
|
||||
modelCache.clear();
|
||||
skinTextureCache.clear();
|
||||
itemTextureCache.clear();
|
||||
}
|
||||
|
||||
// 渲染数据持有类
|
||||
private record DollRenderData(String itemType, GameProfile gameProfile) {
|
||||
public boolean hasItem() {
|
||||
return itemType != null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResourceManagerReload(@NotNull ResourceManager resourceManager) {
|
||||
super.onResourceManagerReload(resourceManager);
|
||||
clearAllCaches();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user