Compare commits

...

No commits in common. "MultiLoader_1_20_1" and "MultiLoader_26_1_2" have entirely different histories.

3331 changed files with 88672 additions and 164983 deletions

5
.gitattributes vendored
View File

@ -1,5 +0,0 @@
# Disable autocrlf on generated files, they always generate with LF
# Add any extra files or paths here to make git stop saying they
# are changed when only line endings change.
src/generated/**/.cache/cache text eol=lf
src/generated/**/*.json text eol=lf

View File

@ -1,454 +0,0 @@
name: Build and Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Make gradlew executable
run: chmod +x ./gradlew
- name: Run Forge data generation
run: |
echo "=== 运行 Forge 数据生成 ==="
./gradlew runData --no-daemon
continue-on-error: false
- name: Build with Gradle
run: ./gradlew build --no-daemon
- name: Prepare release files
run: |
mkdir -p release-files
# 收集所有模块的构建产物
echo "=== 收集 common 模块构建产物 ==="
if [ -d "common/build/libs" ]; then
cp common/build/libs/*.jar release-files/ 2>/dev/null || echo "common 模块没有 jar 文件"
fi
echo "=== 收集 fabric 模块构建产物 ==="
if [ -d "fabric/build/libs" ]; then
cp fabric/build/libs/*-dev.jar release-files/ 2>/dev/null || true # 排除dev jar
cp fabric/build/libs/*-sources.jar release-files/ 2>/dev/null || true
cp fabric/build/libs/*-javadoc.jar release-files/ 2>/dev/null || true
# 只复制主jar没有sources/javadoc/dev classifier的jar
find fabric/build/libs -name "*.jar" ! -name "*-sources.jar" ! -name "*-javadoc.jar" ! -name "*-dev.jar" -exec cp {} release-files/ \;
fi
echo "=== 收集 forge 模块构建产物 ==="
if [ -d "forge/build/libs" ]; then
cp forge/build/libs/*-sources.jar release-files/ 2>/dev/null || true
cp forge/build/libs/*-javadoc.jar release-files/ 2>/dev/null || true
# 只复制主jar没有sources/javadoc classifier的jar
find forge/build/libs -name "*.jar" ! -name "*-sources.jar" ! -name "*-javadoc.jar" -exec cp {} release-files/ \;
fi
echo "=== 准备发布的文件 ==="
ls -la release-files/
- name: Upload release artifacts
uses: actions/upload-artifact@v4
with:
name: release-files
path: release-files/
retention-days: 7
release:
runs-on: ubuntu-latest
needs: build
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout with full history
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Determine version type
id: version_type
run: |
if [[ "${{ github.ref_name }}" == *"alpha"* ]]; then
echo "type=alpha" >> $GITHUB_OUTPUT
elif [[ "${{ github.ref_name }}" == *"beta"* ]]; then
echo "type=beta" >> $GITHUB_OUTPUT
elif [[ "${{ github.ref_name }}" == *"rc"* ]]; then
echo "type=beta" >> $GITHUB_OUTPUT
else
echo "type=release" >> $GITHUB_OUTPUT
fi
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: release-files
path: ./dist
- name: Extract version info
id: version_info
run: |
# 从tag中提取版本号去掉v前缀
VERSION="${GITHUB_REF_NAME#v}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "minecraft_version=$(grep "^minecraft_version=" gradle.properties | cut -d'=' -f2)" >> $GITHUB_OUTPUT
# 从 gradle.properties 提取 mod_id如果没有则尝试从文件名推断
MOD_ID=$(grep "^mod_id=" gradle.properties | cut -d'=' -f2 || echo "")
if [ -z "$MOD_ID" ]; then
# 尝试从现有的 jar 文件名提取 mod_id
SAMPLE_JAR=$(ls dist/ | grep -m 1 -E ".*-(fabric|forge)-.*\.jar" || echo "")
if [ -n "$SAMPLE_JAR" ]; then
MOD_ID=$(echo "$SAMPLE_JAR" | sed -E 's/-(fabric|forge)-.*//')
else
MOD_ID="mymod" # 默认值,请根据实际情况修改
fi
fi
echo "mod_id=$MOD_ID" >> $GITHUB_OUTPUT
# 从 gradle.properties 提取 mod_name用于显示
MOD_NAME=$(grep "^mod_name=" gradle.properties | cut -d'=' -f2 || echo "My Mod")
echo "mod_name=$MOD_NAME" >> $GITHUB_OUTPUT
# 从 gradle.properties 提取 modrinth_id
MODRINTH_ID=$(grep "^modrinth_id=" gradle.properties | cut -d'=' -f2 || echo "")
echo "modrinth_id=$MODRINTH_ID" >> $GITHUB_OUTPUT
# 从 gradle.properties 提取 curseforge_id
CURSEFORGE_ID=$(grep "^curseforge_id=" gradle.properties | cut -d'=' -f2 || echo "")
echo "curseforge_id=$CURSEFORGE_ID" >> $GITHUB_OUTPUT
# Java版本 - 使用简单格式不用JSON
JAVA_VERSIONS=$(grep "^java_versions=" gradle.properties | cut -d'=' -f2- || echo "21,17")
# 清理格式,移除无效字符
JAVA_VERSIONS=$(echo "$JAVA_VERSIONS" | sed 's/\[//g; s/\]//g; s/"//g; s/ //g; s/21a/21/g' | tr -d '\r')
echo "java_versions=$JAVA_VERSIONS" >> $GITHUB_OUTPUT
# 读取发布控制布尔值(默认都为 true
PUBLISH_GITHUB=$(grep "^publish_github=" gradle.properties | cut -d'=' -f2 | tr '[:upper:]' '[:lower:]' | tr -d ' ' || echo "true")
if [ "$PUBLISH_GITHUB" = "true" ] || [ "$PUBLISH_GITHUB" = "1" ] || [ "$PUBLISH_GITHUB" = "yes" ]; then
echo "publish_github=true" >> $GITHUB_OUTPUT
else
echo "publish_github=false" >> $GITHUB_OUTPUT
fi
PUBLISH_MODRINTH=$(grep "^publish_modrinth=" gradle.properties | cut -d'=' -f2 | tr '[:upper:]' '[:lower:]' | tr -d ' ' || echo "true")
if [ "$PUBLISH_MODRINTH" = "true" ] || [ "$PUBLISH_MODRINTH" = "1" ] || [ "$PUBLISH_MODRINTH" = "yes" ]; then
echo "publish_modrinth=true" >> $GITHUB_OUTPUT
else
echo "publish_modrinth=false" >> $GITHUB_OUTPUT
fi
PUBLISH_CURSEFORGE=$(grep "^publish_curseforge=" gradle.properties | cut -d'=' -f2 | tr '[:upper:]' '[:lower:]' | tr -d ' ' || echo "true")
if [ "$PUBLISH_CURSEFORGE" = "true" ] || [ "$PUBLISH_CURSEFORGE" = "1" ] || [ "$PUBLISH_CURSEFORGE" = "yes" ]; then
echo "publish_curseforge=true" >> $GITHUB_OUTPUT
else
echo "publish_curseforge=false" >> $GITHUB_OUTPUT
fi
# 读取依赖配置 - 使用简单字符串不用JSON
FABRIC_MODRINTH_DEPS=$(grep "^fabric_modrinth_dependencies=" gradle.properties | cut -d'=' -f2- || echo "")
echo "fabric_modrinth_dependencies=$FABRIC_MODRINTH_DEPS" >> $GITHUB_OUTPUT
FORGE_MODRINTH_DEPS=$(grep "^forge_modrinth_dependencies=" gradle.properties | cut -d'=' -f2- || echo "")
echo "forge_modrinth_dependencies=$FORGE_MODRINTH_DEPS" >> $GITHUB_OUTPUT
FABRIC_CURSEFORGE_DEPS=$(grep "^fabric_curseforge_dependencies=" gradle.properties | cut -d'=' -f2- || echo "")
echo "fabric_curseforge_dependencies=$FABRIC_CURSEFORGE_DEPS" >> $GITHUB_OUTPUT
FORGE_CURSEFORGE_DEPS=$(grep "^forge_curseforge_dependencies=" gradle.properties | cut -d'=' -f2- || echo "")
echo "forge_curseforge_dependencies=$FORGE_CURSEFORGE_DEPS" >> $GITHUB_OUTPUT
- name: Generate CZ-compliant changelog
id: generate_changelog
run: |
CURRENT_TAG="${{ github.ref_name }}"
PREV_TAG=$(git describe --tags --abbrev=0 $(git rev-list --tags --skip=1 --max-count=1) 2>/dev/null || echo "")
# 创建临时文件
TEMP_FILE=$(mktemp)
echo "# 🚀 版本 $CURRENT_TAG 发布" > $TEMP_FILE
echo "" >> $TEMP_FILE
echo "## 📋 变更摘要" >> $TEMP_FILE
echo "" >> $TEMP_FILE
if [ -z "$PREV_TAG" ]; then
echo "### 初始版本发布" >> $TEMP_FILE
echo "" >> $TEMP_FILE
echo "这是项目的第一个正式版本。" >> $TEMP_FILE
echo "" >> $TEMP_FILE
# 获取所有提交并按类型分组
git log --pretty=format:"%s" --reverse | while read -r line; do
echo "- $line" >> $TEMP_FILE
done
else
echo "### 从 $PREV_TAG 到 $CURRENT_TAG 的变更" >> $TEMP_FILE
echo "" >> $TEMP_FILE
# 定义符合CZ规范的提交类型映射
declare -A commit_types
commit_types=(
["✨ 新功能"]="^(feat|feature)(\(.*\))?:"
["🐛 修复"]="^(fix|bugfix)(\(.*\))?:"
["📝 文档"]="^(docs|documentation)(\(.*\))?:"
["🎨 样式"]="^(style)(\(.*\))?:"
["🔨 重构"]="^(refactor)(\(.*\))?:"
["⚡️ 性能"]="^(perf|performance)(\(.*\))?:"
["✅ 测试"]="^(test)(\(.*\))?:"
["🔧 构建"]="^(build)(\(.*\))?:"
["👷 CI"]="^(ci)(\(.*\))?:"
["📦 依赖"]="^(chore|deps)(\(.*\))?:"
["⏪ 回退"]="^(revert)(\(.*\))?:"
["🛠 合并"]="^Merge "
)
# 获取所有提交
COMMITS=$(git log --pretty=format:"%s" $PREV_TAG..HEAD)
# 处理每种类型的提交
for type_name in "${!commit_types[@]}"; do
pattern="${commit_types[$type_name]}"
# 提取匹配的提交
matched_commits=$(echo "$COMMITS" | grep -E "$pattern" || true)
if [ -n "$matched_commits" ]; then
echo "#### $type_name" >> $TEMP_FILE
echo "" >> $TEMP_FILE
# 处理每条提交提取scope和subject
echo "$matched_commits" | while read -r commit; do
# 解析scope和subject
if [[ $commit =~ ^[a-z]+\((.*)\):\ (.*) ]]; then
scope="${BASH_REMATCH[1]}"
subject="${BASH_REMATCH[2]}"
echo "- **$scope**: $subject" >> $TEMP_FILE
elif [[ $commit =~ ^[a-z]+:\ (.*) ]]; then
subject="${BASH_REMATCH[1]}"
echo "- $subject" >> $TEMP_FILE
else
echo "- $commit" >> $TEMP_FILE
fi
done
echo "" >> $TEMP_FILE
fi
done
# 处理破坏性变更BREAKING CHANGE
breaking_changes=$(git log --pretty=format:"%b" $PREV_TAG..HEAD | grep -i "BREAKING CHANGE" || true)
if [ -n "$breaking_changes" ]; then
echo "#### ⚠️ 破坏性变更" >> $TEMP_FILE
echo "" >> $TEMP_FILE
echo "$breaking_changes" | while read -r line; do
echo "- $line" >> $TEMP_FILE
done
echo "" >> $TEMP_FILE
fi
# 处理未分类的提交
uncategorized="$COMMITS"
for pattern in "${commit_types[@]}"; do
uncategorized=$(echo "$uncategorized" | grep -v -E "$pattern" || true)
done
if [ -n "$uncategorized" ]; then
echo "#### 📝 其他更改" >> $TEMP_FILE
echo "" >> $TEMP_FILE
echo "$uncategorized" | while read -r commit; do
echo "- $commit" >> $TEMP_FILE
done
echo "" >> $TEMP_FILE
fi
fi
echo "## 📊 统计信息" >> $TEMP_FILE
echo "" >> $TEMP_FILE
if [ -z "$PREV_TAG" ]; then
TOTAL_COMMITS=$(git rev-list --count HEAD)
echo "- 总提交数: $TOTAL_COMMITS" >> $TEMP_FILE
echo "- 首次发布" >> $TEMP_FILE
else
COMMITS=$(git rev-list --count $PREV_TAG..HEAD)
echo "- 本次发布提交数: $COMMITS" >> $TEMP_FILE
echo "- 上一个版本: $PREV_TAG" >> $TEMP_FILE
fi
echo "- 发布日期: $(date '+%Y年%m月%d日')" >> $TEMP_FILE
echo "- 当前版本: $CURRENT_TAG" >> $TEMP_FILE
echo "- Minecraft版本: ${{ steps.version_info.outputs.minecraft_version }}" >> $TEMP_FILE
echo "" >> $TEMP_FILE
echo "---" >> $TEMP_FILE
echo "" >> $TEMP_FILE
echo "### 📜 详细提交历史" >> $TEMP_FILE
echo "" >> $TEMP_FILE
echo "<details>" >> $TEMP_FILE
echo "<summary>点击展开查看完整提交历史</summary>" >> $TEMP_FILE
echo "" >> $TEMP_FILE
echo "\`\`\`" >> $TEMP_FILE
if [ -z "$PREV_TAG" ]; then
# 使用 while 循环确保每条提交独立一行
git log --pretty=format:"%h %s - %an (%ad)" --date=short --reverse | while IFS= read -r line; do
echo "$line" >> $TEMP_FILE
done
else
git log --pretty=format:"%h %s - %an (%ad)" --date=short $PREV_TAG..HEAD | while IFS= read -r line; do
echo "$line" >> $TEMP_FILE
done
fi
# 确保文件末尾有换行
echo "" >> $TEMP_FILE
echo "\`\`\`" >> $TEMP_FILE
echo "</details>" >> $TEMP_FILE
# 将文件内容输出到变量
CHANGELOG_CONTENT=$(cat $TEMP_FILE)
echo "changelog<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGELOG_CONTENT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create Release
if: steps.version_info.outputs.publish_github == 'true'
uses: ncipollo/release-action@v1
with:
artifacts: |
dist/*.jar
tag: ${{ github.ref_name }}
name: "${{ steps.version_info.outputs.minecraft_version }} - ${{ github.ref_name }}"
body: ${{ steps.generate_changelog.outputs.changelog }}
draft: false
prerelease: ${{ contains(github.ref_name, 'rc') || contains(github.ref_name, 'beta') || contains(github.ref_name, 'alpha') }}
token: ${{ secrets.GITHUB_TOKEN }}
allowUpdates: true
removeArtifacts: true
# Fabric 发布到 Modrinth 和 CurseForge
- name: Publish Fabric to Modrinth & CurseForge
uses: Kir-Antipov/mc-publish@v3.3
if: success() && (steps.version_info.outputs.publish_modrinth == 'true' || steps.version_info.outputs.publish_curseforge == 'true')
continue-on-error: true
with:
# 文件匹配规则 - 只匹配 fabric 的文件
files: |
dist/${{ steps.version_info.outputs.mod_id }}-fabric-${{ steps.version_info.outputs.minecraft_version }}-${{ steps.version_info.outputs.version }}.jar
dist/${{ steps.version_info.outputs.mod_id }}-fabric-${{ steps.version_info.outputs.minecraft_version }}-${{ steps.version_info.outputs.version }}-javadoc.jar
dist/${{ steps.version_info.outputs.mod_id }}-fabric-${{ steps.version_info.outputs.minecraft_version }}-${{ steps.version_info.outputs.version }}-sources.jar
# 版本信息
name: ${{ steps.version_info.outputs.mod_name }} ${{ steps.version_info.outputs.version }} (Fabric/${{ steps.version_info.outputs.minecraft_version }})
version: "${{ steps.version_info.outputs.minecraft_version }}-fabric-${{ steps.version_info.outputs.version }}"
# 更新日志
changelog: ${{ steps.generate_changelog.outputs.changelog }}
# 版本类型
version-type: ${{ steps.version_type.outputs.type }}
# 只指定 Fabric 加载器
loaders: fabric
# 游戏版本
game-versions: |
${{ steps.version_info.outputs.minecraft_version }}
# Java版本
java: |
${{ steps.version_info.outputs.java_versions }}
# Modrinth 配置
modrinth-id: ${{ steps.version_info.outputs.modrinth_id }}
modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
modrinth-featured: true
modrinth-unfeature-mode: any
modrinth-dependencies: ${{ steps.version_info.outputs.fabric_modrinth_dependencies }}
# CurseForge 配置
curseforge-id: ${{ steps.version_info.outputs.curseforge_id }}
curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }}
curseforge-dependencies: ${{ steps.version_info.outputs.fabric_curseforge_dependencies }}
# 失败处理
fail-mode: skip
# Forge 发布到 Modrinth 和 CurseForge
- name: Publish Forge to Modrinth & CurseForge
uses: Kir-Antipov/mc-publish@v3.3
if: success() && (steps.version_info.outputs.publish_modrinth == 'true' || steps.version_info.outputs.publish_curseforge == 'true')
continue-on-error: true
with:
# 文件匹配规则 - 只匹配 forge 的文件
files: |
dist/${{ steps.version_info.outputs.mod_id }}-forge-${{ steps.version_info.outputs.minecraft_version }}-${{ steps.version_info.outputs.version }}.jar
dist/${{ steps.version_info.outputs.mod_id }}-forge-${{ steps.version_info.outputs.minecraft_version }}-${{ steps.version_info.outputs.version }}-javadoc.jar
dist/${{ steps.version_info.outputs.mod_id }}-forge-${{ steps.version_info.outputs.minecraft_version }}-${{ steps.version_info.outputs.version }}-sources.jar
# 版本信息
name: ${{ steps.version_info.outputs.mod_name }} ${{ steps.version_info.outputs.version }} (Forge/${{ steps.version_info.outputs.minecraft_version }})
version: "${{ steps.version_info.outputs.minecraft_version }}-forge-${{ steps.version_info.outputs.version }}"
# 更新日志
changelog: ${{ steps.generate_changelog.outputs.changelog }}
# 版本类型
version-type: ${{ steps.version_type.outputs.type }}
# 只指定 Forge 加载器
loaders: forge
# 游戏版本
game-versions: |
${{ steps.version_info.outputs.minecraft_version }}
# Java版本
java: |
${{ steps.version_info.outputs.java_versions }}
# Modrinth 配置
modrinth-id: ${{ steps.version_info.outputs.modrinth_id }}
modrinth-token: ${{ secrets.MODRINTH_TOKEN }}
modrinth-featured: true
modrinth-unfeature-mode: any
modrinth-dependencies: ${{ steps.version_info.outputs.forge_modrinth_dependencies }}
# CurseForge 配置
curseforge-id: ${{ steps.version_info.outputs.curseforge_id }}
curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }}
curseforge-dependencies: ${{ steps.version_info.outputs.forge_curseforge_dependencies }}
# 失败处理
fail-mode: skip
# 发布完成后列出结果
- name: Summary
if: always()
run: |
echo "## 发布结果摘要" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### GitHub Release" >> $GITHUB_STEP_SUMMARY
echo "- 标签: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
echo "- URL: https://github.com/${{ github.repository }}/releases/tag/${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Modrinth" >> $GITHUB_STEP_SUMMARY
echo "- 项目ID: ${{ steps.version_info.outputs.modrinth_id }}" >> $GITHUB_STEP_SUMMARY
echo "- Fabric版本: ${{ steps.version_info.outputs.version }}-fabric" >> $GITHUB_STEP_SUMMARY
echo "- Forge版本: ${{ steps.version_info.outputs.version }}-forge" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### CurseForge" >> $GITHUB_STEP_SUMMARY
echo "- 项目ID: ${{ steps.version_info.outputs.curseforge_id }}" >> $GITHUB_STEP_SUMMARY
echo "- Fabric版本: ${{ steps.version_info.outputs.version }}-fabric" >> $GITHUB_STEP_SUMMARY
echo "- Forge版本: ${{ steps.version_info.outputs.version }}-forge" >> $GITHUB_STEP_SUMMARY

View File

@ -1,30 +0,0 @@
name: Check Style in Pull Request
on:
pull_request_target:
jobs:
checkstyle:
runs-on: ubuntu-latest
permissions:
pull-requests: write
checks: write
contents: read
steps:
- name: checkout
uses: actions/checkout@v4
with:
ref: refs/pull/${{ github.event.number }}/merge
- name: Setup Java 17
uses: actions/setup-java@v3.6.0
with:
distribution: zulu
java-version: 17
- uses: reviewdog/action-setup@v1
with:
reviewdog_version: latest
- name: download checkstyle
run: curl -o checkstyle.jar -L https://github.com/checkstyle/checkstyle/releases/download/checkstyle-12.1.2/checkstyle-12.1.2-all.jar
- name: checkstyle
env:
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: java -jar checkstyle.jar -c style.xml -f xml */src | reviewdog -f=checkstyle -name="Checkstyle" -reporter=github-pr-review -fail-level=any

41
.gitignore vendored
View File

@ -1,28 +1,21 @@
# eclipse
bin
*.launch
.settings
.metadata
.classpath
.project
cpp/cmake-build-debug
# MacOS DS_Store files
.DS_Store
# idea
out
*.ipr
*.iws
*.iml
.idea
# gradle
build
# Gradle cache folder
.gradle
# other
eclipse
run
generated
runs
run-data
# Gradle build folder
build
repo
# IntelliJ
out/
.idea
*.iml
# mpeltonen/sbt-idea plugin
.idea_modules/
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# Common working directory
run

View File

@ -1,57 +0,0 @@
# Lib39
![GitHub Release](https://img.shields.io/github/v/release/3944Realms/Lib39) [![License](https://img.shields.io/github/license/3944realms/lib39)]()
[![CurseForge Download](https://img.shields.io/curseforge/dt/1445917?logo=curseforge&label=CurseForge)](https://www.curseforge.com/minecraft/mc-mods/lib-39)
[![Modrinth Download](https://img.shields.io/modrinth/dt/n65Vs1Vk?logo=modrinth&label=Modrinth)](https://modrinth.com/mod/lib-39)
**Lib39** is a general-purpose dependency library for Minecraft mods.
It provides utility methods and core functionality that other mods can build upon.
### How to implementation? ( Only for Version 0.5.0+ )
#### **In repositories:**
```groovy
maven {
name = "LTD Maven"
url = "https://nexus.bot.leisuretimedock.top/repository/maven-public/"
}
```
#### **In dependencies:**
##### General
**gradle.properties**
```properties
lib39_version=0.5.1
````
##### For Loom
**build.gradle**
```groovy
dependencies {
modImplementation("top.r3944realms.lib39:lib39-fabric-1.20.1:${lib39_version}")
}
```
##### For ForgeGradle
**build.gradle**
```groovy
dependencies {
implementation fg.deof("top.r3944realms.lib39:lib39-forge-1.20.1:${lib39_version}")
}
```
##### For NeoForgeGradle / ModDevGradle
**build.gradle**
```groovy
dependencies {
modImplementation("top.r3944realms.lib39:lib39-forge-1.20.1:${lib39_version}")
}
```
##### For MultiLoader Project
Add this in your common subproject.
**build.gradle**
```groovy
dependencies {
implementation("top.r3944realms.lib39:lib39-common-1.20.1:${lib39_version}")
}
```

View File

@ -1,4 +1,174 @@
plugins {
id 'fabric-loom' version '1.9-SNAPSHOT' apply(false)
id 'net.neoforged.moddev.legacyforge' version '2.0.103' apply(false)
}
id 'java-library'
id 'maven-publish'
id 'idea'
id 'net.neoforged.moddev' version '2.0.141'
}
version = mod_version
group = mod_group_id
repositories {
mavenLocal()
}
base {
archivesName = mod_id
}
java.toolchain.languageVersion = JavaLanguageVersion.of(25)
neoForge {
// Specify the version of NeoForge to use.
version = project.neo_version
parchment {
mappingsVersion = project.parchment_mappings_version
minecraftVersion = project.parchment_minecraft_version
}
// This line is optional. Access Transformers are automatically detected
// accessTransformers.add('src/main/resources/META-INF/accesstransformer.cfg')
// Default run configurations.
// These can be tweaked, removed, or duplicated as needed.
runs {
client {
client()
// Comma-separated list of namespaces to load gametests from. Empty = all namespaces.
systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id
}
clientAuth {
client()
devLogin = true
// Comma-separated list of namespaces to load gametests from. Empty = all namespaces.
systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id
}
server {
server()
programArgument '--nogui'
systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id
}
// This run config launches GameTestServer and runs all registered gametests, then exits.
// By default, the server will crash when no gametests are provided.
// The gametest system is also enabled by default for other run configs under the /test command.
gameTestServer {
type = "gameTestServer"
systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id
}
data {
clientData()
// example of overriding the workingDirectory set in configureEach above, uncomment if you want to use it
// gameDirectory = project.file('run-data')
// Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources.
programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath()
}
// applies to all the run configs above
configureEach {
// Recommended logging data for a userdev environment
// The markers can be added/remove as needed separated by commas.
// "SCAN": For mods scan.
// "REGISTRIES": For firing of registry events.
// "REGISTRYDUMP": For getting the contents of all registries.
systemProperty 'forge.logging.markers', 'REGISTRIES'
// Recommended logging level for the console
// You can set various levels here.
// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
logLevel = org.slf4j.event.Level.DEBUG
}
}
mods {
// define mod <-> source bindings
// these are used to tell the game which sources are for which mod
// mostly optional in a single mod project
// but multi mod projects should define one per mod
"${mod_id}" {
sourceSet(sourceSets.main)
}
}
}
// Include resources generated by data generators.
sourceSets.main.resources { srcDir 'src/generated/resources' }
dependencies {
// Example mod dependency with JEI
// The JEI API is declared for compile time use, while the full JEI artifact is used at runtime
// compileOnly "mezz.jei:jei-${mc_version}-common-api:${jei_version}"
// compileOnly "mezz.jei:jei-${mc_version}-forge-api:${jei_version}"
// runtimeOnly "mezz.jei:jei-${mc_version}-forge:${jei_version}"
// Example mod dependency using a mod jar from ./libs with a flat dir repository
// This maps to ./libs/coolmod-${mc_version}-${coolmod_version}.jar
// The group id is ignored when searching -- in this case, it is "blank"
// implementation "blank:coolmod-${mc_version}:${coolmod_version}"
// Example mod dependency using a file as dependency
// implementation files("libs/coolmod-${mc_version}-${coolmod_version}.jar")
// Example project dependency using a sister or child project:
// implementation project(":myproject")
// For more info:
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
// http://www.gradle.org/docs/current/userguide/dependency_management.html
}
// This block of code expands all declared replace properties in the specified resource targets.
// A missing property will result in an error. Properties are expanded using ${} Groovy notation.
var generateModMetadata = tasks.register("generateModMetadata", ProcessResources) {
var replaceProperties = [minecraft_version : minecraft_version,
minecraft_version_range: minecraft_version_range,
neo_version : neo_version,
neo_version_range : neo_version_range,
loader_version_range : loader_version_range,
mod_id : mod_id,
mod_name : mod_name,
mod_license : mod_license,
mod_version : mod_version,
mod_authors : mod_authors,
mod_description : mod_description]
inputs.properties replaceProperties
expand replaceProperties
from "src/main/templates"
into "build/generated/sources/modMetadata"
}
// Include the output of "generateModMetadata" as an input directory for the build
// this works with both building through Gradle and the IDE.
sourceSets.main.resources.srcDir generateModMetadata
// To avoid having to run "generateModMetadata" manually, make it run on every project reload
neoForge.ideSyncTask generateModMetadata
// Example configuration to allow publishing using the maven-publish plugin
publishing {
publications {
register('mavenJava', MavenPublication) {
from components.java
}
}
repositories {
maven {
url "file://${project.projectDir}/repo"
}
}
}
// IDEA no longer automatically downloads sources/javadoc jars for dependencies, so we need to explicitly enable the behavior.
idea {
module {
downloadSources = true
downloadJavadoc = true
}
}

View File

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

View File

@ -1,207 +0,0 @@
plugins {
id 'java-library'
id 'maven-publish'
}
base {
archivesName = "${mod_id}-${project.name}-${minecraft_version}"
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(java_version)
withSourcesJar()
withJavadocJar()
}
repositories {
mavenCentral()
// https://docs.gradle.org/current/userguide/declaring_repositories.html#declaring_content_exclusively_found_in_one_repository
exclusiveContent {
forRepository {
maven {
name = 'Sponge'
url = 'https://repo.spongepowered.org/repository/maven-public'
}
}
filter { includeGroupAndSubgroups('org.spongepowered') }
}
exclusiveContent {
forRepositories(
maven {
name = 'ParchmentMC'
url = 'https://maven.parchmentmc.org/'
},
maven { url = "https://neoforged.forgecdn.net/releases" },
maven { url = "https://neoforged.forgecdn.net/mojang-meta" }
)
filter { includeGroup('org.parchmentmc.data') }
}
maven { url = "https://libraries.minecraft.net/" }
maven {
url "https://cursemaven.com"
content { includeGroup "curse.maven" }
}
maven {
name = 'BlameJared'
url = 'https://maven.blamejared.com'
}
}
// Declare capabilities on the outgoing configurations.
// Read more about capabilities here: https://docs.gradle.org/current/userguide/component_capabilities.html#sec:declaring-additional-capabilities-for-a-local-component
['apiElements', 'runtimeElements', 'sourcesElements', 'javadocElements'].each { variant ->
configurations."$variant".outgoing {
capability("$group:${project.name}:$version")
capability("$group:${base.archivesName.get()}:$version")
capability("$group:$mod_id-${project.name}-${minecraft_version}:$version")
capability("$group:$mod_id:$version")
}
publishing.publications.configureEach {
suppressPomMetadataWarningsFor(variant)
}
}
sourcesJar {
from(rootProject.file('LICENSE')) {
rename { "${it}_${mod_name}" }
}
}
jar {
from(rootProject.file('LICENSE')) {
rename { "${it}_${mod_name}" }
}
manifest {
attributes([
'Specification-Title' : mod_name,
'Specification-Vendor' : mod_author,
'Specification-Version' : project.jar.archiveVersion,
'Implementation-Title' : project.name,
'Implementation-Version': project.jar.archiveVersion,
'Implementation-Vendor' : mod_author,
'Built-On-Minecraft' : minecraft_version
])
}
}
processResources {
var expandProps = [
'version' : version,
'group' : project.group, //Else we target the task's group.
'minecraft_version' : minecraft_version,
'minecraft_version_range' : minecraft_version_range,
'fabric_version' : fabric_version,
'fabric_loader_version' : fabric_loader_version,
'mod_name' : mod_name,
'mod_author' : mod_author,
'mod_id' : mod_id,
'license' : license,
'description' : project.description,
"forge_version" : forge_version,
"forge_loader_version_range" : forge_loader_version_range,
'credits' : credits,
'java_version' : java_version
]
var jsonExpandProps = expandProps.collectEntries {
key, value -> [(key): value instanceof String ? value.replace("\n", "\\\\n") : value]
}
filesMatching(['META-INF/mods.toml']) {
expand expandProps
}
filesMatching(['pack.mcmeta', 'fabric.mod.json', '*.mixins.json']) {
expand jsonExpandProps
}
inputs.properties(expandProps)
}
publishing {
publications {
register('mavenJava', MavenPublication) {
artifactId base.archivesName.get()
from components.java
pom {
name = 'Lib39'
description = 'Lib39 is a general-purpose dependency library for Minecraft mods.'
url = 'https://github.com/3944Realms/lib39'
properties = [
'minecraft.version': project.minecraft_version,
'mod.version': project.version,
'forge.version': project.forge_version,
'java.version': '17'
]
licenses {
license {
name = 'MIT'
url = 'https://raw.githubusercontent.com/3944Realms/lib39/refs/heads/main/LICENSE'
distribution = 'repo'
}
}
developers {
developer {
id = 'R3944Realms'
name = "${mod_author}"
email = 'f256198830@hotmail.com'
}
}
scm {
connection = 'scm:git:https://github.com/3944Realms/lib39.git'
developerConnection = 'scm:git:ssh://git@github.com:3944Realms/lib39.git'
url = 'https://github.com/3944Realms/lib39'
tag = 'main'
}
issueManagement {
system = 'GitHub'
url = 'https://github.com/3944Realms/lib39/issues'
}
}
}
}
repositories {
//
maven {
name = 'local'
url = layout.buildDirectory.dir("repo")
}
// Nexus
maven {
name = 'LTDNexus'
url = 'https://nexus.bot.leisuretimedock.top/repository/maven-releases/'
credentials {
username = System.getenv('LTDNexusUsername') ?: ''
password = System.getenv('LTDNexusPassword') ?: ''
}
}
}
}
// ==================== ====================
tasks.withType(PublishToMavenRepository) {
dependsOn assemble
dependsOn javadoc
}
tasks.named('build') {
dependsOn javadoc, sourcesJar
}
tasks.register('cleanRepo', Delete) {
delete layout.buildDirectory.dir("repo")
}
tasks.named('clean') {
dependsOn cleanRepo
}

View File

@ -1,50 +0,0 @@
plugins {
id 'multiloader-common'
}
configurations {
commonJava{
canBeResolved = true
}
commonResources{
canBeResolved = true
}
}
dependencies {
compileOnly(project(':common')) {
capabilities {
requireCapability "$group:$mod_id"
}
}
commonJava project(path: ':common', configuration: 'commonJava')
commonResources project(path: ':common', configuration: 'commonResources')
}
tasks.named('compileJava', JavaCompile) {
dependsOn(configurations.commonJava)
source(configurations.commonJava)
}
processResources {
dependsOn(configurations.commonResources)
from(configurations.commonResources)
}
tasks.named('javadoc', Javadoc).configure {
dependsOn(configurations.commonJava)
source(configurations.commonJava)
options.encoding = 'UTF-8'
options.charSet = 'UTF-8'
options.links("https://docs.oracle.com/en/java/javase/17/docs/api/")
options.memberLevel = JavadocMemberLevel.PUBLIC
options.addBooleanOption('Xdoclint:none', true)
options.addStringOption('doctitle', "${mod_id} ${minecraft_version} ${version} Javadoc")
}
tasks.named('sourcesJar', Jar) {
dependsOn(configurations.commonJava)
from(configurations.commonJava)
dependsOn(configurations.commonResources)
from(configurations.commonResources)
}

View File

@ -1,43 +0,0 @@
plugins {
id 'multiloader-common'
id 'net.neoforged.moddev.legacyforge'
}
legacyForge {
mcpVersion = minecraft_version
if (file("src/main/resources/META-INF/accesstransformer.cfg").exists()) {
accessTransformers = ["src/main/resources/META-INF/accesstransformer.cfg"]
}
parchment {
minecraftVersion = parchment_minecraft
mappingsVersion = parchment_version
}
}
dependencies {
compileOnly(group: 'org.spongepowered', name: 'mixin', version: '0.8.5')
implementation(group: 'tschipp.carryon', name: 'carryon-common-1.20.1', version: '2.1.2') {
transitive = false
}
implementation(annotationProcessor("io.github.llamalad7:mixinextras-common:0.2.0"))
implementation(group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.1')
}
configurations {
commonJava {
canBeResolved = false
canBeConsumed = true
}
commonResources {
canBeResolved = false
canBeConsumed = true
}
}
artifacts {
commonJava sourceSets.main.java.sourceDirectories.singleFile
commonResources sourceSets.main.resources.sourceDirectories.singleFile, file('src/generated/resources')
}
clean {
delete 'generated'
}

View File

@ -1,125 +0,0 @@
package top.r3944realms.lib39;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.r3944realms.lib39.example.Lib39Example;
import top.r3944realms.lib39.platform.Services;
/**
* The type Lib 39.
*/
public class Lib39 {
/**
* The constant MOD_ID.
*/
public static final String MOD_ID = "lib39";
/**
* The constant MOD_NAME.
*/
public static final String MOD_NAME = "3944Realms 's Lib Mod";
/**
* The constant LOGGER.
*/
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_NAME);
/**
* The constant ENABLE_EXAMPLES_PROPERTY_KEY.
*/
public static final String ENABLE_EXAMPLES_PROPERTY_KEY = "lib39.enable_examples";
/**
* Initialize.
*/
public static void initialize() {
Lib39.LOGGER.info("[Lib39-Common] Lib39-Common start initialization.");
if (shouldRegisterExamples()) {
LOGGER.info("[Lib39-Common] Registering Examples");
registerExamples();
}
Lib39.LOGGER.info("[Lib39-Common] Finished Lib39-Common!.");
}
/**
* Rl resource location.
*
* @param path the path
* @return the resource location
*/
@Contract("_ -> new")
public static @NotNull ResourceLocation rl(String path) {
return new ResourceLocation(Lib39.MOD_ID, path);
}
/**
* Rl resource location.
*
* @param modId the mod id
* @param path the path
* @return the resource location
*/
@Contract("_, _ -> new")
public static @NotNull ResourceLocation rl(String modId, String path) {
return new ResourceLocation(modId, path);
}
/**
* Mrl resource location.
*
* @param path the path
* @return the resource location
*/
@Contract("_ -> new")
public static @NotNull ResourceLocation mrl(String path) {
return new ResourceLocation(path);
}
/**
* Is client environment boolean.
*
* @return the boolean
*/
public static boolean isClientEnvironment() {
return Services.PLATFORM.isClientEnvironment();
}
/**
* Should register examples boolean.
*
* @return the boolean
*/
public static boolean shouldRegisterExamples() {
return Services.PLATFORM.isDevelopmentEnvironment() || Boolean.getBoolean(ENABLE_EXAMPLES_PROPERTY_KEY);
}
/**
* Register examples.
*/
static void registerExamples() {
LOGGER.info("[Lib39-Common] Starting example demonstrations");
try {
// 创建示例实例并演示功能
Lib39Example example = new Lib39Example();
example.demonstrateFeature();
LOGGER.info("[Lib39-Common] Example demonstrations completed successfully");
} catch (Exception e) {
LOGGER.error("[Lib39-Common] Failed to demonstrate examples", e);
}
}
/**
* The type Mod info.
*/
public static class ModInfo {
/**
* The constant VERSION.
*/
public static final String VERSION;
static {
VERSION = Services.PLATFORM.getModVersion();
}
}
}

View File

@ -1,42 +0,0 @@
package top.r3944realms.lib39.base.command;
import net.minecraft.resources.ResourceLocation;
import top.r3944realms.lib39.Lib39;
import top.r3944realms.lib39.core.command.SimpleCommandHelpManager;
/**
* <pre>
* 命令帮助注册管理类
* 这是一个模组内置的示例
* </pre>
*/
public class Lib39CommandHelpManager extends SimpleCommandHelpManager {
/**
* 单例模式
*/
public static volatile Lib39CommandHelpManager INSTANCE = new Lib39CommandHelpManager();
/**
* 作为唯一标识符
*/
ResourceLocation ID = Lib39.rl("command_helper");
/**
* <pre>
* 一定要在构造器方法里调用 {@link #initialize 初始化方法}
* Instantiates a new Lib 39 command help manager.
* </pre>
*/
public Lib39CommandHelpManager() {
initialize();
}
@Override
public ResourceLocation getID() {
return ID;
}
@Override
public String getHeadKey() {
return "lib39";
}
}

View File

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

View File

@ -1,36 +0,0 @@
package top.r3944realms.lib39.base.datagen.provider;
import net.minecraft.data.PackOutput;
import net.minecraft.data.recipes.FinishedRecipe;
import net.minecraft.data.recipes.RecipeCategory;
import net.minecraft.data.recipes.RecipeProvider;
import net.minecraft.data.recipes.ShapelessRecipeBuilder;
import net.minecraft.tags.ItemTags;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.lib39.core.register.Lib39Items;
import java.util.function.Consumer;
/**
* The type Lib 39 recipe provider.
*/
public class Lib39RecipeProvider extends RecipeProvider {
/**
* Instantiates a new Lib 39 recipe provider.
*
* @param output the output
*/
public Lib39RecipeProvider(PackOutput output) {
super(output);
}
@Override
public void buildRecipes(@NotNull Consumer<FinishedRecipe> consumer) {
ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, Lib39Items.DOLL.get())
.requires(ItemTags.WOOL)
.requires(Items.ARMOR_STAND)
.unlockedBy("has_armor_stand",has(Items.ARMOR_STAND))
.save(consumer);
}
}

View File

@ -1,873 +0,0 @@
package top.r3944realms.lib39.base.datagen.value;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Unmodifiable;
import top.r3944realms.lib39.Lib39;
import top.r3944realms.lib39.core.register.Lib39Blocks;
import top.r3944realms.lib39.core.register.Lib39Items;
import top.r3944realms.lib39.core.register.Lib39SoundEvents;
import top.r3944realms.lib39.datagen.value.ILangKeyValueCollection;
import top.r3944realms.lib39.datagen.value.LangKeyValue;
import top.r3944realms.lib39.datagen.value.ModPartEnum;
import top.r3944realms.lib39.example.core.register.ExLib39Items;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
/**
* The enum Lib 39 lang key.
*/
public enum Lib39LangKey implements ILangKeyValueCollection {
/**
* Instance lib 39 lang key.
*/
INSTANCE;
Lib39LangKey() {
initLangKeyValues();
}
/**
* The type Message.
*/
public static final class Message {
private static final Set<LangKeyValue> items = new HashSet<>();
/**
* The constant HELP_HEADER.
*/
public static final LangKeyValue HELP_HEADER = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.help.header", ModPartEnum.MESSAGE,
"===== %s =====",
"===== %s 命令帮助 =====",
"===== %s 命令幫助 =====",
" %s ", // 文言文表示分隔線
true
)
);
/**
* The constant HELP_CLICK_EXPAND.
*/
public static final LangKeyValue HELP_CLICK_EXPAND = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.help.click_expand", ModPartEnum.MESSAGE,
"Click to expand",
"點擊展開",
"點擊展開",
"點展",
true
)
);
/**
* The constant HELP_PAGE_INFO.
*/
public static final LangKeyValue HELP_PAGE_INFO = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.help.page_info", ModPartEnum.MESSAGE,
"Page %d of %d",
"第 %d 页,共 %d 页",
"第 %d 頁,共 %d 頁",
"第 %d 卷,凡 %d 卷", // 文言文表示頁
true
)
);
/**
* The constant HELP_NO_ENTRIES.
*/
public static final LangKeyValue HELP_NO_ENTRIES = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.help.no_entries", ModPartEnum.MESSAGE,
"No help entries available",
"暂无帮助条目",
"暫無幫助條目",
"尚無助之目錄", // 文言文尚無幫助的目錄
true
)
);
/**
* The constant HELP_TOGGLE_FAILED.
*/
public static final LangKeyValue HELP_TOGGLE_FAILED = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.help.toggle_failed", ModPartEnum.MESSAGE,
"Toggle Failed: No Hash Cached",
"切换失败: 无缓存Hash",
"切換失敗: 無緩存Hash",
"變更未果: 無貯存之哈希", // 文言文變更沒有成功沒有貯存的哈希
true
)
);
/**
* The constant HELP_COMMAND_NOT_FOUND.
*/
public static final LangKeyValue HELP_COMMAND_NOT_FOUND = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.help.command_not_found", ModPartEnum.MESSAGE,
"Command not found: %s",
"命令不存在: %s",
"指令不存在: %s",
"令未見之: %s", // 文言文命令沒有見到
true
)
);
/**
* The constant DOLL_SOUND.
*/
public static final LangKeyValue DOLL_SOUND = addAndRet(
LangKeyValue.ofKey(
Lib39SoundEvents.getSubTitleTranslateKey("duck_toy"), ModPartEnum.SOUND,
"Duck Doll Sound",
"玩偶声音",
"玩偶聲音",
"偶音",
true
)
);
/**
* The constant HELP_SUBCOMMANDS_TITLE.
*/
public static final LangKeyValue HELP_SUBCOMMANDS_TITLE = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.help.subcommands_title", ModPartEnum.MESSAGE,
"Subcommands:",
"子命令:",
"子指令:",
"子令:", // 文言文子命令
true
)
);
/**
* The constant HELP_NODE_EXPAND.
*/
public static final LangKeyValue HELP_NODE_EXPAND = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.help.node.expand", ModPartEnum.MESSAGE,
"%d subcommands collapsed",
"%d 个子命令已折叠",
"%d 個子指令已折疊",
"%d 子令已收", // 文言文子命令已經收起
true
)
);
/**
* The constant HELP_NODE_TOGGLE_EXPAND.
*/
public static final LangKeyValue HELP_NODE_TOGGLE_EXPAND = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.help.node.toggle.expand", ModPartEnum.MESSAGE,
"Expand",
"展开",
"展開",
"", // 文言文展開
true
)
);
/**
* The constant HELP_NODE_TOGGLE_COLLAPSE.
*/
public static final LangKeyValue HELP_NODE_TOGGLE_COLLAPSE = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.help.node.toggle.collapse", ModPartEnum.MESSAGE,
"Collapse",
"折叠",
"折疊",
"", // 文言文收起
true
)
);
/**
* The constant BASIC_HELP.
*/
public static final LangKeyValue BASIC_HELP = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.help.basic.help", ModPartEnum.MESSAGE,
"Show Help Info",
"显示帮助信息",
"顯示幫助信息",
"示助之訊", // 文言文顯示幫助的訊息
true
)
);
/**
* The constant HELP_HOVER_COPY_TIP.
*/
public static final LangKeyValue HELP_HOVER_COPY_TIP = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.help.hover.copy", ModPartEnum.MESSAGE,
"Copy to clipboard",
"点击复制",
"點擊複製",
"點之複刻", // 文言文點之複刻
true
)
);
private static LangKeyValue addAndRet(LangKeyValue item) {
items.add(item);
return item;
}
/**
* Gets items.
*
* @return the items
*/
public static Set<LangKeyValue> getItems() {
return items;
}
}
/**
* The Lang key values.
*/
final List<LangKeyValue> langKeyValues = new ArrayList<>();
/**
* Init lang key values.
*/
public void initLangKeyValues() {
Message.getItems().forEach(this::addLang);
LangKeyValue dollName = LangKeyValue.ofSupplier(
Lib39Items.DOLL, ModPartEnum.ITEM,
"Doll", "人偶", "人偶", "", false
);
addLang(dollName);
addLang(LangKeyValue.copyOf(
Lib39Blocks.DOLL, ModPartEnum.BLOCK, dollName
));
addLang(LangKeyValue.copyOf(
Lib39Blocks.WALL_DOLL, ModPartEnum.BLOCK, dollName
));
addLang(
LangKeyValue.ofKey("config.jade.plugin_lib39.lib39", ModPartEnum.DEFAULT,
"Lib 39", "叁玖库", "叁玖庫", "叁玖庫"
));
addLang(LangKeyValue.ofKey(
"tooltip.lib39.content.doll.hover.1", ModPartEnum.DESCRIPTION,
"§eSkinOwner §7:§a %s ", "§e皮肤所有者§7:§a%s", "§e皮膚所有者§7:§a%s", "§e膚主§7:§a%s"
));
addLang(LangKeyValue.ofKey(
"tooltip.lib39.content.doll.hover.2", ModPartEnum.DESCRIPTION,
"§7Rename with a player name in an anvil to change skin",
"§7在铁砧上可通过重命名对应玩家名来改变皮肤", "§7在鐵砧上可通過重命名對應玩家名來改變皮膚", "§7鐵砧之上更名以易膚"
));
addLang(LangKeyValue.ofKey(
"invalid.player_name.too_long", ModPartEnum.DESCRIPTION,
"§c§lPlayer 's Name is too long than 16 characters.",
"§c§l玩家名称过长最多16个字符", "§c§l玩家名稱過長最多16個字符", "§c§l玩家名過長限十六字"
));
if (Lib39.shouldRegisterExamples()) {
addLang(LangKeyValue.ofSupplier(
ExLib39Items.FABRIC, ModPartEnum.ITEM,
"Fabric", "织布", "織布", "", true
));
addLang(LangKeyValue.ofSupplier(
ExLib39Items.NEOFORGE, ModPartEnum.ITEM,
"NeoForge", "小狐狸", "狐狸", "", true
));
addLang(LangKeyValue.ofSupplier(
ExLib39Items.FORGE, ModPartEnum.ITEM,
"Forge", "铁砧", "铁砧", "", true
));
TestMessage.getItems().forEach(this::addLang);
}
}
/**
* Add lang.
*
* @param keyValue the key value
*/
public void addLang(LangKeyValue keyValue) {
langKeyValues.add(keyValue);
}
/**
* Clear.
*/
public void clear() {
langKeyValues.clear();
}
@Contract(pure = true)
@Override
public @Unmodifiable List<LangKeyValue> getValues() {
return List.copyOf(langKeyValues);
}
/**
* The type Test message.
*/
@SuppressWarnings("unused")
public static final class TestMessage {
private static final Set<LangKeyValue> items = new HashSet<>();
// ===== lib39 測試命令翻譯 =====
/**
* The constant LIB39_ROOT.
*/
public static final LangKeyValue LIB39_ROOT = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.root", ModPartEnum.MESSAGE,
"Lib39 Command System",
"Lib39 命令系統",
"Lib39 指令系統",
"Lib39 令系",
true
)
);
/**
* The constant LIB39_TEST.
*/
public static final LangKeyValue LIB39_TEST = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.test", ModPartEnum.MESSAGE,
"Test command",
"測試命令",
"測試指令",
"試令",
true
)
);
/**
* The constant LIB39_TEST_SUCCESS.
*/
public static final LangKeyValue LIB39_TEST_SUCCESS = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.test.success", ModPartEnum.MESSAGE,
"Test command executed successfully!",
"測試命令執行成功!",
"測試指令執行成功!",
"試令行成!",
true
)
);
/**
* The constant LIB39_TEST_WITH_PARAM.
*/
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
)
);
/**
* The constant LIB39_DEMO.
*/
public static final LangKeyValue LIB39_DEMO = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.demo", ModPartEnum.MESSAGE,
"Demo command",
"演示命令",
"演示指令",
"演令",
true
)
);
/**
* The constant LIB39_DEMO_MESSAGE.
*/
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
)
);
/**
* The constant LIB39_GREET_BASIC.
*/
public static final LangKeyValue LIB39_GREET_BASIC = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.greet.basic", ModPartEnum.MESSAGE,
"Greet everyone",
"向大家問好",
"向大家問好",
"問眾安",
true
)
);
/**
* The constant LIB39_GREET_DEFAULT.
*/
public static final LangKeyValue LIB39_GREET_DEFAULT = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.greet.default", ModPartEnum.MESSAGE,
"Hello everyone from Lib39!",
"來自 Lib39 的問候!",
"來自 Lib39 的問候!",
"自 Lib39 問安!",
true
)
);
/**
* The constant LIB39_GREET_PLAYER.
*/
public static final LangKeyValue LIB39_GREET_PLAYER = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.greet.player", ModPartEnum.MESSAGE,
"Greet specific player",
"問候特定玩家",
"問候特定玩家",
"問特者安",
true
)
);
/**
* The constant LIB39_GREET_RECEIVED.
*/
public static final LangKeyValue LIB39_GREET_RECEIVED = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.greet.received", ModPartEnum.MESSAGE,
"%s greeted you!",
"%s 向你問好!",
"%s 向你問好!",
"%s 問汝安!",
true
)
);
/**
* The constant LIB39_CALCULATE.
*/
public static final LangKeyValue LIB39_CALCULATE = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.calculate", ModPartEnum.MESSAGE,
"Calculate sum of two numbers",
"計算兩個數字的和",
"計算兩個數字的和",
"算二數和",
true
)
);
/**
* The constant LIB39_CALCULATE_RESULT.
*/
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
)
);
/**
* The constant LIB39_TELEPORT.
*/
public static final LangKeyValue LIB39_TELEPORT = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.teleport", ModPartEnum.MESSAGE,
"Teleport to player (OP only)",
"傳送到玩家僅OP",
"傳送到玩家僅OP",
"送至者(唯管)",
true
)
);
/**
* The constant LIB39_TELEPORT_SUCCESS.
*/
public static final LangKeyValue LIB39_TELEPORT_SUCCESS = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.teleport.success", ModPartEnum.MESSAGE,
"Teleported to %s",
"已傳送至 %s",
"已傳送至 %s",
"已送至 %s",
true
)
);
/**
* The constant LIB39_INFO.
*/
public static final LangKeyValue LIB39_INFO = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.info", ModPartEnum.MESSAGE,
"Show player information",
"顯示玩家信息",
"顯示玩家資訊",
"示者訊",
true
)
);
/**
* The constant LIB39_INFO_MESSAGE.
*/
public static final LangKeyValue LIB39_INFO_MESSAGE = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.info.message", ModPartEnum.MESSAGE,
"=== Player Information ===",
"=== 玩家信息 ===",
"=== 玩家資訊 ===",
" 者訊 ",
true
)
);
/**
* The constant LIB39_INFO_DIMENSION.
*/
public static final LangKeyValue LIB39_INFO_DIMENSION = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.info.dimension", ModPartEnum.MESSAGE,
"Dimension: %s",
"維度:%s",
"維度:%s",
"界:%s",
true
)
);
/**
* The constant LIB39_INFO_POSITION.
*/
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
)
);
/**
* The constant LIB39_TEAM.
*/
public static final LangKeyValue LIB39_TEAM = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.team", ModPartEnum.MESSAGE,
"Team management",
"隊伍管理",
"隊伍管理",
"隊管",
true
)
);
/**
* The constant LIB39_TEAM_CREATE.
*/
public static final LangKeyValue LIB39_TEAM_CREATE = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.team.create", ModPartEnum.MESSAGE,
"Create a new team",
"創建新隊伍",
"創建新隊伍",
"創新隊",
true
)
);
/**
* The constant LIB39_TEAM_CREATE_SUCCESS.
*/
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
)
);
/**
* The constant LIB39_TEAM_JOIN.
*/
public static final LangKeyValue LIB39_TEAM_JOIN = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.team.join", ModPartEnum.MESSAGE,
"Join a team",
"加入隊伍",
"加入隊伍",
"入隊",
true
)
);
/**
* The constant LIB39_TEAM_JOIN_SUCCESS.
*/
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
)
);
/**
* The constant LIB39_TEAM_LEAVE.
*/
public static final LangKeyValue LIB39_TEAM_LEAVE = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.team.leave", ModPartEnum.MESSAGE,
"Leave current team",
"離開當前隊伍",
"離開當前隊伍",
"離現隊",
true
)
);
/**
* The constant LIB39_TEAM_LEAVE_SUCCESS.
*/
public static final LangKeyValue LIB39_TEAM_LEAVE_SUCCESS = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.team.leave.success", ModPartEnum.MESSAGE,
"Left the team",
"已離開隊伍",
"已離開隊伍",
"已離隊",
true
)
);
/**
* The constant LIB39_GAME.
*/
public static final LangKeyValue LIB39_GAME = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.game", ModPartEnum.MESSAGE,
"Game control",
"遊戲控制",
"遊戲控制",
"戲控",
true
)
);
/**
* The constant LIB39_GAME_START.
*/
public static final LangKeyValue LIB39_GAME_START = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.game.start", ModPartEnum.MESSAGE,
"Start a game",
"開始遊戲",
"開始遊戲",
"啟戲",
true
)
);
/**
* The constant LIB39_GAME_START_SUCCESS.
*/
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
)
);
/**
* The constant LIB39_GAME_STOP.
*/
public static final LangKeyValue LIB39_GAME_STOP = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.game.stop", ModPartEnum.MESSAGE,
"Stop current game",
"停止當前遊戲",
"停止當前遊戲",
"止現戲",
true
)
);
/**
* The constant LIB39_GAME_STOP_SUCCESS.
*/
public static final LangKeyValue LIB39_GAME_STOP_SUCCESS = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.game.stop.success", ModPartEnum.MESSAGE,
"Game stopped!",
"遊戲已停止!",
"遊戲已停止!",
"戲已止!",
true
)
);
/**
* The constant LIB39_GAME_PAUSE.
*/
public static final LangKeyValue LIB39_GAME_PAUSE = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.game.pause", ModPartEnum.MESSAGE,
"Pause current game",
"暫停當前遊戲",
"暫停當前遊戲",
"暫現戲",
true
)
);
/**
* The constant LIB39_GAME_PAUSE_SUCCESS.
*/
public static final LangKeyValue LIB39_GAME_PAUSE_SUCCESS = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.game.pause.success", ModPartEnum.MESSAGE,
"Game paused!",
"遊戲已暫停!",
"遊戲已暫停!",
"戲已暫!",
true
)
);
/**
* The constant LIB39_GAME_RESUME.
*/
public static final LangKeyValue LIB39_GAME_RESUME = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.game.resume", ModPartEnum.MESSAGE,
"Resume paused game",
"恢復暫停的遊戲",
"恢復暫停的遊戲",
"復暫戲",
true
)
);
/**
* The constant LIB39_GAME_RESUME_SUCCESS.
*/
public static final LangKeyValue LIB39_GAME_RESUME_SUCCESS = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.game.resume.success", ModPartEnum.MESSAGE,
"Game resumed!",
"遊戲已恢復!",
"遊戲已恢復!",
"戲已復!",
true
)
);
/**
* The constant LIB39_SETTINGS.
*/
public static final LangKeyValue LIB39_SETTINGS = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.settings", ModPartEnum.MESSAGE,
"Show settings",
"顯示設置",
"顯示設定",
"示置",
true
)
);
/**
* The constant LIB39_CONFIG.
*/
public static final LangKeyValue LIB39_CONFIG = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.config", ModPartEnum.MESSAGE,
"Show configuration",
"顯示配置",
"顯示設定",
"示配",
true
)
);
/**
* The constant LIB39_RELOAD.
*/
public static final LangKeyValue LIB39_RELOAD = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.reload", ModPartEnum.MESSAGE,
"Reload configuration",
"重新加載配置",
"重新載入設定",
"重載配",
true
)
);
/**
* The constant LIB39_DEBUG.
*/
public static final LangKeyValue LIB39_DEBUG = addAndRet(
LangKeyValue.ofKey(
"commands.lib39.debug", ModPartEnum.MESSAGE,
"Debug information",
"調試信息",
"除錯資訊",
"調訊",
true
)
);
// ===== 添加缺失的導入 =====
private static final Consumer<LangKeyValue> addConsumer = items::add;
private static LangKeyValue addAndRet(LangKeyValue item) {
items.add(item);
return item;
}
/**
* Gets items.
*
* @return the items
*/
public static Set<LangKeyValue> getItems() {
return items;
}
}
}

View File

@ -1,771 +0,0 @@
package top.r3944realms.lib39.client.gui.component;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import net.minecraft.MethodsReturnNonnullByDefault;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.narration.NarrationElementOutput;
import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.network.chat.Component;
import org.joml.Matrix4f;
import org.joml.Vector2f;
import top.r3944realms.lib39.client.shader.Lib39Shaders;
import top.r3944realms.lib39.util.MathUtil;
import top.r3944realms.lib39.util.lang.FourConsumer;
import top.r3944realms.lib39.util.lang.Pair;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* The type Wheel widget.
*
* @author QiuShui1012
*/
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
public class WheelWidget extends AbstractWidget {
/**
* The constant IGNORE_CURSOR_MOVE_LENGTH.
*/
public static final int IGNORE_CURSOR_MOVE_LENGTH = 15;
private static final Vector2f ROTATION_START = new Vector2f(0, 1);
private static final int SELECTION_EFFECT_COLOR = 0xddFFFF00;
private static final int SELECTION_EFFECT_RADIUS = 20;
private final Minecraft minecraft = Minecraft.getInstance();
private final Vector2f centerPos;
private final float ringInnerRadius;
private final float ringOuterRadius;
private final int delay;
private final int animationMs;
private final int closingAnimationMs; //ms
private final int ringColor;
private final int selectionEffectColor;
private final int selectionEffectRadius;
private final float selectionAnimationSpeedFactor;
private final int textColor;
private final float textScale;
private final List<WheelSection> sections = new ArrayList<>();
private long displayTime = System.currentTimeMillis();
private float currentAngle = 0;
/**
* Gets current section index.
*
* @return the current section index
*/
public int getCurrentSectionIndex() {
return currentSectionIndex;
}
/**
* Is closing animation started boolean.
*
* @return the boolean
*/
public boolean isClosingAnimationStarted() {
return closingAnimationStarted;
}
private int currentSectionIndex = -1;
private Vector2f selectionEffectPos;
private boolean animationStarted = false;
/**
* Sets closing animation started.
*
* @param closingAnimationStarted the closing animation started
*/
public void setClosingAnimationStarted(boolean closingAnimationStarted) {
this.closingAnimationStarted = closingAnimationStarted;
}
private boolean closingAnimationStarted = false;
/**
* Instantiates a new Wheel widget.
*
* @param x the x
* @param y the y
* @param width the width
* @param height the height
* @param ringInnerRadius the ring inner radius
* @param ringOuterRadius the ring outer radius
* @param textScale the text scale
* @param sections the sections
*/
public WheelWidget(
int x, int y, int width, int height,
float ringInnerRadius, float ringOuterRadius, float textScale,
List<Pair<Component, FourConsumer<GuiGraphics, PoseStack, Integer, Integer>>> sections
) {
this(x, y, width, height, Component.empty(), ringInnerRadius, ringOuterRadius, textScale, sections);
}
/**
* Instantiates a new Wheel widget.
*
* @param x the x
* @param y the y
* @param width the width
* @param height the height
* @param ringInnerRadius the ring inner radius
* @param ringOuterRadius the ring outer radius
* @param textScale the text scale
* @param degreeOffsetAngle the degree offset angle
* @param sections the sections
*/
public WheelWidget(
int x, int y, int width, int height,
float ringInnerRadius, float ringOuterRadius, float textScale, float degreeOffsetAngle,
List<Pair<Component, FourConsumer<GuiGraphics, PoseStack, Integer, Integer>>> sections
) {
this(x, y, width, height, Component.empty(), ringInnerRadius, ringOuterRadius, textScale, degreeOffsetAngle, sections);
}
/**
* Instantiates a new Wheel widget.
*
* @param x the x
* @param y the y
* @param width the width
* @param height the height
* @param ringInnerRadius the ring inner radius
* @param ringOuterRadius the ring outer radius
* @param sections the sections
*/
public WheelWidget(
int x, int y, int width, int height,
float ringInnerRadius, float ringOuterRadius,
List<Pair<Component, FourConsumer<GuiGraphics, PoseStack, Integer, Integer>>> sections
) {
this(x, y, width, height, Component.empty(), ringInnerRadius, ringOuterRadius, sections);
}
/**
* Instantiates a new Wheel widget.
*
* @param x the x
* @param y the y
* @param width the width
* @param height the height
* @param message the message
* @param ringInnerRadius the ring inner radius
* @param ringOuterRadius the ring outer radius
* @param textScale the text scale
* @param degreeOffsetAngle the degree offset angle
* @param sections the sections
*/
public WheelWidget(
int x, int y, int width, int height, Component message,
float ringInnerRadius, float ringOuterRadius, float textScale, float degreeOffsetAngle,
List<Pair<Component, FourConsumer<GuiGraphics, PoseStack, Integer, Integer>>> sections
) {
this(
x, y, width, height, message,
ringInnerRadius, ringOuterRadius,
150, 300, 150,
0x00000000,
0xddffff00, 20, 5f,
0xfdfdfd, textScale, degreeOffsetAngle,
sections
);
}
/**
* Instantiates a new Wheel widget.
*
* @param x the x
* @param y the y
* @param width the width
* @param height the height
* @param message the message
* @param ringInnerRadius the ring inner radius
* @param ringOuterRadius the ring outer radius
* @param sections the sections
*/
public WheelWidget(
int x, int y, int width, int height, Component message,
float ringInnerRadius, float ringOuterRadius,
List<Pair<Component, FourConsumer<GuiGraphics, PoseStack, Integer, Integer>>> sections
) {
this(
x, y, width, height, message,
ringInnerRadius, ringOuterRadius,
150, 300, 150,
0x00000000,
0xddffff00, 20, 5f,
0xfdfdfd, 1f, 0f,
sections
);
}
/**
* Instantiates a new Wheel widget.
*
* @param x the x
* @param y the y
* @param width the width
* @param height the height
* @param message the message
* @param ringInnerRadius the ring inner radius
* @param ringOuterRadius the ring outer radius
* @param degreeOffsetAngle the degree offset angle
* @param sections the sections
*/
public WheelWidget(
int x, int y, int width, int height, Component message,
float ringInnerRadius, float ringOuterRadius, float degreeOffsetAngle,
List<Pair<Component, FourConsumer<GuiGraphics, PoseStack, Integer, Integer>>> sections
) {
this(
x, y, width, height, message,
ringInnerRadius, ringOuterRadius,
150, 300, 150,
0x00000000,
0xddffff00, 20, 5f,
0xfdfdfd, 1f, degreeOffsetAngle,
sections
);
}
/**
* Instantiates a new Wheel widget.
*
* @param x the x
* @param y the y
* @param width the width
* @param height the height
* @param ringInnerRadius the ring inner radius
* @param ringOuterRadius the ring outer radius
* @param delay the delay
* @param animationMs the animation ms
* @param closingAnimationMs the closing animation ms
* @param ringColor the ring color
* @param selectionEffectColor the selection effect color
* @param selectionEffectRadius the selection effect radius
* @param selectionAnimationSpeedFactor the selection animation speed factor
* @param textColor the text color
* @param textScale the text scale
* @param sections the sections
*/
public WheelWidget(
int x, int y, int width, int height,
float ringInnerRadius, float ringOuterRadius,
int delay, int animationMs, int closingAnimationMs,
int ringColor,
int selectionEffectColor, int selectionEffectRadius, float selectionAnimationSpeedFactor,
int textColor, float textScale,
List<Pair<Component, FourConsumer<GuiGraphics, PoseStack, Integer, Integer>>> sections
) {
this(
x, y, width, height, Component.empty(),
ringInnerRadius, ringOuterRadius,
delay, animationMs, closingAnimationMs,
ringColor,
selectionEffectColor, selectionEffectRadius, selectionAnimationSpeedFactor,
textColor, textScale, 0f,
sections
);
}
/**
* Instantiates a new Wheel widget.
*
* @param x the x
* @param y the y
* @param width the width
* @param height the height
* @param message the message
* @param ringInnerRadius the ring inner radius
* @param ringOuterRadius the ring outer radius
* @param delay the delay
* @param animationMs the animation ms
* @param closingAnimationMs the closing animation ms
* @param ringColor the ring color
* @param selectionEffectColor the selection effect color
* @param selectionEffectRadius the selection effect radius
* @param selectionAnimationSpeedFactor the selection animation speed factor
* @param textColor the text color
* @param textScale the text scale
* @param degreeOffsetAngle the degree offset angle
* @param sections the sections
*/
public WheelWidget(
int x, int y, int width, int height, Component message,
float ringInnerRadius, float ringOuterRadius,
int delay, int animationMs, int closingAnimationMs,
int ringColor,
int selectionEffectColor, int selectionEffectRadius, float selectionAnimationSpeedFactor,
int textColor, float textScale, float degreeOffsetAngle,
List<Pair<Component, FourConsumer<GuiGraphics, PoseStack, Integer, Integer>>> sections
) {
super(x, y, width, height, message);
this.centerPos = new Vector2f(this.getX() + this.getWidth() / 2f, this.getY() + this.getHeight() / 2f);
this.ringInnerRadius = Math.max(ringInnerRadius, IGNORE_CURSOR_MOVE_LENGTH);
this.ringOuterRadius = ringOuterRadius;
this.delay = delay;
this.animationMs = animationMs;
this.closingAnimationMs = closingAnimationMs;
this.ringColor = ringColor;
this.selectionEffectColor = selectionEffectColor;
this.selectionEffectRadius = selectionEffectRadius;
this.selectionAnimationSpeedFactor = selectionAnimationSpeedFactor;
this.textColor = textColor;
this.textScale = textScale;
float degreeEachRotation = 360f / sections.size();
for (int i = 0; i < sections.size(); i++) {
Pair<Component, FourConsumer<GuiGraphics, PoseStack, Integer, Integer>> section = sections.get(i);
float rotation = MathUtil.clampWithProportion((degreeEachRotation * i + degreeOffsetAngle) % 360, 0, 360);
Vector2f rotated = MathUtil.rotationDegrees(ROTATION_START, rotation)
.mul(1, -1)
.mul(this.getSectionCircleDiameter())
.add(this.centerPos);
float detectionStart = (float) (Math.toRadians(rotation - degreeEachRotation / 2f) + Math.PI * 2);
float detectionEnd = (float) (Math.toRadians(rotation + degreeEachRotation / 2f) + Math.PI * 2);
detectionStart = detectionStart % (float) (Math.PI * 2);
detectionEnd = detectionEnd % (float) (Math.PI * 2);
this.sections.add(new WheelSection(
rotated,
(float) (Math.toRadians(rotation) % (Math.PI * 2)),
detectionStart,
detectionEnd,
section.first,
section.second
));
}
this.selectionEffectPos = MathUtil.rotate(
MathUtil.copy(ROTATION_START)
.mul(this.getSectionCircleDiameter()),
this.currentAngle
);
}
/**
* Gets section circle diameter.
*
* @return the section circle diameter
*/
public float getSectionCircleDiameter() {
// 滚轮选择器中每个扇形的圆形直径
return this.ringOuterRadius + this.ringInnerRadius;
}
/**
* Sets current index.
*
* @param index the index
* @return the current index
*/
public WheelWidget setCurrentIndex(int index) {
this.currentSectionIndex = index;
this.currentAngle = this.sections.get(index).angle;
this.selectionEffectPos = MathUtil.rotate(
MathUtil.copy(ROTATION_START)
.mul(this.getSectionCircleDiameter()),
this.currentAngle
);
return this;
}
/**
* Gets section size.
*
* @return the section size
*/
public int getSectionSize() {
return this.sections.size();
}
@Override
public boolean mouseScrolled(double mouseX, double mouseY, double delta) {
if (delta > 0) {
if (this.currentSectionIndex == this.getSectionSize() - 1) {
this.currentSectionIndex = 0;
} else {
this.currentSectionIndex++;
}
} else if (delta < 0) {
if (this.currentSectionIndex == 0) {
this.currentSectionIndex = this.getSectionSize() - 1;
} else {
this.currentSectionIndex--;
}
}
for (WheelSection section : this.sections) {
if (this.sections.indexOf(section) == this.currentSectionIndex) {
this.currentAngle = section.angle;
return true;
}
}
return true;
}
/**
* Check mouse pos.
*
* @param mouseX the mouse x
* @param mouseY the mouse y
*/
public void checkMousePos(double mouseX, double mouseY) {
if (this.closingAnimationStarted) return;
float centerX = this.centerPos.x;
float centerY = this.centerPos.y;
// 鼠标距离屏幕中心的位置向量
Vector2f cursorPos = new Vector2f((float) mouseX - centerX, (float) mouseY - centerY);
if (cursorPos.length() < IGNORE_CURSOR_MOVE_LENGTH) return;
Vector2f rotationStart = new Vector2f(0, 1);
cursorPos.normalize();
// 计算夹角弧度
double rot = Math.acos(rotationStart.dot(cursorPos) / (rotationStart.length() * cursorPos.length()));
double rotation = cursorPos.x < 0 ? Math.PI - rot : Math.PI + rot;
for (WheelSection section : this.sections) {
if (section.angleStart > section.angleEnd && rotation >= section.angleStart
|| rotation >= section.angleStart && rotation <= section.angleEnd
) {
this.currentAngle = section.angle;
this.currentSectionIndex = this.sections.indexOf(section);
break;
}
}
}
/**
* Should render boolean.
*
* @return the boolean
*/
public boolean shouldRender() {
if (this.animationStarted) return true;
return (this.displayTime + this.delay) <= System.currentTimeMillis();
}
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
this.checkMousePos(mouseX, mouseY);
this.renderWidget(guiGraphics, mouseX, mouseY, partialTick);
}
@Override
protected void renderWidget(GuiGraphics guiGraphics, int i, int i1, float v) {
RenderSystem.enableDepthTest();
RenderSystem.enableBlend();
this.renderClosingAnimation(guiGraphics);
if (!this.shouldRender()) {
return;
}
if (this.closingAnimationStarted) return;
if (!this.animationStarted) {
this.animationStarted = true;
this.displayTime = System.currentTimeMillis();
}
PoseStack poseStack = guiGraphics.pose();
float delta = this.displayTime + this.animationMs - System.currentTimeMillis();
if (delta > 0) {
float progress = 1 - (delta / this.animationMs);
progress = (float) (-Math.pow(progress, 2) + 2 * progress);
if (progress == 0) return;
this.renderProgressAnimation(guiGraphics, progress);
return;
}
renderRing(
guiGraphics,
this.centerPos.x,
this.centerPos.y,
this.ringColor,
this.ringInnerRadius * 2,
this.ringOuterRadius * 2
);
this.renderSelection(guiGraphics);
for (WheelSection value : this.sections) {
float x = value.center.x;
float y = value.center.y;
poseStack.pushPose();
poseStack.translate(x - 10, y - 10, 100);
value.renderer.accept(guiGraphics, poseStack, 20, 20);
poseStack.popPose();
poseStack.pushPose();
float coordinateScale = 0.7f;
float offsetX = 0.1f * this.width;
float offsetY = 0.1f * this.height;
float adjustedX = (x - offsetX) / coordinateScale;
float adjustedY = (y - offsetY - 20 * this.textScale) / coordinateScale;
poseStack.translate(offsetX, offsetY, 0);
poseStack.scale(coordinateScale, coordinateScale, coordinateScale);
poseStack.translate(adjustedX, adjustedY, 0);
poseStack.scale(this.textScale / coordinateScale, this.textScale / coordinateScale, this.textScale / coordinateScale);
guiGraphics.drawCenteredString(
minecraft.font,
value.subTitle,
0,
0,
(0xff << 24) | this.textColor
);
poseStack.popPose();
}
RenderSystem.disableDepthTest();
RenderSystem.disableBlend();
}
/**
* Render closing animation.
*
* @param guiGraphics the gui graphics
*/
public void renderClosingAnimation(GuiGraphics guiGraphics) {
if (!this.closingAnimationStarted) return;
float delta = this.displayTime + this.closingAnimationMs - System.currentTimeMillis();
float progress = delta / this.closingAnimationMs;
if(progress >= 1 || progress <= 0) {
this.minecraft.setScreen(null);
}
this.renderProgressAnimation(guiGraphics, progress);
}
private void renderProgressAnimation(GuiGraphics guiGraphics, float progress) {
progress = (float) (-Math.pow(progress, 2) + 2 * progress);
if (progress == 0) return;
PoseStack poseStack = guiGraphics.pose();
poseStack.pushPose();
renderRing(
guiGraphics,
this.centerPos.x,
this.centerPos.y,
this.ringColor,
this.ringInnerRadius * 2 * progress,
this.ringOuterRadius * 2 * progress
);
poseStack.popPose();
if(this.currentSectionIndex != -1) {
WheelSection section = this.sections.get(this.currentSectionIndex);
Vector2f center = new Vector2f(
(section.center.x - this.centerPos.x) / this.getSectionCircleDiameter(),
(section.center.y - this.centerPos.y) / this.getSectionCircleDiameter()
).mul(this.getSectionCircleDiameter() * progress).add(this.centerPos.x, this.centerPos.y);
renderSelectionEffect(
guiGraphics,
center.x,
center.y,
this.selectionEffectColor,
this.selectionEffectRadius
);
}
for (WheelSection value : this.sections) {
if (sections.get(0) != value) continue;
Vector2f center = new Vector2f(
(value.center.x - this.centerPos.x) / this.getSectionCircleDiameter(),
(value.center.y - this.centerPos.y) / this.getSectionCircleDiameter()
).mul(this.getSectionCircleDiameter() * progress).add(this.centerPos.x, this.centerPos.y);
float x = center.x;
float y = center.y;
poseStack.pushPose();
poseStack.translate(x - 10, y - 10, 100);
value.renderer.accept(guiGraphics, poseStack, 20, 20);
poseStack.pushPose();
}
}
/**
* Render ring.
*
* @param guiGraphics the gui graphics
* @param centerX the center x
* @param centerY the center y
* @param color the color
* @param innerRadius the inner radius
* @param outerRadius the outer radius
*/
public static void renderRing(
GuiGraphics guiGraphics,
float centerX,
float centerY,
int color,
float innerRadius, // 改为半径
float outerRadius // 改为半径
) {
PoseStack poseStack = guiGraphics.pose();
poseStack.pushPose();
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder buffer = tesselator.getBuilder();
// 计算足够大的绘制区域来覆盖整个环形基于外半径
float margin = outerRadius + 100f; // 使用半径计算边距
float x1 = centerX - margin;
float y1 = centerY - margin;
float x2 = centerX + margin;
float y2 = centerY + margin;
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
Matrix4f matrix = poseStack.last().pose();
buffer.vertex(matrix, x1, y1, -300).color(color).endVertex();
buffer.vertex(matrix, x1, y2, -300).color(color).endVertex();
buffer.vertex(matrix, x2, y2, -300).color(color).endVertex();
buffer.vertex(matrix, x2, y1, -300).color(color).endVertex();
setupRingShader(centerX, centerY, innerRadius, outerRadius);
BufferUploader.drawWithShader(buffer.end());
poseStack.popPose();
}
private static void setupRingShader(float centerX, float centerY, float innerRadius, float outerRadius) {
Window window = Minecraft.getInstance().getWindow();
float guiScale = (float) window.getGuiScale();
RenderSystem.setShader(Lib39Shaders::getRingShader);
// 转换到像素坐标考虑GUI缩放
float pixelCenterX = centerX * guiScale;
float pixelCenterY = window.getHeight() - (centerY * guiScale); // 翻转Y坐标
// 半径考虑GUI缩放
float pixelInnerRadius = innerRadius * guiScale;
float pixelOuterRadius = outerRadius * guiScale;
float pixelAntiAliasing = 2.0f * guiScale; // 抗锯齿范围
// if (Services.PLATFORM.isDevelopmentEnvironment()) {
// System.out.println("Shader Params - Center: (" + pixelCenterX + ", " + pixelCenterY +
// "), InnerRadius: " + pixelInnerRadius + ", OuterRadius: " + pixelOuterRadius);
// }
ShaderInstance shader = Lib39Shaders.getRingShader();
shader.safeGetUniform("Center").set(pixelCenterX, pixelCenterY);
shader.safeGetUniform("InnerRadius").set(pixelInnerRadius);
shader.safeGetUniform("OuterRadius").set(pixelOuterRadius);
shader.safeGetUniform("AntiAliasing").set(pixelAntiAliasing);
shader.safeGetUniform("ColorModulator").set(1.0f, 1.0f, 1.0f, .5f);
}
private void renderSelection(GuiGraphics guiGraphics) {
float selectionEffectAngle = MathUtil.angle(
MathUtil.copy(ROTATION_START),
this.selectionEffectPos
);
float diffAngle = this.currentAngle - selectionEffectAngle;
if (diffAngle > Math.PI) {
diffAngle -= (float) (Math.PI * 2);
} else if (diffAngle < -Math.PI) {
diffAngle += (float) (Math.PI * 2);
}
this.selectionEffectPos = MathUtil.rotate(
this.selectionEffectPos,
diffAngle / this.selectionAnimationSpeedFactor
);
Vector2f pos = MathUtil.copy(this.selectionEffectPos)
.mul(1, -1)
.add(this.centerPos);
// 调用时使用半径
renderSelectionEffect(
guiGraphics,
pos.x,
pos.y,
SELECTION_EFFECT_COLOR,
SELECTION_EFFECT_RADIUS // 确保这是半径值
);
}
/**
* Render selection effect.
*
* @param guiGraphics the gui graphics
* @param centerX the center x
* @param centerY the center y
* @param color the color
* @param radius the radius
*/
public static void renderSelectionEffect(
GuiGraphics guiGraphics,
float centerX,
float centerY,
int color,
float radius
) {
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.disableDepthTest();
PoseStack poseStack = guiGraphics.pose();
Matrix4f matrix4f = poseStack.last().pose();
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder buffer = tesselator.getBuilder();
buffer.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
float x1 = centerX - radius - 5;
float y1 = centerY - radius - 5;
float x2 = centerX + radius + 5;
float y2 = centerY + radius + 5;
buffer.vertex(matrix4f, x1, y1, -200).color(color).endVertex();
buffer.vertex(matrix4f, x1, y2, -200).color(color).endVertex();
buffer.vertex(matrix4f, x2, y2, -200).color(color).endVertex();
buffer.vertex(matrix4f, x2, y1, -200).color(color).endVertex();
Window window = Minecraft.getInstance().getWindow();
float guiScale = (float) window.getGuiScale();
RenderSystem.setShader(Lib39Shaders::getSelectionShader);
System.out.println("Selection Effect Params:");
System.out.println(" Center: " + centerX + ", " + centerY);
System.out.println(" Radius: " + radius);
System.out.println(" GUI Scale: " + guiScale);
System.out.println(" Framebuffer: " + window.getWidth() + "x" + window.getHeight());
Lib39Shaders.getSelectionShader()
.safeGetUniform("Center")
.set(centerX * guiScale, centerY * guiScale);
Lib39Shaders.getSelectionShader()
.safeGetUniform("FramebufferSize")
.set((float) window.getWidth(), (float) window.getHeight());
Lib39Shaders.getSelectionShader()
.safeGetUniform("Radius")
.set(radius * guiScale);
Lib39Shaders.getSelectionShader()
.safeGetUniform("AntiAliasingRadius")
.set(guiScale); // 根据需要调整
RenderSystem.setShaderColor(1, 1, 1, 1);
BufferUploader.drawWithShader(Objects.requireNonNull(buffer.end()));
RenderSystem.enableDepthTest();
}
/**
* On closing.
*/
public void onClosing() {
if (this.shouldRender() && !this.closingAnimationStarted) {
this.displayTime = System.currentTimeMillis();
this.closingAnimationStarted = true;
} else {
this.minecraft.setScreen(null);
}
}
@Override
protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) {
}
/**
* The type Wheel section.
*/
public record WheelSection(
Vector2f center,
float angle,
float angleStart,
float angleEnd,
Component subTitle,
FourConsumer<GuiGraphics, PoseStack, Integer, Integer> renderer
) {
}
}

View File

@ -1,218 +0,0 @@
package top.r3944realms.lib39.client.model;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.model.Model;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.client.model.geom.builders.*;
import net.minecraft.client.renderer.RenderType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.lib39.Lib39;
/**
* The type Doll model.
*/
public class DollModel extends Model {
/**
* The constant LAYER_LOCATION.
*/
public static final ModelLayerLocation LAYER_LOCATION = new ModelLayerLocation(Lib39.rl("doll_model"), "main");
/**
* The Slim.
*/
public boolean slim = false;
private final ModelPart head;
private final ModelPart body;
private final ModelPart rightArm;
private final ModelPart leftArm;
private final ModelPart rightLeg;
private final ModelPart rightArmSlim;
private final ModelPart leftArmSlim;
private final ModelPart leftLeg;
@Nullable
private IDollPose currentPose;
/**
* Gets current pose.
*
* @return the current pose
*/
@Nullable
public IDollPose getCurrentPose() {
return currentPose;
}
/**
* Sets doll pose.
*
* @param dollPose the doll pose
*/
public void setDollPose(@NotNull IDollPose dollPose) {
this.currentPose = dollPose;
this.head.loadPose(dollPose.getHeadPose());
this.body.loadPose(dollPose.getBodyPose());
this.rightArm.loadPose(dollPose.getRightArmPose());
this.leftArm.loadPose(dollPose.getLeftArmPose());
this.rightArmSlim.loadPose(dollPose.getRightArmPose());
this.leftArmSlim.loadPose(dollPose.getLeftArmPose());
this.rightLeg.loadPose(dollPose.getRightLegPose());
this.leftLeg.loadPose(dollPose.getLeftLegPose());
}
/**
* Reset pose.
*/
public void resetPose() {
this.currentPose = DollPoses.DEFAULT;
this.head.resetPose();
this.body.resetPose();
this.rightArm.resetPose();
this.leftArm.resetPose();
this.rightArmSlim.resetPose();
this.leftArmSlim.resetPose();
this.rightLeg.resetPose();
this.leftLeg.resetPose();
}
/**
* Instantiates a new Doll model.
*
* @param root the root
*/
public DollModel(@NotNull ModelPart root) {
super(RenderType::entityTranslucent);
this.head = root.getChild("head");
this.body = root.getChild("body");
this.rightArm = root.getChild("right_arm");
this.leftArm = root.getChild("left_arm");
this.rightArmSlim = root.getChild("right_arm_slim");
this.leftArmSlim = root.getChild("left_arm_slim");
this.rightLeg = root.getChild("right_leg");
this.leftLeg = root.getChild("left_leg");
}
/**
* Create body layer layer definition.
*
* @return the layer definition
*/
public static @NotNull LayerDefinition createBodyLayer() {
return createBodyLayer(DollPoses.DEFAULT);
}
private static @NotNull LayerDefinition createBodyLayer(@NotNull IDollPose dollPoses) {
MeshDefinition meshdefinition = new MeshDefinition();
PartDefinition partdefinition = meshdefinition.getRoot();
partdefinition.addOrReplaceChild(
"head",
CubeListBuilder.create()
.texOffs(0, 0)
.addBox(-4.0F, -8.0F, -4.0F, 8.0F, 8.0F, 8.0F, new CubeDeformation(0.0F))
.texOffs(32, 0)
.addBox(
-4.0F, -8.0F, -4.0F, 8.0F, 8.0F, 8.0F,
new CubeDeformation(0.5F)
), dollPoses.getHeadPose()
);
partdefinition.addOrReplaceChild(
"body",
CubeListBuilder.create()
.texOffs(16, 16)
.addBox(-4.0F, 0.0F, -2.0F, 8.0F, 12.0F, 4.0F, new CubeDeformation(0.0F))
.texOffs(16, 32)
.addBox(
-4.0F, 0.0F, -2.0F, 8.0F, 12.0F, 4.0F,
new CubeDeformation(0.25F)
), dollPoses.getBodyPose()
);
partdefinition.addOrReplaceChild(
"right_arm",
CubeListBuilder.create()
.texOffs(40, 16)
.addBox(-3.0F, -2.0F, -2.0F, 4.0F, 12.0F, 4.0F, new CubeDeformation(0.0F))
.texOffs(40, 32)
.addBox(
-3.0F, -2.0F, -2.0F, 4.0F, 12.0F, 4.0F,
new CubeDeformation(0.25F)
), dollPoses.getRightArmPose()
);
partdefinition.addOrReplaceChild(
"right_arm_slim",
CubeListBuilder.create()
.texOffs(40, 16)
.addBox(-2.0F, -2.0F, -2.0F, 3.0F, 12.0F, 4.0F, new CubeDeformation(0.0F))
.texOffs(40, 32)
.addBox(-2.0F, -2.0F, -2.0F, 3.0F, 12.0F, 4.0F,
new CubeDeformation(0.25F)
), dollPoses.getRightArmPose()
);
partdefinition.addOrReplaceChild(
"left_arm_slim",
CubeListBuilder.create()
.texOffs(32, 48)
.addBox(-1.0F, -2.0F, -2.0F, 3.0F, 12.0F, 4.0F, new CubeDeformation(0.0F))
.texOffs(48, 48)
.addBox(
-1.0F, -2.0F, -2.0F, 3.0F, 12.0F, 4.0F,
new CubeDeformation(0.25F)
), dollPoses.getLeftArmPose()
);
partdefinition.addOrReplaceChild(
"left_arm",
CubeListBuilder.create()
.texOffs(32, 48)
.addBox(-1.0F, -2.0F, -2.0F, 4.0F, 12.0F, 4.0F, new CubeDeformation(0.0F))
.texOffs(48, 48)
.addBox(
-1.0F, -2.0F, -2.0F, 4.0F, 12.0F, 4.0F,
new CubeDeformation(0.25F)
), dollPoses.getLeftArmPose()
);
partdefinition.addOrReplaceChild(
"right_leg",
CubeListBuilder.create()
.texOffs(0, 16)
.addBox(-2.0F, 0.0F, -2.0F, 4.0F, 12.0F, 4.0F, new CubeDeformation(0.0F))
.texOffs(0, 32)
.addBox(
-2.0F, 0.0F, -2.0F, 4.0F, 12.0F, 4.0F, new CubeDeformation(0.25F)
), dollPoses.getRightLegPose()
);
partdefinition.addOrReplaceChild(
"left_leg",
CubeListBuilder.create()
.texOffs(16, 48)
.addBox(-2.0F, 0.0F, -2.0F, 4.0F, 12.0F, 4.0F, new CubeDeformation(0.0F))
.texOffs(0, 48)
.addBox(
-2.0F, 0.0F, -2.0F, 4.0F, 12.0F, 4.0F,
new CubeDeformation(0.25F)
), dollPoses.getLeftLegPose()
);
return LayerDefinition.create(meshdefinition, 64, 64);
}
@Override
public void renderToBuffer(PoseStack poseStack, @NotNull VertexConsumer vertexConsumer, int packedLight, int packedOverlay, float red, float green, float blue, float alpha) {
poseStack.pushPose();
poseStack.scale(0.5F, 0.5F, 0.5F);
poseStack.translate(0.0, 1.5010000467300415, 0.0);
this.head.render(poseStack, vertexConsumer, packedLight, packedOverlay, red, green, blue, alpha);
this.body.render(poseStack, vertexConsumer, packedLight, packedOverlay, red, green, blue, alpha);
if (this.slim) {
this.rightArmSlim.render(poseStack, vertexConsumer, packedLight, packedOverlay, red, green, blue, alpha);
this.leftArmSlim.render(poseStack, vertexConsumer, packedLight, packedOverlay, red, green, blue, alpha);
} else {
this.rightArm.render(poseStack, vertexConsumer, packedLight, packedOverlay, red, green, blue, alpha);
this.leftArm.render(poseStack, vertexConsumer, packedLight, packedOverlay, red, green, blue, alpha);
}
this.rightLeg.render(poseStack, vertexConsumer, packedLight, packedOverlay, red, green, blue, alpha);
this.leftLeg.render(poseStack, vertexConsumer, packedLight, packedOverlay, red, green, blue, alpha);
poseStack.popPose();
}
}

View File

@ -1,93 +0,0 @@
package top.r3944realms.lib39.client.model;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.lib39.Lib39;
/**
* The enum Doll poses.
*/
public enum DollPoses implements IDollPose{
/**
* Default doll poses.
*/
DEFAULT(
"default",
PartPose.offset(0.0F, 9.0F, 0.0F),
PartPose.offset(0.0F, 9.0F, 0.0F),
PartPose.offsetAndRotation(-5.0F, 11.0F, 0.0F, 0.0F, 0.0F, 0.3927F),
PartPose.offsetAndRotation(5.0F, 11.0F, 0.0F, 0.0F, 0.0F, -0.3927F),
PartPose.offsetAndRotation(-2.0F, 19.0F, -2.0F, -1.5708F, 0.3927F, 0.0F),
PartPose.offsetAndRotation(2.0F, 19.0F, -2.0F, -1.5708F, -0.3927F, 0.0F)
);
// 注册全局
private final ResourceLocation id;
private final Vec3 offset;
private final PartPose headPose;
private final PartPose bodyPose;
private final PartPose rightArmPose;
private final PartPose leftArmPose;
private final PartPose rightLegPose;
private final PartPose leftLegPose;
DollPoses(String name, PartPose headPose, PartPose bodyPose,
PartPose rightArmPose, PartPose leftArmPose,
PartPose rightLegPose, PartPose leftLegPose) {
this(name, Vec3.ZERO, headPose, bodyPose, rightArmPose, leftArmPose, rightLegPose, leftLegPose);
}
DollPoses(String name, Vec3 offset, PartPose headPose, PartPose bodyPose,
PartPose rightArmPose, PartPose leftArmPose,
PartPose rightLegPose, PartPose leftLegPose) {
this.id = Lib39.rl(name);
this.offset = offset;
this.headPose = headPose;
this.bodyPose = bodyPose;
this.rightArmPose = rightArmPose;
this.leftArmPose = leftArmPose;
this.rightLegPose = rightLegPose;
this.leftLegPose = leftLegPose;
}
@Override
public @NotNull ResourceLocation getId() {
return id;
}
@Override
public PartPose getHeadPose() {
return headPose;
}
@Override
public PartPose getBodyPose() {
return bodyPose;
}
@Override
public PartPose getRightArmPose() {
return rightArmPose;
}
@Override
public PartPose getLeftArmPose() {
return leftArmPose;
}
@Override
public PartPose getRightLegPose() {
return rightLegPose;
}
@Override
public PartPose getLeftLegPose() {
return leftLegPose;
}
@Override
public @NotNull Vec3 getTotalOffset() {
return offset;
}
}

View File

@ -1,69 +0,0 @@
package top.r3944realms.lib39.client.model;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
/**
* The interface Doll pose.
*/
public interface IDollPose {
/**
* Gets id.
*
* @return the id
*/
@NotNull ResourceLocation getId();
/**
* Gets total offset.
*
* @return the total offset
*/
@NotNull default Vec3 getTotalOffset() {
return Vec3.ZERO;
}
/**
* Gets head pose.
*
* @return the head pose
*/
@NotNull PartPose getHeadPose();
/**
* Gets body pose.
*
* @return the body pose
*/
@NotNull PartPose getBodyPose();
/**
* Gets right arm pose.
*
* @return the right arm pose
*/
@NotNull PartPose getRightArmPose();
/**
* Gets left arm pose.
*
* @return the left arm pose
*/
@NotNull PartPose getLeftArmPose();
/**
* Gets right leg pose.
*
* @return the right leg pose
*/
@NotNull PartPose getRightLegPose();
/**
* Gets left leg pose.
*
* @return the left leg pose
*/
@NotNull PartPose getLeftLegPose();
}

View File

@ -1,381 +0,0 @@
package top.r3944realms.lib39.client.renderer;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth;
import net.minecraft.world.item.ItemStack;
import org.joml.Matrix4f;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
/**
* 圆形径向菜单渲染器
* 用于创建美观的圆形选择菜单
*
* @param <T> the type parameter
*/
public class RadialMenuRenderer<T> {
/**
* The constant DEFAULT_INNER_RADIUS.
*/
// 默认配置常量
public static final float DEFAULT_INNER_RADIUS = 30f;
/**
* The constant DEFAULT_OUTER_RADIUS.
*/
public static final float DEFAULT_OUTER_RADIUS = 80f;
/**
* The constant DEFAULT_MIDDLE_RADIUS.
*/
public static final float DEFAULT_MIDDLE_RADIUS = 55f;
/**
* The constant DEFAULT_SEGMENTS.
*/
public static final int DEFAULT_SEGMENTS = 64;
// 配置选项
private final float innerRadius;
private final float outerRadius;
private final float middleRadius;
private final int segments;
private final boolean enableHoverAnimation;
private final ColorScheme colorScheme;
// 状态
private int hoveredIndex = -1;
private final float[] hoverAnimations;
private long lastAnimationTime = 0;
/**
* The type Color scheme.
*/
public static class ColorScheme {
/**
* The Normal color.
*/
public final float[] normalColor;
/**
* The Hovered color.
*/
public final float[] hoveredColor;
/**
* The Selected color.
*/
public final float[] selectedColor;
/**
* The Background color.
*/
public final float[] backgroundColor;
/**
* Instantiates a new Color scheme.
*
* @param normalColor the normal color
* @param hoveredColor the hovered color
* @param selectedColor the selected color
* @param backgroundColor the background color
*/
public ColorScheme(float[] normalColor, float[] hoveredColor, float[] selectedColor, float[] backgroundColor) {
this.normalColor = normalColor;
this.hoveredColor = hoveredColor;
this.selectedColor = selectedColor;
this.backgroundColor = backgroundColor;
}
/**
* The constant DEFAULT.
*/
// 预定义颜色方案
public static final ColorScheme DEFAULT = new ColorScheme(
new float[]{0.3f, 0.3f, 0.8f, 0.6f}, // 正常 - 蓝色
new float[]{0.9f, 0.7f, 0.1f, 0.8f}, // 悬停 - 金色
new float[]{0.2f, 0.8f, 0.2f, 0.9f}, // 选中 - 绿色
new float[]{0.1f, 0.1f, 0.1f, 0.7f} // 背景
);
/**
* The constant FIRE.
*/
public static final ColorScheme FIRE = new ColorScheme(
new float[]{0.8f, 0.3f, 0.1f, 0.6f}, // 正常 - 红色
new float[]{1.0f, 0.5f, 0.0f, 0.8f}, // 悬停 - 橙色
new float[]{1.0f, 0.9f, 0.0f, 0.9f}, // 选中 - 黄色
new float[]{0.2f, 0.1f, 0.0f, 0.7f} // 背景
);
/**
* The constant NATURE.
*/
public static final ColorScheme NATURE = new ColorScheme(
new float[]{0.2f, 0.6f, 0.3f, 0.6f}, // 正常 - 绿色
new float[]{0.4f, 0.8f, 0.4f, 0.8f}, // 悬停 - 亮绿
new float[]{0.1f, 0.9f, 0.7f, 0.9f}, // 选中 - 青绿
new float[]{0.1f, 0.2f, 0.1f, 0.7f} // 背景
);
}
/**
* Instantiates a new Radial menu renderer.
*/
public RadialMenuRenderer() {
this(DEFAULT_INNER_RADIUS, DEFAULT_OUTER_RADIUS, ColorScheme.DEFAULT);
}
/**
* Instantiates a new Radial menu renderer.
*
* @param innerRadius the inner radius
* @param outerRadius the outer radius
*/
public RadialMenuRenderer(float innerRadius, float outerRadius) {
this(innerRadius, outerRadius, ColorScheme.DEFAULT);
}
/**
* Instantiates a new Radial menu renderer.
*
* @param innerRadius the inner radius
* @param outerRadius the outer radius
* @param colorScheme the color scheme
*/
public RadialMenuRenderer(float innerRadius, float outerRadius, ColorScheme colorScheme) {
this(innerRadius, outerRadius, (innerRadius + outerRadius) / 2f, DEFAULT_SEGMENTS, true, colorScheme);
}
/**
* Instantiates a new Radial menu renderer.
*
* @param innerRadius the inner radius
* @param outerRadius the outer radius
* @param middleRadius the middle radius
* @param segments the segments
* @param enableHoverAnimation the enable hover animation
* @param colorScheme the color scheme
*/
public RadialMenuRenderer(float innerRadius, float outerRadius, float middleRadius,
int segments, boolean enableHoverAnimation, ColorScheme colorScheme) {
this.innerRadius = innerRadius;
this.outerRadius = outerRadius;
this.middleRadius = middleRadius;
this.segments = segments;
this.enableHoverAnimation = enableHoverAnimation;
this.colorScheme = colorScheme;
this.hoverAnimations = new float[0];
}
/**
* 渲染圆形菜单
*
* @param guiGraphics the gui graphics
* @param entries the entries
* @param titleProvider the title provider
* @param iconProvider the icon provider
* @param selectedIndex the selected index
* @param trackMouse the track mouse
*/
public void render(GuiGraphics guiGraphics, List<T> entries,
Function<T, Component> titleProvider,
Function<T, ItemStack> iconProvider,
int selectedIndex, boolean trackMouse) {
if (entries.isEmpty()) return;
// 更新动画状态
updateHoverAnimations(entries.size());
// 设置渲染状态
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.setShader(GameRenderer::getPositionColorShader);
float centerX = guiGraphics.guiWidth() / 2f;
float centerY = guiGraphics.guiHeight() / 2f;
guiGraphics.pose().pushPose();
guiGraphics.pose().translate(centerX, centerY, 0f);
// 渲染所有扇形区域
renderSectors(guiGraphics, entries, selectedIndex);
// 渲染图标和文本
renderIconsAndText(guiGraphics, entries, titleProvider, iconProvider);
guiGraphics.pose().popPose();
RenderSystem.disableBlend();
}
/**
* 渲染扇形区域
*/
private void renderSectors(GuiGraphics guiGraphics, List<T> entries, int selectedIndex) {
int count = entries.size();
float angleSize = 360f / count;
for (int i = 0; i < count; i++) {
float startAngle = -90f + i * angleSize;
float currentOuterRadius = outerRadius;
// 悬停动画效果
if (enableHoverAnimation && i < hoverAnimations.length) {
currentOuterRadius += hoverAnimations[i] * 5f;
}
// 颜色设置
float[] color = getSectorColor(i, selectedIndex, entries.get(i));
// 绘制扇形
drawSector(guiGraphics, startAngle, angleSize, innerRadius, currentOuterRadius, color);
}
}
/**
* 获取扇形颜色
*/
private float[] getSectorColor(int index, int selectedIndex, T entry) {
if (index == selectedIndex) {
return colorScheme.selectedColor; // 选中状态
} else if (index == hoveredIndex) {
return colorScheme.hoveredColor; // 悬停状态
} else {
return colorScheme.normalColor; // 普通状态
}
}
/**
* 绘制单个扇形
*/
private void drawSector(GuiGraphics guiGraphics, float startAngle, float angleSize,
float innerRadius, float outerRadius, float[] color) {
BufferBuilder buffer = Tesselator.getInstance().getBuilder();
buffer.begin(VertexFormat.Mode.TRIANGLE_STRIP, DefaultVertexFormat.POSITION_COLOR);
Matrix4f matrix = guiGraphics.pose().last().pose();
float segments = Math.max(8, this.segments * (angleSize / 360f));
for (int i = 0; i <= segments; i++) {
float progress = i / segments;
float angle = startAngle + progress * angleSize;
float rad = angle * Mth.DEG_TO_RAD;
float cos = Mth.cos(rad);
float sin = Mth.sin(rad);
// 外圈顶点
buffer.vertex(matrix, outerRadius * cos, outerRadius * sin, 0)
.color(color[0], color[1], color[2], color[3]).endVertex();
// 内圈顶点
buffer.vertex(matrix, innerRadius * cos, innerRadius * sin, 0)
.color(color[0], color[1], color[2], color[3] * 0.6f).endVertex();
}
BufferUploader.drawWithShader(buffer.end());
}
/**
* 渲染图标和文本
*/
private void renderIconsAndText(GuiGraphics guiGraphics, List<T> entries,
Function<T, Component> titleProvider,
Function<T, ItemStack> iconProvider) {
int count = entries.size();
var font = Minecraft.getInstance().font;
for (int i = 0; i < count; i++) {
T entry = entries.get(i);
float angle = (-90f + 360f * (i + 0.5f) / count) * Mth.DEG_TO_RAD;
// 计算位置
float x = Mth.cos(angle) * middleRadius;
float y = Mth.sin(angle) * middleRadius;
// 渲染图标
ItemStack icon = iconProvider.apply(entry);
if (!icon.isEmpty()) {
guiGraphics.renderItem(icon, (int)(x - 8), (int)(y - 8));
}
// 渲染文本
Component title = titleProvider.apply(entry);
guiGraphics.pose().pushPose();
guiGraphics.pose().translate(x, y + 12, 0);
guiGraphics.pose().scale(0.7f, 0.7f, 0.7f);
guiGraphics.drawString(font, title, -font.width(title) / 2, 0, 0xFFFFFF, true);
guiGraphics.pose().popPose();
}
}
/**
* 更新悬停动画
*/
private void updateHoverAnimations(int entryCount) {
if (!enableHoverAnimation) return;
long currentTime = System.currentTimeMillis();
float deltaTime = Math.min((currentTime - lastAnimationTime) / 1000f, 0.1f);
lastAnimationTime = currentTime;
// 确保数组大小正确
if (hoverAnimations.length != entryCount) {
// 这里需要重新初始化数组实际使用时应该处理数组大小变化
}
// 更新动画值
for (int i = 0; i < hoverAnimations.length && i < entryCount; i++) {
if (i == hoveredIndex) {
hoverAnimations[i] = Mth.clamp(hoverAnimations[i] + deltaTime * 2f, 0f, 1f);
} else {
hoverAnimations[i] = Mth.clamp(hoverAnimations[i] - deltaTime * 3f, 0f, 1f);
}
}
}
/**
* 获取鼠标下的条目索引
*
* @param entries the entries
* @param mouseX the mouse x
* @param mouseY the mouse y
* @return the hovered entry
*/
public int getHoveredEntry(List<T> entries, double mouseX, double mouseY) {
float centerX = Minecraft.getInstance().getWindow().getGuiScaledWidth() / 2f;
float centerY = Minecraft.getInstance().getWindow().getGuiScaledHeight() / 2f;
double relX = mouseX - centerX;
double relY = mouseY - centerY;
double distance = Math.sqrt(relX * relX + relY * relY);
// 检查是否在有效范围内
if (distance < innerRadius || distance > outerRadius) {
hoveredIndex = -1;
return -1;
}
// 计算角度
double angle = Math.atan2(relY, relX) * Mth.RAD_TO_DEG;
angle = (angle + 450) % 360; // 标准化到 0-360
int count = entries.size();
int index = (int) (angle / (360f / count)) % count;
hoveredIndex = index;
return index;
}
/**
* 清除状态
*/
public void clearState() {
hoveredIndex = -1;
// 重置动画数组
Arrays.fill(hoverAnimations, 0f);
}
}

View File

@ -1,60 +0,0 @@
package top.r3944realms.lib39.client.renderer.block;
import com.mojang.authlib.GameProfile;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.SkullBlock;
import net.minecraft.world.level.block.WallSkullBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.RotationSegment;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.lib39.client.model.DollModel;
import top.r3944realms.lib39.client.renderer.item.DollItemRenderer;
import top.r3944realms.lib39.content.block.AbstractDollBlock;
import top.r3944realms.lib39.content.block.WallDollBlock;
import top.r3944realms.lib39.content.block.blockentity.DollBlockEntity;
import top.r3944realms.lib39.util.lang.Pair;
/**
* The type Doll block entity renderer.
*/
public class DollBlockEntityRenderer implements BlockEntityRenderer<DollBlockEntity> {
private final DollModel dollModel;
/**
* Instantiates a new Doll block entity renderer.
*
* @param context the context
*/
public DollBlockEntityRenderer(BlockEntityRendererProvider.@NotNull Context context) {
this.dollModel = new DollModel(context.bakeLayer(DollModel.LAYER_LOCATION));
}
@Override
public void render(@NotNull DollBlockEntity dollBlockEntity, float partialTick, @NotNull PoseStack poseStack, @NotNull MultiBufferSource buffer, int packedLight, int packedOverlay) {
BlockState blockState = dollBlockEntity.getBlockState();
if (blockState.getBlock() instanceof AbstractDollBlock dollBlock) {
boolean isWall = dollBlock instanceof WallDollBlock;
Direction direction = isWall ? blockState.getValue(WallSkullBlock.FACING) : null;
float rotation = isWall ? direction.toYRot() : RotationSegment.convertToDegrees(blockState.getValue(SkullBlock.ROTATION));
GameProfile profile = dollBlockEntity.getOwnerProfile();
Pair<ResourceLocation, Boolean> resourceLocationBooleanPair = DollItemRenderer.loadSkin(profile);
poseStack.pushPose();
poseStack.translate(0.5, 1.5, 0.5);
poseStack.scale(1.0F, -1.0F, -1.0F);
poseStack.mulPose(Axis.YP.rotationDegrees(rotation));
VertexConsumer vertexConsumer = buffer.getBuffer(RenderType.entityTranslucent(resourceLocationBooleanPair.first));
this.dollModel.slim = resourceLocationBooleanPair.second;
this.dollModel.renderToBuffer(poseStack, vertexConsumer, packedLight, OverlayTexture.NO_OVERLAY, 1.0F, 1.0F, 1.0F, 1.0F);
poseStack.popPose();
}
}
}

View File

@ -1,114 +0,0 @@
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 com.mojang.math.Axis;
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.resources.DefaultPlayerSkin;
import net.minecraft.client.resources.SkinManager;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.lib39.Lib39;
import top.r3944realms.lib39.client.model.DollModel;
import top.r3944realms.lib39.content.item.DollItem;
import top.r3944realms.lib39.util.GameProfileHelper;
import top.r3944realms.lib39.util.lang.Pair;
/**
* The type Doll item renderer.
*/
public class DollItemRenderer extends BlockEntityWithoutLevelRenderer {
private static DollItemRenderer instance;
private DollModel dollModel;
private boolean initialized = false;
private DollItemRenderer() {
super(
Minecraft.getInstance().getBlockEntityRenderDispatcher(),
Minecraft.getInstance().getEntityModels()
);
}
private void lazyInit() {
if (!initialized) {
try {
// 确保 Minecraft 实例已初始化
Minecraft mc = Minecraft.getInstance();
mc.getEntityModels();
this.dollModel = new DollModel(
mc.getEntityModels().bakeLayer(DollModel.LAYER_LOCATION)
);
initialized = true;
Lib39.LOGGER.info("Doll model initialized successfully");
} catch (Exception e) {
Lib39.LOGGER.error("Failed to initialize doll model", e);
}
}
}
/**
* Gets instance.
*
* @return the instance
*/
public static DollItemRenderer getInstance() {
if (instance == null) {
instance = new DollItemRenderer();
}
return instance;
}
@Override
public void renderByItem(@NotNull ItemStack stack, @NotNull ItemDisplayContext displayContext, @NotNull PoseStack poseStack, @NotNull MultiBufferSource buffer, int packedLight, int packedOverlay) {
if (!(stack.getItem() instanceof DollItem)) {
return;
}
lazyInit();
GameProfile profile = GameProfileHelper.getProfileFromItemStack(stack);
Pair<ResourceLocation, Boolean> resourceLocationBooleanPair = loadSkin(profile);
ResourceLocation playerSkin = resourceLocationBooleanPair.first;
boolean isSlim = resourceLocationBooleanPair.second;
poseStack.pushPose();
VertexConsumer vertexConsumer = buffer.getBuffer(
RenderType.entityTranslucent(playerSkin)
);
poseStack.translate(0.5, 2.6, 0.8);
poseStack.scale(1.8F, -1.8F, -1.8F);
poseStack.mulPose(Axis.YP.rotationDegrees(180));
this.dollModel.slim = isSlim;
this.dollModel.renderToBuffer(
poseStack,
vertexConsumer,
packedLight,
packedOverlay,
1.0F, 1.0F, 1.0F, 1.0F
);
poseStack.popPose();
}
/**
* Load skin pair.
*
* @param profile the profile
* @return the pair
*/
public static @NotNull Pair<ResourceLocation,Boolean> loadSkin(GameProfile profile) {
SkinManager skinManager = Minecraft.getInstance().getSkinManager();
ResourceLocation playerSkin;
boolean isSlim;
if (profile != null) {
playerSkin = skinManager.getInsecureSkinLocation(profile);
isSlim = GameProfileHelper.isSlimArms(profile);
} else {
playerSkin = DefaultPlayerSkin.getDefaultSkin(); //6 new SkinType("textures/entity/player/slim/steve.png", DefaultPlayerSkin.ModelType.SLIM),
isSlim = true;
}
return Pair.of(playerSkin, isSlim);
}
}

View File

@ -1,55 +0,0 @@
package top.r3944realms.lib39.client.shader;
import net.minecraft.client.renderer.ShaderInstance;
/**
* The type Lib 39 shaders.
*/
public class Lib39Shaders {
/**
* Gets ring shader.
*
* @return the ring shader
*/
public static ShaderInstance getRingShader() {
return ringShader;
}
/**
* The Ring shader.
*/
static ShaderInstance ringShader;
/**
* Gets selection shader.
*
* @return the selection shader
*/
public static ShaderInstance getSelectionShader() {
return selectionShader;
}
/**
* Sets ring shader.
*
* @param ringShader the ring shader
*/
public static void setRingShader(ShaderInstance ringShader) {
Lib39Shaders.ringShader = ringShader;
}
/**
* Sets selection shader.
*
* @param selectionShader the selection shader
*/
public static void setSelectionShader(ShaderInstance selectionShader) {
Lib39Shaders.selectionShader = selectionShader;
}
/**
* The Selection shader.
*/
static ShaderInstance selectionShader;
}

View File

@ -1,226 +0,0 @@
package top.r3944realms.lib39.content.block;
import com.mojang.authlib.GameProfile;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.material.PushReaction;
import net.minecraft.world.level.storage.loot.LootParams;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.lib39.content.block.blockentity.DollBlockEntity;
import top.r3944realms.lib39.content.block.property.DollPose;
import top.r3944realms.lib39.core.register.Lib39BlockEntities;
import top.r3944realms.lib39.core.register.Lib39Items;
import top.r3944realms.lib39.core.register.Lib39SoundEvents;
import top.r3944realms.lib39.util.GameProfileHelper;
import java.util.List;
/**
* The type Abstract doll block.
*/
@SuppressWarnings("deprecation")
public abstract class AbstractDollBlock extends BaseEntityBlock implements SimpleWaterloggedBlock {
/**
* The constant WATERLOGGED.
*/
public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
private static final Properties properties = Properties.of()
.instrument(NoteBlockInstrument.BASEDRUM)
.sound(SoundType.WOOL)
.pushReaction(PushReaction.DESTROY)
.strength(0f, 10f)
.noOcclusion();
private static final double PARTICLE_OFFSET_RANGE = 0.25;
private static final double PARTICLE_HEIGHT_OFFSET = 1.0;
private static final double PARTICLE_HEIGHT_VARIANCE = 0.2;
private static final float NOTE_COLOR_DIVISOR = 24.0F;
private static final int MAX_NOTE_COLORS = 4;
private static final float BASE_VOLUME = 1.0f;
private static final float PITCH_VARIANCE = 0.5f;
private static final float BASE_PITCH = 0.75f;
private static final VoxelShape DOLL_SHAPE = Block.box(2.0d, 0.0d, 2.0d, 14.0d, 12.0d, 14.0d);
/**
* The constant POSE.
*/
public static final EnumProperty<DollPose> POSE = EnumProperty.create("pose", DollPose.class);
/**
* Instantiates a new Abstract doll block.
*/
public AbstractDollBlock() {
super(properties);
}
@Override
public boolean canBeReplaced(@NotNull BlockState state, @NotNull BlockPlaceContext useContext) {
return false;
}
@Override
public @NotNull BlockState updateShape(@NotNull BlockState currentState, @NotNull Direction direction, @NotNull BlockState neighborState,
@NotNull LevelAccessor level, @NotNull BlockPos currentPos, @NotNull BlockPos neighborPos) {
if (currentState.getValue(WATERLOGGED)) {
level.scheduleTick(currentPos, Fluids.WATER, Fluids.WATER.getTickDelay(level));
}
return super.updateShape(currentState, direction, neighborState, level, currentPos, neighborPos);
}
@Override
public @NotNull FluidState getFluidState(@NotNull BlockState blockState) {
return blockState.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(blockState);
}
@Override
public @NotNull InteractionResult use(@NotNull BlockState blockState, @NotNull Level level, @NotNull BlockPos blockPos, @NotNull Player player,
@NotNull InteractionHand hand, @NotNull BlockHitResult hitResult) {
if (level instanceof ServerLevel serverLevel) {
// 播放粒子效果
spawnNoteParticles(serverLevel, blockPos);
// 播放音效
playDollSound(serverLevel, blockPos);
}
return InteractionResult.SUCCESS;
}
/**
* 在玩偶位置生成音符粒子效果
*/
private void spawnNoteParticles(ServerLevel serverLevel, BlockPos blockPos) {
Vec3 particlePosition = calculateParticlePosition(serverLevel, blockPos);
float noteColor = calculateNoteColor(serverLevel);
serverLevel.sendParticles(ParticleTypes.NOTE,
particlePosition.x(), particlePosition.y(), particlePosition.z(),
0, noteColor, 0, 0, 1);
}
/**
* 计算粒子生成位置添加随机偏移
*/
private @NotNull Vec3 calculateParticlePosition(@NotNull ServerLevel serverLevel, BlockPos blockPos) {
return Vec3.atBottomCenterOf(blockPos).add(
(serverLevel.getRandom().nextFloat() - 0.5) * PARTICLE_OFFSET_RANGE * 2,
PARTICLE_HEIGHT_OFFSET + serverLevel.getRandom().nextFloat() * PARTICLE_HEIGHT_VARIANCE,
(serverLevel.getRandom().nextFloat() - 0.5) * PARTICLE_OFFSET_RANGE * 2
);
}
/**
* 计算音符粒子的颜色
*/
private float calculateNoteColor(@NotNull ServerLevel serverLevel) {
return serverLevel.getRandom().nextInt(MAX_NOTE_COLORS) / NOTE_COLOR_DIVISOR;
}
/**
* 播放玩偶音效
*/
private void playDollSound(@NotNull ServerLevel serverLevel, BlockPos blockPos) {
float pitch = BASE_PITCH + serverLevel.random.nextFloat() * PITCH_VARIANCE;
serverLevel.playSound(null, blockPos, Lib39SoundEvents.DUCK_TOY.get(),
SoundSource.BLOCKS, BASE_VOLUME, pitch);
}
@Override
public @NotNull VoxelShape getShape(@NotNull BlockState blockState, @NotNull BlockGetter level, @NotNull BlockPos blockPos, @NotNull CollisionContext context) {
return DOLL_SHAPE;
}
public @NotNull VoxelShape getOcclusionShape(@NotNull BlockState state, @NotNull BlockGetter level, @NotNull BlockPos pos) {
return Shapes.empty();
}
@Nullable
@Override
public BlockEntity newBlockEntity(@NotNull BlockPos blockPos, @NotNull BlockState blockState) {
return Lib39BlockEntities.DOLL_BLOCK_ENTITY.get().create(blockPos, blockState);
}
@SuppressWarnings("deprecation")
@Override
public @NotNull RenderShape getRenderShape(@NotNull BlockState state) {
return RenderShape.ENTITYBLOCK_ANIMATED;
}
@Override
public @NotNull ItemStack getCloneItemStack(@NotNull BlockGetter level, @NotNull BlockPos pos, @NotNull BlockState state) {
ItemStack stack = super.getCloneItemStack(level, pos, state);
BlockEntity blockEntity = level.getBlockEntity(pos);
if (blockEntity instanceof DollBlockEntity doll) {
GameProfile profile = doll.getOwnerProfile();
if (profile != null) {
GameProfileHelper.saveProfileToItemStack(stack, profile);
}
}
return stack;
}
/**
* 最重要的方法重写掉落逻辑
*/
@Override
@NotNull
public List<ItemStack> getDrops(@NotNull BlockState state, @NotNull LootParams.Builder params) {
// 获取方块实体
BlockEntity blockEntity = params.getOptionalParameter(LootContextParams.BLOCK_ENTITY);
if (blockEntity instanceof DollBlockEntity dollEntity) {
List<ItemStack> customDrops = getCustomDrops(dollEntity, params);
if (customDrops != null) return customDrops;
}
return super.getDrops(state, params);
}
@Override
protected void createBlockStateDefinition(StateDefinition.@NotNull Builder<Block, BlockState> builder) {
super.createBlockStateDefinition(builder);
builder.add(WATERLOGGED, POSE);
}
/**
* 生成自定义掉落物
*/
@Nullable
private List<ItemStack> getCustomDrops(DollBlockEntity dollEntity, LootParams.Builder params) {
if (params.getOptionalParameter(LootContextParams.THIS_ENTITY) instanceof Player player) {
if (player.isCreative()) {
return List.of();
}
}
GameProfile profile = dollEntity.getOwnerProfile();
if (profile != null) {
ItemStack instance = Lib39Items.DOLL.get().getDefaultInstance();
GameProfileHelper.saveProfileToItemStack(instance, profile);
return List.of(instance);
}
return null;
}
}

View File

@ -1,65 +0,0 @@
package top.r3944realms.lib39.content.block;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.block.state.properties.RotationSegment;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.lib39.content.block.property.DollPose;
/**
* The type Doll block.
*/
@SuppressWarnings("deprecation")
public class DollBlock extends AbstractDollBlock{
/**
* The constant MAX.
*/
public static final int MAX = RotationSegment.getMaxSegmentIndex();
private static final int ROTATIONS = MAX + 1;
/**
* The constant ROTATION.
*/
public static final IntegerProperty ROTATION = BlockStateProperties.ROTATION_16;
/**
* Instantiates a new Doll block.
*/
public DollBlock() {
super();
this.registerDefaultState(
this.stateDefinition.any()
.setValue(POSE, DollPose.DEFAULT)
.setValue(WATERLOGGED, false)
.setValue(ROTATION, 0)
);
}
@Override
public @Nullable BlockState getStateForPlacement(@NotNull BlockPlaceContext context) {
BlockState stateForPlacement = super.getStateForPlacement(context);
return stateForPlacement != null ? stateForPlacement.setValue(ROTATION, RotationSegment.convertToSegment((context.getRotation()+180) % 360)) : null;
}
@Override
public @NotNull BlockState rotate(@NotNull BlockState state, @NotNull Rotation rotation) {
return state.setValue(ROTATION, rotation.rotate(state.getValue(ROTATION), ROTATIONS));
}
@Override
public @NotNull BlockState mirror(@NotNull BlockState state, @NotNull Mirror mirror) {
return state.setValue(ROTATION, mirror.mirror(state.getValue(ROTATION), ROTATIONS));
}
@Override
protected void createBlockStateDefinition(StateDefinition.@NotNull Builder<Block, BlockState> builder) {
super.createBlockStateDefinition(builder);
builder.add(ROTATION);
}
}

View File

@ -1,56 +0,0 @@
package top.r3944realms.lib39.content.block;
import net.minecraft.core.Direction;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.lib39.content.block.property.DollPose;
/**
* The type Wall doll block.
*/
@SuppressWarnings({"deprecation"})
public class WallDollBlock extends AbstractDollBlock {
/**
* The constant FACING.
*/
public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING;
/**
* Instantiates a new Wall doll block.
*/
public WallDollBlock() {
super();
this.registerDefaultState(
this.stateDefinition.any()
.setValue(POSE, DollPose.DEFAULT)
.setValue(WATERLOGGED, false)
.setValue(FACING, Direction.NORTH)
);
}
public @NotNull BlockState rotate(@NotNull BlockState state, @NotNull Rotation rotation) {
return state.setValue(FACING, rotation.rotate(state.getValue(FACING)));
}
public @NotNull BlockState mirror(@NotNull BlockState state, @NotNull Mirror mirror) {
return state.rotate(mirror.getRotation(state.getValue(FACING)));
}
@Override
public @Nullable BlockState getStateForPlacement(@NotNull BlockPlaceContext context) {
BlockState stateForPlacement = super.getStateForPlacement(context);
return stateForPlacement != null ? stateForPlacement.setValue(FACING, context.getHorizontalDirection().getOpposite()) : null;
}
protected void createBlockStateDefinition(StateDefinition.@NotNull Builder<Block, BlockState> builder) {
super.createBlockStateDefinition(builder);
builder.add(FACING);
}
}

View File

@ -1,98 +0,0 @@
package top.r3944realms.lib39.content.block.blockentity;
import com.mojang.authlib.GameProfile;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.SkullBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.lib39.core.register.Lib39BlockEntities;
import top.r3944realms.lib39.util.GameProfileHelper;
import top.r3944realms.lib39.util.nbt.NBTReader;
import top.r3944realms.lib39.util.nbt.NBTWriter;
import javax.annotation.Nullable;
/**
* The type Doll block entity.
*/
public class DollBlockEntity extends BlockEntity {
@Nullable
private GameProfile owner;
/**
* Instantiates a new Doll block entity.
*
* @param pos the pos
* @param blockState the block state
*/
public DollBlockEntity(BlockPos pos, BlockState blockState) {
super(Lib39BlockEntities.DOLL_BLOCK_ENTITY.get(), pos, blockState);
}
protected void saveAdditional(@NotNull CompoundTag tag) {
super.saveAdditional(tag);
NBTWriter.of(tag)
.compoundIf(GameProfileHelper.TAG_OWN_PROFILE, owner != null, () -> NbtUtils.writeGameProfile(new CompoundTag(), this.owner));
}
public void load(@NotNull CompoundTag tag) {
super.load(tag);
NBTReader.of(tag)
.compound(GameProfileHelper.TAG_OWN_PROFILE, compoundTag -> setOwner(NbtUtils.readGameProfile(compoundTag)));
}
/**
* Gets owner profile.
*
* @return the owner profile
*/
@Nullable
public GameProfile getOwnerProfile() {
return this.owner;
}
public ClientboundBlockEntityDataPacket getUpdatePacket() {
return ClientboundBlockEntityDataPacket.create(this);
}
public @NotNull CompoundTag getUpdateTag() {
return this.saveWithoutMetadata();
}
/**
* Sets owner.
*
* @param owner the owner
*/
public void setOwner(@Nullable GameProfile owner) {
synchronized (this) {
this.owner = owner;
}
this.updateOwnerProfile();
}
/**
* Sets owner.
*
* @param ownerName the owner name
*/
public void setOwner(@Nullable String ownerName) {
setOwner(new GameProfile(Util.NIL_UUID, ownerName));
}
private void updateOwnerProfile() {
SkullBlockEntity.updateGameprofile(this.owner, gameProfile -> {
this.owner = gameProfile;
this.setChanged();
});
}
}

View File

@ -1,28 +0,0 @@
package top.r3944realms.lib39.content.block.property;
import net.minecraft.util.StringRepresentable;
import org.jetbrains.annotations.NotNull;
/**
* The enum Doll pose.
*/
public enum DollPose implements StringRepresentable {
/**
* Default doll pose.
*/
DEFAULT("default"),
/**
* further support
*/
FURTHER("further"),
;
private final String name;
DollPose(String name) {
this.name = name;
}
@Override
public @NotNull String getSerializedName() {
return name;
}
}

View File

@ -1,46 +0,0 @@
package top.r3944realms.lib39.content.item;
import com.mojang.authlib.GameProfile;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.Equipable;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.StandingAndWallBlockItem;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.lib39.core.register.Lib39Blocks;
import top.r3944realms.lib39.util.GameProfileHelper;
import java.util.List;
/**
* The type Doll item.
*/
public class DollItem extends StandingAndWallBlockItem implements Equipable {
/**
* Instantiates a new Doll item.
*
* @param properties the properties
*/
public DollItem(Properties properties) {
super(Lib39Blocks.DOLL.get(), Lib39Blocks.WALL_DOLL.get(), properties, Direction.DOWN);
}
@Override
public void appendHoverText(@NotNull ItemStack stack, @Nullable Level level, @NotNull List<Component> tooltip, @NotNull TooltipFlag flag) {
GameProfile profileFromItemStack = GameProfileHelper.getProfileFromItemStack(stack);
if (profileFromItemStack != null && profileFromItemStack.getName() != null) {
tooltip.add(Component.translatable("tooltip.lib39.content.doll.hover.1", profileFromItemStack.getName()));
}
tooltip.add(Component.translatable("tooltip.lib39.content.doll.hover.2"));
}
@Override
public @NotNull EquipmentSlot getEquipmentSlot() {
return EquipmentSlot.HEAD;
}
}

View File

@ -1,549 +0,0 @@
package top.r3944realms.lib39.core.command;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.*;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.lib39.base.datagen.value.Lib39LangKey;
import top.r3944realms.lib39.core.command.model.CommandNode;
import top.r3944realms.lib39.core.command.model.CommandPath;
import top.r3944realms.lib39.core.command.model.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
/**
* The interface Command help manager.
*/
public interface ICommandHelpManager {
/**
* The constant NEWLINE.
*/
String NEWLINE = "\n";
/**
* Gets id.
*
* @return the id
*/
ResourceLocation getID();
/**
* Gets head key.
*
* @return the head key
*/
String getHeadKey();
/**
* Gets command head.
*
* @return the command head
*/
default String getCommandHead() {
return getID().getNamespace();
}
/**
* Init command node.
*
* @return the command node
*/
default CommandNode init() {
CommandNode root;
root = new CommandNode(this, getCommandHead(), Component.translatable(Lib39LangKey.Message.HELP_HEADER.getKey(), Component.translatable(getHeadKey())), true);
root.addChild(new CommandNode(this, "help", Component.translatable(Lib39LangKey.Message.BASIC_HELP.getKey())));
return root;
}
/**
* Gets cache.
*
* @return the cache
*/
Map<Integer, CommandNode> getCache();
/**
* Gets root node.
*
* @return the root node
*/
CommandNode getRootNode();
/**
* Register command help.
*
* @param commandNode the command node
* @param description the description
*/
default void registerCommandHelp(@NotNull CommandNode commandNode, MutableComponent description) {
registerCommandHelp(commandNode.getFullPath(), description);
}
/**
* Register command help.
*
* @param commandNode the command node
* @param descriptionKey the description key
*/
default void registerCommandHelp(@NotNull CommandNode commandNode, String descriptionKey) {
registerCommandHelp(commandNode.getFullPath(), Component.translatable(descriptionKey));
}
/**
* Register command help.
*
* @param commandPath the command path
*/
default void registerCommandHelp(@NotNull CommandPath commandPath) {
registerCommandHelp(commandPath.fullPath(), Component.literal(""));
}
/**
* Register command parameters.
*
* @param commandPath the command path
* @param parameters the parameters
*/
default void registerCommandParameters(@NotNull CommandPath commandPath, @NotNull Parameter.Builder parameters) {
registerCommandParameters(commandPath.fullPath(), parameters.build());
}
private void registerCommandHelp(@NotNull String commandPath, MutableComponent description) {
String[] pathParts = commandPath.split(" ");
CommandNode currentNode = getRootNode();
int startIndex = pathParts[0].equals(getRootNode().getName()) ? 1 : 0;
for (int i = startIndex; i < pathParts.length; i++) {
String part = pathParts[i];
CommandNode child = currentNode.getChild(part);
if (child == null) {
MutableComponent nodeDescription = (i == pathParts.length - 1) ? description : Component.literal("");
child = new CommandNode(this, part, nodeDescription);
currentNode.addChild(child);
}
currentNode = child;
}
}
/**
* 註冊命令幫助節點
*
* @param builder the builder
*/
default void registerCommand(@NotNull CommandNode.Builder builder) {
CommandNode newRoot = builder.build();
mergeTree(getRootNode(), newRoot);
}
/**
* Merge tree.
*
* @param target the target
* @param source the source
*/
void mergeTree(@NotNull CommandNode target, @NotNull CommandNode source);
/**
* 使用Builder模式註冊命令樹
*
* @param builder the builder
*/
default void registerCommandTree(@NotNull CommandNode.Builder builder) {
registerCommand(builder);
}
/**
* 使用Builder模式快速註冊命令
*
* @param configurator the configurator
*/
default void registerCommands(@NotNull Consumer<CommandNode.Builder> configurator) {
CommandNode.Builder builder = CommandNode.Builder.of(this);
configurator.accept(builder);
registerCommand(builder);
}
/**
* 根据路径查找节点
*
* @param path the path
* @return the optional
*/
default Optional<CommandNode> findNode(CommandPath path) {
CommandNode currentNode = getRootNode();
String[] segments = path.segments();
// 跳过根节点如果路径包含
int startIndex = segments[0].equals(getRootNode().getName()) ? 1 : 0;
for (int i = startIndex; i < segments.length; i++) {
currentNode = currentNode.getChild(segments[i]);
if (currentNode == null) {
return Optional.empty();
}
}
return Optional.of(currentNode);
}
/**
* 检查命令是否存在
*
* @param path the path
* @return the boolean
*/
default boolean hasCommand(CommandPath path) {
return findNode(path).isPresent();
}
private void registerCommandHelp(String commandPath, String descriptionKey) {
registerCommandHelp(commandPath, Component.translatable(descriptionKey));
}
private void registerCommandHelp(String commandPath) {
registerCommandHelp(commandPath, Component.literal(""));
}
/**
* 注册命令参数支持单个参数可选标记使用*前缀表示必选参数
*
* @param commandPath 命令路径 "fpsm tacz dummy"
* @param parameters 参数列表 "*requiredParam", "optionalParam"
*/
private void registerCommandParameters(@NotNull String commandPath, Parameter... parameters) {
String[] pathParts = commandPath.split(" ");
CommandNode currentNode = getRootNode();
// 遍历命令路径找到目标节点
int startIndex = pathParts[0].equals(getRootNode().getName()) ? 1 : 0;
for (int i = startIndex; i < pathParts.length; i++) {
String part = pathParts[i];
CommandNode child = currentNode.getChild(part);
if (child == null) {
// 如果节点不存在创建空描述节点
child = new CommandNode(this, part, Component.literal(""));
currentNode.addChild(child);
}
currentNode = child;
}
// 添加参数处理可选标记
for (Parameter param : parameters) {
currentNode.addParameter(param.name(), param.required());
}
}
/**
* 动态添加子指令到指定命令路径
*
* @param commandPath 命令路径 "fpsm map modify"
* @param childName 子指令名称
* @param description 子指令描述
* @return 是否添加成功 boolean
*/
default boolean addChildCommand(@NotNull String commandPath, String childName, MutableComponent description) {
String[] pathParts = commandPath.split(" ");
CommandNode currentNode = getRootNode();
// 遍历命令路径找到目标父节点
for (String part : pathParts) {
if (!part.equals(currentNode.getName())) {
CommandNode child = currentNode.getChild(part);
if (child == null) {
// 路径不存在创建中间节点
child = new CommandNode(this, part, Component.literal(""));
currentNode.addChild(child);
}
currentNode = child;
}
}
// 添加子指令
CommandNode childNode = new CommandNode(this, childName, description);
currentNode.addChild(childNode);
return true;
}
/**
* 构建单个命令节点的显示格式
*
* @param node 当前命令节点
* @param indent 当前缩进
* @param isRoot 是否为根节点
* @return 格式化后的命令节点组件
*/
private @NotNull MutableComponent buildCommandLine(CommandNode node, String indent, boolean isRoot, @Nullable String currentFullPath) {
if (isRoot) {
// 根节点特殊处理
String rootCommand = "/" + node.getName();
return Component.literal(rootCommand)
.withStyle(ChatFormatting.AQUA)
.withStyle(Style.EMPTY
.withClickEvent(new ClickEvent(
ClickEvent.Action.SUGGEST_COMMAND,
rootCommand + " "
))
.withHoverEvent(new HoverEvent(
HoverEvent.Action.SHOW_TEXT,
Component.translatable(Lib39LangKey.Message.HELP_HOVER_COPY_TIP.getKey())
))
);
} else {
// 构建完整命令路径
String fullCommand = (currentFullPath != null && !currentFullPath.isEmpty())
? currentFullPath + " " + node.getName()
: "/" + getRootNode().getName() + " " + node.getFullPath();
// 构建建议的命令带参数占位符
String suggestedCommand = buildSuggestedCommand(node, fullCommand);
// 非根节点显示命令和描述
MutableComponent prefix = Component.literal(indent + "└─ ").withStyle(ChatFormatting.GRAY);
// 命令名称可点击
MutableComponent commandName = Component.literal(node.getName())
.withStyle(ChatFormatting.DARK_AQUA)
.withStyle(Style.EMPTY
.withClickEvent(new ClickEvent(
ClickEvent.Action.SUGGEST_COMMAND,
suggestedCommand
))
.withHoverEvent(new HoverEvent(
HoverEvent.Action.SHOW_TEXT,
Component.translatable(Lib39LangKey.Message.HELP_HOVER_COPY_TIP.getKey(), suggestedCommand)
))
);
MutableComponent displayLine = prefix.append(commandName);
// 添加参数显示只显示不添加额外空格
if (!node.getParameters().isEmpty()) {
displayLine.append(Component.literal(" ")); // 命令名和参数之间的空格
for (int i = 0; i < node.getParameters().size(); i++) {
Parameter param = node.getParameters().get(i);
if (param.required()) {
displayLine.append(Component.literal("<").withStyle(ChatFormatting.GRAY))
.append(Component.literal(param.name()).withStyle(ChatFormatting.WHITE))
.append(Component.literal(">").withStyle(ChatFormatting.GRAY));
} else {
displayLine.append(Component.literal("[").withStyle(ChatFormatting.GRAY))
.append(Component.literal(param.name()).withStyle(ChatFormatting.WHITE))
.append(Component.literal("]").withStyle(ChatFormatting.GRAY));
}
// 参数之间添加空格除了最后一个
if (i < node.getParameters().size() - 1) {
displayLine.append(Component.literal(" "));
}
}
}
// 添加分隔符和描述
displayLine.append(Component.literal(" - ").withStyle(ChatFormatting.DARK_GRAY))
.append(node.getDescription().copy().withStyle(ChatFormatting.GRAY));
// 如果有子节点添加展开/折叠按钮
boolean shouldShowToggle = node.hasChildren() && !node.isLeaf();
if (shouldShowToggle) {
String toggleKey = node.isExpanded()
? Lib39LangKey.Message.HELP_NODE_TOGGLE_COLLAPSE.getKey()
: Lib39LangKey.Message.HELP_NODE_TOGGLE_EXPAND.getKey();
MutableComponent toggleButton = Component.literal(" [")
.withStyle(ChatFormatting.GRAY)
.append(Component.translatable(toggleKey).withStyle(ChatFormatting.YELLOW))
.append(Component.literal("]").withStyle(ChatFormatting.GRAY));
// 为按钮添加点击事件
toggleButton.withStyle(Style.EMPTY
.withClickEvent(new ClickEvent(
ClickEvent.Action.RUN_COMMAND,
"/" + getCommandHead() + " help toggle " + node.hashCode()
))
.withHoverEvent(new HoverEvent(
HoverEvent.Action.SHOW_TEXT,
Component.translatable(Lib39LangKey.Message.HELP_CLICK_EXPAND.getKey())
.withStyle(ChatFormatting.GRAY)
))
);
displayLine.append(toggleButton);
}
return displayLine;
}
}
/**
* 构建建议的命令包含参数占位符
*/
private @NotNull String buildSuggestedCommand(@NotNull CommandNode node, @NotNull String baseCommand) {
StringBuilder sb = new StringBuilder(baseCommand);
// 如果有参数添加参数占位符
if (!node.getParameters().isEmpty()) {
for (Parameter param : node.getParameters()) {
sb.append(" ");
if (param.required()) {
sb.append("<").append(param.name()).append(">");
} else {
sb.append("[").append(param.name()).append("]");
}
}
}
// 如果是叶子节点且没有参数添加空格以便继续输入
if (node.isLeaf() && node.getParameters().isEmpty()) {
sb.append(" ");
}
return sb.toString();
}
/**
* 檢查節點是否應該顯示摺疊信息
*/
private boolean shouldShowCollapsedInfo(@NotNull CommandNode node) {
return node.hasChildren() && !node.isExpanded() && !node.getChildren().isEmpty();
}
/**
* 獲取有效的子命令數量過濾掉空描述的命令
*/
private long getValidChildCount(@NotNull CommandNode node) {
return node.getChildren().stream()
.filter(child -> !child.getDescription().getString().isEmpty())
.count();
}
/**
* 遞歸構建命令樹
*/
private void buildCommandTreeString(@NotNull CommandNode node,
@NotNull String indent,
@Nullable String currentFullPath,
@NotNull List<MutableComponent> result,
CommandSourceStack commandSourceStack) {
boolean isRoot = indent.isEmpty();
if (node.testPermission(commandSourceStack)) {
MutableComponent commandLine = buildCommandLine(node, indent, isRoot, currentFullPath);
result.add(commandLine.append(Component.literal(NEWLINE)));
// 遞歸處理子節點
String childIndent = indent + "| ";
if (node.isExpanded()) {
String newFullPath = (currentFullPath != null && !currentFullPath.isEmpty())
? currentFullPath + " " + node.getName()
: "/" + node.getName();
for (CommandNode child : node.getChildren()) {
// 只顯示有描述的子命令
if (!child.getDescription().getString().isEmpty() && node.testPermission(commandSourceStack)) {
buildCommandTreeString(child, childIndent, newFullPath, result, commandSourceStack);
}
}
} else if (shouldShowCollapsedInfo(node)) {
long childCount = getValidChildCount(node);
if (childCount > 0) {
MutableComponent collapsedInfo = Component.literal(indent + "| " + "└─ ")
.withStyle(ChatFormatting.GRAY)
.append(Component.translatable(
Lib39LangKey.Message.HELP_NODE_EXPAND.getKey(),
childCount
).withStyle(ChatFormatting.GRAY));
collapsedInfo.withStyle(Style.EMPTY
.withClickEvent(new ClickEvent(
ClickEvent.Action.RUN_COMMAND,
"/" + getCommandHead() + " help toggle " + node.hashCode()
))
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable(Lib39LangKey.Message.HELP_CLICK_EXPAND.getKey())))
);
result.add(collapsedInfo.append(Component.literal(NEWLINE)));
}
}
}
}
/**
* 获取命令树的字符串表示
*
* @param commandSourceStack the command source stack
* @return 命令树列表 command tree
*/
default List<MutableComponent> getCommandTree(CommandSourceStack commandSourceStack) {
List<MutableComponent> result = new ArrayList<>();
buildCommandTreeString(getRootNode(), "", "", result, commandSourceStack);
return result;
}
/**
* 切换指定节点的展开/闭合状态
*
* @param hashCode 节点哈希值
* @return 是否成功切换 boolean
*/
default boolean toggleNodeExpanded(int hashCode) {
CommandNode currentNode = getCache().getOrDefault(hashCode, null);
if (currentNode == null || currentNode.getChildren().isEmpty()) {
return false;
}
currentNode.toggleExpanded();
return true;
}
/**
* 构建帮助消息
*
* @param header 帮助头部
* @param entries 帮助条目列表
* @return the mutable component
*/
default MutableComponent buildHelpMessage(@NotNull Component header, @NotNull List<MutableComponent> entries) {
MutableComponent helpMessage = Component.empty();
// 添加头部
helpMessage.append(header.copy()).append(NEWLINE);
// 添加分隔线
helpMessage.append(Component.literal("\n"));
// 添加当前页的帮助内容
if (entries.isEmpty()) {
helpMessage.append(Component.translatable(Lib39LangKey.Message.HELP_NO_ENTRIES.getKey())).append(NEWLINE);
} else {
for (MutableComponent entry : entries) {
helpMessage.append(entry);
}
}
return helpMessage;
}
/**
* Build command tree help mutable component.
*
* @param commandSourceStack the command source stack
* @return the mutable component
*/
default MutableComponent buildCommandTreeHelp(CommandSourceStack commandSourceStack) {
List<MutableComponent> commandTree = getCommandTree(commandSourceStack);
return buildHelpMessage(Component.translatable(Lib39LangKey.Message.HELP_HEADER.getKey(), Component.translatable(getHeadKey())), commandTree);
}
}

View File

@ -1,131 +0,0 @@
package top.r3944realms.lib39.core.command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.lib39.base.datagen.value.Lib39LangKey;
import top.r3944realms.lib39.platform.Services;
import javax.annotation.Nullable;
/**
* The interface Help command.
*/
public interface IHelpCommand {
/**
* Should show toggle failed boolean.
*
* @return the boolean
*/
default boolean shouldShowToggleFailed() {
return false;
}
/**
* Gets help head.
*
* @return the help head
*/
@Nullable
default LiteralArgumentBuilder<CommandSourceStack> getHelpHead() {
return null;
}
/**
* Gets command help manager.
*
* @return the command help manager
*/
ICommandHelpManager getCommandHelpManager();
/**
* Build command literal argument builder.
*
* @param dispatcher the dispatcher
* @param context the context
* @return the literal argument builder
*/
default LiteralArgumentBuilder<CommandSourceStack> buildCommand(CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext context) {
LiteralArgumentBuilder<CommandSourceStack> head = getHelpHead();
if (head == null) {
head = LiteralArgumentBuilder.literal(getCommandHelpManager().getID().getNamespace());
}
LiteralArgumentBuilder<CommandSourceStack> tree = head.requires(this::requestPermission)
.then(Commands.literal("help").executes(this::handleHelp)
.then(Commands.literal("toggle")
.then(Commands.argument("hash", IntegerArgumentType.integer()).executes(this::handleHelpToggle))
));
Services.PLATFORM.getHelpCommandHook().onRegister(tree, getCommandHelpManager(), context);
dispatcher.register(head);
return head;
}
/**
* Request permission boolean.
*
* @param context the context
* @return the boolean
*/
default boolean requestPermission(CommandSourceStack context) {
return true;
}
/**
* Handle help int.
*
* @param context the context
* @return the int
*/
default int handleHelp(@NotNull CommandContext<CommandSourceStack> context) {
ICommandHelpManager commandHelpManager = getCommandHelpManager();
MutableComponent helpMessage = commandHelpManager.buildCommandTreeHelp(context.getSource());
sendSuccess(context.getSource(), helpMessage);
return 1;
}
/**
* Handle help toggle int.
*
* @param context the context
* @return the int
*/
default int handleHelpToggle(@NotNull CommandContext<CommandSourceStack> context) {
int hash = IntegerArgumentType.getInteger(context, "hash");
ICommandHelpManager commandHelpManager = getCommandHelpManager();
boolean success = commandHelpManager.toggleNodeExpanded(hash);
if (success) {
MutableComponent helpMessage = Component.literal("\n".repeat(2)).append(commandHelpManager.buildCommandTreeHelp(context.getSource()));
sendSuccess(context.getSource(), helpMessage);
} else if (shouldShowToggleFailed()) {
sendFailure(context.getSource(), Component.translatable(Lib39LangKey.Message.HELP_TOGGLE_FAILED.getKey()));
}
return 1;
}
/**
* Send success.
*
* @param source the source
* @param key the key
*/
static void sendSuccess(@NotNull CommandSourceStack source, Component key) {
source.sendSuccess(() -> key, true);
}
/**
* Send failure.
*
* @param source the source
* @param key the key
*/
static void sendFailure(@NotNull CommandSourceStack source, Component key) {
source.sendFailure(key);
}
}

View File

@ -1,87 +0,0 @@
package top.r3944realms.lib39.core.command;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.lib39.core.command.model.CommandNode;
import top.r3944realms.lib39.core.command.model.Parameter;
import java.util.HashMap;
import java.util.Map;
/**
* The type Simple command help manager.
*/
public abstract class SimpleCommandHelpManager implements ICommandHelpManager {
private CommandNode root;
private final Map<Integer, CommandNode> nodeCache = new HashMap<>();
/**
* Instantiates a new Simple command help manager.
*/
public SimpleCommandHelpManager() {
//
}
/**
* 延遲初始化根節點
*/
public void initialize() {
if (root == null) {
// 現在子類的字段已經初始化完成
ResourceLocation id = getID();
if (id == null) {
throw new IllegalStateException("getID() must return non-null");
}
this.root = init();
}
}
@Override
public final @NotNull String getCommandHead() {
return getID().getNamespace();
}
@Override
public final Map<Integer, CommandNode> getCache() {
return nodeCache;
}
@Override
public void mergeTree(@NotNull CommandNode target, @NotNull CommandNode source) {
// 合併參數
for (Parameter param : source.getParameters()) {
if (!target.getParameters().contains(param)) {
target.addParameter(param.name(), param.required());
}
}
// 合併子節點
for (CommandNode sourceChild : source.getChildren()) {
CommandNode targetChild = target.getChild(sourceChild.getName());
if (targetChild == null) {
target.addChild(sourceChild.deepCopy());
} else {
mergeTree(targetChild, sourceChild);
}
}
}
/**
* 獲取根節點如果未初始化則初始化
*/
@Override
public final CommandNode getRootNode() {
if (root == null) {
initialize();
}
return root;
}
/**
* 檢查是否已初始化
*
* @return the boolean
*/
public boolean isInitialized() {
return root != null;
}
}

View File

@ -1,39 +0,0 @@
package top.r3944realms.lib39.core.command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.CommandSourceStack;
/**
* The type Simple help command.
*/
public abstract class SimpleHelpCommand implements IHelpCommand {
/**
* The Root.
*/
protected final LiteralArgumentBuilder<CommandSourceStack> root;
/**
* <pre>
* 需要{@link CommandDispatcher<CommandSourceStack> 指令注册调度器} {@link CommandBuildContext 指令上下文}
* </pre>
* Instantiates a new Simple help command.
*
* @param dispatcher the dispatcher
* @param context the context
*/
public SimpleHelpCommand(CommandDispatcher<CommandSourceStack> dispatcher,
CommandBuildContext context) {
root = buildCommand(dispatcher, context);
}
/**
* Gets root.
*
* @return the root
*/
public LiteralArgumentBuilder<CommandSourceStack> getRoot() {
return root;
}
}

View File

@ -1,127 +0,0 @@
package top.r3944realms.lib39.core.command.model;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* 命令路径构建器 - 提供编译时类型安全
*/
public final class CommandPath {
private final List<String> segments;
private final String fullPath;
private CommandPath(List<String> segments) {
this.segments = List.copyOf(segments);
this.fullPath = String.join(" ", segments);
}
/**
* Of command path.
*
* @param segments the segments
* @return the command path
*/
@Contract("_ -> new")
public static @NotNull CommandPath of(String... segments) {
validateSegments(segments);
return new CommandPath(List.of(segments));
}
/**
* From string command path.
*
* @param path the path
* @return the command path
*/
@Contract("_ -> new")
public static @NotNull CommandPath fromString(@NotNull String path) {
if (path.charAt(0) == '/') {
path = path.substring(1);
}
return of(path.split(" "));
}
/**
* Then command path.
*
* @param subSegments the sub segments
* @return the command path
*/
@Contract("_ -> new")
public @NotNull CommandPath then(String... subSegments) {
validateSegments(subSegments);
List<String> newSegments = new ArrayList<>(this.segments);
newSegments.addAll(List.of(subSegments));
return new CommandPath(newSegments);
}
/**
* Parent optional.
*
* @return the optional
*/
public Optional<CommandPath> parent() {
if (segments.size() <= 1) {
return Optional.empty();
}
return Optional.of(new CommandPath(segments.subList(0, segments.size() - 1)));
}
/**
* Last segment string.
*
* @return the string
*/
public String lastSegment() {
return segments.isEmpty() ? "" : segments.get(segments.size() - 1);
}
/**
* Segments string @ not null [ ].
*
* @return the string @ not null [ ]
*/
public String @NotNull [] segments() {
return segments.toArray(new String[0]);
}
/**
* Full path string.
*
* @return the string
*/
public String fullPath() {
return fullPath;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CommandPath that = (CommandPath) o;
return Objects.equals(fullPath, that.fullPath);
}
@Override
public int hashCode() {
return fullPath.hashCode();
}
@Override
public String toString() {
return fullPath;
}
private static void validateSegments(String @NotNull [] segments) {
for (String segment : segments) {
if (segment == null || segment.isEmpty() || segment.contains(" ")) {
throw new IllegalArgumentException("Invalid command segment: " + segment);
}
}
}
}

View File

@ -1,78 +0,0 @@
package top.r3944realms.lib39.core.command.model;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* The type Parameter.
*/
public record Parameter(String name, boolean required) {
/**
* 獲取參數類型標識
*
* @return the type indicator
*/
@Contract(pure = true)
public @NotNull String getTypeIndicator() {
return required ? "required" : "optional";
}
@Override
public String toString() {
return String.format("Parameter{name='%s', required=%s}", name, required);
}
/**
* The type Builder.
*/
public static class Builder {
private final List<Parameter> parameters = new ArrayList<>();
/**
* Required builder.
*
* @param name the name
* @return the builder
*/
public Builder required(String name) {
parameters.add(new Parameter(name, true));
return this;
}
/**
* Optional builder.
*
* @param name the name
* @return the builder
*/
public Builder optional(String name) {
parameters.add(new Parameter(name, false));
return this;
}
/**
* Build parameter [ ].
*
* @return the parameter [ ]
*/
public Parameter[] build() {
return parameters.toArray(new Parameter[0]);
}
/**
* Builder parameter . builder.
*
* @return the parameter . builder
*/
// 链式调用的便利方法
@Contract(" -> new")
public static @NotNull Parameter.Builder builder() {
return new Builder();
}
}
}

View File

@ -1,192 +0,0 @@
package top.r3944realms.lib39.core.compat;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.r3944realms.lib39.Lib39;
import java.util.*;
import java.util.stream.Collectors;
/**
* The type Compat manager.
*/
@SuppressWarnings("unused")
public abstract class CompatManager {
/**
* Gets id.
*
* @return the id
*/
public ResourceLocation getId() {
return id;
}
/**
* The Logger.
*/
protected final Logger logger;
/**
* The Id.
*/
protected final ResourceLocation id;
/**
* The Compats.
*/
protected final Map<ResourceLocation, ICompat> compats = new HashMap<>();
/**
* The Initialized.
*/
protected boolean initialized = false;
/**
* The Pending tasks.
*/
protected final List<Runnable> pendingTasks = new ArrayList<>();
/**
* Initialize.
*/
public void initialize() {
initializeAllCompat();
onLoadComplete();
}
/**
* Instantiates a new Compat manager.
*
* @param id the id
*/
public CompatManager(@NotNull ResourceLocation id) {
this.id = id;
this.logger = LoggerFactory.getLogger(id.toString());
}
/**
* Register compat.
*
* @param id the id
* @param compat the compat
*/
public void registerCompat(ResourceLocation id, ICompat compat) {
if (initialized) {
// 已初始化直接注册
doRegisterCompat(id, compat);
} else {
// 未初始化缓存起来
pendingTasks.add(() -> doRegisterCompat(id, compat));
logger.debug("Cached compat registration for: {}", id);
}
}
/**
* Do register compat.
*
* @param id the id
* @param compat the compat
*/
protected void doRegisterCompat(ResourceLocation id, ICompat compat) {
if (compats.containsKey(id)) {
logger.warn("Compat with id {} is already registered!", id);
return;
}
compats.put(id, compat);
logger.debug("Registered compat: {}", id);
}
/**
* Register compat.
*
* @param namespace the namespace
* @param path the path
* @param compat the compat
*/
public void registerCompat(String namespace, String path, ICompat compat) {
registerCompat(Lib39.rl(namespace, path), compat);
}
// ===================== 初始化和管理 =====================
/**
* 初始化所有兼容模块并应用事件监听器
*/
protected synchronized void initializeAllCompat() {
logger.info("Initializing {} compatibility modules", compats.size());
// 先处理所有缓存的注册
pendingTasks.forEach(Runnable::run);
pendingTasks.clear();
// 初始化所有兼容模块
for (Map.Entry<ResourceLocation, ICompat> entry : compats.entrySet()) {
if (!entry.getValue().isInitialized() && entry.getValue().isModLoaded()) {
try {
entry.getValue().initialize();
entry.getValue().setInitialize(true);
logger.info("Initialized compat: {}", entry.getKey());
} catch (Exception e) {
logger.error("Failed to initialize compat: {}", entry.getKey(), e);
}
}
}
initialized = true;
}
/**
* Gets compat.
*
* @param id the id
* @return the compat
*/
public Optional<ICompat> getCompat(ResourceLocation id) {
return Optional.ofNullable(compats.get(id));
}
/**
* Has compat boolean.
*
* @param id the id
* @return the boolean
*/
public boolean hasCompat(ResourceLocation id) {
return compats.containsKey(id);
}
/**
* Unregister compat.
*
* @param id the id
*/
public void unregisterCompat(ResourceLocation id) {
ICompat removed = compats.remove(id);
if (removed != null) {
logger.debug("Unregistered compat: {}", id);
}
}
/**
* Gets loaded compats.
*
* @return the loaded compats
*/
public List<ICompat> getLoadedCompats() {
return compats.values().stream()
.filter(ICompat::isModLoaded)
.collect(Collectors.toList());
}
/**
* On load complete.
*/
public void onLoadComplete() {
logger.info("Calling onLoadComplete for {} compatibility modules", compats.size());
for (Map.Entry<ResourceLocation, ICompat> entry : compats.entrySet()) {
try {
entry.getValue().onLoadComplete();
} catch (Exception e) {
logger.error("Error in onLoadComplete for compat: {}", entry.getKey(), e);
}
}
}
}

View File

@ -1,89 +0,0 @@
package top.r3944realms.lib39.core.compat;
import net.minecraft.resources.ResourceLocation;
import java.util.concurrent.Callable;
/**
* The interface Compat.
*/
public interface ICompat {
/**
* Sets initialize.
*
* @param initialize the initialize
*/
void setInitialize(boolean initialize);
/**
* Is initialized boolean.
*
* @return the boolean
*/
boolean isInitialized();
/**
* Id resource location.
*
* @return the resource location
*/
ResourceLocation id();
/**
* Initialize.
*/
void initialize();
/**
* On load complete.
*/
default void onLoadComplete() {}
/**
* Is mod loaded boolean.
*
* @return the boolean
*/
default boolean isModLoaded() {
return false;
}
/**
* Call if present t.
*
* @param <T> the type parameter
* @param callable the callable
* @return the t
* @throws Exception the exception
*/
default <T> T callIfPresent(Callable<T> callable) throws Exception {
if (isModLoaded()) return callable.call();
else return null;
}
/**
* Call if pesent t.
*
* @param <T> the type parameter
* @param callable the callable
* @param elseCall the else call
* @return the t
* @throws Exception the exception
*/
default <T> T callIfPresent(Callable<T> callable, Callable<T> elseCall) throws Exception {
if (isModLoaded()) return callable.call();
else return elseCall.call();
}
/**
* Run if present boolean.
*
* @param runnable the runnable
* @return the boolean
* @throws Exception the exception
*/
default boolean runIfPresent(Runnable runnable) throws Exception {
if (isModLoaded()) runnable.run(); else return false;
return true;
}
}

View File

@ -1,92 +0,0 @@
package top.r3944realms.lib39.core.lang;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
/**
* The type Class encryptor.
*/
public class ClassEncryptor {
static {
// System.loadLibrary("ClassEncrypt");
}
/**
* Encrypt class byte [ ].
*
* @param classData the class data
* @param key the key
* @return the byte [ ]
*/
public native byte[] encryptClass(byte[] classData, String key);
/**
* Decrypt class byte [ ].
*
* @param encryptedData the encrypted data
* @param key the key
* @return the byte [ ]
*/
public native byte[] decryptClass(byte[] encryptedData, String key);
/**
* Is encrypted file boolean.
*
* @param fileData the file data
* @return the boolean
*/
public native boolean isEncryptedFile(byte[] fileData);
/**
* Encrypt class file.
*
* @param inputPath the input path
* @param outputPath the output path
* @param key the key
* @throws IOException the io exception
*/
public void encryptClassFile(String inputPath, String outputPath, String key)
throws IOException {
byte[] classData = Files.readAllBytes(Paths.get(inputPath));
byte[] encryptedData = encryptClass(classData, key);
Files.write(Paths.get(outputPath), encryptedData);
System.out.println("Encrypted: " + inputPath + " -> " + outputPath);
}
/**
* Encrypt directory.
*
* @param inputDir the input dir
* @param outputDir the output dir
* @param key the key
* @throws IOException the io exception
*/
public void encryptDirectory(String inputDir, String outputDir, String key)
throws IOException {
try (Stream<Path> walk = Files.walk(Paths.get(inputDir))) {
walk
.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".class"))
.forEach(p -> {
try {
String relativePath = inputDir.equals(p.getParent().toString())
? p.getFileName().toString()
: inputDir.equals(p.getParent().getParent().toString())
? p.getParent().getFileName() + "/" + p.getFileName()
: p.toString().substring(inputDir.length() + 1);
Path outputPath = Paths.get(outputDir, relativePath);
Files.createDirectories(outputPath.getParent());
encryptClassFile(p.toString(), outputPath.toString(), key);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
}

View File

@ -1,227 +0,0 @@
package top.r3944realms.lib39.core.lang;
import java.io.*;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
/**
* The type Encrypted class loader.
*/
public class EncryptedClassLoader extends ClassLoader {
static {
// System.loadLibrary("ClassEncrypt");
}
private native byte[] decryptClass(byte[] encryptedData, String key);
private final String encryptedClassPath;
private final String decryptionKey;
// 缓存已加载的类字节码避免重复加载
private final Map<String, byte[]> classBytesCache = new HashMap<>();
/**
* Instantiates a new Encrypted class loader.
*
* @param encryptedClassPath the encrypted class path
* @param key the key
*/
public EncryptedClassLoader(String encryptedClassPath, String key) {
this.encryptedClassPath = encryptedClassPath;
this.decryptionKey = key;
}
/**
* Instantiates a new Encrypted class loader.
*
* @param encryptedClassPath the encrypted class path
* @param key the key
* @param parent the parent
*/
public EncryptedClassLoader(String encryptedClassPath, String key, ClassLoader parent) {
super(parent);
this.encryptedClassPath = encryptedClassPath;
this.decryptionKey = key;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// 从缓存获取或加载类字节码
byte[] classData;
synchronized (classBytesCache) {
classData = classBytesCache.get(name);
if (classData == null) {
// 1. 读取加密的class文件
byte[] encryptedData = loadEncryptedClass(name);
// 2. 使用JNI解密
classData = decryptClass(encryptedData, decryptionKey);
// 3. 验证解密后的数据是否是有效的class文件
if (!isValidClass(classData)) {
throw new ClassNotFoundException("Invalid class data after decryption");
}
// 缓存类字节码
classBytesCache.put(name, classData);
}
}
// 4. 定义类
return defineClass(name, classData, 0, classData.length);
} catch (Exception e) {
throw new ClassNotFoundException("Failed to load encrypted class: " + name, e);
}
}
private byte [] loadEncryptedClass(String className) throws IOException {
String path = className.replace('.', File.separatorChar) + ".class";
Path fullPath = Paths.get(encryptedClassPath, path);
if (!Files.exists(fullPath)) {
// 尝试寻找内部类
int dollarIndex = className.lastIndexOf('$');
if (dollarIndex != -1) {
String outerClass = className.substring(0, dollarIndex);
path = outerClass.replace('.', File.separatorChar) +
"$" + className.substring(dollarIndex + 1) + ".class";
fullPath = Paths.get(encryptedClassPath, path);
}
}
if (!Files.exists(fullPath)) {
// 尝试其他可能的文件扩展名
fullPath = Paths.get(encryptedClassPath, className.replace('.', File.separatorChar) + ".enc");
if (!Files.exists(fullPath)) {
throw new FileNotFoundException("Encrypted class not found: " + className);
}
}
return Files.readAllBytes(fullPath);
}
private boolean isValidClass(byte [] data) {
// Java class文件的魔数是0xCAFEBABE
return data.length >= 4 &&
data[0] == (byte)0xCA &&
data[1] == (byte)0xFE &&
data[2] == (byte)0xBA &&
data[3] == (byte)0xBE;
}
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 优先检查是否已加载
Class<?> clazz = findLoadedClass(name);
if (clazz != null) {
return clazz;
}
// Java核心类库使用父加载器
if (name.startsWith("java.") || name.startsWith("javax.") ||
name.startsWith("sun.") || name.startsWith("jdk.")) {
return super.loadClass(name, resolve);
}
try {
// 尝试用自定义ClassLoader加载
clazz = findClass(name);
} catch (ClassNotFoundException e) {
// 如果找不到委托给父加载器
clazz = super.loadClass(name, resolve);
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
/**
* Get class bytes byte [ ].
*
* @param className the class name
* @return the byte [ ]
* @throws ClassNotFoundException the class not found exception
*/
// 添加获取类字节码的方法
public byte[] getClassBytes(String className) throws ClassNotFoundException {
synchronized (classBytesCache) {
byte[] bytes = classBytesCache.get(className);
if (bytes == null) {
// 触发类加载以填充缓存
loadClass(className);
bytes = classBytesCache.get(className);
}
return bytes != null ? bytes.clone() : null; // 返回副本
}
}
@Override
public InputStream getResourceAsStream(String name) {
try {
// 处理.class资源请求
if (name.endsWith(".class")) {
String className = name.substring(0, name.length() - 6)
.replace('/', '.');
byte[] classData = getClassBytes(className);
if (classData != null) {
return new ByteArrayInputStream(classData);
}
}
// 处理其他资源文件
Path resourcePath = Paths.get(encryptedClassPath, name);
if (Files.exists(resourcePath)) {
return Files.newInputStream(resourcePath);
}
} catch (Exception e) {
// 忽略异常返回null让父加载器处理
}
// 委托给父加载器
return super.getResourceAsStream(name);
}
@Override
public URL getResource(String name) {
try {
Path resourcePath = Paths.get(encryptedClassPath, name);
if (Files.exists(resourcePath)) {
return resourcePath.toUri().toURL();
}
} catch (Exception e) {
// 忽略异常
}
return super.getResource(name);
}
/**
* Clear cache.
*/
public void clearCache() {
synchronized (classBytesCache) {
classBytesCache.clear();
}
}
/**
* Clear cache.
*
* @param className the class name
*/
public void clearCache(String className) {
synchronized (classBytesCache) {
classBytesCache.remove(className);
}
}
}

View File

@ -1,17 +0,0 @@
package top.r3944realms.lib39.core.register;
import net.minecraft.world.level.block.entity.BlockEntityType;
import top.r3944realms.lib39.content.block.blockentity.DollBlockEntity;
import java.util.function.Supplier;
/**
* The type Lib 39 block entities.
*/
public class Lib39BlockEntities {
/**
* The constant DOLL_BLOCK_ENTITY.
*/
public static Supplier<BlockEntityType<DollBlockEntity>> DOLL_BLOCK_ENTITY;
}

View File

@ -1,21 +0,0 @@
package top.r3944realms.lib39.core.register;
import net.minecraft.world.level.block.Block;
import java.util.function.Supplier;
/**
* The type Lib 39 blocks.
*/
public class Lib39Blocks {
/**
* The constant DOLL.
*/
public static Supplier<Block> DOLL;
/**
* The Wall doll.
*/
public static Supplier<Block> WALL_DOLL;
}

View File

@ -1,15 +0,0 @@
package top.r3944realms.lib39.core.register;
import net.minecraft.world.item.Item;
import java.util.function.Supplier;
/**
* The type Ex lib 39 items.
*/
public class Lib39Items {
/**
* The constant DOLL.
*/
public static Supplier<Item> DOLL;
}

View File

@ -1,31 +0,0 @@
package top.r3944realms.lib39.core.register;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import top.r3944realms.lib39.Lib39;
import java.util.function.Supplier;
/**
* The type Lib 39 sound events.
*/
public class Lib39SoundEvents {
/**
* The constant RL_DUCK_TOY.
*/
public static final ResourceLocation RL_DUCK_TOY = Lib39.rl("duck_toy");
/**
* The constant DUCK_TOY.
*/
public static Supplier<SoundEvent> DUCK_TOY;
/**
* Gets sub title translate key.
*
* @param name the name
* @return the sub title translate key
*/
public static String getSubTitleTranslateKey(String name) {
return "sound." + Lib39.MOD_ID + ".subtitle." + name;
}
}

View File

@ -1,78 +0,0 @@
package top.r3944realms.lib39.core.registry;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnmodifiableView;
import top.r3944realms.lib39.datagen.value.ILocaleEntry;
import top.r3944realms.lib39.datagen.value.McLocale;
import java.util.*;
/**
* The type Locale registry.
*/
@SuppressWarnings("unused")
public class LocaleRegistry {
private static final Map<String, ILocaleEntry> REGISTRY = new LinkedHashMap<>();
// 初始化注册所有枚举值
static {
for (McLocale loc : McLocale.values()) {
register(loc);
}
}
/**
* 注册覆盖已有时直接返回旧值 @param entry the entry
*
* @param entry the entry
* @return the locale entry
*/
@SuppressWarnings("UnusedReturnValue")
public static ILocaleEntry register(ILocaleEntry entry) {
return REGISTRY.putIfAbsent(entry.mcCode().toLowerCase(), entry);
}
/**
* 通过 Minecraft 代码查找 @param code the code
*
* @param code the code
* @return the locale entry
*/
public static ILocaleEntry fromMcCode(@NotNull String code) {
return REGISTRY.get(code.toLowerCase());
}
/**
* 列出所有 @return the collection
*
* @return the collection
*/
public static @NotNull @UnmodifiableView Collection<ILocaleEntry> allValues() {
return Collections.unmodifiableCollection(REGISTRY.values());
}
/**
* 动态注册一个扩展 Locale @param mcCode the mc code
*
* @param mcCode the mc code
* @param locale the locale
* @return the locale entry
*/
public static ILocaleEntry registerDynamic(@NotNull String mcCode, Locale locale) {
return REGISTRY.computeIfAbsent(mcCode.toLowerCase(),
k -> new ExtendedLocale(mcCode.toLowerCase(), locale));
}
/**
* 扩展类型
*/
private record ExtendedLocale(String mcCode, Locale javaLocale) implements ILocaleEntry {
@Contract(pure = true)
@Override
public @NotNull String toString() {
return "ExtendedLocale[" + mcCode + "]";
}
}
}

View File

@ -1,74 +0,0 @@
package top.r3944realms.lib39.core.sync;
import java.util.Map;
import java.util.Set;
/**
* The type Cached sync manager.
*
* @param <K> the type parameter
* @param <T> the type parameter
*/
@SuppressWarnings("unused")
public abstract class CachedSyncManager<K, T extends ISyncData<?>> implements ISyncManager<K, T> {
private volatile Set<T> cachedSet;
private volatile int mapSize = -1;
@Override
public Set<T> getSyncSet() {
Map<K, T> syncMap = getSyncMap();
if (syncMap == null) {
throw new IllegalStateException("SyncMap is not initialized");
}
// 检查是否需要更新缓存
if (cachedSet == null || mapSize != syncMap.size()) {
synchronized (this) {
if (cachedSet == null || mapSize != syncMap.size()) {
cachedSet = Set.copyOf(syncMap.values());
mapSize = syncMap.size();
}
}
}
return cachedSet;
}
/**
* 当Map发生变化时调用此方法清除缓存
*/
protected void invalidateCache() {
cachedSet = null;
mapSize = -1;
}
@Override
public void track(K key, T instance) {
Map<K, T> syncMap = getSyncMap();
if (syncMap == null) {
throw new IllegalStateException("SyncMap is not initialized");
}
syncMap.put(key, instance);
invalidateCache();
}
@Override
public void untrack(K key, T instance) {
Map<K, T> syncMap = getSyncMap();
if (syncMap == null) {
throw new IllegalStateException("SyncMap is not initialized");
}
// 只有当key对应的value确实是instance时才移除避免误删
syncMap.remove(key, instance);
invalidateCache();
}
@Override
public void clear() {
Map<K, T> syncMap = getSyncMap();
if (syncMap != null) {
syncMap.clear();
}
invalidateCache();
}
}

View File

@ -1,13 +0,0 @@
package top.r3944realms.lib39.core.sync;
/**
* The interface Entity.
*/
public interface IEntity {
/**
* Entity id int.
*
* @return the int
*/
int entityId();
}

View File

@ -1,24 +0,0 @@
package top.r3944realms.lib39.core.sync;
import net.minecraft.nbt.Tag;
/**
* The interface Inbt serializable.
*
* @param <T> the type parameter
*/
public interface INBTSerializable <T extends Tag>{
/**
* Serialize nbt t.
*
* @return the t
*/
T serializeNBT();
/**
* Deserialize nbt.
*
* @param var1 the var 1
*/
void deserializeNBT(T var1);
}

View File

@ -1,50 +0,0 @@
package top.r3944realms.lib39.core.sync;
import net.minecraft.resources.ResourceLocation;
/**
* The interface Sync data.
*
* @param <T> the type parameter
*/
public interface ISyncData<T> {
/**
* Id resource location.
*
* @return the resource location
*/
ResourceLocation id();
/**
* Is dirty boolean.
*
* @return the boolean
*/
boolean isDirty();
/**
* Sets dirty.
*
* @param dirty the dirty
*/
void setDirty(boolean dirty);
/**
* Mark dirty.
*/
default void markDirty() {
setDirty(true);
}
/**
* Copy from.
*
* @param src the src
*/
void copyFrom(T src);
/**
* Check if dirty then update.
*/
void checkIfDirtyThenUpdate();
}

View File

@ -1,129 +0,0 @@
package top.r3944realms.lib39.core.sync;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
/**
* The interface Sync manager.
*
* @param <K> the type parameter
* @param <T> the type parameter
*/
@SuppressWarnings("unused")
public interface ISyncManager<K, T extends ISyncData<?>> {
/**
* 获取同步映射
*
* @return the sync map
*/
Map<K, T> getSyncMap();
/**
* 获取同步集合
*
* @return the sync set
*/
default Set<T> getSyncSet() {
Map<K, T> syncMap = getSyncMap();
return Set.copyOf(syncMap.values());
}
/**
* 跟踪实例
*
* @param key the key
* @param instance the instance
*/
default void track(K key, T instance) {
Map<K, T> syncMap = getSyncMap();
if (syncMap == null) {
throw new IllegalStateException("SyncMap is not initialized");
}
syncMap.put(key, instance);
}
/**
* 取消跟踪
*
* @param key the key
* @param instance the instance
*/
default void untrack(K key, T instance) {
Map<K, T> syncMap = getSyncMap();
if (syncMap == null) {
throw new IllegalStateException("SyncMap is not initialized");
}
// 只有当key对应的value确实是instance时才移除避免误删
syncMap.remove(key, instance);
}
/**
* 遍历操作
*
* @param consumer the consumer
*/
default void foreach(Consumer<T> consumer) {
Map<K, T> syncMap = getSyncMap();
if (syncMap == null) {
throw new IllegalStateException("SyncMap is not initialized");
}
syncMap.values().forEach(consumer);
}
/**
* 批量操作
*
* @param instances the instances
*/
default void trackAll(Map<K, T> instances) {
Map<K, T> syncMap = getSyncMap();
if (syncMap == null) {
throw new IllegalStateException("SyncMap is not initialized");
}
syncMap.putAll(instances);
}
/**
* 获取大小
*
* @return the int
*/
default int size() {
Map<K, T> syncMap = getSyncMap();
return syncMap != null ? syncMap.size() : 0;
}
/**
* 检查是否包含key
*
* @param key the key
* @return the boolean
*/
default boolean containsKey(K key) {
Map<K, T> syncMap = getSyncMap();
return syncMap != null && syncMap.containsKey(key);
}
/**
* 检查是否包含value
*
* @param value the value
* @return the boolean
*/
default boolean containsValue(T value) {
Map<K, T> syncMap = getSyncMap();
return syncMap != null && syncMap.containsValue(value);
}
/**
* 清空所有数据
*/
default void clear() {
Map<K, T> syncMap = getSyncMap();
if (syncMap != null) {
syncMap.clear();
}
}
}

View File

@ -1,18 +0,0 @@
package top.r3944realms.lib39.core.sync;
/**
* The interface Update.
*/
public interface IUpdate {
/**
* Update.
*/
void update();
/**
* Gets sync data.
*
* @return the sync data
*/
NBTEntitySyncData getSyncData();
}

View File

@ -1,61 +0,0 @@
package top.r3944realms.lib39.core.sync;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
/**
* The type Nbt entity sync data.
*/
public abstract class NBTEntitySyncData implements IEntity, ISyncData<NBTEntitySyncData>, INBTSerializable<CompoundTag>, IUpdate {
/**
* The Dirty.
*/
protected boolean dirty;
/**
* The Id.
*/
protected final ResourceLocation id;
/**
* Instantiates a new Nbt sync data.
*
* @param id the id
*/
protected NBTEntitySyncData(ResourceLocation id) {
this.id = id;
}
@Override
public ResourceLocation id() {
return id;
}
@Override
public boolean isDirty() {
return dirty;
}
@Override
public void setDirty(boolean dirty) {
this.dirty = dirty;
}
@Override
public void copyFrom(@NotNull NBTEntitySyncData src) {
this.dirty = src.isDirty();
}
@Override
public void checkIfDirtyThenUpdate() {
if (isDirty()) {
update();
}
dirty = false;
}
@Override
public NBTEntitySyncData getSyncData() {
return this;
}
}

View File

@ -1,483 +0,0 @@
package top.r3944realms.lib39.core.sync;
import com.google.common.collect.Sets;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
/**
* The type Sync data 2 manager.
*
* @param <V> the type parameter
*/
@SuppressWarnings({"unused", "DuplicatedCode"})
public abstract class SyncData2Manager<V extends SyncData2Manager.TypedSyncEntry<?, ?>> {
/**
* Gets typed entries.
*
* @return the typed entries
*/
protected abstract Map<ResourceLocation, V> getTypedEntries();
/**
* 数据提供者接口 - 用于通过键获取数据
*
* @param <K> the type parameter
* @param <T> the type parameter
*/
@FunctionalInterface
public interface DataProvider<K, T> {
/**
* 通过键获取数据的 Optional
*
* @param key
* @return 数据的 Optional
*/
Optional<T> getData(K key);
}
/**
* The type Typed sync entry.
*
* @param <K> the type parameter
* @param <T> the type parameter
*/
protected static class TypedSyncEntry<K, T extends ISyncData<?>> {
/**
* The Manager.
*/
final ISyncManager<K, T> manager;
/**
* The Data provider.
*/
@Nullable
final DataProvider<Entity, T> dataProvider;
/**
* The Allowed classes.
*/
final Set<Class<?>> allowedClasses;
/**
* Instantiates a new Typed sync entry.
*
* @param manager the manager
* @param dataProvider the data provider
*/
public TypedSyncEntry(ISyncManager<K, T> manager, @Nullable DataProvider<Entity, T> dataProvider) {
this.manager = manager;
this.dataProvider = dataProvider;
this.allowedClasses = Sets.newConcurrentHashSet();
}
}
/**
* Register manager with data provider.
*
* @param <K> the type parameter
* @param <T> the type parameter
* @param key the key
* @param manager the manager
* @param dataProvider the data provider
*/
@SuppressWarnings("unchecked")
public <K, T extends ISyncData<?>> void registerManagerWithProvider(
ResourceLocation key,
ISyncManager<K, T> manager,
DataProvider<Entity, T> dataProvider
) {
Objects.requireNonNull(key, "ResourceLocation key cannot be null");
Objects.requireNonNull(manager, "Sync manager cannot be null");
Objects.requireNonNull(dataProvider, "Data provider cannot be null");
getTypedEntries().put(key, (V) new TypedSyncEntry<>(manager, dataProvider));
}
/**
* Register manager with function getter.
*
* @param <K> the type parameter
* @param <T> the type parameter
* @param key the key
* @param manager the manager
* @param getter the data getter function
*/
@SuppressWarnings("unchecked")
public <K, T extends ISyncData<?>> void registerManager(
ResourceLocation key,
ISyncManager<K, T> manager,
Function<Entity, Optional<T>> getter
) {
Objects.requireNonNull(key, "ResourceLocation key cannot be null");
Objects.requireNonNull(manager, "Sync manager cannot be null");
Objects.requireNonNull(getter, "Data getter function cannot be null");
getTypedEntries().put(key, (V) new TypedSyncEntry<>(manager, getter::apply));
}
/**
* 向后兼容的注册方法只注册管理器不注册数据提供者
*
* @param key the key
* @param manager the manager
*/
@SuppressWarnings("unchecked")
public void registerManager(ResourceLocation key, ISyncManager<?, ? extends ISyncData<?>> manager) {
Objects.requireNonNull(key, "ResourceLocation key cannot be null");
Objects.requireNonNull(manager, "Sync manager cannot be null");
// 创建一个没有数据提供者的 TypedSyncEntry
getTypedEntries().put(key, (V) new TypedSyncEntry<>(
(ISyncManager<?, ISyncData<?>>) manager,
null
));
}
/**
* Gets manager.
*
* @param <K> the type parameter
* @param <T> the type parameter
* @param key the key
* @return the manager
*/
@SuppressWarnings("unchecked")
public <K, T extends ISyncData<?>> Optional<ISyncManager<K, T>> getManager(ResourceLocation key) {
TypedSyncEntry<?,?> entry = getTypedEntries().get(key);
return entry != null ? Optional.of((ISyncManager<K,T>) entry.manager) : Optional.empty();
}
/**
* Gets data provider.
*
* @param <T> the type parameter
* @param key the key
* @return the data provider
*/
@SuppressWarnings("unchecked")
public <T extends ISyncData<?>> Optional<DataProvider<Entity, T>> getDataProvider(ResourceLocation key) {
TypedSyncEntry<?, ?> entry = getTypedEntries().get(key);
if (entry != null && entry.dataProvider != null) {
return Optional.of((DataProvider<Entity, T>) entry.dataProvider);
}
return Optional.empty();
}
/**
* 获取实体数据
*
* @param <T> the type parameter
* @param key the key
* @param entity the entity
* @return the entity data
*/
@SuppressWarnings("unchecked")
public <T extends ISyncData<?>> Optional<T> getEntityData(ResourceLocation key, Entity entity) {
return getDataProvider(key)
.flatMap(provider -> {
Optional<ISyncData<?>> result = provider.getData(entity);
return (Optional<T>) result;
});
}
/**
* Allow entity class.
*
* @param key the key
* @param classes the classes
*/
public final void allowEntityClass(ResourceLocation key, Class<?>... classes) {
Objects.requireNonNull(key, "ResourceLocation key cannot be null");
Objects.requireNonNull(classes, "Classes array cannot be null");
if (classes.length == 0) {
return;
}
TypedSyncEntry<?, ?> entry = getTypedEntries().get(key);
if (entry != null) {
entry.allowedClasses.addAll(Arrays.asList(classes));
}
}
/**
* 移除允许的实体类
*
* @param key the key
* @param classes the classes
*/
public final void disallowEntityClass(ResourceLocation key, Class<?>... classes) {
Objects.requireNonNull(key, "ResourceLocation key cannot be null");
Objects.requireNonNull(classes, "Classes array cannot be null");
TypedSyncEntry<?, ?> entry = getTypedEntries().get(key);
if (entry != null && classes.length > 0) {
Arrays.asList(classes).forEach(entry.allowedClasses::remove);
}
}
/**
* 绑定数据提供者用于分离注册的情况
*
* @param <T> the type parameter
* @param key the key
* @param dataProvider the data provider
*/
public <T extends ISyncData<?>> void bindDataProvider(ResourceLocation key, DataProvider<Entity, T> dataProvider) {
Objects.requireNonNull(key, "ResourceLocation key cannot be null");
Objects.requireNonNull(dataProvider, "Data provider cannot be null");
TypedSyncEntry<?, ?> entry = getTypedEntries().get(key);
if (entry != null) {
// 更新现有条目的数据提供者
updateDataProviderInEntry(key, entry, dataProvider);
} else {
throw new IllegalArgumentException("No manager found for " + key);
}
}
/**
* 绑定简单的数据获取器
*
* @param <T> the type parameter
* @param key the key
* @param getter the data getter function
*/
public <T extends ISyncData<?>> void bindDataGetter(ResourceLocation key, @NotNull Function<Entity, Optional<T>> getter) {
bindDataProvider(key, getter::apply);
}
/**
* 解绑数据提供者
*
* @param key the key
*/
public void unbindDataProvider(ResourceLocation key) {
Objects.requireNonNull(key, "ResourceLocation key cannot be null");
TypedSyncEntry<?, ?> entry = getTypedEntries().get(key);
if (entry != null) {
// 将数据提供者设置为null但保留管理器和其他配置
updateDataProviderInEntry(key, entry, null);
}
}
/**
* 清除允许的实体类
*
* @param key the key
*/
public void clearAllowedEntityClasses(ResourceLocation key) {
Objects.requireNonNull(key, "ResourceLocation key cannot be null");
TypedSyncEntry<?, ?> entry = getTypedEntries().get(key);
if (entry != null) {
entry.allowedClasses.clear();
}
}
/**
* Is entity class allowed boolean.
*
* @param key the key
* @param entityClass the entity class
* @return the boolean
*/
public boolean isEntityClassAllowed(ResourceLocation key, Class<?> entityClass) {
Objects.requireNonNull(key, "ResourceLocation key cannot be null");
Objects.requireNonNull(entityClass, "Entity class cannot be null");
TypedSyncEntry<?, ?> entry = getTypedEntries().get(key);
boolean isAllowed = false;
if (entry != null) {
for (Class<?> allowedClass : entry.allowedClasses) {
if (allowedClass.isAssignableFrom(entityClass)) {
isAllowed = true;
break;
}
}
}
return entry != null && isAllowed ;
}
/**
* Track entity for manager.
*
* @param entity the entity
* @param managerId the manager id
*/
@SuppressWarnings("unchecked")
public void trackEntityForManager(Entity entity, ResourceLocation managerId) {
TypedSyncEntry<UUID, ?> entry = (TypedSyncEntry<UUID, ?>) getTypedEntries().get(managerId);
if (entry != null) {
trackEntityWithTypedEntry(entity, entry);
}
}
private <T extends ISyncData<?>> void trackEntityWithTypedEntry(Entity entity, @NotNull TypedSyncEntry<UUID, T> entry) {
if (entry.dataProvider != null) {
entry.dataProvider.getData(entity)
.ifPresent(data -> entry.manager.track(entity.getUUID(), data));
}
}
/**
* Untrack entity for manager.
*
* @param entity the entity
* @param managerId the manager id
*/
@SuppressWarnings("unchecked")
public void untrackEntityForManager(Entity entity, ResourceLocation managerId) {
TypedSyncEntry<UUID, ?> entry = (TypedSyncEntry<UUID, ?>) getTypedEntries().get(managerId);
if (entry != null) {
untrackEntityWithTypedEntry(entity, entry);
}
}
private <T extends ISyncData<?>> void untrackEntityWithTypedEntry(Entity entity, @NotNull TypedSyncEntry<UUID, T> entry) {
if (entry.dataProvider != null) {
entry.dataProvider.getData(entity)
.ifPresent(data -> entry.manager.untrack(entity.getUUID(), data));
}
}
/**
* 从所有管理器中移除实体跟踪
*
* @param entity the entity
*/
public void untrackEntityFromAllManagers(Entity entity) {
for (ResourceLocation id : getRegisteredKeys()) {
if (isEntityClassAllowed(id, entity.getClass())) {
untrackEntityForManager(entity, id);
}
}
}
/**
* 批量从管理器中移除实体跟踪
*
* @param entities the entities
* @param managerId the manager id
*/
public void untrackEntitiesForManager(@NotNull Iterable<Entity> entities, ResourceLocation managerId) {
for (Entity entity : entities) {
untrackEntityForManager(entity, managerId);
}
}
/**
* 从所有管理器中批量移除实体跟踪
*
* @param entities the entities
*/
public void untrackEntitiesFromAllManagers(@NotNull Iterable<Entity> entities) {
for (Entity entity : entities) {
untrackEntityFromAllManagers(entity);
}
}
/**
* 强制清理管理器中的所有跟踪数据
*
* @param managerId the manager id
*/
public void clearAllTrackedData(ResourceLocation managerId) {
TypedSyncEntry<?, ?> entry = getTypedEntries().get(managerId);
if (entry != null) {
clearTrackedDataForEntry(entry);
}
}
private <K, T extends ISyncData<?>> void clearTrackedDataForEntry(@NotNull TypedSyncEntry<K, T> entry) {
Set<T> syncSet = entry.manager.getSyncSet();
if (syncSet != null) {
syncSet.clear();
}
}
/**
* 清理所有管理器的跟踪数据
*/
public void clearAllTrackedData() {
for (ResourceLocation id : getRegisteredKeys()) {
clearAllTrackedData(id);
}
}
/**
* Update data provider in entry.
*
* @param <K> the type parameter
* @param <T> the type parameter
* @param id the id
* @param entry the entry
* @param newDataProvider the new data provider
*/
// 辅助方法更新条目的数据提供者
@SuppressWarnings("unchecked")
protected <K, T extends ISyncData<?>> void updateDataProviderInEntry(
ResourceLocation id,
TypedSyncEntry<?,?> entry,
DataProvider<Entity, T> newDataProvider
) {
// 由于 DataProvider final我们需要创建一个新的 TypedSyncEntry
TypedSyncEntry<K, T> newEntry = new TypedSyncEntry<>(
(ISyncManager<K, T>) entry.manager,
newDataProvider
);
newEntry.allowedClasses.addAll(entry.allowedClasses);
getTypedEntries().put(id, (V) newEntry);
}
/**
* Gets registered keys.
*
* @return the registered keys
*/
public Set<ResourceLocation> getRegisteredKeys() {
return Collections.unmodifiableSet(getTypedEntries().keySet());
}
/**
* For each.
*
* @param consumer the consumer
*/
public void forEach(BiConsumer<ResourceLocation, ISyncManager<?,?>> consumer) {
Objects.requireNonNull(consumer, "Consumer cannot be null");
getTypedEntries().forEach((key, entry) -> consumer.accept(key, entry.manager));
}
/**
* Gets manager count.
*
* @return the manager count
*/
public int getManagerCount() {
return getTypedEntries().size();
}
/**
* Clear all.
*/
public void clearAll() {
getTypedEntries().clear();
}
/**
* 移除管理器包括所有相关配置
*
* @param key the key
*/
public void removeManager(ResourceLocation key) {
Objects.requireNonNull(key, "ResourceLocation key cannot be null");
getTypedEntries().remove(key);
}
}

View File

@ -1,197 +0,0 @@
package top.r3944realms.lib39.datagen.provider;
import com.google.gson.JsonObject;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.data.PackOutput.Target;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.level.block.Block;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
/**
* The type Language provider.
*/
public abstract class LanguageProvider implements DataProvider {
private final Map<String, String> data = new TreeMap<>();
private final PackOutput output;
private final String modid;
private final String locale;
/**
* Instantiates a new Language provider.
*
* @param output the output
* @param modid the modid
* @param locale the locale
*/
public LanguageProvider(PackOutput output, String modid, String locale) {
this.output = output;
this.modid = modid;
this.locale = locale;
}
/**
* Add translations.
*/
protected abstract void addTranslations();
public @NotNull CompletableFuture<?> run(@NotNull CachedOutput cache) {
this.addTranslations();
return !this.data.isEmpty() ? this.save(cache, this.output.getOutputFolder(Target.RESOURCE_PACK).resolve(this.modid).resolve("lang").resolve(this.locale + ".json")) : CompletableFuture.allOf();
}
public @NotNull String getName() {
return "Languages: " + this.locale;
}
private @NotNull CompletableFuture<?> save(CachedOutput cache, Path target) {
JsonObject json = new JsonObject();
Objects.requireNonNull(json);
this.data.forEach(json::addProperty);
return DataProvider.saveStable(cache, json, target);
}
/**
* Add block.
*
* @param key the key
* @param name the name
*/
public void addBlock(@NotNull Supplier<? extends Block> key, String name) {
this.add(key.get(), name);
}
/**
* Add.
*
* @param key the key
* @param name the name
*/
public void add(@NotNull Block key, String name) {
this.add(key.getDescriptionId(), name);
}
/**
* Add item.
*
* @param key the key
* @param name the name
*/
public void addItem(@NotNull Supplier<? extends Item> key, String name) {
this.add(key.get(), name);
}
/**
* Add.
*
* @param key the key
* @param name the name
*/
public void add(@NotNull Item key, String name) {
this.add(key.getDescriptionId(), name);
}
/**
* Add item stack.
*
* @param key the key
* @param name the name
*/
public void addItemStack(@NotNull Supplier<ItemStack> key, String name) {
this.add(key.get(), name);
}
/**
* Add.
*
* @param key the key
* @param name the name
*/
public void add(@NotNull ItemStack key, String name) {
this.add(key.getDescriptionId(), name);
}
/**
* Add enchantment.
*
* @param key the key
* @param name the name
*/
public void addEnchantment(@NotNull Supplier<? extends Enchantment> key, String name) {
this.add(key.get(), name);
}
/**
* Add.
*
* @param key the key
* @param name the name
*/
public void add(@NotNull Enchantment key, String name) {
this.add(key.getDescriptionId(), name);
}
/**
* Add effect.
*
* @param key the key
* @param name the name
*/
public void addEffect(@NotNull Supplier<? extends MobEffect> key, String name) {
this.add(key.get(), name);
}
/**
* Add.
*
* @param key the key
* @param name the name
*/
public void add(@NotNull MobEffect key, String name) {
this.add(key.getDescriptionId(), name);
}
/**
* Add entity type.
*
* @param key the key
* @param name the name
*/
public void addEntityType(@NotNull Supplier<? extends EntityType<?>> key, String name) {
this.add(key.get(), name);
}
/**
* Add.
*
* @param key the key
* @param name the name
*/
public void add(@NotNull EntityType<?> key, String name) {
this.add(key.getDescriptionId(), name);
}
/**
* Add.
*
* @param key the key
* @param value the value
*/
public void add(String key, String value) {
if (this.data.put(key, value) != null) {
throw new IllegalStateException("Duplicate translation key " + key);
}
}
}

View File

@ -1,104 +0,0 @@
package top.r3944realms.lib39.datagen.provider;
import net.minecraft.data.PackOutput;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.lib39.Lib39;
import top.r3944realms.lib39.datagen.value.ILangKeyValue;
import top.r3944realms.lib39.datagen.value.ILangKeyValueCollection;
import top.r3944realms.lib39.datagen.value.McLocale;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* The type Simple language provider.
*/
public class SimpleLanguageProvider extends LanguageProvider {
private final McLocale language;
private final ILangKeyValueCollection langKeyValueCollection;
@Nullable
private ILangKeyValueCollection[] langKeyValueCollections;
private final Map<String, String> translationMap; // Better naming
private final List<String> orderedKeys; // Better naming than "objects"
/**
* Instantiates a new Simple language provider.
*
* @param output the output
* @param modId the mod id
* @param language the language
* @param langKeyValueCollection the lang key value collection
*/
public SimpleLanguageProvider(PackOutput output, String modId,
@NotNull McLocale language,
ILangKeyValueCollection langKeyValueCollection) {
super(output, modId, language.mcCode());
this.language = language;
this.langKeyValueCollection = langKeyValueCollection;
this.translationMap = new HashMap<>();
this.orderedKeys = new ArrayList<>();
initializeTranslations();
}
/**
* Instantiates a new Simple language provider.
*
* @param output the output
* @param modId the mod id
* @param language the language
* @param langKeyValueCollection the lang key value collection
*/
public SimpleLanguageProvider(PackOutput output, String modId,
@NotNull McLocale language,
ILangKeyValueCollection... langKeyValueCollection) {
super(output, modId, language.mcCode());
this.language = language;
this.langKeyValueCollection = null;
this.langKeyValueCollections = langKeyValueCollection;
this.translationMap = new HashMap<>();
this.orderedKeys = new ArrayList<>();
initializeTranslations();
}
private void initializeTranslations() {
if (langKeyValueCollection != null) {
addToTranslationMap(langKeyValueCollection);
} else if (langKeyValueCollections != null) {
for (ILangKeyValueCollection keyValueCollection : langKeyValueCollections) {
if (keyValueCollection != null) {
addToTranslationMap(keyValueCollection);
}
}
}
}
private void addToTranslationMap(ILangKeyValueCollection keyValueCollection) {
for (ILangKeyValue langKeyValue : keyValueCollection.getValues()) {
String key = langKeyValue.getKey();
String value = langKeyValue.getLang(language);
if (!translationMap.containsKey(key)) {
orderedKeys.add(key);
}
translationMap.put(key, value);
}
}
@Override
protected void addTranslations() {
orderedKeys.forEach(key -> add(key, translationMap.get(key)));
validateTranslations();
}
private void validateTranslations() {
long addedCount = orderedKeys.stream()
.filter(translationMap::containsKey)
.count();
Lib39.LOGGER.info("Added {}/{} translations for {}",
addedCount, orderedKeys.size(), language.mcCode());
}
}

View File

@ -1,22 +0,0 @@
package top.r3944realms.lib39.datagen.value;
/**
* The interface Lang key value.
*/
public interface ILangKeyValue {
/**
* Gets key.
*
* @return the key
*/
String getKey();
/**
* Gets lang.
*
* @param locale the locale
* @return the lang
*/
String getLang(McLocale locale);
}

View File

@ -1,30 +0,0 @@
package top.r3944realms.lib39.datagen.value;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* The interface Lang key value collection.
*/
public interface ILangKeyValueCollection {
/**
* Gets values.
*
* @return the values
*/
List<? extends ILangKeyValue> getValues();
/**
* Gets lang.
*
* @param locale the locale
* @param key the key
* @return the lang
*/
static String getLang(McLocale locale, @NotNull ILangKeyValue key) {
return key.getLang(locale);
}
}

View File

@ -1,22 +0,0 @@
package top.r3944realms.lib39.datagen.value;
import java.util.Locale;
/**
* The interface Locale entry.
*/
public interface ILocaleEntry {
/**
* Mc code string.
*
* @return the string
*/
String mcCode();
/**
* Java locale locale.
*
* @return the locale
*/
Locale javaLocale();
}

View File

@ -1,617 +0,0 @@
package top.r3944realms.lib39.datagen.value;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.function.Supplier;
/**
* The type Lang key value.
*/
@SuppressWarnings("unused")
public class LangKeyValue implements ILangKeyValue {
/**
* The Supplier.
*/
protected final Supplier<?> supplier;
/**
* The Key.
*/
protected final String key;
/**
* The Us en.
*/
protected final String US_EN;
/**
* The Sim cn.
*/
protected final String SIM_CN;
/**
* The Tra cn.
*/
protected final String TRA_CN;
/**
* The Lzh.
*/
protected final String LZH;
/**
* The Default.
*/
protected final Boolean Default;
/**
* The Mpe.
*/
protected final ModPartEnum MPE;
/**
* Instantiates a new Lang key value.
*
* @param builder the builder
*/
protected LangKeyValue(Builder builder) {
this.supplier = builder.supplier;
this.key = builder.key;
this.MPE = builder.MPE;
this.US_EN = builder.US_EN;
this.SIM_CN = builder.SIM_CN;
this.TRA_CN = builder.TRA_CN;
this.LZH = builder.LZH;
this.Default = builder.Default;
}
/**
* Builder for LangKeyValue
*/
public static class Builder {
private Supplier<?> supplier;
private String key;
private ModPartEnum MPE;
private String US_EN;
private String SIM_CN;
private String TRA_CN;
private String LZH;
private Boolean Default = false;
/**
* Set supplier
*
* @param supplier the supplier
* @return the builder
*/
@Contract("_ -> this")
public Builder supplier(Supplier<?> supplier) {
this.supplier = supplier;
return this;
}
/**
* Set key
*
* @param key the key
* @return the builder
*/
@Contract("_ -> this")
public Builder key(String key) {
this.key = key;
return this;
}
/**
* Set mod part enum
*
* @param MPE the mpe
* @return the builder
*/
@Contract("_ -> this")
public Builder MPE(ModPartEnum MPE) {
this.MPE = MPE;
return this;
}
/**
* Set US English translation
*
* @param US_EN the us en
* @return the builder
*/
@Contract("_ -> this")
public Builder US_EN(String US_EN) {
this.US_EN = US_EN;
return this;
}
/**
* Set Simplified Chinese translation
*
* @param SIM_CN the sim cn
* @return the builder
*/
@Contract("_ -> this")
public Builder SIM_CN(String SIM_CN) {
this.SIM_CN = SIM_CN;
return this;
}
/**
* Set Traditional Chinese translation
*
* @param TRA_CN the tra cn
* @return the builder
*/
@Contract("_ -> this")
public Builder TRA_CN(String TRA_CN) {
this.TRA_CN = TRA_CN;
return this;
}
/**
* Set Literary Chinese translation
*
* @param LZH the lzh
* @return the builder
*/
@Contract("_ -> this")
public Builder LZH(String LZH) {
this.LZH = LZH;
return this;
}
/**
* Set as default
*
* @param isDefault the is default
* @return the builder
*/
@Contract("_ -> this")
public Builder isDefault(Boolean isDefault) {
this.Default = isDefault;
return this;
}
/**
* Build the LangKeyValue instance
*
* @return the lang key value
*/
@NotNull
public LangKeyValue build() {
// Validate required fields
if (MPE == null) {
throw new IllegalStateException("MPE (ModPartEnum) is required");
}
if (US_EN == null) {
throw new IllegalStateException("US_EN translation is required");
}
if (SIM_CN == null) {
throw new IllegalStateException("SIM_CN translation is required");
}
if (TRA_CN == null) {
throw new IllegalStateException("TRA_CN translation is required");
}
// Either supplier or key must be provided, but not both
if (supplier == null && key == null) {
throw new IllegalStateException("Either supplier or key must be provided");
}
if (supplier != null && key != null) {
throw new IllegalStateException("Cannot provide both supplier and key");
}
return new LangKeyValue(this);
}
}
/**
* Create a new builder instance
*
* @return the builder
*/
@Contract(" -> new")
public static @NotNull Builder builder() {
return new Builder();
}
/**
* Create builder with supplier
*
* @param supplier the supplier
* @return the builder
*/
@Contract("_ -> new")
public static @NotNull Builder withSupplier(Supplier<?> supplier) {
return new Builder().supplier(supplier);
}
/**
* Create builder with key
*
* @param key the key
* @return the builder
*/
@Contract("_ -> new")
public static @NotNull Builder withKey(String key) {
return new Builder().key(key);
}
// 保持原有的静态工厂方法作为便捷方法
/**
* Of supplier lang key value.
*
* @param supplier the supplier
* @param MPE the mpe
* @param US_EN the us en
* @param SIM_CN the sim cn
* @param TRA_CN the tra cn
* @return the lang key value
*/
@Contract(value = "_, _, _, _, _ -> new", pure = true)
public static @NotNull LangKeyValue ofSupplier(Supplier<?> supplier, ModPartEnum MPE,
String US_EN, String SIM_CN, String TRA_CN) {
return builder()
.supplier(supplier)
.MPE(MPE)
.US_EN(US_EN)
.SIM_CN(SIM_CN)
.TRA_CN(TRA_CN)
.build();
}
/**
* Of supplier lang key value.
*
* @param supplier the supplier
* @param MPE the mpe
* @param US_EN the us en
* @param SIM_CN the sim cn
* @param TRA_CN the tra cn
* @param isDefault the is default
* @return the lang key value
*/
@Contract(value = "_, _, _, _, _, _ -> new", pure = true)
public static @NotNull LangKeyValue ofSupplier(Supplier<?> supplier, ModPartEnum MPE,
String US_EN, String SIM_CN, String TRA_CN, boolean isDefault) {
return builder()
.supplier(supplier)
.MPE(MPE)
.US_EN(US_EN)
.SIM_CN(SIM_CN)
.TRA_CN(TRA_CN)
.isDefault(isDefault)
.build();
}
/**
* Of supplier lang key value.
*
* @param supplier the supplier
* @param MPE the mpe
* @param US_EN the us en
* @param SIM_CN the sim cn
* @param TRA_CN the tra cn
* @param LZH the lzh
* @return the lang key value
*/
@Contract(value = "_, _, _, _, _, _ -> new", pure = true)
public static @NotNull LangKeyValue ofSupplier(Supplier<?> supplier, ModPartEnum MPE,
String US_EN, String SIM_CN, String TRA_CN, String LZH) {
return builder()
.supplier(supplier)
.MPE(MPE)
.US_EN(US_EN)
.SIM_CN(SIM_CN)
.TRA_CN(TRA_CN)
.LZH(LZH)
.build();
}
/**
* Of supplier lang key value.
*
* @param supplier the supplier
* @param MPE the mpe
* @param US_EN the us en
* @param SIM_CN the sim cn
* @param TRA_CN the tra cn
* @param LZH the lzh
* @param isDefault the is default
* @return the lang key value
*/
@Contract(value = "_, _, _, _, _, _, _ -> new", pure = true)
public static @NotNull LangKeyValue ofSupplier(Supplier<?> supplier, ModPartEnum MPE,
String US_EN, String SIM_CN, String TRA_CN, String LZH, boolean isDefault) {
return builder()
.supplier(supplier)
.MPE(MPE)
.US_EN(US_EN)
.SIM_CN(SIM_CN)
.TRA_CN(TRA_CN)
.LZH(LZH)
.isDefault(isDefault)
.build();
}
/**
* Of key lang key value.
*
* @param key the key
* @param MPE the mpe
* @param US_EN the us en
* @param SIM_CN the sim cn
* @param TRA_CN the tra cn
* @return the lang key value
*/
@Contract(value = "_, _, _, _, _ -> new", pure = true)
public static @NotNull LangKeyValue ofKey(String key, ModPartEnum MPE,
String US_EN, String SIM_CN, String TRA_CN) {
return builder()
.key(key)
.MPE(MPE)
.US_EN(US_EN)
.SIM_CN(SIM_CN)
.TRA_CN(TRA_CN)
.build();
}
/**
* Of key lang key value.
*
* @param key the key
* @param MPE the mpe
* @param US_EN the us en
* @param SIM_CN the sim cn
* @param TRA_CN the tra cn
* @param isDefault the is default
* @return the lang key value
*/
@Contract(value = "_, _, _, _, _, _ -> new", pure = true)
public static @NotNull LangKeyValue ofKey(String key, ModPartEnum MPE,
String US_EN, String SIM_CN, String TRA_CN, boolean isDefault) {
return builder()
.key(key)
.MPE(MPE)
.US_EN(US_EN)
.SIM_CN(SIM_CN)
.TRA_CN(TRA_CN)
.isDefault(isDefault)
.build();
}
/**
* Of key lang key value.
*
* @param key the key
* @param MPE the mpe
* @param US_EN the us en
* @param SIM_CN the sim cn
* @param TRA_CN the tra cn
* @param LZH the lzh
* @return the lang key value
*/
@Contract(value = "_, _, _, _, _, _ -> new", pure = true)
public static @NotNull LangKeyValue ofKey(String key, ModPartEnum MPE,
String US_EN, String SIM_CN, String TRA_CN, String LZH) {
return builder()
.key(key)
.MPE(MPE)
.US_EN(US_EN)
.SIM_CN(SIM_CN)
.TRA_CN(TRA_CN)
.LZH(LZH)
.build();
}
/**
* Of key lang key value.
*
* @param key the key
* @param MPE the mpe
* @param US_EN the us en
* @param SIM_CN the sim cn
* @param TRA_CN the tra cn
* @param LZH the lzh
* @param isDefault the is default
* @return the lang key value
*/
@Contract(value = "_, _, _, _, _, _, _ -> new", pure = true)
public static @NotNull LangKeyValue ofKey(String key, ModPartEnum MPE,
String US_EN, String SIM_CN, String TRA_CN, String LZH, boolean isDefault) {
return builder()
.key(key)
.MPE(MPE)
.US_EN(US_EN)
.SIM_CN(SIM_CN)
.TRA_CN(TRA_CN)
.LZH(LZH)
.isDefault(isDefault)
.build();
}
/**
* Copy of lang key value.
*
* @param supplier the supplier
* @param modPartEnum the mod part enum
* @param other the other
* @return the lang key value
*/
public static @NotNull LangKeyValue copyOf(Supplier<?> supplier, ModPartEnum modPartEnum, @NotNull LangKeyValue other) {
return builder()
.supplier(supplier)
.MPE(modPartEnum)
.US_EN(other.US_EN)
.SIM_CN(other.SIM_CN)
.TRA_CN(other.TRA_CN)
.LZH(other.LZH)
.build();
}
/**
* Copy of lang key value.
*
* @param key the key
* @param modPartEnum the mod part enum
* @param other the other
* @return the lang key value
*/
public static @NotNull LangKeyValue copyOf(String key, ModPartEnum modPartEnum, @NotNull LangKeyValue other) {
return builder()
.key(key)
.MPE(modPartEnum)
.US_EN(other.US_EN)
.SIM_CN(other.SIM_CN)
.TRA_CN(other.TRA_CN)
.LZH(other.LZH)
.build();
}
/**
* Copy of lang key value.
*
* @param supplier the supplier
* @param modPartEnum the mod part enum
* @param other the other
* @param isDefault the is default
* @return the lang key value
*/
public static @NotNull LangKeyValue copyOf(Supplier<?> supplier, ModPartEnum modPartEnum, @NotNull LangKeyValue other, boolean isDefault) {
return builder()
.supplier(supplier)
.MPE(modPartEnum)
.US_EN(other.US_EN)
.SIM_CN(other.SIM_CN)
.TRA_CN(other.TRA_CN)
.LZH(other.LZH)
.isDefault(isDefault)
.build();
}
/**
* Copy of lang key value.
*
* @param key the key
* @param modPartEnum the mod part enum
* @param other the other
* @param isDefault the is default
* @return the lang key value
*/
public static @NotNull LangKeyValue copyOf(String key, ModPartEnum modPartEnum, @NotNull LangKeyValue other, boolean isDefault) {
return builder()
.key(key)
.MPE(modPartEnum)
.US_EN(other.US_EN)
.SIM_CN(other.SIM_CN)
.TRA_CN(other.TRA_CN)
.LZH(other.LZH)
.isDefault(isDefault)
.build();
}
@Override
public String getKey() {
return Objects.requireNonNullElseGet(key, () -> switch (MPE) {
case ITEM -> getItem().getDescriptionId();
case BLOCK -> getBlock().getDescriptionId();
default ->
throw new UnsupportedOperationException("The Key value is NULL! Please use the correct constructor and write the parameters correctly");
});
}
@Override
public String getLang(@NotNull McLocale locale) {
return switch (locale) {
case EN_US, JA_JP, KO_KR, RU_RU, DE_DE, ES_ES, FR_FR -> US_EN;
case ZH_CN -> SIM_CN;
case ZH_TW -> TRA_CN;
case LZH -> LZH != null ? LZH : TRA_CN; // Fallback to TRA_CN if LZH is null
};
}
/**
* Gets supplier.
*
* @return the supplier
*/
// Getters for all fields
public Supplier<?> getSupplier() { return supplier; }
/**
* Gets us en.
*
* @return the us en
*/
public String getUS_EN() { return US_EN; }
/**
* Gets sim cn.
*
* @return the sim cn
*/
public String getSIM_CN() { return SIM_CN; }
/**
* Gets tra cn.
*
* @return the tra cn
*/
public String getTRA_CN() { return TRA_CN; }
/**
* Gets lzh.
*
* @return the lzh
*/
public String getLZH() { return LZH; }
/**
* Is default boolean.
*
* @return the boolean
*/
public Boolean isDefault() { return Default; }
/**
* Gets mpe.
*
* @return the mpe
*/
public ModPartEnum getMPE() { return MPE; }
/**
* Gets item.
*
* @return the item
* @throws IllegalArgumentException the illegal argument exception
*/
public Item getItem() throws IllegalArgumentException {
if(MPE == ModPartEnum.ITEM) {
return (Item) supplier.get();
}
else throw new IllegalCallerException("Target's MPE is not ModPartEnum#ITEM.");
}
/**
* Gets block.
*
* @return the block
* @throws IllegalArgumentException the illegal argument exception
*/
public Block getBlock() throws IllegalArgumentException {
if(MPE == ModPartEnum.BLOCK) {
return (Block) supplier.get();
}
else throw new IllegalCallerException("Target's MPE is not ModPartEnum#BLOCK.");
}
@Override
public String toString() {
return "LangKeyValue{" +
"key='" + key + '\'' +
", US_EN='" + US_EN + '\'' +
", SIM_CN='" + SIM_CN + '\'' +
", MPE=" + MPE +
'}';
}
}

View File

@ -1,63 +0,0 @@
package top.r3944realms.lib39.datagen.value;
import java.util.Locale;
/**
* The enum Mc locale.
*/
public enum McLocale implements ILocaleEntry {
/**
* En us mc locale.
*/
EN_US("en_us", Locale.US),
/**
* Zh cn mc locale.
*/
ZH_CN("zh_cn", Locale.SIMPLIFIED_CHINESE),
/**
* Zh tw mc locale.
*/
ZH_TW("zh_tw", Locale.TRADITIONAL_CHINESE),
/**
* The Lzh.
*/
LZH("lzh", new Locale("lzh", "ZH")),
/**
* Ja jp mc locale.
*/
JA_JP("ja_jp", Locale.JAPAN),
/**
* Ko kr mc locale.
*/
KO_KR("ko_kr", Locale.KOREA),
/**
* The Ru ru.
*/
RU_RU("ru_ru", new Locale("ru", "RU")),
/**
* Fr fr mc locale.
*/
FR_FR("fr_fr", Locale.FRANCE),
/**
* De de mc locale.
*/
DE_DE("de_de", Locale.GERMANY),
/**
* The Es es.
*/
ES_ES("es_es", new Locale("es", "ES"));
private final String mcCode;
private final Locale javaLocale;
McLocale(String mcCode, Locale javaLocale) {
this.mcCode = mcCode;
this.javaLocale = javaLocale;
}
@Override
public String mcCode() { return mcCode; }
@Override
public Locale javaLocale() { return javaLocale; }
}

View File

@ -1,173 +0,0 @@
package top.r3944realms.lib39.datagen.value;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* 模组各部分的类型枚举用于数据生成与分类
*/
public enum ModPartEnum {
/**
* 默认/未指定类型
*/
DEFAULT,
/**
* 物品
*/
ITEM(Item.class),
/**
* 方块
*/
BLOCK(Block.class),
/**
* 附魔
*/
ENCHANTMENT,
/**
* 进度标题
*/
ADVANCEMENT_TITLE,
/**
* 成就描述
*/
ADVANCEMENT_DESCRIPTION,
/**
* 创造模式物品栏
*/
CREATIVE_TAB,
/**
* 配置项
*/
CONFIG,
/**
* 实体生物载具等
*/
ENTITY,
/**
* 图形界面
*/
GUI,
/**
* 容器
*/
CONTAINER,
/**
* 画作描述
*/
PAINTING_TITLE,
/**
* 画作作者
*/
PAINTING_AUTHOR,
/**
* 标题
*/
TITLE,
/**
* 名称
*/
NAME,
/**
* 游戏规则/gamerule
*/
GAME_RULE,
/**
* 描述文本
*/
DESCRIPTION,
/**
* 一般信息
*/
INFO,
/**
* 消息聊天提示等
*/
MESSAGE,
/**
* 生物群系
*/
BIOME,
/**
* 命令
*/
COMMAND,
/**
* 声音资源
*/
SOUND;
;
@Nullable
private final Class<?> clazz;
ModPartEnum() {
clazz = null;
}
ModPartEnum(@Nullable Class<?> clazz) {
this.clazz = clazz;
}
/**
* Gets full key.
*
* @param modId the mod id
* @param name the name
* @return the full key
*/
@Contract(pure = true)
public @NotNull String getFullKey(String modId, String name) {
return switch (this) {
case ITEM -> "item." + modId + "." + name;
case BLOCK -> "block." + modId + "." + name;
case ENCHANTMENT -> "enchantment." + modId + "." + name;
case ADVANCEMENT_TITLE -> "advancement." + modId + "." + name + ".title";
case ADVANCEMENT_DESCRIPTION -> "advancement." + modId + "." + name + ".description";
case CREATIVE_TAB -> "creativetab." + modId + "." + name;
case BIOME -> "biome." + modId + "." + name;
case CONFIG -> "config." + modId + "." + name;
case ENTITY -> "entity." + modId + "." + name;
case GUI -> "gui." + modId + "." + name;
case CONTAINER -> "container." + modId + "." + name;
case PAINTING_AUTHOR -> "painting." + modId + "." + name + ".author";
case PAINTING_TITLE -> "painting." + modId + "." + name + ".title";
case TITLE -> "title." + modId + "." + name;
case NAME -> "name." + modId + "." + name;
case GAME_RULE -> "gamerule."+ modId + "." + name;
case DESCRIPTION -> "description." + modId + "." + name;
case INFO -> "info." + modId + "." + name;
case MESSAGE -> "message." + modId + "." + name;
case COMMAND -> "command." + modId + "." + name;
case SOUND -> "sound." + modId + "." + name;
default -> modId + "." + name;
};
}
/**
* Gets clazz.
*
* @return the clazz
*/
public @Nullable Class<?> getClazz() {
return clazz;
}
}

View File

@ -1,33 +0,0 @@
package top.r3944realms.lib39.example;
/**
* The type Lib 39 example.
*/
public class Lib39Example {
private static boolean registered = false;
/**
* Instantiates a new Lib 39 example.
*/
public Lib39Example() {
if (!registered) {
init();
registered = true;
}
}
/**
* Init.
*/
public void init() {
}
/**
* Demonstrate feature.
*/
public void demonstrateFeature() {
}
}

View File

@ -1,128 +0,0 @@
package top.r3944realms.lib39.example.client.screen;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Renderable;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.network.chat.Component;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.joml.Vector2f;
import top.r3944realms.lib39.client.gui.component.WheelWidget;
import top.r3944realms.lib39.mixin.minecraft.ScreenAccessor;
import top.r3944realms.lib39.util.lang.FourConsumer;
import top.r3944realms.lib39.util.lang.Pair;
import java.util.List;
import java.util.Objects;
import static top.r3944realms.lib39.client.gui.component.WheelWidget.IGNORE_CURSOR_MOVE_LENGTH;
/**
* The type Forge screen.
*/
public class ForgeScreen extends Screen {
private final LocalPlayer player = Objects.requireNonNull(Minecraft.getInstance().player);
private final InteractionHand hand;
private final int mode;
/**
* The Wheel.
*/
public WheelWidget wheel;
/**
* Instantiates a new Forge screen.
*
* @param hand the hand
* @param mode the mode
*/
public ForgeScreen(InteractionHand hand, int mode) {
super(Component.literal("Test"));
this.hand = hand;
this.mode = mode;
}
@Override
protected void init() {
int leftPos = (this.width - 75) / 2;
int topPos = (this.height - 75) / 2;
ItemStack holding = player.getItemInHand(this.hand);
WheelWidget wheel = new WheelWidget(
leftPos, topPos, 75, 75,
12.5f, 32.5f, 0.75f,
List.of(
Pair.of(
Component.literal("auto"),
renderItem(holding)),
Pair.of(
Component.literal("axe"),
renderItem(holding)),
Pair.of(
Component.literal("shovel"),
renderItem(holding)),
Pair.of(
Component.literal("hoe"),
renderItem(holding)),
Pair.of(
Component.literal("pickaxe"),
renderItem(holding))
)
).setCurrentIndex(this.wheel != null ? this.wheel.getCurrentSectionIndex() : this.mode);
this.clearWidgets();
this.wheel = this.addRenderableWidget(wheel);
}
@Contract(pure = true)
private static @NotNull FourConsumer<GuiGraphics, PoseStack, Integer, Integer> renderItem(ItemStack holding) {
return (graphics, pose, width, height) -> {
ItemStack stack = holding.copy();
graphics.renderItem(stack, 2, 2, 9910597);
};
}
@Override
public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) {
if (wheel != null && wheel.isClosingAnimationStarted()) return true;
float screenCenterX = this.width / 2f;
float screenCenterY = this.height / 2f;
Vector2f cursorVec2 = new Vector2f(
(float) mouseX - screenCenterX,
(float) mouseY - screenCenterY
);
if (cursorVec2.length() < IGNORE_CURSOR_MOVE_LENGTH) {
return true;
}
return super.mouseDragged(mouseX, mouseY, button, dragX, dragY);
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int button) {
if (wheel != null ) {
wheel.onClosing();
}
return super.mouseReleased(mouseX, mouseY, button);
}
@Override
public void removed() {
super.removed();
}
@Override
public void render(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
for (Renderable renderable : ((ScreenAccessor) this).getrRenderables()) {
renderable.render(guiGraphics, mouseX, mouseY, partialTick);
}
}
@Override
public boolean isPauseScreen() {
return false;
}
}

View File

@ -1,281 +0,0 @@
package top.r3944realms.lib39.example.content.data;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.lib39.core.sync.NBTEntitySyncData;
/**
* The type Abstracted test sync data.
*/
@SuppressWarnings("unused")
public abstract class AbstractedTestSyncData extends NBTEntitySyncData {
/**
* The constant DEFAULT_TEST_STRING.
*/
public final static String DEFAULT_TEST_STRING = "default_value";
/**
* The constant DEFAULT_TEST_INT.
*/
public final static int DEFAULT_TEST_INT = 42;
/**
* The constant DEFAULT_TEST_BOOLEAN.
*/
public final static boolean DEFAULT_TEST_BOOLEAN = true;
/**
* The constant DEFAULT_TEST_DOUBLE.
*/
public final static double DEFAULT_TEST_DOUBLE = 3.14159;
/**
* The constant DEFAULT_TEST_DATA.
*/
public final static TestData DEFAULT_TEST_DATA = new TestData("default", 100, false);
/**
* Instantiates a new Nbt sync data.
*
* @param id the id
*/
protected AbstractedTestSyncData(ResourceLocation id) {
super(id);
}
/**
* Gets test string.
*
* @return the test string
*/
public abstract String getTestString();
/**
* Sets test string.
*
* @param value the value
*/
public abstract void setTestString(String value);
/**
* Gets test int.
*
* @return the test int
*/
public abstract int getTestInt();
/**
* Sets test int.
*
* @param value the value
*/
public abstract void setTestInt(int value);
/**
* Is test boolean boolean.
*
* @return the boolean
*/
public abstract boolean isTestBoolean();
/**
* Sets test boolean.
*
* @param value the value
*/
public abstract void setTestBoolean(boolean value);
/**
* Gets test double.
*
* @return the test double
*/
public abstract double getTestDouble();
/**
* Sets test double.
*
* @param value the value
*/
public abstract void setTestDouble(double value);
/**
* Gets counter.
*
* @return the counter
*/
public abstract int getCounter();
/**
* Increment counter.
*/
public abstract void incrementCounter();
/**
* Clear counter.
*/
public abstract void clearCounter();
/**
* Gets last sync time.
*
* @return the last sync time
*/
public abstract long getLastSyncTime();
/**
* Update sync time.
*/
public abstract void updateSyncTime();
/**
* Clear sync time.
*/
public abstract void clearSyncTime();
/**
* Gets custom data.
*
* @return the custom data
*/
public abstract TestData getCustomData();
/**
* Sets custom data.
*
* @param data the data
*/
public abstract void setCustomData(TestData data);
/**
* Validate data boolean.
*
* @return the boolean
*/
public abstract boolean validateData();
/**
* Reset to defaults.
*/
public void resetToDefaults() {
setTestString(DEFAULT_TEST_STRING);
setTestInt(DEFAULT_TEST_INT);
setTestBoolean(DEFAULT_TEST_BOOLEAN);
setTestDouble(DEFAULT_TEST_DOUBLE);
setCustomData(DEFAULT_TEST_DATA);
clearCounter();
clearSyncTime();
markDirty();
}
/**
* Generate random data.
*/
public void generateRandomData() {
setTestString("random_" + System.currentTimeMillis());
setTestInt((int) (Math.random() * 1000));
setTestBoolean(Math.random() > 0.5);
setTestDouble(Math.random() * 100.0);
setCustomData(new TestData(
"custom_" + getCounter(),
(int) (Math.random() * 500),
Math.random() > 0.5
));
updateSyncTime();
incrementCounter();
markDirty();
}
/**
* To bytes.
*
* @param buf the buf
*/
public abstract void toBytes(FriendlyByteBuf buf);
/**
* From bytes.
*
* @param buf the buf
*/
public abstract void fromBytes(@NotNull FriendlyByteBuf buf);
/**
* 测试数据对象
*/
public static class TestData {
private String name;
private int value;
private boolean flag;
/**
* Instantiates a new Test data.
*/
public TestData() {}
/**
* Instantiates a new Test data.
*
* @param name the name
* @param value the value
* @param flag the flag
*/
public TestData(String name, int value, boolean flag) {
this.name = name;
this.value = value;
this.flag = flag;
}
/**
* Gets name.
*
* @return the name
*/
public String getName() { return name; }
/**
* Sets name.
*
* @param name the name
*/
public void setName(String name) { this.name = name; }
/**
* Gets value.
*
* @return the value
*/
public int getValue() { return value; }
/**
* Sets value.
*
* @param value the value
*/
public void setValue(int value) { this.value = value; }
/**
* Is flag boolean.
*
* @return the boolean
*/
public boolean isFlag() { return flag; }
/**
* Sets flag.
*
* @param flag the flag
*/
public void setFlag(boolean flag) { this.flag = flag; }
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof TestData other)) return false;
return value == other.value && flag == other.flag &&
java.util.Objects.equals(name, other.name);
}
@Override
public int hashCode() {
return java.util.Objects.hash(name, value, flag);
}
}
}

View File

@ -1,558 +0,0 @@
package top.r3944realms.lib39.example.content.item;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.ProjectileUtil;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.lib39.Lib39;
import top.r3944realms.lib39.example.content.data.AbstractedTestSyncData;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
* 用于执行数据查询并检查同步状态的物品
* Shift + 右键客户端与服务器双端同时查询检查同步
* 普通右键单端查询目标生物数据
*/
public abstract class AbstractFabricItem extends Item {
/**
* Instantiates a new Fabric item.
*
* @param properties the properties
*/
public AbstractFabricItem(Properties properties) {
super(properties);
}
@Override
public @NotNull InteractionResultHolder<ItemStack> use(@NotNull Level level, @NotNull Player player, @NotNull InteractionHand hand) {
ItemStack itemStack = player.getItemInHand(hand);
if (level.isClientSide()) {
// 客户端逻辑
if (player.isShiftKeyDown()) {
// Shift + 右键双端检查 - 先获取客户端数据然后发送到服务器
handleClientDualCheck(player);
} else {
// 普通右键客户端单端查询
handleClientSideQuery(player);
}
} else {
// 服务器逻辑
ServerPlayer serverPlayer = (ServerPlayer) player;
if (player.isShiftKeyDown()) {
// 服务器端已经通过数据包处理双端检查这里只发送开始消息
player.sendSystemMessage(Component.literal("§b开始双端同步检查请等待客户端数据..."));
} else {
// 服务器单端查询
handleServerSingleEndQuery(serverPlayer);
}
// 添加冷却时间
player.getCooldowns().addCooldown(this, 20); // 1秒冷却
}
return InteractionResultHolder.sidedSuccess(itemStack, level.isClientSide());
}
/**
* 客户端处理双端检查
*/
private void handleClientDualCheck(Player player) {
Entity targetEntity = getClientTargetedEntity(player);
if (targetEntity instanceof LivingEntity livingTarget) {
// 在客户端获取本地数据
AbstractedTestSyncData clientData = getLocalClientData(livingTarget);
if (clientData != null) {
// 发送客户端数据到服务器
sendClientDataToServer(clientData, livingTarget.getId());
// 客户端提示
player.sendSystemMessage(Component.literal("§b已发送客户端数据到服务器等待对比结果..."));
} else {
player.sendSystemMessage(Component.literal("§c无法获取客户端本地数据"));
}
} else {
if (targetEntity == null && player.isShiftKeyDown()) {
handlePlayerSelfData(player);
} else {
player.sendSystemMessage(Component.literal("§c请对准一个生物进行同步检查"));
}
}
}
/**
* 处理玩家自身数据的双端检查
*/
private void handlePlayerSelfData(Player player) {
// 获取玩家自身的客户端数据
AbstractedTestSyncData clientData = getLocalClientData(player);
if (clientData != null) {
// 发送玩家自身数据到服务器
sendClientDataToServer(clientData, player.getId());
// 客户端提示
player.sendSystemMessage(Component.literal("§b已发送玩家自身客户端数据到服务器等待对比结果..."));
} else {
player.sendSystemMessage(Component.literal("§c无法获取玩家自身客户端数据"));
}
}
/**
* 客户端单端查询
*/
private void handleClientSideQuery(Player player) {
Entity targetEntity = getClientTargetedEntity(player);
if (targetEntity instanceof LivingEntity livingTarget) {
AbstractedTestSyncData clientData = getLocalClientData(livingTarget);
if (clientData != null) {
displayClientSideResults(player, livingTarget, clientData);
} else {
player.sendSystemMessage(Component.literal("§c无法查询客户端本地数据"));
}
} else {
player.sendSystemMessage(Component.literal("§c请对准一个生物使用"));
}
}
/**
* 服务器端处理单端查询
*/
private void handleServerSingleEndQuery(ServerPlayer player) {
Entity targetEntity = getServerTargetedEntity(player);
if (targetEntity instanceof LivingEntity livingTarget) {
player.sendSystemMessage(Component.literal(
String.format("§b开始查询 §e%s§b 的数据3秒后显示结果...", livingTarget.getName().getString())
));
// 启动异步数据查询
startServerSingleEndQuery(player, livingTarget);
} else {
player.sendSystemMessage(Component.literal("§c请对准一个生物使用"));
}
}
/**
* Gets data.
*
* @param target the target
* @return the data
*/
protected abstract AbstractedTestSyncData getData(Entity target);
/**
* 在客户端获取本地数据
*
* @param target the target
* @return the local client data
*/
protected AbstractedTestSyncData getLocalClientData(LivingEntity target) {
try {
return getData(target);
} catch (Exception e) {
Lib39.LOGGER.error("[FabricItem] 获取客户端数据失败", e);
}
return null;
}
/**
* 发送客户端数据到服务器
*
* @param clientData the client data
* @param targetEntityId the target entity id
*/
protected abstract void sendClientDataToServer(AbstractedTestSyncData clientData, int targetEntityId);
/**
* 启动服务器单端查询
*/
private void startServerSingleEndQuery(ServerPlayer player, LivingEntity target) {
CompletableFuture.runAsync(() -> {
try {
// 等待 3
Thread.sleep(3000);
// 在服务器线程中执行结果处理
player.server.execute(() -> {
displayServerSingleEndResults(player, target);
});
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Lib39.LOGGER.error("[FabricItem] 数据查询被中断", e);
player.sendSystemMessage(Component.literal("§c数据查询被中断"));
} catch (Exception e) {
Lib39.LOGGER.error("[FabricItem] 数据查询出错", e);
player.server.execute(() ->
player.sendSystemMessage(Component.literal("§c数据查询出错: " + e.getMessage()))
);
}
});
}
/**
* 显示服务器单端查询结果
*/
private void displayServerSingleEndResults(ServerPlayer player, @NotNull LivingEntity target) {
Lib39.LOGGER.info("[FabricItem] 查询生物 {} 的数据", target.getName().getString());
// 获取目标生物的数据
AbstractedTestSyncData abstractData = getTestSyncData(target);
if (abstractData != null) {
// 显示详细数据
displayServerDetailedData(player, target, abstractData);
} else {
player.sendSystemMessage(Component.literal(
String.format("§c生物 §e%s§c 没有测试数据或数据无效", target.getName().getString())
));
}
}
/**
* 显示客户端查询结果
*/
private void displayClientSideResults(Player player, LivingEntity target, AbstractedTestSyncData clientData) {
player.sendSystemMessage(Component.literal("§6=== 客户端数据查询结果 ==="));
player.sendSystemMessage(Component.literal("§7目标生物: §e" + target.getName().getString()));
player.sendSystemMessage(Component.literal("§7数据来源: §9客户端本地"));
player.sendSystemMessage(Component.literal(""));
player.sendSystemMessage(Component.literal("§a基础数据:"));
player.sendSystemMessage(Component.literal("§7字符串: §f" + clientData.getTestString()));
player.sendSystemMessage(Component.literal("§7整数值: §f" + clientData.getTestInt()));
player.sendSystemMessage(Component.literal("§7布尔值: §f" + clientData.isTestBoolean()));
player.sendSystemMessage(Component.literal("§7双精度值: §f" + String.format("%.2f", clientData.getTestDouble())));
player.sendSystemMessage(Component.literal("§7计数器: §f" + clientData.getCounter()));
// 显示客户端特定信息
player.sendSystemMessage(Component.literal(""));
player.sendSystemMessage(Component.literal("§e客户端状态:"));
player.sendSystemMessage(Component.literal("§7数据验证: " + (clientData.validateData() ? "§a通过" : "§c失败")));
player.sendSystemMessage(Component.literal("§7同步状态: " + (clientData.isDirty() ? "§6待同步" : "§a已同步")));
}
/**
* 显示服务器详细数据单端查询
*/
private void displayServerDetailedData(ServerPlayer player, LivingEntity target, AbstractedTestSyncData testData) {
player.sendSystemMessage(Component.literal("§6=== 数据查询结果 ==="));
player.sendSystemMessage(Component.literal(
String.format("§7目标生物: §e%s", target.getName().getString())
));
player.sendSystemMessage(Component.literal(
String.format("§7实体ID: §e%d", target.getId())
));
player.sendSystemMessage(Component.literal(""));
// 显示基础数据
player.sendSystemMessage(Component.literal("§a基础数据:"));
player.sendSystemMessage(Component.literal(
String.format("§7字符串: §f%s", testData.getTestString())
));
player.sendSystemMessage(Component.literal(
String.format("§7整数值: §f%d", testData.getTestInt())
));
player.sendSystemMessage(Component.literal(
String.format("§7布尔值: §f%s", testData.isTestBoolean())
));
player.sendSystemMessage(Component.literal(
String.format("§7双精度值: §f%.2f", testData.getTestDouble())
));
player.sendSystemMessage(Component.literal(
String.format("§7计数器: §f%d", testData.getCounter())
));
player.sendSystemMessage(Component.literal(
String.format("§7最后同步: §f%dms前", System.currentTimeMillis() - testData.getLastSyncTime())
));
player.sendSystemMessage(Component.literal(""));
// 显示自定义数据
AbstractedTestSyncData.TestData customData = testData.getCustomData();
player.sendSystemMessage(Component.literal("§a自定义数据:"));
player.sendSystemMessage(Component.literal(
String.format("§7名称: §f%s", customData.getName())
));
player.sendSystemMessage(Component.literal(
String.format("§7数值: §f%d", customData.getValue())
));
player.sendSystemMessage(Component.literal(
String.format("§7标志: §f%s", customData.isFlag())
));
player.sendSystemMessage(Component.literal(""));
// 显示验证状态
boolean isValid = testData.validateData();
player.sendSystemMessage(Component.literal(
String.format("§7数据验证: %s", isValid ? "§a通过" : "§c失败")
));
player.sendSystemMessage(Component.literal(
String.format("§7数据状态: %s", testData.isDirty() ? "§6未同步" : "§a已同步")
));
}
/**
* 显示双端比较结果
*
* @param player the player
* @param target the target
* @param serverData the server data
* @param clientData the client data
*/
protected static void displayDualEndComparison(ServerPlayer player, LivingEntity target, AbstractedTestSyncData serverData, AbstractedTestSyncData clientData) {
player.sendSystemMessage(Component.literal("§6=== 客户端-服务器双端同步检查结果 ==="));
player.sendSystemMessage(Component.literal(
String.format("§7目标生物: §e%s", target.getName().getString())
));
player.sendSystemMessage(Component.literal(""));
// 显示双端数据来源
player.sendSystemMessage(Component.literal("§a数据来源:"));
player.sendSystemMessage(Component.literal("§7- §c服务器端§7: 实体ID " + serverData.entityId()));
player.sendSystemMessage(Component.literal("§7- §9客户端§7: 实体ID " + clientData.entityId()));
player.sendSystemMessage(Component.literal(""));
// 比较各个字段
boolean stringSynced = serverData.getTestString().equals(clientData.getTestString());
boolean intSynced = serverData.getTestInt() == clientData.getTestInt();
boolean booleanSynced = serverData.isTestBoolean() == clientData.isTestBoolean();
boolean doubleSynced = Math.abs(serverData.getTestDouble() - clientData.getTestDouble()) < 0.001;
boolean counterSynced = serverData.getCounter() == clientData.getCounter();
boolean customDataSynced = compareCustomData(serverData.getCustomData(), clientData.getCustomData());
// 显示字段同步状态
player.sendSystemMessage(Component.literal("§a字段同步状态:"));
displayDualEndSyncStatus(player, "字符串", stringSynced,
serverData.getTestString(), clientData.getTestString());
displayDualEndSyncStatus(player, "整数值", intSynced,
serverData.getTestInt(), clientData.getTestInt());
displayDualEndSyncStatus(player, "布尔值", booleanSynced,
serverData.isTestBoolean(), clientData.isTestBoolean());
displayDualEndSyncStatus(player, "双精度值", doubleSynced,
serverData.getTestDouble(), clientData.getTestDouble());
displayDualEndSyncStatus(player, "计数器", counterSynced,
serverData.getCounter(), clientData.getCounter());
displayDualEndSyncStatus(player, "自定义数据", customDataSynced,
serverData.getCustomData().toString(), clientData.getCustomData().toString());
player.sendSystemMessage(Component.literal(""));
// 计算总体同步率
int totalFields = 6;
int syncedFields = (stringSynced ? 1 : 0) + (intSynced ? 1 : 0) +
(booleanSynced ? 1 : 0) + (doubleSynced ? 1 : 0) +
(counterSynced ? 1 : 0) + (customDataSynced ? 1 : 0);
double syncRate = (double) syncedFields / totalFields * 100;
// 显示总体同步状态
player.sendSystemMessage(Component.literal("§a总体同步状态:"));
player.sendSystemMessage(Component.literal(
String.format("§7同步字段: §e%d§7/§e%d", syncedFields, totalFields)
));
player.sendSystemMessage(Component.literal(
String.format("§7同步率: %s", getSyncRateColor(syncRate) + String.format("%.1f%%", syncRate))
));
player.sendSystemMessage(Component.literal(
String.format("§7同步状态: %s", getOverallSyncStatus(syncRate))
));
// 显示数据状态差异
player.sendSystemMessage(Component.literal(""));
player.sendSystemMessage(Component.literal("§a数据状态差异:"));
player.sendSystemMessage(Component.literal(
String.format("§7服务器脏数据状态: %s", serverData.isDirty() ? "§6脏" : "§a干净")
));
player.sendSystemMessage(Component.literal(
String.format("§7客户端脏数据状态: %s", clientData.isDirty() ? "§6脏" : "§a干净")
));
player.sendSystemMessage(Component.literal(
String.format("§7服务器验证状态: %s", serverData.validateData() ? "§a通过" : "§c失败")
));
player.sendSystemMessage(Component.literal(
String.format("§7客户端验证状态: %s", clientData.validateData() ? "§a通过" : "§c失败")
));
// 显示同步建议
player.sendSystemMessage(Component.literal(""));
player.sendSystemMessage(Component.literal("§e同步建议:"));
if (syncRate == 100) {
player.sendSystemMessage(Component.literal("§a✓ 数据完全同步,无需操作"));
} else if (syncRate >= 80) {
player.sendSystemMessage(Component.literal("§e⚠ 数据基本同步,建议观察"));
} else if (syncRate >= 50) {
player.sendSystemMessage(Component.literal("§6⚠ 数据部分不同步,建议检查网络"));
} else {
player.sendSystemMessage(Component.literal("§c✗ 数据严重不同步,建议重新同步"));
}
}
/**
* 显示双端同步状态
*/
private static void displayDualEndSyncStatus(ServerPlayer player, String fieldName, boolean synced, Object serverValue, Object clientValue) {
String status = synced ? "§a✓ 同步" : "§c✗ 不同步";
if (synced) {
player.sendSystemMessage(Component.literal(
String.format("§7%s: %s §8(值: §7%s§8)", fieldName, status, serverValue)
));
} else {
player.sendSystemMessage(Component.literal(
String.format("§7%s: %s", fieldName, status)
));
player.sendSystemMessage(Component.literal(
String.format("§8 §c服务器: §7%s", serverValue)
));
player.sendSystemMessage(Component.literal(
String.format("§8 §9客户端: §7%s", clientValue)
));
}
}
/**
* 比较自定义数据
*/
private static boolean compareCustomData(AbstractedTestSyncData.TestData first, AbstractedTestSyncData.TestData second) {
return first.getName().equals(second.getName()) &&
first.getValue() == second.getValue() &&
first.isFlag() == second.isFlag();
}
/**
* 获取同步率颜色
*/
private static String getSyncRateColor(double syncRate) {
if (syncRate >= 90) return "§a";
if (syncRate >= 70) return "§e";
if (syncRate >= 50) return "§6";
return "§c";
}
/**
* 获取总体同步状态
*/
private static String getOverallSyncStatus(double syncRate) {
if (syncRate == 100) return "§a完全同步";
if (syncRate >= 90) return "§a优秀同步";
if (syncRate >= 70) return "§e良好同步";
if (syncRate >= 50) return "§6部分同步";
return "§c同步较差";
}
/**
* 客户端获取准星目标实体
*/
private Entity getClientTargetedEntity(Player player) {
double reachDistance = 20.0;
float partialTicks = 1.0f; // 服务器端通常用1.0
// 获取玩家的视线向量和位置
Vec3 eyePosition = player.getEyePosition(partialTicks);
Vec3 lookVector = player.getViewVector(partialTicks);
Vec3 endPosition = eyePosition.add(lookVector.x * reachDistance, lookVector.y * reachDistance, lookVector.z * reachDistance);
// 先检测实体
EntityHitResult entityHit = ProjectileUtil.getEntityHitResult(
player,
eyePosition,
endPosition,
player.getBoundingBox().expandTowards(lookVector.scale(reachDistance)).inflate(1.0),
entity -> !entity.isSpectator() && entity.isPickable(),
reachDistance * reachDistance // 平方距离
);
if (entityHit != null) {
return entityHit.getEntity();
}
return null;
}
/**
* 服务器获取准星目标实体
*/
private Entity getServerTargetedEntity(ServerPlayer player) {
double reachDistance = 20.0;
float partialTicks = 1.0f; // 服务器端通常用1.0
// 获取玩家的视线向量和位置
Vec3 eyePosition = player.getEyePosition(partialTicks);
Vec3 lookVector = player.getViewVector(partialTicks);
Vec3 endPosition = eyePosition.add(lookVector.x * reachDistance, lookVector.y * reachDistance, lookVector.z * reachDistance);
// 先检测实体
EntityHitResult entityHit = ProjectileUtil.getEntityHitResult(
player,
eyePosition,
endPosition,
player.getBoundingBox().expandTowards(lookVector.scale(reachDistance)).inflate(1.0),
entity -> !entity.isSpectator() && entity.isPickable(),
reachDistance * reachDistance // 平方距离
);
if (entityHit != null) {
return entityHit.getEntity();
}
return null;
}
/**
* 获取测试同步数据
*/
private AbstractedTestSyncData getTestSyncData(Entity entity) {
try {
return getData(entity);
} catch (Exception e) {
Lib39.LOGGER.debug("[FabricItem] 获取生物 {} 的 TestSyncData 失败: {}",
entity.getName().getString(), e.getMessage());
return null;
}
}
@Override
public void appendHoverText(@NotNull ItemStack stack, @Nullable Level level,
@NotNull List<Component> tooltip, @NotNull TooltipFlag flag) {
super.appendHoverText(stack, level, tooltip, flag);
tooltip.add(Component.literal("§7右键点击在 3 秒后执行"));
tooltip.add(Component.literal("§7§e准星瞄准生物§7的数据查询"));
tooltip.add(Component.literal("§7§oShift + 右键§7进行§e客户端-服务器双端同步检查§7"));
tooltip.add(Component.literal(""));
tooltip.add(Component.literal("§6查询延迟: §e3秒"));
tooltip.add(Component.literal("§6瞄准距离: §e20格"));
tooltip.add(Component.literal("§6冷却时间: §e1秒"));
tooltip.add(Component.literal(""));
tooltip.add(Component.literal("§a单端查询内容:"));
tooltip.add(Component.literal("§7- 基础数据字段"));
tooltip.add(Component.literal("§7- 自定义数据结构"));
tooltip.add(Component.literal("§7- 数据验证状态"));
tooltip.add(Component.literal("§7- 同步状态信息"));
tooltip.add(Component.literal(""));
tooltip.add(Component.literal("§e双端同步检查:"));
tooltip.add(Component.literal("§7- 客户端和服务器同时查询"));
tooltip.add(Component.literal("§7- 字段级同步状态对比"));
tooltip.add(Component.literal("§7- 总体同步率计算"));
tooltip.add(Component.literal("§7- 双端数据状态差异"));
tooltip.add(Component.literal("§7- 同步建议"));
}
}

View File

@ -1,294 +0,0 @@
package top.r3944realms.lib39.example.content.item;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.ProjectileUtil;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.lib39.Lib39;
import top.r3944realms.lib39.example.content.data.AbstractedTestSyncData;
import java.util.List;
import java.util.Random;
/**
* 用于对准星生物触发 TestSyncData 随机变换的物品
* Shift + 右键操作自己的数据
* 普通右键操作瞄准生物的数据
*/
public abstract class AbstractNeoForgeItem extends Item {
private static final Random RANDOM = new Random();
/**
* Instantiates a new Neo forge item.
*
* @param properties the properties
*/
public AbstractNeoForgeItem(Properties properties) {
super(properties);
}
@Override
public @NotNull InteractionResultHolder<ItemStack> use(@NotNull Level level, @NotNull Player player, @NotNull InteractionHand hand) {
ItemStack itemStack = player.getItemInHand(hand);
if (!level.isClientSide()) {
ServerPlayer serverPlayer = (ServerPlayer) player;
if (player.isShiftKeyDown()) {
// Shift + 右键操作自己的数据
handleSelfDataOperation(serverPlayer);
} else {
// 普通右键操作瞄准生物的数据
handleTargetDataOperation(serverPlayer);
}
// 添加冷却时间
player.getCooldowns().addCooldown(this, 20); // 1秒冷却
}
return InteractionResultHolder.sidedSuccess(itemStack, level.isClientSide());
}
/**
* 处理玩家自身数据操作
*/
private void handleSelfDataOperation(ServerPlayer player) {
boolean success = triggerRandomTransformation(player);
if (success) {
player.sendSystemMessage(Component.literal("§a已触发§e自身§a测试数据的随机变换"));
Lib39.LOGGER.info("[NeoForgeItem] 玩家 {} 触发了自身数据变换", player.getName().getString());
} else {
player.sendSystemMessage(Component.literal("§c无法触发自身数据变换"));
}
}
/**
* 处理目标生物数据操作
*/
private void handleTargetDataOperation(ServerPlayer player) {
// 获取玩家准星瞄准的生物
Entity targetEntity = getTargetedEntity(player);
if (targetEntity instanceof LivingEntity livingTarget) {
// 触发对准星生物的数据变换
boolean success = triggerRandomTransformation(livingTarget);
if (success) {
player.sendSystemMessage(Component.literal(
String.format("§a已触发 §e%s§a 的测试数据随机变换!", livingTarget.getName().getString())
));
Lib39.LOGGER.info("[NeoForgeItem] 玩家 {} 触发生物 {} 的数据变换",
player.getName().getString(), livingTarget.getName().getString());
} else {
player.sendSystemMessage(Component.literal(
String.format("§c无法触发 §e%s§c 的数据变换", livingTarget.getName().getString())
));
}
} else {
// 没有瞄准生物
player.sendSystemMessage(Component.literal("§c请对准一个生物使用"));
}
}
/**
* 获取玩家准星瞄准的实体
*/
private Entity getTargetedEntity(ServerPlayer player) {
double reachDistance = 20.0;
float partialTicks = 1.0f; // 服务器端通常用1.0
// 获取玩家的视线向量和位置
Vec3 eyePosition = player.getEyePosition(partialTicks);
Vec3 lookVector = player.getViewVector(partialTicks);
Vec3 endPosition = eyePosition.add(lookVector.x * reachDistance, lookVector.y * reachDistance, lookVector.z * reachDistance);
// 先检测实体
EntityHitResult entityHit = ProjectileUtil.getEntityHitResult(
player,
eyePosition,
endPosition,
player.getBoundingBox().expandTowards(lookVector.scale(reachDistance)).inflate(1.0),
entity -> !entity.isSpectator() && entity.isPickable(),
reachDistance * reachDistance // 平方距离
);
if (entityHit != null) {
return entityHit.getEntity();
}
return null;
}
/**
* 为实体触发随机数据变换
*/
private boolean triggerRandomTransformation(LivingEntity entity) {
try {
AbstractedTestSyncData testData = getOrCreateTestSyncData(entity);
// 随机选择一种变换方式
int transformationType = RANDOM.nextInt(6); // 增加更多变换类型
switch (transformationType) {
case 0 -> {
// 完全随机数据
testData.generateRandomData();
Lib39.LOGGER.debug("[NeoForgeItem] 为 {} 生成完全随机数据", getEntityName(entity));
}
case 1 -> {
// 只修改字符串和计数器
testData.setTestString("transformed_" + System.currentTimeMillis());
testData.incrementCounter();
testData.updateSyncTime();
Lib39.LOGGER.debug("[NeoForgeItem] 为 {} 修改字符串和计数器", getEntityName(entity));
}
case 2 -> {
// 修改数值数据
testData.setTestInt(RANDOM.nextInt(1000));
testData.setTestDouble(RANDOM.nextDouble() * 100.0);
testData.setTestBoolean(RANDOM.nextBoolean());
testData.updateSyncTime();
Lib39.LOGGER.debug("[NeoForgeItem] 为 {} 修改数值数据", getEntityName(entity));
}
case 3 -> {
// 修改自定义数据
AbstractedTestSyncData.TestData newCustomData = new AbstractedTestSyncData.TestData(
"custom_" + RANDOM.nextInt(100),
RANDOM.nextInt(500),
RANDOM.nextBoolean()
);
testData.setCustomData(newCustomData);
testData.incrementCounter();
Lib39.LOGGER.debug("[NeoForgeItem] 为 {} 修改自定义数据", getEntityName(entity));
}
case 4 -> {
// 重置为默认值
testData.resetToDefaults();
Lib39.LOGGER.debug("[NeoForgeItem] 为 {} 重置数据", getEntityName(entity));
}
case 5 -> {
// 特殊变换玩家专属数据
if (entity instanceof Player) {
testData.setTestString("player_special_" + entity.getUUID().toString().substring(0, 8));
testData.setTestInt(entity.getId() * 10);
testData.setTestDouble(entity.getHealth());
testData.incrementCounter();
testData.updateSyncTime();
Lib39.LOGGER.debug("[NeoForgeItem] 为玩家 {} 设置专属数据", getEntityName(entity));
} else {
// 非玩家生物使用普通变换
testData.generateRandomData();
}
}
}
// 验证数据有效性
if (!testData.validateData()) {
Lib39.LOGGER.warn("[NeoForgeItem] {} 的数据验证失败,重置为默认值", getEntityName(entity));
testData.resetToDefaults();
}
// 显示数据预览仅对玩家自己操作时显示
if (entity instanceof Player) {
displayDataPreview((Player) entity, testData);
}
return true;
} catch (Exception e) {
Lib39.LOGGER.error("[NeoForgeItem] 为 {} 触发数据变换时出错: {}",
getEntityName(entity), e.getMessage());
return false;
}
}
/**
* 显示数据预览给玩家
*/
private void displayDataPreview(Player player, AbstractedTestSyncData testData) {
player.sendSystemMessage(Component.literal("§6数据预览:"));
player.sendSystemMessage(Component.literal(
String.format("§7字符串: §f%s", testData.getTestString())
));
player.sendSystemMessage(Component.literal(
String.format("§7计数器: §f%d", testData.getCounter())
));
player.sendSystemMessage(Component.literal(
String.format("§7验证状态: %s", testData.validateData() ? "§a通过" : "§c失败")
));
}
/**
* 获取实体名称用于日志
*/
private String getEntityName(LivingEntity entity) {
if (entity instanceof Player) {
return "玩家 " + entity.getName().getString();
} else {
return "生物 " + entity.getName().getString();
}
}
/**
* Gets data.
*
* @param entity the entity
* @return the data
*/
protected abstract AbstractedTestSyncData getData(Entity entity);
/**
* Gets or create test sync data.
*
* @param entity the entity
* @return the or create test sync data
*/
protected AbstractedTestSyncData getOrCreateTestSyncData(Entity entity) {
try {
return getData(entity);
} catch (Exception e) {
Lib39.LOGGER.error("[NeoForgeItem] 获取 {} 的 TestSyncData 失败: {}",
getEntityName((LivingEntity) entity), e.getMessage());
return null;
}
}
@Override
public void appendHoverText(@NotNull ItemStack stack, @Nullable Level level,
@NotNull List<Component> tooltip, @NotNull TooltipFlag flag) {
super.appendHoverText(stack, level, tooltip, flag);
tooltip.add(Component.literal("§7右键点击触发§e准星瞄准生物§7的"));
tooltip.add(Component.literal("§7测试数据随机变换"));
tooltip.add(Component.literal("§7§oShift + 右键§7操作§e自身§7数据"));
tooltip.add(Component.literal(""));
tooltip.add(Component.literal("§6冷却时间: §e1秒"));
tooltip.add(Component.literal("§6瞄准距离: §e20格"));
tooltip.add(Component.literal(""));
tooltip.add(Component.literal("§a变换类型:"));
tooltip.add(Component.literal("§7- 完全随机数据"));
tooltip.add(Component.literal("§7- 字符串+计数器"));
tooltip.add(Component.literal("§7- 数值数据"));
tooltip.add(Component.literal("§7- 自定义数据"));
tooltip.add(Component.literal("§7- 重置默认值"));
tooltip.add(Component.literal("§7- 玩家专属数据"));
tooltip.add(Component.literal(""));
tooltip.add(Component.literal("§e自身操作特性:"));
tooltip.add(Component.literal("§7- 显示数据预览"));
tooltip.add(Component.literal("§7- 玩家专属数据变换"));
}
}

View File

@ -1,43 +0,0 @@
package top.r3944realms.lib39.example.content.item;
import net.minecraft.client.Minecraft;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.lib39.example.client.screen.ForgeScreen;
import top.r3944realms.lib39.util.IClientOnly;
/**
* The type Forge item.
*/
public class ForgeItem extends Item {
/**
* Instantiates a new Forge item.
*
* @param properties the properties
*/
public ForgeItem(Properties properties) {
super(properties);
}
@Override
public @NotNull InteractionResultHolder<ItemStack> use(@NotNull Level level, @NotNull Player player, @NotNull InteractionHand usedHand) {
if (level.isClientSide() && usedHand == InteractionHand.MAIN_HAND) {
ClientOpt.clientUse(usedHand);
}
return super.use(level, player, usedHand);
}
/**
* The type Client opt.
*/
static class ClientOpt implements IClientOnly {
private static void clientUse(@NotNull InteractionHand usedHand) {
IClientOnly.check(() -> Minecraft.getInstance().setScreen(new ForgeScreen(usedHand, 0)));
}
}
}

View File

@ -1,24 +0,0 @@
package top.r3944realms.lib39.example.core.register;
import net.minecraft.world.item.Item;
import java.util.function.Supplier;
/**
* The type Ex lib 39 items.
*/
public class ExLib39Items {
/**
* The constant SUPER_LEAD_ROPE.
*/
public static Supplier<Item> FABRIC;
/**
* The constant ETERNAL_POTATO.
*/
public static Supplier<Item> NEOFORGE;
/**
* The constant FORGE.
*/
public static Supplier<Item> FORGE;
}

View File

@ -1,51 +0,0 @@
package top.r3944realms.lib39.mixin.carryon;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Pseudo;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import top.r3944realms.lib39.content.item.DollItem;
import top.r3944realms.lib39.util.GameProfileHelper;
import tschipp.carryon.client.render.CarriedObjectRender;
import tschipp.carryon.common.carry.CarryOnDataManager;
/**
* The type Mixin carried object render.
*/
@Pseudo
@Mixin(value = CarriedObjectRender.class, remap = false)
public class MixinCarriedObjectRender {
@ModifyVariable(
method = "drawFirstPersonBlock",
at = @At(
value = "LOAD",
target = "Ltschipp/carryon/client/render/CarryRenderHelper;renderBakedModel(Lnet/minecraft/world/item/ItemStack;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/client/resources/model/BakedModel;)V"
)
)
private static ItemStack warpDollItem$1(ItemStack stack, @Local(ordinal = 0, argsOnly = true) Player player) {
if (stack.getItem() instanceof DollItem) {
CompoundTag compound = CarryOnDataManager.getCarryData(player).getNbt().getCompound("tile").getCompound(GameProfileHelper.TAG_OWN_PROFILE);
stack.getOrCreateTag().put(GameProfileHelper.TAG_OWN_PROFILE, compound);
}
return stack;
}
@ModifyVariable(
method = "drawThirdPerson",
at = @At(
value = "LOAD",
target = "Ltschipp/carryon/client/render/CarryRenderHelper;renderBakedModel(Lnet/minecraft/world/item/ItemStack;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;ILnet/minecraft/client/resources/model/BakedModel;)V"
)
)
private static ItemStack warpDollItem$2(ItemStack stack, @Local(ordinal = 0) Player player) {
if (stack.getItem() instanceof DollItem) {
CompoundTag compound = CarryOnDataManager.getCarryData(player).getNbt().getCompound("tile").getCompound(GameProfileHelper.TAG_OWN_PROFILE);
stack.getOrCreateTag().put(GameProfileHelper.TAG_OWN_PROFILE, compound);
}
return stack;
}
}

View File

@ -1,175 +0,0 @@
package top.r3944realms.lib39.mixin.minecraft;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.CreativeModeTabs;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.Comparator;
/**
* The interface Creative mode tabs accessor.
*/
@Mixin(CreativeModeTabs.class)
public interface CreativeModeTabsAccessor {
/**
* Gets building blocks.
*
* @return the building blocks
*/
@Accessor("BUILDING_BLOCKS")
static ResourceKey<CreativeModeTab> getBuildingBlocks() {
throw new AssertionError();
}
/**
* Gets colored blocks.
*
* @return the colored blocks
*/
@Accessor("COLORED_BLOCKS")
static ResourceKey<CreativeModeTab> getColoredBlocks() {
throw new AssertionError();
}
/**
* Gets natural blocks.
*
* @return the natural blocks
*/
@Accessor("NATURAL_BLOCKS")
static ResourceKey<CreativeModeTab> getNaturalBlocks() {
throw new AssertionError();
}
/**
* Gets functional blocks.
*
* @return the functional blocks
*/
@Accessor("FUNCTIONAL_BLOCKS")
static ResourceKey<CreativeModeTab> getFunctionalBlocks() {
throw new AssertionError();
}
/**
* Gets redstone blocks.
*
* @return the redstone blocks
*/
@Accessor("REDSTONE_BLOCKS")
static ResourceKey<CreativeModeTab> getRedstoneBlocks() {
throw new AssertionError();
}
/**
* Gets hotbar.
*
* @return the hotbar
*/
@Accessor("HOTBAR")
static ResourceKey<CreativeModeTab> getHotbar() {
throw new AssertionError();
}
/**
* Gets search.
*
* @return the search
*/
@Accessor("SEARCH")
static ResourceKey<CreativeModeTab> getSearch() {
throw new AssertionError();
}
/**
* Gets tools and utilities.
*
* @return the tools and utilities
*/
@Accessor("TOOLS_AND_UTILITIES")
static ResourceKey<CreativeModeTab> getToolsAndUtilities() {
throw new AssertionError();
}
/**
* Gets combat.
*
* @return the combat
*/
@Accessor("COMBAT")
static ResourceKey<CreativeModeTab> getCombat() {
throw new AssertionError();
}
/**
* Gets food and drinks.
*
* @return the food and drinks
*/
@Accessor("FOOD_AND_DRINKS")
static ResourceKey<CreativeModeTab> getFoodAndDrinks() {
throw new AssertionError();
}
/**
* Gets ingredients.
*
* @return the ingredients
*/
@Accessor("INGREDIENTS")
static ResourceKey<CreativeModeTab> getIngredients() {
throw new AssertionError();
}
/**
* Gets spawn eggs.
*
* @return the spawn eggs
*/
@Accessor("SPAWN_EGGS")
static ResourceKey<CreativeModeTab> getSpawnEggs() {
throw new AssertionError();
}
/**
* Gets op blocks.
*
* @return the op blocks
*/
@Accessor("OP_BLOCKS")
static ResourceKey<CreativeModeTab> getOpBlocks() {
throw new AssertionError();
}
/**
* Gets inventory.
*
* @return the inventory
*/
@Accessor("INVENTORY")
static ResourceKey<CreativeModeTab> getInventory() {
throw new AssertionError();
}
/**
* Gets cached parameters.
*
* @return the cached parameters
*/
@Accessor("CACHED_PARAMETERS")
static CreativeModeTab.ItemDisplayParameters getCachedParameters() {
throw new AssertionError();
}
/**
* Gets painting comparator.
*
* @return the painting comparator
*/
@Accessor("PAINTING_COMPARATOR")
static Comparator<net.minecraft.world.item.CreativeModeTab> getPaintingComparator() {
throw new AssertionError();
}
}

View File

@ -1,22 +0,0 @@
package top.r3944realms.lib39.mixin.minecraft;
import net.minecraft.client.gui.components.Renderable;
import net.minecraft.client.gui.screens.Screen;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.List;
/**
* The interface Screen accessor.
*/
@Mixin(Screen.class)
public interface ScreenAccessor {
/**
* Gets renderables.
*
* @return the renderables
*/
@Accessor("renderables")
List<Renderable> getrRenderables();
}

View File

@ -1,43 +0,0 @@
package top.r3944realms.lib39.platform;
import top.r3944realms.lib39.Lib39;
import top.r3944realms.lib39.platform.services.IPlatformHelper;
import java.util.ServiceLoader;
/**
* The type Services.
*/
// Service loaders are a built-in Java feature that allow us to locate implementations of an interface that vary from one
// environment to another. In the context of MultiLoader we use this feature to access a mock API in the common code that
// is swapped out for the platform specific implementation at runtime.
public class Services {
/**
* The constant PLATFORM.
*/
// In this example we provide a platform helper which provides information about what platform the mod is running on.
// For example this can be used to check if the code is running on Forge vs Fabric, or to ask the modloader if another
// mod is loaded.
public static final IPlatformHelper PLATFORM = load(IPlatformHelper.class);
/**
* Load t.
*
* @param <T> the type parameter
* @param clazz the clazz
* @return the t
*/
// This code is used to load a service for the current environment. Your implementation of the service must be defined
// manually by including a text file in META-INF/services named with the fully qualified class name of the service.
// Inside the file you should write the fully qualified class name of the implementation to load for the platform. For
// example our file on Forge points to ForgePlatformHelper while Fabric points to FabricPlatformHelper.
public static <T> T load(Class<T> clazz) {
final T loadedService = ServiceLoader.load(clazz)
.findFirst()
.orElseThrow(() -> new NullPointerException("Failed to load service for " + clazz.getName()));
Lib39.LOGGER.debug("Loaded {} for service {}", loadedService, clazz);
return loadedService;
}
}

View File

@ -1,21 +0,0 @@
package top.r3944realms.lib39.platform.services;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.commands.CommandSourceStack;
import top.r3944realms.lib39.core.command.ICommandHelpManager;
/**
* The interface Help command hook.
*/
@FunctionalInterface
public interface IHelpCommandHook {
/**
* On register.
*
* @param tree the tree
* @param manager the manager
* @param context the context
*/
void onRegister(LiteralArgumentBuilder<CommandSourceStack> tree, ICommandHelpManager manager, CommandBuildContext context);
}

View File

@ -1,67 +0,0 @@
package top.r3944realms.lib39.platform.services;
/**
* The interface Platform helper.
*/
public interface IPlatformHelper {
/**
* Gets the name of the current platform
*
* @return The name of the current platform.
*/
String getPlatformName();
/**
* Checks if a mod with the given id is loaded.
*
* @param modId The mod to check if it is loaded.
* @return True if the mod is loaded, false otherwise.
*/
boolean isModLoaded(String modId);
/**
* Check if the game is currently in a development environment.
*
* @return True if in a development environment, false otherwise.
*/
boolean isDevelopmentEnvironment();
/**
* Gets the name of the environment type as a string.
*
* @return The name of the environment type.
*/
default String getEnvironmentName() {
return isDevelopmentEnvironment() ? "development" : "production";
}
/**
* Is client environment boolean.
*
* @return the boolean
*/
boolean isClientEnvironment();
/**
* Gets mod version.
*
* @return the mod version
*/
String getModVersion();
/**
* Gets util helper.
*
* @return the util helper
*/
IUtilHelper getUtilHelper();
/**
* Gets help command hook.
*
* @return the help command hook
*/
IHelpCommandHook getHelpCommandHook();
}

View File

@ -1,15 +0,0 @@
package top.r3944realms.lib39.platform.services;
import top.r3944realms.lib39.util.block.BlockRegistryBuilder;
/**
* The interface Util helper.
*/
public interface IUtilHelper {
/**
* Gets block registry builder.
*
* @return the block registry builder
*/
BlockRegistryBuilder getBlockRegistryBuilder();
}

View File

@ -1,379 +0,0 @@
package top.r3944realms.lib39.util;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.PlayerInfo;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.resources.DefaultPlayerSkin;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.lib39.util.nbt.NBTReader;
import top.r3944realms.lib39.util.nbt.NBTWriter;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
/**
* The type GameProfile helper.
*/
public class GameProfileHelper {
/**
* Client Only Class
*/
public static class ClientOpt implements IClientOnly {
/**
* Resolve skin texture resource location.
*
* @param gameProfile the game profile
* @return the resource location
*/
public static @NotNull ResourceLocation resolveSkinTexture(@NotNull GameProfile gameProfile) {
return IClientOnly.check(() ->
Minecraft.getInstance().getSkinManager()
.getInsecureSkinLocation(gameProfile));
}
/**
* Gets skin texture.
*
* @param gameProfile the game profile
* @return the skin texture
*/
public static ResourceLocation getSkinTexture(@Nullable GameProfile gameProfile) {
return IClientOnly.check(() -> {
if (gameProfile == null) {
return DefaultPlayerSkin.getDefaultSkin();
}
return resolveSkinTexture(gameProfile);
});
}
/**
* Has slim arms client boolean.
*
* @param player the player
* @return the boolean
*/
public static boolean hasSlimArmsClient(Player player) {
return IClientOnly.check(() -> {
if (player instanceof AbstractClientPlayer clientPlayer) {
PlayerInfo playerInfo = Objects.requireNonNull(Minecraft.getInstance()
.getConnection())
.getPlayerInfo(clientPlayer.getUUID());
return playerInfo != null && "slim".equals(playerInfo.getModelName());
}
return false;
});
}
/**
* Gets skin model name.
*
* @param player the player
* @return the skin model name
*/
public static @NotNull String getSkinModelName(@NotNull Player player) {
return IClientOnly.check(() -> {
if (player.level().isClientSide && player instanceof AbstractClientPlayer) {
PlayerInfo info = Objects.requireNonNull(Minecraft.getInstance().getConnection())
.getPlayerInfo(player.getUUID());
return info != null ? info.getModelName() : "default";
}
return "default";
});
}
}
/**
* The constant TAG_BE.
*/
public static final String TAG_BE = "BlockEntityTag";
/**
* The constant TAG_OWN_PROFILE.
*/
public static final String TAG_OWN_PROFILE = "OwnerProfile";
/**
* Gets skin texture.
*
* @param gameProfile the game profile
* @return the skin texture
*/
public static ResourceLocation getSkinTexture(@Nullable GameProfile gameProfile) {
return ClientOpt.getSkinTexture(gameProfile);
}
/**
* Resolve skin texture resource location.
*
* @param gameProfile the game profile
* @return the resource location
*/
public static @NotNull ResourceLocation resolveSkinTexture(@NotNull GameProfile gameProfile) {
return ClientOpt.resolveSkinTexture(gameProfile);
}
/**
* Has slim arms boolean.
*
* @param player the player
* @return the boolean
*/
public static boolean hasSlimArms(@NotNull Player player) {
if (player.level().isClientSide) {
return hasSlimArmsClient(player);
} else {
return hasSlimArmsServer(player);
}
}
private static boolean hasSlimArmsClient(Player player) {
return ClientOpt.hasSlimArmsClient(player);
}
// 服务器端判断
private static boolean hasSlimArmsServer(@NotNull Player player) {
GameProfile profile = player.getGameProfile();
for (Property property : profile.getProperties().get("textures")) {
try {
String json = new String(Base64.getDecoder().decode(property.getValue()));
JsonObject obj = JsonParser.parseString(json).getAsJsonObject();
JsonObject textures = obj.getAsJsonObject("textures");
JsonObject skin = textures.getAsJsonObject("SKIN");
if (skin.has("metadata")) {
JsonObject metadata = skin.getAsJsonObject("metadata");
if (metadata.has("model")) {
return "slim".equals(metadata.get("model").getAsString());
}
}
} catch (Exception e) {
// 解析失败使用默认
}
}
return false;
}
/**
* Gets skin model name.
*
* @param player the player
* @return the skin model name
*/
public static @NotNull String getSkinModelName(@NotNull Player player) {
return ClientOpt.getSkinModelName(player);
}
/**
* 判断玩家是否为纤细手臂Alex模型
*
* @param profile 玩家的GameProfile
* @return true =纤细手臂false=正常手臂
*/
public static boolean isSlimArms(GameProfile profile) {
if (profile == null) {
return false;
}
// 获取textures属性
Collection<Property> textures = profile.getProperties().get("textures");
if (textures.isEmpty()) {
return false; // 没有皮肤数据使用默认
}
// 获取第一个texture属性通常是皮肤
Property textureProperty = textures.iterator().next();
String value = textureProperty.getValue();
try {
return isSlimFromTextureData(value);
} catch (Exception e) {
// 解析失败使用默认
return false;
}
}
/**
* 从Base64编码的皮肤数据判断
* @param encodedTexture Base64编码的皮肤数据
* @return true=纤细手臂
*/
private static boolean isSlimFromTextureData(String encodedTexture) {
if (encodedTexture == null || encodedTexture.isEmpty()) {
return false;
}
try {
// 1. Base64解码
byte[] decodedBytes = Base64.getDecoder().decode(encodedTexture);
String jsonString = new String(decodedBytes, StandardCharsets.UTF_8);
// 2. 解析JSON
JsonObject root = JsonParser.parseString(jsonString).getAsJsonObject();
// 3. 导航到textures -> SKIN
JsonObject textures = root.getAsJsonObject("textures");
if (textures == null) {
return false;
}
JsonObject skin = textures.getAsJsonObject("SKIN");
if (skin == null) {
return false;
}
// 4. 检查metadata -> model
JsonObject metadata = skin.getAsJsonObject("metadata");
if (metadata == null) {
return false; // 没有metadata使用默认
}
String model = metadata.get("model").getAsString();
return "slim".equals(model);
} catch (Exception e) {
// 解析过程中出现任何错误返回默认值
return false;
}
}
/**
* 获取皮肤模型名称
*
* @param profile GameProfile
* @return "slim" "default"
*/
public static String getSkinModelName(GameProfile profile) {
if (profile == null) {
return "default";
}
Collection<Property> textures = profile.getProperties().get("textures");
if (textures.isEmpty()) {
return "default";
}
Property textureProperty = textures.iterator().next();
String value = textureProperty.getValue();
try {
byte[] decodedBytes = Base64.getDecoder().decode(value);
String jsonString = new String(decodedBytes, StandardCharsets.UTF_8);
JsonObject root = JsonParser.parseString(jsonString).getAsJsonObject();
JsonObject texturesObj = root.getAsJsonObject("textures");
JsonObject skin = texturesObj.getAsJsonObject("SKIN");
if (skin.has("metadata")) {
JsonObject metadata = skin.getAsJsonObject("metadata");
if (metadata.has("model")) {
return metadata.get("model").getAsString();
}
}
} catch (Exception e) {
// 忽略错误
}
return "default";
}
/**
* 从ItemStack的NBT中读取GameProfile
*
* @param stack the stack
* @return the profile from item stack
*/
@Nullable
public static GameProfile getProfileFromItemStack(ItemStack stack) {
if (stack.isEmpty()) {
return null;
}
CompoundTag tag = stack.getTag();
if (tag == null) {
return null;
}
AtomicReference<GameProfile> profileRef = new AtomicReference<>();
// 检查方块实体数据
NBTReader.of(tag)
.compound(TAG_BE, compoundTag ->
NBTReader.of(compoundTag)
.compound("OwnerProfile", ct -> profileRef.set(NbtUtils.readGameProfile(ct)))
)
.compound("OwnerProfile", ct -> {
if (profileRef.get() == null) { //兼容写法
profileRef.set(NbtUtils.readGameProfile(ct));
}
});
return profileRef.get();
}
/**
* 将GameProfile保存到ItemStack的NBT
*
* @param stack the stack
* @param profile the profile
*/
public static void saveProfileToItemStack(@NotNull ItemStack stack, @Nullable GameProfile profile) {
if (stack.isEmpty()) {
return;
}
CompoundTag tag = stack.getOrCreateTag();
if (profile == null) {
// 移除现有数据
NBTReader.of(tag)
.compound(TAG_BE, ct -> tag.remove(TAG_OWN_PROFILE));
tag.remove(TAG_BE);
tag.remove(TAG_OWN_PROFILE);
return;
}
// 创建方块实体数据
NBTWriter.of(tag)
.compound(TAG_BE, writer ->
writer
.compound(TAG_OWN_PROFILE, NbtUtils.writeGameProfile(new CompoundTag(), profile))
);
}
/**
* 检查ItemStack是否有保存的皮肤数据
*
* @param stack the stack
* @return the boolean
*/
public static boolean hasProfileData(@NotNull ItemStack stack) {
if (stack.isEmpty()) {
return false;
}
CompoundTag tag = stack.getTag();
if (tag == null) {
return false;
}
if (tag.contains(TAG_BE)) {
CompoundTag blockEntityTag = tag.getCompound(TAG_BE);
return blockEntityTag.contains(TAG_OWN_PROFILE);
}
return tag.contains(TAG_OWN_PROFILE);
}
}

View File

@ -1,66 +0,0 @@
package top.r3944realms.lib39.util;
import top.r3944realms.lib39.Lib39;
import java.util.function.Supplier;
/**
* The interface Client only.
*/
public interface IClientOnly {
/**
* Check.
*
* @param runnable the runnable
*/
static void check(Runnable runnable) {
if (Lib39.isClientEnvironment()) {
runnable.run();
return;
}
throw new RuntimeException("This method should be called in ClientEnvironment");
}
/**
* Check.
*
* @param runnable the runnable
* @param fallback the fallback
*/
static void check(Runnable runnable, Runnable fallback) {
if (Lib39.isClientEnvironment()) {
runnable.run();
return;
}
fallback.run();
}
/**
* Check t.
*
* @param <T> the type parameter
* @param supplier the supplier
* @return the t
*/
static <T> T check(Supplier<T> supplier) {
if (Lib39.isClientEnvironment()) {
return supplier.get();
}
throw new RuntimeException("This method should be called in ClientEnvironment");
}
/**
* Check t.
*
* @param <T> the type parameter
* @param supplier the supplier
* @param fallback the fallback
* @return the t
*/
static <T> T check(Supplier<T> supplier, Supplier<T> fallback) {
if (Lib39.isClientEnvironment()) {
return supplier.get();
}
return fallback.get();
}
}

View File

@ -1,70 +0,0 @@
package top.r3944realms.lib39.util;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
/**
* The interface Level helper.
*/
public interface ILevelHelper {
/**
* Gets level.
*
* @return the level
*/
Level getLevel();
/**
* The enum Level helper.
*/
enum LevelHelper implements ILevelHelper {
/**
* Server level helper.
*/
SERVER,
/**
* Client level helper.
*/
CLIENT;
/**
* The Level.
*/
Level level;
@Override
@Nullable
public Level getLevel() {
return level;
}
/**
* Sets level.
*
* @param level the level
*/
@ApiStatus.Internal
public void setLevel(Level level) {
this.level = level;
}
}
/**
* Gets server level.
*
* @return the server level
*/
@Nullable
static Level getServerLevel() {
return LevelHelper.SERVER.getLevel();
}
/**
* Gets client level.
*
* @return the client level
*/
@Nullable
static Level getClientLevel() {
return LevelHelper.CLIENT.getLevel();
}
}

View File

@ -1,205 +0,0 @@
package top.r3944realms.lib39.util;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
/**
* 一个对象接口该对象可声称对特定维度中的 BlockPos 拥有唯一所有权
* 这个系统被称为 "uniPos"可确保LevelBlockPos对在同一时间只能被一个对象 "拥有"
* 一次只能被一个对象 "拥有"
*
* <p><b>重要</b>由于使用了 weakValues()实现类必须被其他对象强引用
* 否则会被 GC 清理导致锁自动释放通常这意味着将实现类实例存储在
* 适当的管理器或容器中
*
* @author sch246
*/
public interface IUniPosOwner {
/**
* 检查特定位置当前是否被 *任意* 对象拥有
*
* @param level 维度 The level (dimension) of the position.
* @param pos 坐标 The position to lock.
* @return true if the position has an owner, false otherwise.
*/
default boolean isLocked(Level level, BlockPos pos) {
return UniPosManager.INSTANCE.getOwner(level, pos) != null;
}
/**
* 获取位置的当前所有者如果存在的话
*
* @param level 维度 The level (dimension) of the position.
* @param pos 坐标 The position to lock.
* @return An Optional containing the owner, or an empty Optional if the position is not owned.
*/
default Optional<IUniPosOwner> getOwner(Level level, BlockPos pos) {
return Optional.ofNullable(UniPosManager.INSTANCE.getOwner(level, pos));
}
/**
* 检查该对象是否可以锁定指定位置
* 如果该位置当前未锁定或者该对象已经是所有者则该值为 true
* 注意此方法不是原子操作仅用于快速检查
* 实际锁定时应使用 tryLock() 并检查返回值
*
* @param level 维度 The level (dimension) of the position.
* @param pos 坐标 The position to lock.
* @return true if this object can claim ownership.
*/
default boolean canLock(Level level, BlockPos pos) {
IUniPosOwner owner = UniPosManager.INSTANCE.getOwner(level, pos);
return owner == null || owner == this;
}
/**
* 尝试对该对象的指定位置声明所有权
* 此操作是原子操作
*
* @param level 维度 The level (dimension) of the position.
* @param pos 坐标 The position to lock.
* @return true if ownership was successfully claimed or was already held by this object, false if the position is owned by another object.
*/
default boolean tryLock(Level level, BlockPos pos) {
return UniPosManager.INSTANCE.tryLock(level, pos, this);
}
/**
* 释放指定位置的所有权
* 只有当该对象是当前所有者时此操作才会成功
*
* @param level 位置的级别维度 The level (dimension) of the position.
* @param pos 要解锁的位置 The position to unlock.
* @return true if the lock was successfully removed, false otherwise.
*/
default boolean unLock(Level level, BlockPos pos) {
return UniPosManager.INSTANCE.unLock(level, pos, this);
}
/**
* 续租继续持有锁
*
* @param level 维度
* @param pos 位置
*/
default void refreshLock(Level level, BlockPos pos) {
UniPosManager.INSTANCE.refreshLock(level, pos, this);
}
}
/**
* 管理 IUniPosOwner 系统的单例存储
* 该类是后台实现大多数类不应直接使用
* 在同一维度键ResourceKey<Level范围内确保任意时刻一个 BlockPos 只能被一个对象拥有
* 注意锁的作用域是维度键级别因此在服务端同一维度的不同 Level 实例之间共享
* 缓存以 BlockPos long 值为键 IUniPosOwner 的弱值weak values为值
* 当所有者不再被强引用时条目会自动移除从而释放锁
* 该实现面向服务端使用
*/
final class UniPosManager {
/**
* The constant INSTANCE.
*/
public static final UniPosManager INSTANCE = new UniPosManager();
// 顶层映射维度键ResourceKey<Level> -> 每维度的坐标缓存
// 缓存键为 BlockPos long 值为 IUniPosOwner 的弱值weak values
// 使用维度键确保同一维度的不同 Level 实例在服务端共享同一套锁
// 使用弱值确保当所有者被 GC 锁能自动释放
private final ConcurrentMap<ResourceKey<Level>, Cache<Long, IUniPosOwner>> dimensionLocks;
private UniPosManager() {
this.dimensionLocks = new ConcurrentHashMap<>();
}
/**
获取或创建特定维度键对应的 Cache
Cache 使用 BlockPos long 值作为键IUniPosOwner 的弱值作为值
ResourceKey<Level> 分隔缓存因此同一维度的不同 Level 实例在服务端共享同一套锁
computeIfAbsent 是原子操作保证线程安全地获取或创建 Cache
*/
private Cache<Long, IUniPosOwner> getDimensionCache(Level level) {
// computeIfAbsent 是原子操作保证线程安全地获取或创建 Cache
return dimensionLocks.computeIfAbsent(level.dimension(), k ->
CacheBuilder.newBuilder()
.expireAfterWrite(5, TimeUnit.SECONDS)
.build()
);
}
/**
* 获取位置的当前所有者
*
* @param level 维度
* @param pos 坐标
* @return 如果存在所有者 则返回所有者对象否则返回 null
*/
@Nullable
public IUniPosOwner getOwner(Level level, BlockPos pos) {
return getDimensionCache(level).getIfPresent(pos.asLong());
}
/**
* 尝试为指定位置设置所有者
* 如果锁已被同一所有者获得或持有
* 此操作是原子操作避免竞态条件
*
* @param level 维度
* @param pos 坐标
* @param owner 要声明所有权的对象
* @return true if the lock was acquired or already held by the same owner, false otherwise.
*/
public boolean tryLock(Level level, BlockPos pos, IUniPosOwner owner) {
IUniPosOwner existing = getDimensionCache(level).asMap().putIfAbsent(pos.asLong(), owner);
return existing == null || existing == owner;
}
/**
* 解锁一个位置但前提是所提供的所有者是当前所有者
* 此操作是原子操作
*
* @param level 维度
* @param pos 要解锁的位置
* @param owner 尝试解锁的对象
* @return true if the lock was successfully released by this owner.
*/
public boolean unLock(Level level, BlockPos pos, IUniPosOwner owner) {
Cache<Long, IUniPosOwner> cache = dimensionLocks.get(level.dimension());
if (cache == null) {
return false; // 该维度没有锁
}
// 使用 asMap() 提供的原子操作
return cache.asMap().remove(pos.asLong(), owner);
}
/**
* 续租继续持有锁
*
* @param level 维度
* @param pos 位置
* @param owner 对象
*/
public void refreshLock(Level level, BlockPos pos, IUniPosOwner owner) {
Cache<Long, IUniPosOwner> cache = dimensionLocks.get(level.dimension());
if (cache != null) {
// 只有当锁的主人是当前 owner 才更新时间
// put 操作会重置 expireAfterWrite 的计时器
cache.asMap().computeIfPresent(pos.asLong(), (k, v) -> {
if (v == owner) return owner;
return v;
});
}
}
}

View File

@ -1,195 +0,0 @@
package top.r3944realms.lib39.util;
import it.unimi.dsi.fastutil.ints.Int2DoubleMap;
import it.unimi.dsi.fastutil.ints.Int2DoubleOpenHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Vec3i;
import org.joml.Vector2f;
import static java.lang.Math.*;
/**
* 直接拿铁砧工艺的
*/
public class MathUtil {
/**
* Calc a vector2 that equals to a vector2 rotated an angle
*
* @param v origin vector, wont be changed
* @param deg angle rotated, in degrees
* @return rotated vector2
*/
public static Vector2f rotationDegrees(Vector2f v, float deg) {
return rotate(v, (float) toRadians(deg));
}
/**
* Calc a vector2 that equals to a vector2 rotated an angle
*
* @param v origin vector, wont be changed
* @param d angle rotated, in radians
* @return rotated vector2
*/
public static Vector2f rotate(Vector2f v, float d) {
return new Vector2f(
(float) (v.x * cos(d) - v.y * sin(d)),
(float) (v.x * sin(d) + v.y * cos(d))
);
}
/**
* Copy vector 2 f.
*
* @param v the v
* @return the vector 2 f
*/
public static Vector2f copy(Vector2f v) {
return new Vector2f(v.x, v.y);
}
/**
* Angle float.
*
* @param from the from
* @param to the to
* @return Angle in radians
*/
public static float angle(Vector2f from, Vector2f to) {
return (float) ((atan2(to.y, to.x) - atan2(from.y, from.x)) % (Math.PI * 2));
}
/**
* Angle degrees float.
*
* @param from the from
* @param to the to
* @return Angle in degrees
*/
public static float angleDegrees(Vector2f from, Vector2f to) {
return (float) toDegrees(angle(from, to));
}
/**
* Safe divide float.
*
* @param a the a
* @param b the b
* @return the float
*/
public static float safeDivide(float a, float b) {
if (a == b) return 1;
return a / b;
}
/**
* Is in range boolean.
*
* @param value the value
* @param min the min
* @param max the max
* @return the boolean
*/
public static boolean isInRange(double value, double min, double max) {
if (min > max) {
double min1 = min;
min = max;
max = min1;
}
return value > min && value < max;
}
/**
* Is in range boolean.
*
* @param valueX the value x
* @param valueY the value y
* @param minX the min x
* @param minY the min y
* @param maxX the max x
* @param maxY the max y
* @return the boolean
*/
public static boolean isInRange(double valueX, double valueY, double minX, double minY, double maxX, double maxY) {
if (minX > maxX) {
double minX1 = minX;
minX = maxX;
maxX = minX1;
}
if (minY > maxY) {
double minY1 = minY;
minY = maxY;
maxY = minY1;
}
return valueX > minX && valueX < maxX && valueY > minY && valueY < maxY;
}
/**
* Dist vec 3 i.
*
* @param a the a
* @param b the b
* @return the vec 3 i
*/
public static Vec3i dist(BlockPos a, BlockPos b) {
return new Vec3i(a.getX() - b.getX(), a.getY() - b.getY(), a.getZ() - b.getZ());
}
/**
* Gets direction.
*
* @param from the from
* @param to the to
* @return the direction
*/
public static Direction getDirection(BlockPos from, BlockPos to) {
return Direction.fromDelta(from.getX() - to.getX(), from.getY() - to.getY(), from.getZ() - to.getZ());
}
private static final Int2DoubleMap FACTORIAL_CACHE = new Int2DoubleOpenHashMap();
/**
* Factorial double.
*
* @param value the value
* @return the double
*/
public static double factorial(int value) {
if (value < 1) return 1;
if (FACTORIAL_CACHE.containsKey(value)) return FACTORIAL_CACHE.get(value);
double result = 1;
for (int i = 2; i <= value; i++) {
result *= i;
}
FACTORIAL_CACHE.put(value, result);
return result;
}
/**
* Clamp with proportion float.
*
* @param value the value
* @param min the min
* @param max the max
* @return the float
*/
public static float clampWithProportion(float value, float min, float max) {
float length = Math.abs(max - min);
if (length == 0) throw new IllegalArgumentException("The min value " + min + " cannot be equal to the max value" + max + "!");
if (value > max) {
while (value > max + length) {
value -= length;
}
return max - (max - value);
} else if (value < min) {
while (value < min + length) {
value += length;
}
return min + (value - min);
}
return value;
}
}

View File

@ -1,285 +0,0 @@
package top.r3944realms.lib39.util;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.lib39.Lib39;
/**
* The type Plant helper.
*/
public class PlantHelper {
/**
* The enum Plant.
*/
public enum Plant implements StringRepresentable {
/**
* Acacia sapling plant.
*/
// 树苗
ACACIA_SAPLING("acacia_sapling", Items.ACACIA_SAPLING),
/**
* Bamboo plant.
*/
BAMBOO("bamboo_stage0", Items.BAMBOO),
/**
* Birch sapling plant.
*/
BIRCH_SAPLING("birch_sapling", Items.BIRCH_SAPLING),
/**
* Cherry sapling plant.
*/
CHERRY_SAPLING("cherry_sapling", Items.CHERRY_SAPLING),
/**
* Dark oak sapling plant.
*/
DARK_OAK_SAPLING("dark_oak_sapling", Items.DARK_OAK_SAPLING),
/**
* Dead bush plant.
*/
DEAD_BUSH("dead_bush", Items.DEAD_BUSH),
/**
* Jungle sapling plant.
*/
JUNGLE_SAPLING("jungle_sapling", Items.JUNGLE_SAPLING),
/**
* Oak sapling plant.
*/
OAK_SAPLING("oak_sapling", Items.OAK_SAPLING),
/**
* Spruce sapling plant.
*/
SPRUCE_SAPLING("spruce_sapling", Items.SPRUCE_SAPLING),
/**
* Allium plant.
*/
//
ALLIUM("allium", Items.ALLIUM),
/**
* Azure bluet plant.
*/
AZURE_BLUET("azure_bluet", Items.AZURE_BLUET),
/**
* Cornflower plant.
*/
CORNFLOWER("cornflower", Items.CORNFLOWER),
/**
* Dandelion plant.
*/
DANDELION("dandelion", Items.DANDELION),
/**
* Lily of the valley plant.
*/
LILY_OF_THE_VALLEY("lily_of_the_valley", Items.LILY_OF_THE_VALLEY),
/**
* Oxeye daisy plant.
*/
OXEYE_DAISY("oxeye_daisy", Items.OXEYE_DAISY),
/**
* Orange tulip plant.
*/
ORANGE_TULIP("orange_tulip", Items.ORANGE_TULIP),
/**
* Pink tulip plant.
*/
PINK_TULIP("pink_tulip", Items.PINK_TULIP),
/**
* Red tulip plant.
*/
RED_TULIP("red_tulip", Items.RED_TULIP),
/**
* White tulip plant.
*/
WHITE_TULIP("white_tulip", Items.WHITE_TULIP),
/**
* Wither rose plant.
*/
WITHER_ROSE("wither_rose", Items.WITHER_ROSE),
/**
* Poppy plant.
*/
POPPY("poppy", Items.POPPY),
/**
* Amethyst cluster plant.
*/
// 晶体
AMETHYST_CLUSTER("amethyst_cluster", Items.AMETHYST_CLUSTER),
/**
* Brain coral plant.
*/
// 珊瑚
BRAIN_CORAL("brain_coral", Items.BRAIN_CORAL),
/**
* Brain coral fan plant.
*/
BRAIN_CORAL_FAN("brain_coral_fan", Items.BRAIN_CORAL_FAN),
/**
* Bubble coral plant.
*/
BUBBLE_CORAL("bubble_coral", Items.BUBBLE_CORAL),
/**
* Bubble coral fan plant.
*/
BUBBLE_CORAL_FAN("bubble_coral_fan", Items.BUBBLE_CORAL_FAN),
/**
* Fire coral plant.
*/
FIRE_CORAL("fire_coral", Items.FIRE_CORAL),
/**
* Fire coral fan plant.
*/
FIRE_CORAL_FAN("fire_coral_fan", Items.FIRE_CORAL_FAN),
/**
* Horn coral plant.
*/
HORN_CORAL("horn_coral", Items.HORN_CORAL),
/**
* Horn coral fan plant.
*/
HORN_CORAL_FAN("horn_coral_fan", Items.HORN_CORAL_FAN),
/**
* Tube coral plant.
*/
TUBE_CORAL("tube_coral", Items.TUBE_CORAL),
/**
* Tube coral fan plant.
*/
TUBE_CORAL_FAN("tube_coral_fan", Items.TUBE_CORAL_FAN),
/**
* Dead fire coral plant.
*/
DEAD_FIRE_CORAL("dead_fire_coral", Items.DEAD_FIRE_CORAL),
/**
* Dead fire coral fan plant.
*/
DEAD_FIRE_CORAL_FAN("dead_fire_coral_fan", Items.DEAD_FIRE_CORAL_FAN),
/**
* Dead horn coral plant.
*/
DEAD_HORN_CORAL("dead_horn_coral", Items.DEAD_HORN_CORAL),
/**
* Dead horn coral fan plant.
*/
DEAD_HORN_CORAL_FAN("dead_horn_coral_fan", Items.DEAD_HORN_CORAL_FAN),
/**
* Dead tube coral plant.
*/
DEAD_TUBE_CORAL("dead_tube_coral", Items.DEAD_TUBE_CORAL),
/**
* Dead tube coral fan plant.
*/
DEAD_TUBE_CORAL_FAN("dead_tube_coral_fan", Items.DEAD_TUBE_CORAL_FAN),
/**
* Dead brain coral plant.
*/
DEAD_BRAIN_CORAL("dead_brain_coral", Items.DEAD_BRAIN_CORAL),
/**
* Dead brain coral fan plant.
*/
DEAD_BRAIN_CORAL_FAN("dead_brain_coral_fan", Items.DEAD_BRAIN_CORAL_FAN),
/**
* Dead bubble coral plant.
*/
DEAD_BUBBLE_CORAL("dead_bubble_coral", Items.DEAD_BUBBLE_CORAL),
/**
* Dead bubble coral fan plant.
*/
DEAD_BUBBLE_CORAL_FAN("dead_bubble_coral_fan", Items.DEAD_BUBBLE_CORAL_FAN),
/**
* Crimson fungus plant.
*/
// 蘑菇
CRIMSON_FUNGUS("crimson_fungus", Items.CRIMSON_FUNGUS),
/**
* Red mushroom plant.
*/
RED_MUSHROOM("red_mushroom", Items.RED_MUSHROOM),
/**
* Brown mushroom plant.
*/
BROWN_MUSHROOM("brown_mushroom", Items.BROWN_MUSHROOM),
/**
* Warped fungus plant.
*/
WARPED_FUNGUS("warped_fungus", Items.WARPED_FUNGUS),
/**
* Crimson roots pot plant.
*/
// Grass
// CRIMSON_ROOTS("crimson_roots"),
CRIMSON_ROOTS_POT("crimson_roots_pot", Items.CRIMSON_ROOTS),
/**
* Warped roots pot plant.
*/
// WARPED_ROOTS("warped_roots"),
WARPED_ROOTS_POT("warped_roots_pot", Items.WARPED_ROOTS),
/**
* Cobweb plant.
*/
// Other
COBWEB("cobweb", Items.COBWEB),
/**
* Redstone torch plant.
*/
REDSTONE_TORCH("redstone_torch", Items.REDSTONE_TORCH),
/**
* Torch plant.
*/
TORCH("torch", Items.TORCH),
;
/**
* The Name.
*/
public final String name;
/**
* The Item.
*/
public final Item item;
Plant(String name, Item item) {
this.name = name;
this.item = item;
}
@Override
public @NotNull String toString() {
return super.toString().toLowerCase();
}
@Override
public @NotNull String getSerializedName() {
return name;
}
}
/**
* Gets texture rl.
*
* @param plant the plant
* @return the texture rl
*/
@Contract("_ -> new")
public static @NotNull ResourceLocation getTextureRL(@NotNull Plant plant) {
return Lib39.mrl("block/" + plant.name);
}
/**
* Gets directly texture rl.
*
* @param plant the plant
* @return the directly texture rl
*/
@Contract("_ -> new")
public static @NotNull ResourceLocation getDirectlyTextureRL(@NotNull Plant plant) {
return Lib39.mrl("textures/block/" + plant.name + ".png");
}
}

View File

@ -1,141 +0,0 @@
package top.r3944realms.lib39.util.block;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.lib39.mixin.minecraft.CreativeModeTabsAccessor;
import top.r3944realms.lib39.platform.Services;
import java.util.function.BiFunction;
import java.util.function.Supplier;
/**
* The type Block registry builder.
*/
@SuppressWarnings({"UnusedReturnValue", "unused"})
public abstract class BlockRegistryBuilder {
private String registryName;
private Supplier<Block> blockObject;
private BiFunction<String, Supplier<Item>, Supplier<Item>> blockItemRegister;
private boolean needBuildItem;
private Item.Properties properties;
/**
* Create block registry builder.
*
* @return the block registry builder
*/
public static BlockRegistryBuilder create() {
return Services.PLATFORM.getUtilHelper().getBlockRegistryBuilder();
}
/**
* 设置注册名称
*
* @param name the name
* @return the block registry builder
*/
public BlockRegistryBuilder withName(String name) {
this.registryName = name;
return this;
}
/**
* 注册方块不自动注册物品
*
* @param blockRegister the block register
* @param blockSupplier the block supplier
* @return the block registry builder
*/
public BlockRegistryBuilder registerBlock(@NotNull BiFunction<String, Supplier<Block>,Supplier<Block>> blockRegister, Supplier<Block> blockSupplier) {
this.blockObject = blockRegister.apply(this.registryName, blockSupplier);
return this;
}
/**
* 注册对应的方块物品
*
* @param itemRegister the item deferred register
* @return the block registry builder
*/
public BlockRegistryBuilder registerItem(BiFunction<String, Supplier<Item>,Supplier<Item>> itemRegister) {
this.blockItemRegister = itemRegister;
needBuildItem = true;
return this;
}
/**
* 注册对应的方块带有对应属性物品
*
* @param itemRegister the item deferred register
* @param properties the item properties
* @return the block registry builder
*/
public BlockRegistryBuilder registerItemWithProperties(BiFunction<String, Supplier<Item>,Supplier<Item>> itemRegister, Item.Properties properties) {
this.blockItemRegister = itemRegister;
this.properties = properties;
needBuildItem = true;
return this;
}
/**
* 对应的方块物品属性
*
* @param properties the item properties
* @return the block registry builder
*/
public BlockRegistryBuilder ItemProperties(Item.Properties properties) {
this.properties = properties;
return this;
}
/**
* 内部方法注册对应的方块物品
*
* @param blockObject the block object
* @param creativeTabs the creative tabs
*/
protected abstract void registerBlockItem(Supplier<Block> blockObject, ResourceKey<CreativeModeTab>... creativeTabs);
/**
* 注册方块和物品到建筑标签页
*
* @param blockRegister the block register
* @param blockSupplier the block supplier
* @return the block registry builder
*/
public BlockRegistryBuilder registerWithBuildingTab(BiFunction<String, Supplier<Block>,Supplier<Block>> blockRegister, Supplier<Block> blockSupplier) {
registerBlock(blockRegister, blockSupplier);
registerBlockItem(this.blockObject, CreativeModeTabsAccessor.getBuildingBlocks());
return this;
}
/**
* 注册方块和物品到功能标签页
*
* @param blockRegister the block register
* @param blockSupplier the block supplier
* @return the block registry builder
*/
public BlockRegistryBuilder registerWithFunctionalTab(BiFunction<String, Supplier<Block>,Supplier<Block>> blockRegister, Supplier<Block> blockSupplier) {
registerBlock(blockRegister, blockSupplier);
registerBlockItem(this.blockObject, CreativeModeTabsAccessor.getFunctionalBlocks());
return this;
}
/**
* 获取注册的方块对象
*
* @return the registry object
*/
public Supplier<Block> build() {
if (needBuildItem) {
blockItemRegister.apply(this.registryName, () -> new BlockItem(this.blockObject.get(), properties == null ? new Item.Properties() : properties));
}
return this.blockObject;
}
}

View File

@ -1,110 +0,0 @@
package top.r3944realms.lib39.util.command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* The type Command alias helper.
*/
@SuppressWarnings("unused")
public class CommandAliasHelper {
/**
* 注册命令及其别名
*
* @param dispatcher the dispatcher
* @param mainCommand the main command
* @param aliases the aliases
*/
public static void registerWithAliases(@NotNull CommandDispatcher<CommandSourceStack> dispatcher,
LiteralArgumentBuilder<CommandSourceStack> mainCommand,
String @NotNull ... aliases) {
// 注册主命令
LiteralCommandNode<CommandSourceStack> mainNode = dispatcher.register(mainCommand);
// 注册所有别名
for (String alias : aliases) {
LiteralArgumentBuilder<CommandSourceStack> aliasCommand = Commands.literal(alias);
// 复制主命令的所有子命令到别名命令递归复制
copyChildren(mainNode, aliasCommand);
dispatcher.register(aliasCommand);
}
}
/**
* 递归复制命令节点的所有子节点
*/
private static void copyChildren(@NotNull CommandNode<CommandSourceStack> source, ArgumentBuilder<CommandSourceStack, ?> target) {
for (CommandNode<CommandSourceStack> child : source.getChildren()) {
ArgumentBuilder<CommandSourceStack, ?> childBuilder = createBuilderFromNode(child);
if (childBuilder != null) {
// 递归复制孙子节点
copyChildren(child, childBuilder);
// 将子命令添加到目标命令
target.then(childBuilder);
}
}
}
/**
* 根据命令节点类型创建对应的构建器
*/
private static @Nullable ArgumentBuilder<CommandSourceStack, ?> createBuilderFromNode(CommandNode<CommandSourceStack> node) {
if (node instanceof LiteralCommandNode<CommandSourceStack> literalNode) {
// 处理字面量节点
LiteralArgumentBuilder<CommandSourceStack> builder = Commands.literal(literalNode.getLiteral());
copyNodeProperties(node, builder);
return builder;
} else if (node instanceof ArgumentCommandNode<CommandSourceStack, ?> argumentNode) {
// 处理参数节点
RequiredArgumentBuilder<CommandSourceStack, ?> builder = Commands.argument(
argumentNode.getName(),
argumentNode.getType()
);
// 设置参数建议提供器
if (argumentNode.getCustomSuggestions() != null) {
builder.suggests(argumentNode.getCustomSuggestions());
}
copyNodeProperties(node, builder);
return builder;
}
return null;
}
/**
* 复制命令节点的通用属性
*/
private static void copyNodeProperties(@NotNull CommandNode<CommandSourceStack> source, ArgumentBuilder<CommandSourceStack, ?> target) {
// 复制重定向
if (source.getRedirect() != null) {
target.redirect(source.getRedirect());
}
// 复制权限要求
if (source.getRequirement() != null) {
target.requires(source.getRequirement());
}
// 复制执行逻辑
if (source.getCommand() != null) {
target.executes(source.getCommand());
}
}
}

View File

@ -1,36 +0,0 @@
package top.r3944realms.lib39.util.lang;
/**
* The interface Four consumer.
*
* @param <X> the type parameter
* @param <Y> the type parameter
* @param <Z> the type parameter
* @param <W> the type parameter
*/
public interface FourConsumer<X, Y, Z, W> {
/**
* Accept.
*
* @param x the x
* @param y the y
* @param z the z
* @param w the w
*/
void accept(X x, Y y, Z z, W w);
/**
* Noop.
*
* @param <X> the type parameter
* @param <Y> the type parameter
* @param <Z> the type parameter
* @param <W> the type parameter
* @param x the x
* @param y the y
* @param z the z
* @param w the w
*/
static <X, Y, Z, W> void noop(X x, Y y, Z z, W w) {
}
}

View File

@ -1,66 +0,0 @@
package top.r3944realms.lib39.util.lang;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
/**
* The type Pair.
*
* @param <F> the type parameter
* @param <S> the type parameter
*/
public final class Pair<F, S> {
/**
* The First.
*/
public F first;
/**
* The Second.
*/
public S second;
private Pair(F first, S second) {
this.first = first;
this.second = second;
}
/**
* Of @ not null pair.
*
* @param <F> the type parameter
* @param <S> the type parameter
* @param first the first
* @param second the second
* @return the @ not null pair
*/
@Contract("null, _ -> fail; !null, null -> fail; !null, !null -> new")
public static <F, S> @NotNull Pair<F, S> of(F first, S second) {
if (first == null || second == null) {
throw new IllegalArgumentException("Pair.of requires non-null argument");
}
return new Pair<>(first, second);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Pair<?, ?> rhs)) {
return false;
}
return first.equals(rhs.first) && second.equals(rhs.second);
}
@Override
public int hashCode() {
return first.hashCode() * 37 + second.hashCode();
}
@Override
public String toString() {
return "Pair{" +
"first=" + first +
", second=" + second +
'}';
}
}

View File

@ -1,76 +0,0 @@
package top.r3944realms.lib39.util.lang;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
/**
* The type Triple.
*
* @param <A> the type parameter
* @param <B> the type parameter
* @param <C> the type parameter
*/
@SuppressWarnings("unused")
public final class Triple<A, B, C> {
/**
* The First.
*/
public A first;
/**
* The Second.
*/
public B second;
/**
* The Third.
*/
public C third;
private Triple(A first, B second, C third) {
this.first = first;
this.second = second;
this.third = third;
}
/**
* Of @ not null triple.
*
* @param <A> the type parameter
* @param <B> the type parameter
* @param <C> the type parameter
* @param first the first
* @param second the second
* @param third the third
* @return the @ not null triple
*/
@Contract(value = "_, _, _ -> new", pure = true)
public static <A, B, C> @NotNull Triple<A, B, C> of(A first, B second, C third) {
return new Triple<>(first, second, third);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Triple<?, ?, ?> triple = (Triple<?, ?, ?>) o;
return Objects.equals(first, triple.first) &&
Objects.equals(second, triple.second) &&
Objects.equals(third, triple.third);
}
@Override
public int hashCode() {
return Objects.hash(first, second, third);
}
@Contract(pure = true)
@Override
public @NotNull String toString() {
return "Triple{" +
"first=" + first +
", second=" + second +
", third=" + third +
'}';
}
}

View File

@ -1,153 +0,0 @@
package top.r3944realms.lib39.util.lang;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
/**
* The type Tuple.
*/
@SuppressWarnings("unused")
public final class Tuple {
private final List<Object> elements;
private Tuple(Object... elements) {
this.elements = List.of(elements);
}
/**
* Of tuple.
*
* @param elements the elements
* @return the tuple
*/
@Contract(value = "_ -> new", pure = true)
public static @NotNull Tuple of(Object... elements) {
return new Tuple(elements);
}
/**
* Size int.
*
* @return the int
*/
public int size() {
return elements.size();
}
/**
* Get t.
*
* @param <T> the type parameter
* @param index the index
* @return the t
*/
@SuppressWarnings("unchecked")
public <T> T get(int index) {
if (index < 0 || index >= elements.size()) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + elements.size());
}
return (T) elements.get(index);
}
/**
* First t.
*
* @param <T> the type parameter
* @return the t
*/
public <T> T first() {
return get(0);
}
/**
* Second t.
*
* @param <T> the type parameter
* @return the t
*/
public <T> T second() {
return get(1);
}
/**
* Third t.
*
* @param <T> the type parameter
* @return the t
*/
public <T> T third() {
return get(2);
}
/**
* Last t.
*
* @param <T> the type parameter
* @return the t
*/
public <T> T last() {
return get(elements.size() - 1);
}
/**
* To list list.
*
* @return the list
*/
@Contract(value = " -> new", pure = true)
public @NotNull List<Object> toList() {
return new ArrayList<>(elements);
}
/**
* To array object [ ].
*
* @return the object [ ]
*/
@Contract(pure = true)
public Object @NotNull [] toArray() {
return elements.toArray();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tuple tuple = (Tuple) o;
return Objects.equals(elements, tuple.elements);
}
@Override
public int hashCode() {
return Objects.hash(elements);
}
@Contract(pure = true)
@Override
public @NotNull String toString() {
return "Tuple" + elements;
}
/**
* Iterator iterator.
*
* @return the iterator
*/
public @NotNull Iterator<Object> iterator() {
return elements.iterator();
}
/**
* Stream java . util . stream . stream.
*
* @return the java . util . stream . stream
*/
public java.util.stream.Stream<Object> stream() {
return elements.stream();
}
}

View File

@ -1,581 +0,0 @@
/*
* Super Lead rope mod
* Copyright (C) 2025 R3944Realms
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package top.r3944realms.lib39.util.nbt;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
import java.util.function.Consumer;
/**
* The type Nbt reader.
*/
@SuppressWarnings("unused")
public class NBTReader {
private final CompoundTag nbt;
private NBTReader(CompoundTag nbt) {
this.nbt = nbt;
}
/**
* 从CompoundTag创建读取器
*
* @param nbt the nbt
* @return the nbt reader
*/
@NotNull
public static NBTReader of(@NotNull CompoundTag nbt) {
return new NBTReader(nbt);
}
/**
* String nbt reader.
*
* @param key the key
* @param setter the setter
* @return the nbt reader
*/
// 基本读取方法 - 直接赋值给成员变量
public NBTReader string(String key, Consumer<String> setter) {
if (nbt.contains(key)) {
setter.accept(nbt.getString(key));
}
return this;
}
/**
* String nbt reader.
*
* @param key the key
* @param setter the setter
* @param defaultValue the default value
* @return the nbt reader
*/
public NBTReader string(String key, @NotNull Consumer<String> setter, String defaultValue) {
setter.accept(nbt.contains(key) ? nbt.getString(key) : defaultValue);
return this;
}
/**
* Byte value nbt reader.
*
* @param key the key
* @param setter the setter
* @return the nbt reader
*/
public NBTReader byteValue(String key, Consumer<Byte> setter) {
if (nbt.contains(key)) {
setter.accept(nbt.getByte(key));
}
return this;
}
/**
* Byte value nbt reader.
*
* @param key the key
* @param setter the setter
* @param defaultValue the default value
* @return the nbt reader
*/
public NBTReader byteValue(String key, @NotNull Consumer<Byte> setter, byte defaultValue) {
setter.accept(nbt.contains(key) ? nbt.getByte(key) : defaultValue);
return this;
}
/**
* Short value nbt reader.
*
* @param key the key
* @param setter the setter
* @return the nbt reader
*/
public NBTReader shortValue(String key, Consumer<Short> setter) {
if (nbt.contains(key)) {
setter.accept(nbt.getShort(key));
}
return this;
}
/**
* Short value nbt reader.
*
* @param key the key
* @param setter the setter
* @param defaultValue the default value
* @return the nbt reader
*/
public NBTReader shortValue(String key, @NotNull Consumer<Short> setter, short defaultValue) {
setter.accept(nbt.contains(key) ? nbt.getShort(key) : defaultValue);
return this;
}
/**
* Int value nbt reader.
*
* @param key the key
* @param setter the setter
* @return the nbt reader
*/
public NBTReader intValue(String key, Consumer<Integer> setter) {
if (nbt.contains(key)) {
setter.accept(nbt.getInt(key));
}
return this;
}
/**
* Int value nbt reader.
*
* @param key the key
* @param setter the setter
* @param defaultValue the default value
* @return the nbt reader
*/
public NBTReader intValue(String key, @NotNull Consumer<Integer> setter, int defaultValue) {
setter.accept(nbt.contains(key) ? nbt.getInt(key) : defaultValue);
return this;
}
/**
* Long value nbt reader.
*
* @param key the key
* @param setter the setter
* @return the nbt reader
*/
public NBTReader longValue(String key, Consumer<Long> setter) {
if (nbt.contains(key)) {
setter.accept(nbt.getLong(key));
}
return this;
}
/**
* Long value nbt reader.
*
* @param key the key
* @param setter the setter
* @param defaultValue the default value
* @return the nbt reader
*/
public NBTReader longValue(String key, @NotNull Consumer<Long> setter, long defaultValue) {
setter.accept(nbt.contains(key) ? nbt.getLong(key) : defaultValue);
return this;
}
/**
* Float value nbt reader.
*
* @param key the key
* @param setter the setter
* @return the nbt reader
*/
public NBTReader floatValue(String key, Consumer<Float> setter) {
if (nbt.contains(key)) {
setter.accept(nbt.getFloat(key));
}
return this;
}
/**
* Float value nbt reader.
*
* @param key the key
* @param setter the setter
* @param defaultValue the default value
* @return the nbt reader
*/
public NBTReader floatValue(String key, @NotNull Consumer<Float> setter, float defaultValue) {
setter.accept(nbt.contains(key) ? nbt.getFloat(key) : defaultValue);
return this;
}
/**
* Double value nbt reader.
*
* @param key the key
* @param setter the setter
* @return the nbt reader
*/
public NBTReader doubleValue(String key, Consumer<Double> setter) {
if (nbt.contains(key)) {
setter.accept(nbt.getDouble(key));
}
return this;
}
/**
* Double value nbt reader.
*
* @param key the key
* @param setter the setter
* @param defaultValue the default value
* @return the nbt reader
*/
public NBTReader doubleValue(String key, @NotNull Consumer<Double> setter, double defaultValue) {
setter.accept(nbt.contains(key) ? nbt.getDouble(key) : defaultValue);
return this;
}
/**
* Boolean value nbt reader.
*
* @param key the key
* @param setter the setter
* @return the nbt reader
*/
public NBTReader booleanValue(String key, Consumer<Boolean> setter) {
if (nbt.contains(key)) {
setter.accept(nbt.getBoolean(key));
}
return this;
}
/**
* Boolean value nbt reader.
*
* @param key the key
* @param setter the setter
* @param defaultValue the default value
* @return the nbt reader
*/
public NBTReader booleanValue(String key, @NotNull Consumer<Boolean> setter, boolean defaultValue) {
setter.accept(nbt.contains(key) ? nbt.getBoolean(key) : defaultValue);
return this;
}
/**
* Byte array nbt reader.
*
* @param key the key
* @param setter the setter
* @return the nbt reader
*/
// 数组类型
public NBTReader byteArray(String key, Consumer<byte[]> setter) {
if (nbt.contains(key)) {
setter.accept(nbt.getByteArray(key));
}
return this;
}
/**
* Int array nbt reader.
*
* @param key the key
* @param setter the setter
* @return the nbt reader
*/
public NBTReader intArray(String key, Consumer<int[]> setter) {
if (nbt.contains(key)) {
setter.accept(nbt.getIntArray(key));
}
return this;
}
/**
* Long array nbt reader.
*
* @param key the key
* @param setter the setter
* @return the nbt reader
*/
public NBTReader longArray(String key, Consumer<long[]> setter) {
if (nbt.contains(key)) {
setter.accept(nbt.getLongArray(key));
}
return this;
}
/**
* Uuid nbt reader.
*
* @param key the key
* @param setter the setter
* @return the nbt reader
*/
// UUID
public NBTReader uuid(String key, Consumer<UUID> setter) {
if (nbt.hasUUID(key)) {
setter.accept(nbt.getUUID(key));
}
return this;
}
/**
* Uuid nbt reader.
*
* @param key the key
* @param setter the setter
* @param defaultValue the default value
* @return the nbt reader
*/
public NBTReader uuid(String key, @NotNull Consumer<UUID> setter, UUID defaultValue) {
setter.accept(nbt.hasUUID(key) ? nbt.getUUID(key) : defaultValue);
return this;
}
/**
* Compound nbt reader.
*
* @param key the key
* @param setter the setter
* @return the nbt reader
*/
// CompoundTag
public NBTReader compound(String key, Consumer<CompoundTag> setter) {
if (nbt.contains(key)) {
setter.accept(nbt.getCompound(key));
}
return this;
}
/**
* Compound nbt reader.
*
* @param key the key
* @param setter the setter
* @param defaultValue the default value
* @return the nbt reader
*/
public NBTReader compound(String key, @NotNull Consumer<CompoundTag> setter, CompoundTag defaultValue) {
setter.accept(nbt.contains(key) ? nbt.getCompound(key) : defaultValue);
return this;
}
/**
* List nbt reader.
*
* @param key the key
* @param type the type
* @param setter the setter
* @return the nbt reader
*/
// ListTag
public NBTReader list(String key, int type, Consumer<ListTag> setter) {
if (nbt.contains(key)) {
setter.accept(nbt.getList(key, type));
}
return this;
}
/**
* Vec 3 nbt reader.
*
* @param key the key
* @param setter the setter
* @return the nbt reader
*/
// Vec3支持
public NBTReader vec3(String key, Consumer<Vec3> setter) {
if (nbt.contains(key)) {
CompoundTag vecTag = nbt.getCompound(key);
if (vecTag.contains("X") && vecTag.contains("Y") && vecTag.contains("Z")) {
setter.accept(new Vec3(
vecTag.getDouble("X"),
vecTag.getDouble("Y"),
vecTag.getDouble("Z")
));
}
}
return this;
}
/**
* Vec 3 nbt reader.
*
* @param key the key
* @param setter the setter
* @param defaultValue the default value
* @return the nbt reader
*/
public NBTReader vec3(String key, Consumer<Vec3> setter, Vec3 defaultValue) {
if (nbt.contains(key)) {
CompoundTag vecTag = nbt.getCompound(key);
if (vecTag.contains("X") && vecTag.contains("Y") && vecTag.contains("Z")) {
setter.accept(new Vec3(
vecTag.getDouble("X"),
vecTag.getDouble("Y"),
vecTag.getDouble("Z")
));
return this;
}
}
setter.accept(defaultValue);
return this;
}
/**
* Enum value nbt reader.
*
* @param <T> the type parameter
* @param key the key
* @param enumClass the enum class
* @param setter the setter
* @return the nbt reader
*/
// 枚举支持
public <T extends Enum<T>> NBTReader enumValue(String key, Class<T> enumClass, Consumer<T> setter) {
if (nbt.contains(key)) {
String value = nbt.getString(key);
try {
setter.accept(Enum.valueOf(enumClass, value.toUpperCase()));
} catch (IllegalArgumentException ignored) {
// 保持setter的当前值
}
}
return this;
}
/**
* Enum value nbt reader.
*
* @param <T> the type parameter
* @param key the key
* @param enumClass the enum class
* @param setter the setter
* @param defaultValue the default value
* @return the nbt reader
*/
public <T extends Enum<T>> NBTReader enumValue(String key, Class<T> enumClass, Consumer<T> setter, T defaultValue) {
if (nbt.contains(key)) {
String value = nbt.getString(key);
try {
setter.accept(Enum.valueOf(enumClass, value.toUpperCase()));
return this;
} catch (IllegalArgumentException ignored) {
}
}
setter.accept(defaultValue);
return this;
}
/**
* Nested nbt reader.
*
* @param key the key
* @param consumer the consumer
* @return the nbt reader
*/
// 嵌套读取支持
public NBTReader nested(String key, Consumer<NBTReader> consumer) {
if (nbt.contains(key)) {
consumer.accept(new NBTReader(nbt.getCompound(key)));
}
return this;
}
/**
* Nested nbt reader.
*
* @param key the key
* @param consumer the consumer
* @param orElse the or else
* @return the nbt reader
*/
public NBTReader nested(String key, Consumer<NBTReader> consumer, Runnable orElse) {
if (nbt.contains(key)) {
consumer.accept(new NBTReader(nbt.getCompound(key)));
} else {
orElse.run();
}
return this;
}
/**
* If present nbt reader.
*
* @param key the key
* @param action the action
* @return the nbt reader
*/
// 条件读取
public NBTReader ifPresent(String key, Runnable action) {
if (nbt.contains(key)) {
action.run();
}
return this;
}
/**
* If absent nbt reader.
*
* @param key the key
* @param action the action
* @return the nbt reader
*/
public NBTReader ifAbsent(String key, Runnable action) {
if (!nbt.contains(key)) {
action.run();
}
return this;
}
/**
* Gets raw.
*
* @return the raw
*/
// 获取原始NBT
@NotNull
public CompoundTag getRaw() {
return nbt;
}
/**
* Read vec 3 vec 3.
*
* @param nbt the nbt
* @return the vec 3
*/
// 便捷的静态方法保持原有功能
@NotNull
public static Vec3 readVec3(@NotNull CompoundTag nbt) {
if (nbt.contains("X") && nbt.contains("Y") && nbt.contains("Z")) {
return new Vec3(
nbt.getDouble("X"),
nbt.getDouble("Y"),
nbt.getDouble("Z")
);
} else {
throw new IllegalArgumentException("NBT is missing X, Y, or Z value for Vec3");
}
}
/**
* Read vec 3 safe vec 3.
*
* @param nbt the nbt
* @return the vec 3
*/
@Nullable
public static Vec3 readVec3Safe(@NotNull CompoundTag nbt) {
if (nbt.contains("X") && nbt.contains("Y") && nbt.contains("Z")) {
return new Vec3(
nbt.getDouble("X"),
nbt.getDouble("Y"),
nbt.getDouble("Z")
);
}
return null;
}
}

View File

@ -1,161 +0,0 @@
package top.r3944realms.lib39.util.resolve;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.EntityType;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.lib39.Lib39;
import java.util.ArrayList;
import java.util.List;
/**
* The type Entity list resolve.
*/
public abstract class EntityListResolve {
/**
* The Result.
*/
protected EntityResolveResult result;
/**
* The type Entity resolve result.
*/
public static class EntityResolveResult {
/**
* The Entity list.
*/
protected final List<String> entityList = new ArrayList<>();
/**
* The Tag list.
*/
protected final List<String> tagList = new ArrayList<>();
/**
* The Mod list.
*/
protected final List<String> modList = new ArrayList<>();
/**
* The enum Type.
*/
public enum Type {
/**
* Entity type.
*/
ENTITY,
/**
* Tag type.
*/
TAG,
/**
* Mod type.
*/
MOD
}
/**
* Gets map.
*
* @param type the type
* @return the map
*/
public List<String> getMap(@NotNull Type type) {
return switch (type) {
case ENTITY -> entityList;
case TAG -> tagList;
case MOD -> modList;
};
}
/**
* Update.
*
* @param entity the entity
* @param tag the tag
* @param mod the mod
*/
public void update(List<String> entity, List<String> tag, List<String> mod) {
entityList.clear();
entityList.addAll(entity);
tagList.clear();
tagList.addAll(tag);
modList.clear();
modList.addAll(mod);
}
}
/**
* Resolve entity resolve result.
*
* @param configs the configs
* @return the entity resolve result
*/
public EntityResolveResult resolve(@NotNull List<String> configs) {
List<String> entityList = new ArrayList<>();
List<String> tagList = new ArrayList<>();
List<String> modList = new ArrayList<>();
for (String config : configs) {
if (!isMatch(config)) continue;
try {
String[] entities = resolveEntities(config);
for (String e : entities) {
String trimmed = e.trim();
if (trimmed.equals("*")) modList.add("*");
else if (trimmed.startsWith("#")) {
String body = trimmed.substring(1).trim();
if (body.contains(":")) tagList.add(body);
else modList.add(body);
} else entityList.add(trimmed);
}
result.update(entityList, tagList, modList);
} catch (NumberFormatException ex) {
Lib39.LOGGER.error("Invalid offset config: {}", config);
}
}
return result;
}
/**
* Is match boolean.
*
* @param input the input
* @return the boolean
*/
protected abstract boolean isMatch(String input);
/**
* Resolve entities string [ ].
*
* @param input the input
* @return the string [ ]
*/
protected abstract String[] resolveEntities(String input);
/**
* Is entity in list boolean.
*
* @param type the type
* @return the boolean
*/
@SuppressWarnings("deprecation")
public boolean isEntityInList(EntityType<?> type) {
String entityId = type.builtInRegistryHolder().key().location().toString();
String modId = entityId.split(":")[0];
for (String rs : result.entityList) {
if (rs.equals(entityId)) return true;
}
for(String rs : result.tagList) {
String body = rs.substring(1);
ResourceLocation tagId = Lib39.rl(body);
TagKey<EntityType<?>> tag = TagKey.create(Registries.ENTITY_TYPE, tagId);
if (type.builtInRegistryHolder().is(tag)) return true;
}
for(String rs : result.modList) {
String body = rs.substring(1);
if (!body.contains(":") && body.equals(modId)) return true;
}
return false;
}
}

View File

@ -1,214 +0,0 @@
package top.r3944realms.lib39.util.resolve;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.EntityType;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.lib39.Lib39;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* The type Entity map resolve.
*
* @param <T> the type parameter
*/
public abstract class EntityMapResolve<T> {
/**
* The Result.
*/
protected EntityResolveResult<T> result;
/**
* The type Entity resolve result.
*
* @param <T> the type parameter
*/
public static class EntityResolveResult<T> {
/**
* The Entity map.
*/
protected final Map<String, T> entityMap = new HashMap<>();
/**
* The Tag map.
*/
protected final Map<String, T> tagMap = new HashMap<>();
/**
* The Mod map.
*/
protected final Map<String, T> modMap = new HashMap<>();
/**
* The enum Type.
*/
public enum Type {
/**
* Entity type.
*/
ENTITY,
/**
* Tag type.
*/
TAG,
/**
* Mod type.
*/
MOD
}
/**
* Gets map.
*
* @param type the type
* @return the map
*/
public Map<String, T> getMap(@NotNull Type type) {
return switch (type) {
case ENTITY -> entityMap;
case TAG -> tagMap;
case MOD -> modMap;
};
}
/**
* Update.
*
* @param entity the entity
* @param tag the tag
* @param mod the mod
*/
public void update(Map<String, T> entity, Map<String, T> tag, Map<String, T> mod) {
entityMap.clear();
entityMap.putAll(entity);
tagMap.clear();
tagMap.putAll(tag);
modMap.clear();
modMap.putAll(mod);
}
}
/**
* Resolve entity resolve result.
*
* @param configs the configs
* @return the entity resolve result
*/
public EntityResolveResult<T> resolve(@NotNull List<String> configs) {
Map<String, T> entityMap = new HashMap<>();
Map<String, T> tagMap = new HashMap<>();
Map<String, T> modMap = new HashMap<>();
for (String config : configs) {
if (!isMatch(config)) continue;
try {
T t = resolveT(config);
String[] entities = resolveEntities(config);
for (String e : entities) {
String trimmed = e.trim();
if (trimmed.equals("*")) modMap.put("*", t);
else if (trimmed.startsWith("#")) {
String body = trimmed.substring(1).trim();
if (body.contains(":")) tagMap.put(body, t);
else modMap.put(body, t);
} else entityMap.put(trimmed, t);
}
result.update(entityMap, tagMap, modMap);
} catch (NumberFormatException ex) {
Lib39.LOGGER.error("Invalid offset config: {}", config);
}
}
return result;
}
/**
* Is match boolean.
*
* @param input the input
* @return the boolean
*/
protected abstract boolean isMatch(String input);
/**
* Resolve t t.
*
* @param input the input
* @return the t
*/
protected abstract T resolveT(String input);
/**
* Resolve entities string [ ].
*
* @param input the input
* @return the string [ ]
*/
protected abstract String[] resolveEntities(String input);
/**
* 查找实体对应的值如果找到返回匹配结果否则返回null
*/
@SuppressWarnings("deprecation")
private EntityMatchResult<T> findEntityMatch(EntityType<?> type) {
String entityId = type.builtInRegistryHolder().key().location().toString();
String modId = entityId.split(":")[0];
// 检查实体ID匹配
for (String rs : result.entityMap.keySet()) {
if (rs.equals(entityId)) {
return new EntityMatchResult<>(EntityResolveResult.Type.ENTITY, rs, result.entityMap.get(rs));
}
}
// 检查标签匹配
for (String rs : result.tagMap.keySet()) {
String body = rs.startsWith("#") ? rs.substring(1) : rs;
ResourceLocation tagId = Lib39.rl(body);
TagKey<EntityType<?>> tag = TagKey.create(Registries.ENTITY_TYPE, tagId);
if (type.builtInRegistryHolder().is(tag)) {
return new EntityMatchResult<>(EntityResolveResult.Type.TAG, rs, result.tagMap.get(rs));
}
}
// 检查模组匹配
for (String rs : result.modMap.keySet()) {
String body = rs.startsWith("#") ? rs.substring(1) : rs;
if (!body.contains(":") && body.equals(modId)) {
return new EntityMatchResult<>(EntityResolveResult.Type.MOD, rs, result.modMap.get(rs));
}
}
return null;
}
/**
* Is entity t in map boolean.
*
* @param type the type
* @return the boolean
*/
public boolean isEntityTInMap(EntityType<?> type) {
return findEntityMatch(type) != null;
}
/**
* Gets entity t in map.
*
* @param type the type
* @return the entity t in map
*/
public T getEntityTInMap(EntityType<?> type) {
EntityMatchResult<T> match = findEntityMatch(type);
return match != null ? match.value : null;
}
/**
* 内部类用于存储实体匹配结果
*/
private record EntityMatchResult<T>(EntityResolveResult.Type type, String key, T value) {
}
}

View File

@ -1,126 +0,0 @@
/*
* Super Lead rope mod
* Copyright (C) 2025 R3944Realms
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package top.r3944realms.lib39.util.riding;
import net.minecraft.world.entity.Entity;
import top.r3944realms.lib39.Lib39;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Queue;
import java.util.UUID;
import java.util.function.Function;
/**
* The type Riding applier.
*/
@SuppressWarnings("unused")
public class RidingApplier {
/**
* 应用骑乘关系在服务器端调用
*
* @param relationship 骑乘关系
* @param entityProvider 实体提供器根据UUID获取实体
* @return 应用成功的实体数量 int
*/
public static int applyRidingRelationship(RidingRelationship relationship,
Function<UUID, Entity> entityProvider) {
if (relationship == null || entityProvider == null) {
return 0;
}
int appliedCount = 0;
Queue<RidingRelationship> queue = new LinkedList<>();
queue.offer(relationship);
while (!queue.isEmpty()) {
RidingRelationship current = queue.poll();
UUID entityId = current.getEntityId();
UUID vehicleId = current.getVehicleId();
// 获取实体和载具
Entity entity = entityProvider.apply(entityId);
Entity vehicle = vehicleId != null ? entityProvider.apply(vehicleId) : null;
if (entity == null) continue;
if (vehicle != null) {
// 将当前节点的乘客挂回上层载具
for (RidingRelationship child : current.getPassengers()) {
child.setVehicleId(vehicle.getUUID());
queue.offer(child);
}
}
appliedCount++;
// 如果实体已经有载具先下车
if (entity.getVehicle() != null) {
entity.stopRiding();
}
// 如果有指定的载具尝试上车
if (vehicle != null) {
if (RidingValidator.wouldCreateCycle(entity, vehicle)) {
throw new RidingCycleException(entityId, vehicleId);
}
boolean success = entity.startRiding(vehicle, true);
if (!success) {
Lib39.LOGGER.error("Failed to mount entity {} to vehicle {}", entityId, vehicleId);
}
}
// 处理子乘客
queue.addAll(current.getPassengers());
}
return appliedCount;
}
/**
* 批量应用骑乘关系适用于世界加载时
*
* @param relationships the relationships
* @param entityProvider the entity provider
*/
public static void applyRidingRelationships(Collection<RidingRelationship> relationships,
Function<UUID, Entity> entityProvider) {
if (relationships == null || relationships.isEmpty()) {
return;
}
for (RidingRelationship relationship : relationships) {
try {
applyRidingRelationship(relationship, entityProvider);
} catch (RidingCycleException e) {
// 记录循环引用错误但继续处理其他关系
Lib39.LOGGER.warn("Cyclic riding reference detected and skipped: {}", e.getMessage());
}
}
}
/**
* 从JSON字符串应用骑乘关系
*
* @param json the json
* @param entityProvider the entity provider
* @return the int
*/
public static int applyRidingRelationshipFromJson(String json,
Function<UUID, Entity> entityProvider) {
RidingRelationship relationship = RidingSerializer.deserialize(json);
return applyRidingRelationship(relationship, entityProvider);
}
}

View File

@ -1,60 +0,0 @@
/*
* Super Lead rope mod
* Copyright (C) 2025 R3944Realms
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package top.r3944realms.lib39.util.riding;
import java.util.UUID;
/**
* The type Riding cycle exception.
*/
@SuppressWarnings("unused")
public class RidingCycleException extends IllegalStateException {
private final UUID entityId;
private final UUID vehicleId;
/**
* Instantiates a new Riding cycle exception.
*
* @param entityId the entity id
* @param vehicleId the vehicle id
*/
public RidingCycleException(UUID entityId, UUID vehicleId) {
super(String.format("Cyclic riding reference detected. " +
"Entity %s cannot be added as passenger to vehicle %s " +
"as it would create a circular dependency.",
entityId, vehicleId));
this.entityId = entityId;
this.vehicleId = vehicleId;
}
/**
* Gets entity id.
*
* @return the entity id
*/
public UUID getEntityId() {
return entityId;
}
/**
* Gets vehicle id.
*
* @return the vehicle id
*/
public UUID getVehicleId() {
return vehicleId;
}
}

View File

@ -1,228 +0,0 @@
/*
* Super Lead rope mod
* Copyright (C) 2025 R3944Realms
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package top.r3944realms.lib39.util.riding;
import net.minecraft.world.entity.Entity;
import java.util.*;
import java.util.function.Function;
/**
* The type Riding dismounts.
*/
@SuppressWarnings("unused")
public class RidingDismounts {
/**
* 解除单个实体的骑乘关系
*
* @param entity the entity
*/
public static void dismountEntity(Entity entity) {
if (entity == null) {
return;
}
// 如果实体正在骑乘先下车
if (entity.isPassenger()) {
entity.stopRiding();
}
// 让所有乘客下车
dismountAllPassengers(entity);
}
/**
* 解除实体及其所有乘客的骑乘关系非递归
*
* @param entity the entity
*/
public static void dismountAllPassengers(Entity entity) {
if (entity == null) {
return;
}
// 使用队列进行广度优先遍历
Queue<Entity> queue = new LinkedList<>();
queue.offer(entity);
while (!queue.isEmpty()) {
Entity current = queue.poll();
// 让当前实体的所有乘客下车
List<Entity> passengers = new ArrayList<>(current.getPassengers());
for (Entity passenger : passengers) {
passenger.stopRiding();
queue.offer(passenger);
}
}
}
/**
* 解除根实体的骑乘关系包括从载具下车
*
* @param entity the entity
*/
public static void dismountRootEntity(Entity entity) {
if (entity == null) {
return;
}
// 找到根载具
Entity rootVehicle = RidingFinder.findRootVehicle(entity);
if (rootVehicle != null) {
// 让根载具的所有乘客下车
dismountAllPassengers(rootVehicle);
// 根载具本身也下车如果有载具的话
if (rootVehicle.isPassenger()) {
rootVehicle.stopRiding();
}
}
}
/**
* 安全解除骑乘关系带超时保护
*
* @param entity the entity
* @param maxIterations the max iterations
* @return the boolean
*/
public static boolean safeDismountAll(Entity entity, int maxIterations) {
if (entity == null) {
return true;
}
int iteration = 0;
Queue<Entity> queue = new LinkedList<>();
queue.offer(entity);
while (!queue.isEmpty() && iteration < maxIterations) {
Entity current = queue.poll();
iteration++;
// 让当前实体下车如果是乘客
if (current.isPassenger()) {
current.stopRiding();
}
// 处理当前实体的乘客
List<Entity> passengers = new ArrayList<>(current.getPassengers());
for (Entity passenger : passengers) {
passenger.stopRiding();
queue.offer(passenger);
}
}
return queue.isEmpty(); // 如果队列为空表示全部解除成功
}
/**
* 批量解除多个实体的骑乘关系
*
* @param entities the entities
*/
public static void dismountEntities(Collection<Entity> entities) {
if (entities == null || entities.isEmpty()) {
return;
}
Set<Entity> processed = new HashSet<>();
Queue<Entity> queue = new LinkedList<>(entities);
while (!queue.isEmpty()) {
Entity current = queue.poll();
if (current != null && !processed.contains(current)) {
processed.add(current);
// 让当前实体下车
if (current.isPassenger()) {
current.stopRiding();
}
// 处理乘客
List<Entity> passengers = new ArrayList<>(current.getPassengers());
for (Entity passenger : passengers) {
if (!processed.contains(passenger)) {
queue.offer(passenger);
}
}
}
}
}
/**
* 根据骑乘关系数据结构解除骑乘
*
* @param relationship the relationship
* @param entityProvider the entity provider
*/
public static void dismountByRelationship(RidingRelationship relationship,
Function<UUID, Entity> entityProvider) {
if (relationship == null || entityProvider == null) {
return;
}
// 使用栈进行深度优先遍历解除
Deque<RidingRelationship> stack = new ArrayDeque<>();
stack.push(relationship);
while (!stack.isEmpty()) {
RidingRelationship current = stack.pop();
// 解除当前实体的骑乘
Entity entity = entityProvider.apply(current.getEntityId());
if (entity != null && entity.isPassenger()) {
entity.stopRiding();
}
// 将子乘客加入栈中后进先出深度优先
List<RidingRelationship> passengers = current.getPassengers();
for (int i = passengers.size() - 1; i >= 0; i--) {
stack.push(passengers.get(i));
}
}
}
/**
* 立即解除所有骑乘关系强制方式
*
* @param entity the entity
*/
public static void forceDismountAll(Entity entity) {
if (entity == null) {
return;
}
// 先让自己下车
if (entity.isPassenger()) {
entity.stopRiding();
}
// 使用广度优先让所有乘客下车
List<Entity> allPassengers = RidingFinder.getAllPassengers(entity, false);
for (Entity passenger : allPassengers) {
if (passenger.isPassenger()) {
passenger.stopRiding();
}
}
// 再次检查并清理确保完全解除
if (!entity.getPassengers().isEmpty()) {
entity.ejectPassengers();
}
}
}

View File

@ -1,120 +0,0 @@
/*
* Super Lead rope mod
* Copyright (C) 2025 R3944Realms
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package top.r3944realms.lib39.util.riding;
import net.minecraft.world.entity.Entity;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Function;
/**
* The type Riding finder.
*/
@SuppressWarnings("unused")
public class RidingFinder {
/**
* 从JSON字符串应用骑乘关系
*
* @param ship the ship
* @param entityProvider the entity provider
* @return the entity from riding ship
*/
public static @NotNull List<Entity> getEntityFromRidingShip(RidingRelationship ship,
Function<UUID, Entity> entityProvider) {
List<Entity> ret = new ArrayList<>();
Queue<RidingRelationship> queue = new LinkedList<>();
queue.offer(ship);
while (!queue.isEmpty()) {
RidingRelationship poll = queue.poll();
ret.add(entityProvider.apply(ship.getEntityId()));
List<RidingRelationship> passengers = poll.getPassengers();
if (!passengers.isEmpty()) {
queue.addAll(passengers);
}
}
return ret;
}
/**
* 查找根载具
*
* @param entity the entity
* @return the entity
*/
@Nullable
public static Entity findRootVehicle(@Nullable Entity entity) {
if (entity == null) {
return null;
}
Entity current = entity;
while (current.getVehicle() != null) {
current = current.getVehicle();
// 安全保护防止意外循环
if (current == entity) {
break;
}
}
return current;
}
/**
* 获取所有乘客包括嵌套乘客
*
* @param entity the entity
* @return the all passengers
*/
public static List<Entity> getAllPassengers(@Nullable Entity entity) {
return getAllPassengers(entity, true);
}
/**
* 获取所有乘客包括嵌套乘客
*
* @param entity the entity
* @param findRoot the find root
* @return the all passengers
*/
public static List<Entity> getAllPassengers(@Nullable Entity entity, boolean findRoot) {
if (entity == null) {
return Collections.emptyList();
}
Entity rootEntity = findRoot ? findRootVehicle(entity) : entity;
if (rootEntity == null) {
return Collections.emptyList();
}
List<Entity> result = new ArrayList<>();
Queue<Entity> queue = new LinkedList<>();
queue.offer(rootEntity);
while (!queue.isEmpty()) {
Entity current = queue.poll();
result.add(current); // 把当前实体加入列表
List<Entity> passengers = current.getPassengers();
if (!passengers.isEmpty()) {
queue.addAll(passengers);
}
}
return Collections.unmodifiableList(result);
}
}

View File

@ -1,151 +0,0 @@
/*
* Super Lead rope mod
* Copyright (C) 2025 R3944Realms
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package top.r3944realms.lib39.util.riding;
import java.util.*;
/**
* 骑乘关系数据结构
*/
@SuppressWarnings("unused")
public class RidingRelationship {
private UUID entityId;
private UUID vehicleId;
private List<RidingRelationship> passengers;
/**
* Instantiates a new Riding relationship.
*/
public RidingRelationship() {
this.passengers = new ArrayList<>();
}
/**
* Instantiates a new Riding relationship.
*
* @param passengers the passengers
* @param vehicleId the vehicle id
* @param entityId the entity id
*/
public RidingRelationship(List<RidingRelationship> passengers, UUID vehicleId, UUID entityId) {
this.passengers = passengers != null ? passengers : new ArrayList<>();
this.vehicleId = vehicleId;
this.entityId = entityId;
}
/**
* Gets entity id.
*
* @return the entity id
*/
public UUID getEntityId() {
return entityId;
}
/**
* Sets entity id.
*
* @param entityId the entity id
*/
public void setEntityId(UUID entityId) {
this.entityId = entityId;
}
/**
* Gets passengers.
*
* @return the passengers
*/
public List<RidingRelationship> getPassengers() {
return Collections.unmodifiableList(passengers);
}
/**
* Sets passengers.
*
* @param passengers the passengers
*/
public void setPassengers(List<RidingRelationship> passengers) {
this.passengers = passengers != null ? passengers : new ArrayList<>();
}
/**
* Add passenger.
*
* @param passenger the passenger
*/
public void addPassenger(RidingRelationship passenger) {
this.passengers.add(passenger);
}
/**
* Gets vehicle id.
*
* @return the vehicle id
*/
public UUID getVehicleId() {
return vehicleId;
}
/**
* Sets vehicle id.
*
* @param vehicleId the vehicle id
*/
public void setVehicleId(UUID vehicleId) {
this.vehicleId = vehicleId;
}
/**
* 获取所有嵌套乘客的数量
*
* @return the total passenger count
*/
public int getTotalPassengerCount() {
int count = passengers.size();
for (RidingRelationship passenger : passengers) {
count += passenger.getTotalPassengerCount();
}
return count;
}
/**
* 检查是否包含特定实体
*
* @param entityId the entity id
* @return the boolean
*/
public boolean containsEntity(UUID entityId) {
if (Objects.equals(this.entityId, entityId)) {
return true;
}
for (RidingRelationship passenger : passengers) {
if (passenger.containsEntity(entityId)) {
return true;
}
}
return false;
}
@Override
public String toString() {
return "RidingRelationship{" +
"entityId=" + entityId +
", vehicleId=" + vehicleId +
", passengers=" + passengers.size() +
'}';
}
}

View File

@ -1,129 +0,0 @@
/*
* Super Lead rope mod
* Copyright (C) 2025 R3944Realms
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package top.r3944realms.lib39.util.riding;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.r3944realms.lib39.util.lang.Pair;
import java.util.*;
import java.util.function.Function;
/**
* The type Riding saver.
*/
@SuppressWarnings("unused")
public class RidingSaver {
/**
* 保存骑乘关系
*
* @param entity the entity
* @return the riding relationship
*/
@Contract("null -> new")
public static @NotNull RidingRelationship save(@Nullable Entity entity) {
return save(entity, true);
}
/**
* 保存骑乘关系
*
* @param entity the entity
* @param findRoot the find root
* @return the riding relationship
*/
@Contract("null, _ -> new")
public static @NotNull RidingRelationship save(@Nullable Entity entity, boolean findRoot) {
if (entity == null) {
return new RidingRelationship(Collections.emptyList(), null, null);
}
Entity rootEntity = findRoot ? RidingFinder.findRootVehicle(entity) : entity;
if (rootEntity == null) {
return new RidingRelationship(Collections.emptyList(), null, null);
}
RidingRelationship rootRelationship = new RidingRelationship();
rootRelationship.setEntityId(rootEntity.getUUID());
rootRelationship.setVehicleId(null);
rootRelationship.setPassengers(new ArrayList<>());
Queue<Pair<Entity, RidingRelationship>> queue = new LinkedList<>();
queue.offer(Pair.of(rootEntity, rootRelationship));
Set<UUID> processedEntities = new HashSet<>();
processedEntities.add(rootEntity.getUUID());
while (!queue.isEmpty()) {
Pair<Entity, RidingRelationship> current = queue.poll();
Entity currentEntity = current.first;
RidingRelationship currentRelation = current.second;
List<Entity> passengers = currentEntity.getPassengers();
if (!passengers.isEmpty()) {
for (Entity passenger : passengers) {
UUID passengerId = passenger.getUUID();
if (!processedEntities.contains(passengerId)) {
processedEntities.add(passengerId);
// 构建子关系
RidingRelationship passengerRelation = new RidingRelationship();
passengerRelation.setEntityId(passengerId);
passengerRelation.setVehicleId(currentEntity.getUUID());
passengerRelation.setPassengers(new ArrayList<>());
currentRelation.addPassenger(passengerRelation);
queue.offer(Pair.of(passenger, passengerRelation));
} else {
throw new RidingCycleException(
passengerId,
currentEntity.getUUID()
);
}
}
}
}
return rootRelationship;
}
// 传入一个实体提供器 Function<UUID, Entity>通常在服务器侧就是 level::getEntity
private static Function<UUID, Entity> entityProvider;
/**
* Sets entity provider.
*
* @param provider the provider
*/
public static void setEntityProvider(Function<UUID, Entity> provider) {
entityProvider = provider;
}
/**
* 根据UUID获取EntityType
*/
private static @Nullable EntityType<?> getEntityType(UUID entityId) {
if (entityProvider == null) return null;
Entity entity = entityProvider.apply(entityId);
if (entity == null) return null;
return entity.getType();
}
}

View File

@ -1,46 +0,0 @@
/*
* Super Lead rope mod
* Copyright (C) 2025 R3944Realms
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package top.r3944realms.lib39.util.riding;
import com.google.gson.Gson;
/**
* The type Riding serializer.
*/
@SuppressWarnings("unused")
public class RidingSerializer {
private static final Gson GSON = new Gson();
/**
* 序列化骑乘关系
*
* @param relationship the relationship
* @return the string
*/
public static String serialize(RidingRelationship relationship) {
return GSON.toJson(relationship);
}
/**
* 反序列化骑乘关系
*
* @param json the json
* @return the riding relationship
*/
public static RidingRelationship deserialize(String json) {
return GSON.fromJson(json, RidingRelationship.class);
}
}

View File

@ -1,70 +0,0 @@
/*
* Super Lead rope mod
* Copyright (C) 2025 R3944Realms
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package top.r3944realms.lib39.util.riding;
import net.minecraft.world.entity.Entity;
import java.util.LinkedList;
import java.util.Queue;
/**
* The type Riding validator.
*/
@SuppressWarnings("unused")
public class RidingValidator {
/**
* 检查骑乘是否会产生循环引用
*
* @param entity the entity
* @param vehicle the vehicle
* @return the boolean
*/
public static boolean wouldCreateCycle(Entity entity, Entity vehicle) {
// 如果实体就是载具本身直接产生循环
if (entity == vehicle) {
return true;
}
// 检查载具是否已经是实体的乘客直接或间接
return isIndirectPassenger(vehicle, entity);
}
/**
* 检查target是否是entity的间接乘客
*
* @param target the target
* @param entity the entity
* @return the boolean
*/
public static boolean isIndirectPassenger(Entity target, Entity entity) {
Queue<Entity> queue = new LinkedList<>();
queue.offer(entity);
while (!queue.isEmpty()) {
Entity current = queue.poll();
if (current == target) {
return true;
}
// 检查当前实体的所有乘客
for (Entity passenger : current.getPassengers()) {
queue.offer(passenger);
}
}
return false;
}
}

View File

@ -1,49 +0,0 @@
package top.r3944realms.lib39.util.shape;
import com.mojang.math.Axis;
import org.joml.Quaternionf;
/**
* The type Quaternions.
*/
public final class Quaternions {
/**
* The constant XP_90.
*/
public static final Quaternionf XP_90 = Axis.XP.rotationDegrees(90);
/**
* The constant XP_180.
*/
public static final Quaternionf XP_180 = Axis.XP.rotationDegrees(180);
/**
* The constant XN_90.
*/
public static final Quaternionf XN_90 = Axis.XN.rotationDegrees(90);
/**
* The constant YP_90.
*/
public static final Quaternionf YP_90 = Axis.YP.rotationDegrees(90);
/**
* The constant YN_90.
*/
public static final Quaternionf YN_90 = Axis.YN.rotationDegrees(90);
/**
* The constant ZP_90.
*/
public static final Quaternionf ZP_90 = Axis.ZP.rotationDegrees(90);
/**
* The constant ZP_180.
*/
public static final Quaternionf ZP_180 = Axis.ZP.rotationDegrees(180);
/**
* The constant ZN_90.
*/
public static final Quaternionf ZN_90 = Axis.ZN.rotationDegrees(90);
private Quaternions() { }
}

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