Compare commits
No commits in common. "MultiLoader_1_20_1" and "MultiLoader_26_1_2" have entirely different histories.
MultiLoade
...
MultiLoade
5
.gitattributes
vendored
5
.gitattributes
vendored
|
|
@ -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
|
||||
454
.github/workflows/buildAndRelease.yml
vendored
454
.github/workflows/buildAndRelease.yml
vendored
|
|
@ -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
|
||||
30
.github/workflows/styleCheck.yml
vendored
30
.github/workflows/styleCheck.yml
vendored
|
|
@ -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
41
.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
57
README.md
57
README.md
|
|
@ -1,57 +0,0 @@
|
|||
# Lib39
|
||||
 []()
|
||||
|
||||
[](https://www.curseforge.com/minecraft/mc-mods/lib-39)
|
||||
[](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}")
|
||||
}
|
||||
```
|
||||
176
build.gradle
176
build.gradle
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
plugins {
|
||||
id 'groovy-gradle-plugin'
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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'
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package top.r3944realms.lib39.core.sync;
|
||||
|
||||
/**
|
||||
* The interface Entity.
|
||||
*/
|
||||
public interface IEntity {
|
||||
/**
|
||||
* Entity id int.
|
||||
*
|
||||
* @return the int
|
||||
*/
|
||||
int entityId();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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- 同步建议"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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- 玩家专属数据变换"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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",可确保(Level、BlockPos)对在同一时间只能被一个对象 "拥有"。
|
||||
* 一次只能被一个对象 "拥有"。
|
||||
*
|
||||
* <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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Reference in New Issue
Block a user