更新内容
1. 加解密ClassLoader 2. 帮助指令管理器
This commit is contained in:
parent
2b0a14d0e9
commit
f062be7f51
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -5,6 +5,7 @@ bin
|
||||||
.metadata
|
.metadata
|
||||||
.classpath
|
.classpath
|
||||||
.project
|
.project
|
||||||
|
cpp/cmake-build-debug
|
||||||
|
|
||||||
# idea
|
# idea
|
||||||
out
|
out
|
||||||
|
|
|
||||||
18
build.gradle
18
build.gradle
|
|
@ -9,6 +9,8 @@ plugins {
|
||||||
id 'com.dorongold.task-tree' version '2.1.1'
|
id 'com.dorongold.task-tree' version '2.1.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply from: 'gradle/jni-heads.gradle'
|
||||||
|
|
||||||
java {
|
java {
|
||||||
toolchain.languageVersion = JavaLanguageVersion.of(17)
|
toolchain.languageVersion = JavaLanguageVersion.of(17)
|
||||||
}
|
}
|
||||||
|
|
@ -103,6 +105,11 @@ obfuscation {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
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'
|
implementation 'org.joml:joml:1.10.5'
|
||||||
// 单元测试依赖
|
// 单元测试依赖
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.3'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.3'
|
||||||
|
|
@ -119,6 +126,13 @@ dependencies {
|
||||||
// 测试工具
|
// 测试工具
|
||||||
testImplementation 'org.hamcrest:hamcrest:2.2'
|
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 {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
|
|
@ -212,7 +226,8 @@ tasks.register('deobfJar', Jar) {
|
||||||
'Implementation-Title': project.name,
|
'Implementation-Title': project.name,
|
||||||
'Implementation-Version': archiveVersion,
|
'Implementation-Version': archiveVersion,
|
||||||
'Implementation-Vendor': mod_authors,
|
'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-Version' : archiveVersion,
|
||||||
'Implementation-Vendor' : mod_authors,
|
'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"
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
dependsOn classes
|
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
|
// 1.20.1 2025-12-08T02:17:25.3481161 Languages: zh_tw
|
||||||
2167da412113ad2aa18c3e8a9a53480f055e7661 assets/lib39/lang/zh_tw.json
|
53425c42eb07613ff9575cf3562ae0b6c06d801c assets/lib39/lang/zh_tw.json
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
// 1.20.1 2025-12-08T00:17:05.2366865 Languages: zh_cn
|
// 1.20.1 2025-12-08T02:17:25.3288872 Languages: zh_cn
|
||||||
f8dfabafd006b9552df1398d234eb8d23913b8e5 assets/lib39/lang/zh_cn.json
|
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
|
14f581c8f8e7f0de004c57a180f371e60e7b12ae assets/lib39/models/item/fabric.json
|
||||||
70583055336790fc837836ea6b49d16cfc8b64b8 assets/lib39/models/item/forge.json
|
70583055336790fc837836ea6b49d16cfc8b64b8 assets/lib39/models/item/forge.json
|
||||||
447b36747d0aa8748dcd86715f4cce2cff19aca7 assets/lib39/models/item/neoforge.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
|
// 1.20.1 2025-12-08T02:17:25.3461162 Languages: lzh
|
||||||
e098d6e171f79cbb5e41a51547abcad520f70edb assets/lib39/lang/lzh.json
|
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
|
// 1.20.1 2025-12-08T02:17:25.3441085 Languages: en_us
|
||||||
96726aafcb2b56b6610f7226299b4442132b8f22 assets/lib39/lang/en_us.json
|
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.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.command_not_found": "Command not found: %s",
|
||||||
"commands.lib39.help.header": "===== %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.no_entries": "No help entries available",
|
||||||
"commands.lib39.help.node.expand": "%d subcommands collapsed",
|
"commands.lib39.help.node.expand": "%d subcommands collapsed",
|
||||||
"commands.lib39.help.node.toggle.collapse": "Collapse",
|
"commands.lib39.help.node.toggle.collapse": "Collapse",
|
||||||
|
|
@ -9,6 +30,25 @@
|
||||||
"commands.lib39.help.page_info": "Page %d of %d",
|
"commands.lib39.help.page_info": "Page %d of %d",
|
||||||
"commands.lib39.help.subcommands_title": "Subcommands:",
|
"commands.lib39.help.subcommands_title": "Subcommands:",
|
||||||
"commands.lib39.help.toggle_failed": "Toggle Failed: No Hash Cached",
|
"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.fabric": "Fabric",
|
||||||
"item.lib39.forge": "Forge",
|
"item.lib39.forge": "Forge",
|
||||||
"item.lib39.neoforge": "NeoForge"
|
"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.basic.help": "示助之訊",
|
||||||
|
"commands.lib39.help.click_expand": "點展",
|
||||||
"commands.lib39.help.command_not_found": "令未見之: %s",
|
"commands.lib39.help.command_not_found": "令未見之: %s",
|
||||||
"commands.lib39.help.header": "〇〇 %s 〇〇",
|
"commands.lib39.help.header": "〇〇 %s 〇〇",
|
||||||
|
"commands.lib39.help.hover.copy": "點之複刻",
|
||||||
"commands.lib39.help.no_entries": "尚無助之目錄",
|
"commands.lib39.help.no_entries": "尚無助之目錄",
|
||||||
"commands.lib39.help.node.expand": "%d 子令已收",
|
"commands.lib39.help.node.expand": "%d 子令已收",
|
||||||
"commands.lib39.help.node.toggle.collapse": "收",
|
"commands.lib39.help.node.toggle.collapse": "收",
|
||||||
|
|
@ -9,6 +30,25 @@
|
||||||
"commands.lib39.help.page_info": "第 %d 卷,凡 %d 卷",
|
"commands.lib39.help.page_info": "第 %d 卷,凡 %d 卷",
|
||||||
"commands.lib39.help.subcommands_title": "子令:",
|
"commands.lib39.help.subcommands_title": "子令:",
|
||||||
"commands.lib39.help.toggle_failed": "變更未果: 無貯存之哈希",
|
"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.fabric": "織",
|
||||||
"item.lib39.forge": "砧",
|
"item.lib39.forge": "砧",
|
||||||
"item.lib39.neoforge": "狸"
|
"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.basic.help": "显示帮助信息",
|
||||||
|
"commands.lib39.help.click_expand": "點擊展開",
|
||||||
"commands.lib39.help.command_not_found": "命令不存在: %s",
|
"commands.lib39.help.command_not_found": "命令不存在: %s",
|
||||||
"commands.lib39.help.header": "===== %s 命令帮助 =====",
|
"commands.lib39.help.header": "===== %s 命令帮助 =====",
|
||||||
|
"commands.lib39.help.hover.copy": "点击复制",
|
||||||
"commands.lib39.help.no_entries": "暂无帮助条目",
|
"commands.lib39.help.no_entries": "暂无帮助条目",
|
||||||
"commands.lib39.help.node.expand": "%d 个子命令已折叠",
|
"commands.lib39.help.node.expand": "%d 个子命令已折叠",
|
||||||
"commands.lib39.help.node.toggle.collapse": "折叠",
|
"commands.lib39.help.node.toggle.collapse": "折叠",
|
||||||
|
|
@ -9,6 +30,25 @@
|
||||||
"commands.lib39.help.page_info": "第 %d 页,共 %d 页",
|
"commands.lib39.help.page_info": "第 %d 页,共 %d 页",
|
||||||
"commands.lib39.help.subcommands_title": "子命令:",
|
"commands.lib39.help.subcommands_title": "子命令:",
|
||||||
"commands.lib39.help.toggle_failed": "切换失败: 无缓存Hash",
|
"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.fabric": "织布",
|
||||||
"item.lib39.forge": "铁砧",
|
"item.lib39.forge": "铁砧",
|
||||||
"item.lib39.neoforge": "小狐狸"
|
"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.basic.help": "顯示幫助信息",
|
||||||
|
"commands.lib39.help.click_expand": "點擊展開",
|
||||||
"commands.lib39.help.command_not_found": "指令不存在: %s",
|
"commands.lib39.help.command_not_found": "指令不存在: %s",
|
||||||
"commands.lib39.help.header": "===== %s 命令幫助 =====",
|
"commands.lib39.help.header": "===== %s 命令幫助 =====",
|
||||||
|
"commands.lib39.help.hover.copy": "點擊複製",
|
||||||
"commands.lib39.help.no_entries": "暫無幫助條目",
|
"commands.lib39.help.no_entries": "暫無幫助條目",
|
||||||
"commands.lib39.help.node.expand": "%d 個子指令已折疊",
|
"commands.lib39.help.node.expand": "%d 個子指令已折疊",
|
||||||
"commands.lib39.help.node.toggle.collapse": "折疊",
|
"commands.lib39.help.node.toggle.collapse": "折疊",
|
||||||
|
|
@ -9,6 +30,25 @@
|
||||||
"commands.lib39.help.page_info": "第 %d 頁,共 %d 頁",
|
"commands.lib39.help.page_info": "第 %d 頁,共 %d 頁",
|
||||||
"commands.lib39.help.subcommands_title": "子指令:",
|
"commands.lib39.help.subcommands_title": "子指令:",
|
||||||
"commands.lib39.help.toggle_failed": "切換失敗: 無緩存Hash",
|
"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.fabric": "織布",
|
||||||
"item.lib39.forge": "铁砧",
|
"item.lib39.forge": "铁砧",
|
||||||
"item.lib39.neoforge": "狐狸"
|
"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() {
|
public static void initialize() {
|
||||||
LOGGER.info("[Lib39] Initializing Lib39");
|
LOGGER.info("[Lib39] Initializing Lib39");
|
||||||
|
// IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
|
||||||
|
// Lib39Blocks.register(modEventBus);
|
||||||
|
// Lib39BlockEntities.register(modEventBus);
|
||||||
|
// Lib39Items.register(modEventBus);
|
||||||
NetworkHandler.register();
|
NetworkHandler.register();
|
||||||
if (shouldRegisterExamples()) {
|
if (shouldRegisterExamples()) {
|
||||||
LOGGER.info("[Lib39] Registering Examples");
|
LOGGER.info("[Lib39] Registering Examples");
|
||||||
|
|
@ -62,6 +66,15 @@ public class Lib39 {
|
||||||
return new ResourceLocation(MOD_ID, path);
|
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.
|
* 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.network.chat.MutableComponent;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraftforge.eventbus.api.Event;
|
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.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 {
|
public class RegisterCommandHelpEvent extends Event {
|
||||||
private final LiteralArgumentBuilder<CommandSourceStack> builder;
|
private final LiteralArgumentBuilder<CommandSourceStack> builder;
|
||||||
|
|
@ -60,7 +60,7 @@ public class RegisterCommandHelpEvent extends Event {
|
||||||
* @param commandPath 命令节点
|
* @param commandPath 命令节点
|
||||||
* @param parametersBuilder 参数列表构造器
|
* @param parametersBuilder 参数列表构造器
|
||||||
*/
|
*/
|
||||||
public void registerParameters(CommandPath commandPath, ParameterBuilder parametersBuilder) {
|
public void registerParameters(CommandPath commandPath, Parameter.Builder parametersBuilder) {
|
||||||
this.helpManager.registerCommandParameters(commandPath, parametersBuilder);
|
this.helpManager.registerCommandParameters(commandPath, parametersBuilder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,11 @@ import top.r3944realms.lib39.core.command.SimpleCommandHelpManager;
|
||||||
public class Lib39CommandHelpManager extends SimpleCommandHelpManager {
|
public class Lib39CommandHelpManager extends SimpleCommandHelpManager {
|
||||||
public static volatile Lib39CommandHelpManager INSTANCE = new Lib39CommandHelpManager();
|
public static volatile Lib39CommandHelpManager INSTANCE = new Lib39CommandHelpManager();
|
||||||
ResourceLocation ID = Lib39.rl("command_helper");
|
ResourceLocation ID = Lib39.rl("command_helper");
|
||||||
|
|
||||||
public Lib39CommandHelpManager() {
|
public Lib39CommandHelpManager() {
|
||||||
initialize();
|
initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResourceLocation getID() {
|
public ResourceLocation getID() {
|
||||||
return ID;
|
return ID;
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,396 @@
|
||||||
package top.r3944realms.lib39.base.command;
|
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 net.minecraftforge.event.RegisterCommandsEvent;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import top.r3944realms.lib39.Lib39;
|
||||||
import top.r3944realms.lib39.core.command.ICommandHelpManager;
|
import top.r3944realms.lib39.core.command.ICommandHelpManager;
|
||||||
import top.r3944realms.lib39.core.command.SimpleHelpCommand;
|
import top.r3944realms.lib39.core.command.SimpleHelpCommand;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class Lib39HelpCommand extends SimpleHelpCommand {
|
public class Lib39HelpCommand extends SimpleHelpCommand {
|
||||||
|
|
||||||
public Lib39HelpCommand(@NotNull RegisterCommandsEvent event) {
|
public Lib39HelpCommand(@NotNull RegisterCommandsEvent event) {
|
||||||
super(event);
|
super(event);
|
||||||
|
if(Lib39.shouldRegisterExamples()) {
|
||||||
|
// 在這裡註冊測試命令
|
||||||
|
registerTestCommands(event.getDispatcher());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ICommandHelpManager getCommandHelpManager() {
|
public ICommandHelpManager getCommandHelpManager() {
|
||||||
return Lib39CommandHelpManager.INSTANCE;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import top.r3944realms.lib39.Lib39;
|
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.base.datagen.value.Lib39LangKey;
|
||||||
import top.r3944realms.lib39.datagen.provider.SimpleLanguageProvider;
|
import top.r3944realms.lib39.datagen.provider.SimpleLanguageProvider;
|
||||||
import top.r3944realms.lib39.datagen.value.McLocale;
|
import top.r3944realms.lib39.datagen.value.McLocale;
|
||||||
|
|
@ -28,9 +30,9 @@ public class Lib39BaseDataGenEvent {
|
||||||
LanguageGenerator(event, McLocale.ZH_CN);
|
LanguageGenerator(event, McLocale.ZH_CN);
|
||||||
LanguageGenerator(event, McLocale.ZH_TW);
|
LanguageGenerator(event, McLocale.ZH_TW);
|
||||||
LanguageGenerator(event, McLocale.LZH);
|
LanguageGenerator(event, McLocale.LZH);
|
||||||
if (Lib39.shouldRegisterExamples()) {
|
BlockModelDataGenerate(event);
|
||||||
ModelDataGenerate(event);
|
BlockStateDataGenerate(event);
|
||||||
}
|
ItemModelDataGenerate(event);
|
||||||
}
|
}
|
||||||
private static void LanguageGenerator(@NotNull GatherDataEvent event, McLocale language) {
|
private static void LanguageGenerator(@NotNull GatherDataEvent event, McLocale language) {
|
||||||
event.getGenerator().addProvider(
|
event.getGenerator().addProvider(
|
||||||
|
|
@ -38,10 +40,22 @@ public class Lib39BaseDataGenEvent {
|
||||||
(DataProvider.Factory<SimpleLanguageProvider>) pOutput -> new SimpleLanguageProvider(pOutput, Lib39.MOD_ID ,language , Lib39LangKey.INSTANCE)
|
(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.getGenerator().addProvider(
|
||||||
event.includeClient(),
|
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.data.PackOutput;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
import net.minecraftforge.client.model.generators.ItemModelProvider;
|
|
||||||
import net.minecraftforge.common.data.ExistingFileHelper;
|
import net.minecraftforge.common.data.ExistingFileHelper;
|
||||||
import net.minecraftforge.registries.ForgeRegistries;
|
import net.minecraftforge.registries.ForgeRegistries;
|
||||||
import top.r3944realms.lib39.Lib39;
|
import top.r3944realms.lib39.Lib39;
|
||||||
import top.r3944realms.lib39.base.datagen.Lib39BaseDataGenEvent;
|
|
||||||
import top.r3944realms.lib39.base.datagen.value.Lib39LangKey;
|
import top.r3944realms.lib39.base.datagen.value.Lib39LangKey;
|
||||||
import top.r3944realms.lib39.datagen.value.LangKeyValue;
|
import top.r3944realms.lib39.datagen.value.LangKeyValue;
|
||||||
import top.r3944realms.lib39.datagen.value.ModPartEnum;
|
import top.r3944realms.lib39.datagen.value.ModPartEnum;
|
||||||
|
|
@ -32,9 +30,9 @@ import java.util.List;
|
||||||
import java.util.Objects;
|
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;
|
private static List<Item> objectList;
|
||||||
/**
|
/**
|
||||||
* The constant GENERATED.
|
* The constant GENERATED.
|
||||||
|
|
@ -46,12 +44,12 @@ public class ExItemModelProvider extends ItemModelProvider {
|
||||||
public static final String HANDHELD = "item/handheld";
|
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 output the output
|
||||||
* @param existingFileHelper the existing file helper
|
* @param existingFileHelper the existing file helper
|
||||||
*/
|
*/
|
||||||
public ExItemModelProvider(PackOutput output, ExistingFileHelper existingFileHelper) {
|
public Lib39ItemModelProvider(PackOutput output, ExistingFileHelper existingFileHelper) {
|
||||||
super(output, Lib39.MOD_ID, existingFileHelper);
|
super(output, Lib39.MOD_ID, existingFileHelper);
|
||||||
objectList = new ArrayList<>();
|
objectList = new ArrayList<>();
|
||||||
init();
|
init();
|
||||||
|
|
@ -59,7 +57,8 @@ public class ExItemModelProvider extends ItemModelProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void registerModels() {
|
protected void registerModels() {
|
||||||
DefaultModItemModelRegister();
|
defaultModItemModelRegister();
|
||||||
|
// generateDollItemModel();
|
||||||
}
|
}
|
||||||
private void init() {
|
private void init() {
|
||||||
for(LangKeyValue obj : Lib39LangKey.INSTANCE.getValues()) {
|
for(LangKeyValue obj : Lib39LangKey.INSTANCE.getValues()) {
|
||||||
|
|
@ -70,7 +69,7 @@ public class ExItemModelProvider extends ItemModelProvider {
|
||||||
/**
|
/**
|
||||||
* @implNote <br/> 先有纹理才会成功构建
|
* @implNote <br/> 先有纹理才会成功构建
|
||||||
*/
|
*/
|
||||||
private void DefaultModItemModelRegister() {
|
private void defaultModItemModelRegister() {
|
||||||
objectList.forEach(this::basicItem);
|
objectList.forEach(this::basicItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,6 +93,11 @@ public class ExItemModelProvider extends ItemModelProvider {
|
||||||
withExistingParent(itemName(item), HANDHELD).texture("layer0", location);
|
withExistingParent(itemName(item), HANDHELD).texture("layer0", location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void generateDollItemModel() {
|
||||||
|
getBuilder("doll")
|
||||||
|
.parent(getExistingFile(Lib39.rl("block/base_doll")));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item name string.
|
* 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(
|
public static final LangKeyValue HELP_PAGE_INFO = addAndRet(
|
||||||
LangKeyValue.ofKey(
|
LangKeyValue.ofKey(
|
||||||
"commands.lib39.help.page_info", ModPartEnum.MESSAGE,
|
"commands.lib39.help.page_info", ModPartEnum.MESSAGE,
|
||||||
|
|
@ -129,6 +140,17 @@ public enum Lib39LangKey implements ILangKeyValueCollection {
|
||||||
true
|
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) {
|
private static LangKeyValue addAndRet(LangKeyValue item) {
|
||||||
items.add(item);
|
items.add(item);
|
||||||
return item;
|
return item;
|
||||||
|
|
@ -161,6 +183,7 @@ public enum Lib39LangKey implements ILangKeyValueCollection {
|
||||||
ExLib39Items.FORGE, ModPartEnum.ITEM,
|
ExLib39Items.FORGE, ModPartEnum.ITEM,
|
||||||
"Forge", "铁砧", "铁砧", "砧", true
|
"Forge", "铁砧", "铁砧", "砧", true
|
||||||
));
|
));
|
||||||
|
TestMessage.getItems().forEach(this::addLang);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,5 +210,450 @@ public enum Lib39LangKey implements ILangKeyValueCollection {
|
||||||
public @Unmodifiable List<LangKeyValue> getValues() {
|
public @Unmodifiable List<LangKeyValue> getValues() {
|
||||||
return List.copyOf(langKeyValues);
|
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