mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-26 18:19:10 +00:00
Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a15739fbb9 | ||
|
|
f4b9124636 | ||
|
|
fecda83fcb | ||
|
|
07228c3661 | ||
|
|
a0fb2e90b3 | ||
|
|
ae69c1c060 | ||
|
|
4992f4492c | ||
|
|
58bd3acdc3 | ||
|
|
af51c035a3 | ||
|
|
85ae2b5fb2 | ||
|
|
7ff10b33a0 | ||
|
|
431c9e13c9 | ||
|
|
c8579fb987 | ||
|
|
2f4eb46456 | ||
|
|
2d547507d5 | ||
|
|
8e4678468e | ||
|
|
c2a32cabc5 | ||
|
|
07f06aac68 | ||
|
|
7ae1001b1b | ||
|
|
e04c19acf5 | ||
|
|
1820a810f4 | ||
|
|
cedd12a048 | ||
|
|
7967d00208 | ||
|
|
00a68be2ad | ||
|
|
da5d991d2a | ||
|
|
c2f6d240ad | ||
|
|
4cde24c536 | ||
|
|
029617bc45 | ||
|
|
0627fb20e4 | ||
|
|
bc1f983684 | ||
|
|
31eb747c55 | ||
|
|
e8facf52ce | ||
|
|
5ee4bdd644 | ||
|
|
92c371e201 | ||
|
|
d27278454a | ||
|
|
16780c149c | ||
|
|
0445ba63bc | ||
|
|
b6aefd6f57 | ||
|
|
f803af0225 | ||
|
|
2675f4a377 | ||
|
|
03341c981f | ||
|
|
38cc654167 | ||
|
|
b347a8d060 | ||
|
|
8733b86b45 | ||
|
|
eda8e72633 | ||
|
|
c942a015d1 | ||
|
|
c00265f1f9 | ||
|
|
e303984dcf | ||
|
|
b449b5dee6 | ||
|
|
48f8c0c967 | ||
|
|
f88c4c3e2c | ||
|
|
e6273fa9a0 | ||
|
|
1ba5585d0d | ||
|
|
73547371ae | ||
|
|
fca6825394 | ||
|
|
53af114f44 | ||
|
|
311cc85c92 | ||
|
|
099a258cf8 | ||
|
|
480f59a166 | ||
|
|
45c2f5350f | ||
|
|
ed88d77852 | ||
|
|
e7fc9f015e | ||
|
|
cabde9e8d8 | ||
|
|
4df7d2def4 | ||
|
|
59ed77c169 | ||
|
|
53da3bd40c | ||
|
|
abdf8223fc | ||
|
|
a5efeecad3 | ||
|
|
4d26b24d13 | ||
|
|
29b3a60c64 | ||
|
|
da894f57c4 | ||
|
|
1bd703641b | ||
|
|
1b1d4c8e8d | ||
|
|
842ec0e28d | ||
|
|
2d5648408e | ||
|
|
41b3240741 | ||
|
|
bc03e8f3e3 | ||
|
|
86799f4c08 | ||
|
|
a3e004cf71 | ||
|
|
a7aeb1de21 | ||
|
|
1a703102c3 | ||
|
|
368c68f42b | ||
|
|
e191713bdc | ||
|
|
1604338498 | ||
|
|
c223797bf4 | ||
|
|
9b10adc8e4 | ||
|
|
5935f1ab5f | ||
|
|
3455b10a20 | ||
|
|
34e08b712d | ||
|
|
605d314a58 | ||
|
|
daaf5147a7 | ||
|
|
50eb9a7543 | ||
|
|
7d8ef7b6b3 | ||
|
|
347d2d0a8f | ||
|
|
bd560fcc99 | ||
|
|
b68aedc99a | ||
|
|
47373d8974 | ||
|
|
a57b8df994 | ||
|
|
17235637a5 | ||
|
|
cd5abd5a65 | ||
|
|
5c6631cdcf | ||
|
|
621afcd5c6 | ||
|
|
112a974a6c | ||
|
|
f9d46b4aff | ||
|
|
dfd828bca1 | ||
|
|
2df9fd897a | ||
|
|
ff2531539e | ||
|
|
f456443da0 | ||
|
|
fc7330213a | ||
|
|
d8272ba52d | ||
|
|
315f0eeb2f | ||
|
|
8e83617ac4 | ||
|
|
212bb0beb8 | ||
|
|
c16231b12b | ||
|
|
93f7294859 |
70
.github/workflows/ci_1.20.1.yml
vendored
Normal file
70
.github/workflows/ci_1.20.1.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
name: CI Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ 'minecraft/1.20.1' ]
|
||||||
|
paths-ignore:
|
||||||
|
- 'docs/**'
|
||||||
|
- 'workflows/**'
|
||||||
|
- 'README.md'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
checks: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: 'Build - 1.20.1'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 'Setup JDK 21 📦'
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '21'
|
||||||
|
distribution: 'temurin'
|
||||||
|
- name: 'Setup Gradle 8.8 🏗️'
|
||||||
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
with:
|
||||||
|
gradle-version: '8.8'
|
||||||
|
- name: 'Checkout for CI 🛎️'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: 'minecraft/1.20.1'
|
||||||
|
- name: '[Current - 1.20.1] Build 🛎️'
|
||||||
|
run: |
|
||||||
|
./gradlew clean build publish
|
||||||
|
env:
|
||||||
|
SNAPSHOTS_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
||||||
|
SNAPSHOTS_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||||
|
- name: 'Publish Test Report 📊'
|
||||||
|
uses: mikepenz/action-junit-report@v5
|
||||||
|
if: success() || failure() # Continue on failure
|
||||||
|
with:
|
||||||
|
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||||
|
- name: 'Fetch Version String 📝'
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=VERSION_NAME::$(./gradlew properties --no-daemon --console=plain -q | grep "^version:" | awk '{printf $2}')"
|
||||||
|
id: fetch-version
|
||||||
|
- name: 'Set Version Variable 📝'
|
||||||
|
run: |
|
||||||
|
echo "version_name=${{steps.fetch-version.outputs.VERSION_NAME}}" >> $GITHUB_ENV
|
||||||
|
- name: 'Publish to William278.net 🚀'
|
||||||
|
uses: WiIIiam278/bones-publish-action@v1
|
||||||
|
with:
|
||||||
|
api-key: ${{ secrets.BONES_API_KEY }}
|
||||||
|
project: 'husksync'
|
||||||
|
channel: 'alpha'
|
||||||
|
version: ${{ env.version_name }}
|
||||||
|
changelog: ${{ github.event.head_commit.message }}
|
||||||
|
distro-names: |
|
||||||
|
paper-1.20.1
|
||||||
|
fabric-1.20.1
|
||||||
|
distro-groups: |
|
||||||
|
paper
|
||||||
|
fabric
|
||||||
|
distro-descriptions: |
|
||||||
|
Paper 1.20.1
|
||||||
|
Fabric 1.20.1
|
||||||
|
files: |
|
||||||
|
target/HuskSync-Paper-${{ env.version_name }}+mc.1.20.1.jar
|
||||||
|
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.20.1.jar
|
||||||
70
.github/workflows/ci_1.21.1.yml
vendored
Normal file
70
.github/workflows/ci_1.21.1.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
name: CI Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ 'minecraft/1.21.1' ]
|
||||||
|
paths-ignore:
|
||||||
|
- 'docs/**'
|
||||||
|
- 'workflows/**'
|
||||||
|
- 'README.md'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
checks: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: 'Build - 1.21.1'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 'Setup JDK 21 📦'
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '21'
|
||||||
|
distribution: 'temurin'
|
||||||
|
- name: 'Setup Gradle 8.8 🏗️'
|
||||||
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
with:
|
||||||
|
gradle-version: '8.8'
|
||||||
|
- name: 'Checkout for CI 🛎️'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: 'minecraft/1.21.1'
|
||||||
|
- name: '[Current - 1.21.1] Build 🛎️'
|
||||||
|
run: |
|
||||||
|
./gradlew clean build publish
|
||||||
|
env:
|
||||||
|
SNAPSHOTS_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
||||||
|
SNAPSHOTS_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||||
|
- name: 'Publish Test Report 📊'
|
||||||
|
uses: mikepenz/action-junit-report@v5
|
||||||
|
if: success() || failure() # Continue on failure
|
||||||
|
with:
|
||||||
|
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||||
|
- name: 'Fetch Version String 📝'
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=VERSION_NAME::$(./gradlew properties --no-daemon --console=plain -q | grep "^version:" | awk '{printf $2}')"
|
||||||
|
id: fetch-version
|
||||||
|
- name: 'Set Version Variable 📝'
|
||||||
|
run: |
|
||||||
|
echo "version_name=${{steps.fetch-version.outputs.VERSION_NAME}}" >> $GITHUB_ENV
|
||||||
|
- name: 'Publish to William278.net 🚀'
|
||||||
|
uses: WiIIiam278/bones-publish-action@v1
|
||||||
|
with:
|
||||||
|
api-key: ${{ secrets.BONES_API_KEY }}
|
||||||
|
project: 'husksync'
|
||||||
|
channel: 'alpha'
|
||||||
|
version: ${{ env.version_name }}
|
||||||
|
changelog: ${{ github.event.head_commit.message }}
|
||||||
|
distro-names: |
|
||||||
|
paper-1.21.1
|
||||||
|
fabric-1.21.1
|
||||||
|
distro-groups: |
|
||||||
|
paper
|
||||||
|
fabric
|
||||||
|
distro-descriptions: |
|
||||||
|
Paper 1.21.1
|
||||||
|
Fabric 1.21.1
|
||||||
|
files: |
|
||||||
|
target/HuskSync-Paper-${{ env.version_name }}+mc.1.21.1.jar
|
||||||
|
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.1.jar
|
||||||
@@ -14,32 +14,36 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
name: 'Build - 1.21.4'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: 'Checkout for CI 🛎️'
|
- name: 'Setup JDK 21 📦'
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: 'Set up JDK 17 📦'
|
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
- name: 'Build with Gradle 🏗️'
|
- name: 'Setup Gradle 8.12 🏗️'
|
||||||
uses: gradle/gradle-build-action@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
arguments: build test publish
|
gradle-version: '8.12'
|
||||||
|
- name: 'Checkout for CI 🛎️'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: '[Current - 1.21.4] Build 🛎️'
|
||||||
|
run: |
|
||||||
|
./gradlew clean build publish
|
||||||
env:
|
env:
|
||||||
SNAPSHOTS_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
SNAPSHOTS_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
||||||
SNAPSHOTS_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
SNAPSHOTS_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||||
- name: 'Publish Test Report 📊'
|
- name: 'Publish Test Report 📊'
|
||||||
uses: mikepenz/action-junit-report@v4
|
uses: mikepenz/action-junit-report@v5
|
||||||
if: success() || failure() # Continue on failure
|
if: success() || failure() # Continue on failure
|
||||||
with:
|
with:
|
||||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||||
- name: 'Fetch Version Name 📝'
|
- name: 'Fetch Version String 📝'
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=VERSION_NAME::$(${{github.workspace}}/gradlew properties --no-daemon --console=plain -q | grep "^version:" | awk '{printf $2}')"
|
echo "::set-output name=VERSION_NAME::$(./gradlew properties --no-daemon --console=plain -q | grep "^version:" | awk '{printf $2}')"
|
||||||
id: fetch-version
|
id: fetch-version
|
||||||
- name: Get Version
|
- name: 'Set Version Variable 📝'
|
||||||
run: |
|
run: |
|
||||||
echo "version_name=${{steps.fetch-version.outputs.VERSION_NAME}}" >> $GITHUB_ENV
|
echo "version_name=${{steps.fetch-version.outputs.VERSION_NAME}}" >> $GITHUB_ENV
|
||||||
- name: 'Publish to William278.net 🚀'
|
- name: 'Publish to William278.net 🚀'
|
||||||
@@ -51,14 +55,14 @@ jobs:
|
|||||||
version: ${{ env.version_name }}
|
version: ${{ env.version_name }}
|
||||||
changelog: ${{ github.event.head_commit.message }}
|
changelog: ${{ github.event.head_commit.message }}
|
||||||
distro-names: |
|
distro-names: |
|
||||||
paper
|
paper-1.21.4
|
||||||
fabric-1.20.1
|
fabric-1.21.4
|
||||||
distro-groups: |
|
distro-groups: |
|
||||||
paper
|
paper
|
||||||
fabric
|
fabric
|
||||||
distro-descriptions: |
|
distro-descriptions: |
|
||||||
Paper
|
Paper 1.21.4
|
||||||
Fabric 1.20.1
|
Fabric 1.21.4
|
||||||
files: |
|
files: |
|
||||||
target/HuskSync-Paper-${{ env.version_name }}.jar
|
target/HuskSync-Paper-${{ env.version_name }}+mc.1.21.4.jar
|
||||||
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.20.1.jar
|
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.4.jar
|
||||||
6
.github/workflows/pr_tests.yml
vendored
6
.github/workflows/pr_tests.yml
vendored
@@ -14,17 +14,17 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: 'Checkout for CI 🛎'
|
- name: 'Checkout for CI 🛎'
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: 'Set up JDK 17 📦'
|
- name: 'Set up JDK 21 📦'
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
- name: 'Build with Gradle 🏗️'
|
- name: 'Build with Gradle 🏗️'
|
||||||
uses: gradle/gradle-build-action@v3
|
uses: gradle/gradle-build-action@v3
|
||||||
with:
|
with:
|
||||||
arguments: test
|
arguments: test
|
||||||
- name: 'Publish Test Report 📊'
|
- name: 'Publish Test Report 📊'
|
||||||
uses: mikepenz/action-junit-report@v4
|
uses: mikepenz/action-junit-report@v5
|
||||||
if: success() || failure() # Continue on failure
|
if: success() || failure() # Continue on failure
|
||||||
with:
|
with:
|
||||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||||
77
.github/workflows/release.yml
vendored
77
.github/workflows/release.yml
vendored
@@ -8,26 +8,65 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
checks: write
|
checks: write
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
name: 'Publish Release'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: 'Checkout for CI 🛎️'
|
- name: 'Setup JDK 21 📦'
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: 'Set up JDK 17 📦'
|
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
- name: 'Build with Gradle 🏗️'
|
- name: 'Setup Gradle 8.12 🏗️'
|
||||||
uses: gradle/gradle-build-action@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
arguments: build test publish
|
gradle-version: '8.12'
|
||||||
|
- name: '[Current - 1.21.4] Checkout for CI 🛎️'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
path: '1_21_4'
|
||||||
|
- name: '[Non-LTS - 1.21.1] Checkout for CI 🛎️'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: 'minecraft/1.21.1'
|
||||||
|
path: '1_21_1'
|
||||||
|
- name: '[LTS - 1.20.1] Checkout for CI 🛎️'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: 'minecraft/1.20.1'
|
||||||
|
path: '1_20_1'
|
||||||
|
- name: '[Current - 1.21.4] Build 🛎️'
|
||||||
|
run: |
|
||||||
|
mkdir target
|
||||||
|
cd 1_21_4
|
||||||
|
./gradlew clean build publish -Dforce-hide-version-meta=1
|
||||||
|
cp -rf target/* ../target/
|
||||||
|
cd ..
|
||||||
|
env:
|
||||||
|
RELEASES_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
||||||
|
RELEASES_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||||
|
- name: '[Non-LTS - 1.21.1] Build 🛎️'
|
||||||
|
run: |
|
||||||
|
cd 1_21_1
|
||||||
|
./gradlew clean build publish -Dforce-hide-version-meta=1
|
||||||
|
cp -rf target/* ../target/
|
||||||
|
cd ..
|
||||||
|
env:
|
||||||
|
RELEASES_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
||||||
|
RELEASES_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||||
|
- name: '[LTS - 1.20.1] Build 🛎️'
|
||||||
|
run: |
|
||||||
|
cd 1_20_1
|
||||||
|
./gradlew clean build publish -Dforce-hide-version-meta=1
|
||||||
|
cp -rf target/* ../target/
|
||||||
|
cd ..
|
||||||
env:
|
env:
|
||||||
RELEASES_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
RELEASES_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
||||||
RELEASES_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
RELEASES_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||||
- name: 'Publish Test Report 📊'
|
- name: 'Publish Test Report 📊'
|
||||||
uses: mikepenz/action-junit-report@v4
|
uses: mikepenz/action-junit-report@v5
|
||||||
if: success() || failure() # Continue on failure
|
if: success() || failure() # Continue on failure
|
||||||
with:
|
with:
|
||||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||||
@@ -40,14 +79,30 @@ jobs:
|
|||||||
version: ${{ github.event.release.tag_name }}
|
version: ${{ github.event.release.tag_name }}
|
||||||
changelog: ${{ github.event.release.body }}
|
changelog: ${{ github.event.release.body }}
|
||||||
distro-names: |
|
distro-names: |
|
||||||
paper
|
paper-1.21.4
|
||||||
|
fabric-1.21.4
|
||||||
|
paper-1.21.1
|
||||||
|
fabric-1.21.1
|
||||||
|
paper-1.20.1
|
||||||
fabric-1.20.1
|
fabric-1.20.1
|
||||||
distro-groups: |
|
distro-groups: |
|
||||||
paper
|
paper
|
||||||
fabric
|
fabric
|
||||||
|
paper
|
||||||
|
fabric
|
||||||
|
paper
|
||||||
|
fabric
|
||||||
distro-descriptions: |
|
distro-descriptions: |
|
||||||
Paper
|
Paper 1.21.4
|
||||||
|
Fabric 1.21.4
|
||||||
|
Paper 1.21.1
|
||||||
|
Fabric 1.21.1
|
||||||
|
Paper 1.20.1
|
||||||
Fabric 1.20.1
|
Fabric 1.20.1
|
||||||
files: |
|
files: |
|
||||||
target/HuskSync-Paper-${{ github.event.release.tag_name }}.jar
|
target/HuskSync-Paper-${{ github.event.release.tag_name }}+mc.1.21.4.jar
|
||||||
|
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.21.4.jar
|
||||||
|
target/HuskSync-Paper-${{ github.event.release.tag_name }}+mc.1.21.1.jar
|
||||||
|
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.21.1.jar
|
||||||
|
target/HuskSync-Paper-${{ github.event.release.tag_name }}+mc.1.20.1.jar
|
||||||
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.20.1.jar
|
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.20.1.jar
|
||||||
3
.github/workflows/update_docs.yml
vendored
3
.github/workflows/update_docs.yml
vendored
@@ -13,7 +13,8 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy-wiki:
|
update-docs:
|
||||||
|
name: 'Update Docs'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: 'Checkout for CI 🛎️'
|
- name: 'Checkout for CI 🛎️'
|
||||||
|
|||||||
31
README.md
31
README.md
@@ -1,8 +1,8 @@
|
|||||||
<!--suppress ALL -->
|
<!--suppress ALL -->
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="images/banner.png" alt="HuskSync" />
|
<img src="images/banner.png" alt="HuskSync" />
|
||||||
<a href="https://github.com/WiIIiam278/HuskSync/actions/workflows/ci.yml">
|
<a href="https://github.com/WiIIiam278/HuskSync/actions/workflows/ci_master.yml">
|
||||||
<img src="https://img.shields.io/github/actions/workflow/status/WiIIiam278/HuskSync/ci.yml?branch=master&logo=github"/>
|
<img src="https://img.shields.io/github/actions/workflow/status/WiIIiam278/HuskSync/ci_master.yml?branch=master&logo=github"/>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://repo.william278.net/#/releases/net/william278/husksync/">
|
<a href="https://repo.william278.net/#/releases/net/william278/husksync/">
|
||||||
<img src="https://repo.william278.net/api/badge/latest/releases/net/william278/husksync/husksync-common?color=00fb9a&name=Maven&prefix=v" />
|
<img src="https://repo.william278.net/api/badge/latest/releases/net/william278/husksync/husksync-common?color=00fb9a&name=Maven&prefix=v" />
|
||||||
@@ -43,8 +43,29 @@
|
|||||||
|
|
||||||
**Ready?** [It's syncing time!](https://william278.net/docs/husksync/setup)
|
**Ready?** [It's syncing time!](https://william278.net/docs/husksync/setup)
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
HuskSync supports the following [compatible versions](https://william278.net/docs/husksync/compatibility) of Minecraft. Since v3.7, you must download the correct version of HuskSync for your server:
|
||||||
|
|
||||||
|
| Minecraft | Latest HuskSync | Java Version | Platforms | Support Status |
|
||||||
|
|:---------------:|:---------------:|:------------:|:--------------|:-----------------------------|
|
||||||
|
| 1.21.4 | _latest_ | 21 | Paper, Fabric | ✅ **Active Release** |
|
||||||
|
| 1.21.3 | 3.7.1 | 21 | Paper, Fabric | 🗃️ Archived (December 2024) |
|
||||||
|
| 1.21.1 | _latest_ | 21 | Paper, Fabric | ✅ **November 2025** (LTS) |
|
||||||
|
| 1.20.6 | 3.6.8 | 17 | Paper | 🗃️ Archived (October 2024) |
|
||||||
|
| 1.20.4 | 3.6.8 | 17 | Paper | 🗃️ Archived (July 2024) |
|
||||||
|
| 1.20.1 | _latest_ | 17 | Paper, Fabric | ✅ **November 2025** (LTS) |
|
||||||
|
| 1.17.1 - 1.19.4 | 3.6.8 | 17 | Paper | 🗃️ Archived |
|
||||||
|
| 1.16.5 | 3.2.1 | 16 | Paper | 🗃️ Archived |
|
||||||
|
|
||||||
|
HuskSync is primarily developed against the latest release. Old Minecraft versions are allocated a support channel based on popularity, mod support, etc:
|
||||||
|
|
||||||
|
* Long Term Support (LTS) – Supported for up to 12-18 months
|
||||||
|
* Non-Long Term Support (Non-LTS) – Supported for 3-6 months
|
||||||
|
|
||||||
|
Verify your purchase on Discord and [Download HuskSync](https://william278.net/project/husksync/download) for your server.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
Requires a MySQL/Mongo/PostgreSQL database, a Redis (v5.0+) server and a network of Spigot (1.17.1+) or Fabric (1.20.1) Minecraft servers, running Java 17+.
|
Requires a [MySQL/MariaDB/Mongo/PostgreSQL database](https://william278.net/docs/husksync/database), a [Redis (v5.0+) server]((https://william278.net/docs/husksync/redis)) and a network of [compatible Spigot or Fabric Minecraft servers](https://william278.net/docs/husksync/compatibility).
|
||||||
|
|
||||||
1. Place the plugin jar file in the `/plugins` or `/mods` directory of each Spigot/Fabric server. You do not need to install HuskSync as a proxy plugin.
|
1. Place the plugin jar file in the `/plugins` or `/mods` directory of each Spigot/Fabric server. You do not need to install HuskSync as a proxy plugin.
|
||||||
2. Start, then stop every server to let HuskSync generate the config file.
|
2. Start, then stop every server to let HuskSync generate the config file.
|
||||||
@@ -52,7 +73,7 @@ Requires a MySQL/Mongo/PostgreSQL database, a Redis (v5.0+) server and a network
|
|||||||
4. Start every server again and synchronization will begin.
|
4. Start every server again and synchronization will begin.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
To build HuskSync, simply run the following in the root of the repository (building requires Java 17). Builds will be output in `/target`:
|
To build HuskSync, simply run the following in the root of the repository (building requires Java 21). Builds will be output in `/target`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./gradlew clean build
|
./gradlew clean build
|
||||||
@@ -82,4 +103,4 @@ Translations of the plugin locales are welcome to help make the plugin more acce
|
|||||||
- [bStats](https://bstats.org/plugin/bukkit/HuskSync%20-%20Bukkit/13140) — View plugin metrics
|
- [bStats](https://bstats.org/plugin/bukkit/HuskSync%20-%20Bukkit/13140) — View plugin metrics
|
||||||
|
|
||||||
---
|
---
|
||||||
© [William278](https://william278.net/), 2023. Licensed under the Apache-2.0 License.
|
© [William278](https://william278.net/), 2025. Licensed under the Apache-2.0 License.
|
||||||
|
|||||||
36
build.gradle
36
build.gradle
@@ -1,10 +1,10 @@
|
|||||||
import org.apache.tools.ant.filters.ReplaceTokens
|
import org.apache.tools.ant.filters.ReplaceTokens
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
id 'com.gradleup.shadow' version '8.3.5'
|
||||||
id 'org.cadixdev.licenser' version '0.6.1' apply false
|
id 'org.cadixdev.licenser' version '0.6.1' apply false
|
||||||
id 'fabric-loom' version '1.7-SNAPSHOT' apply false
|
id 'fabric-loom' version "$fabric_loom_version" apply false
|
||||||
id 'org.ajoberstar.grgit' version '5.2.2'
|
id 'org.ajoberstar.grgit' version '5.3.0'
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
id 'java'
|
id 'java'
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,7 @@ ext {
|
|||||||
set 'version', version.toString()
|
set 'version', version.toString()
|
||||||
set 'description', description.toString()
|
set 'description', description.toString()
|
||||||
|
|
||||||
|
set 'minecraft_version', minecraft_version.toString()
|
||||||
set 'jedis_version', jedis_version.toString()
|
set 'jedis_version', jedis_version.toString()
|
||||||
set 'mysql_driver_version', mysql_driver_version.toString()
|
set 'mysql_driver_version', mysql_driver_version.toString()
|
||||||
set 'mariadb_driver_version', mariadb_driver_version.toString()
|
set 'mariadb_driver_version', mariadb_driver_version.toString()
|
||||||
@@ -58,12 +59,13 @@ publishing {
|
|||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
apply plugin: 'com.github.johnrengelman.shadow'
|
apply plugin: 'com.gradleup.shadow'
|
||||||
apply plugin: 'org.cadixdev.licenser'
|
apply plugin: 'org.cadixdev.licenser'
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
|
|
||||||
compileJava.options.encoding = 'UTF-8'
|
compileJava.options.encoding = 'UTF-8'
|
||||||
compileJava.options.release.set 17
|
compileJava.options.compilerArgs += ['-Xlint:unchecked', '-Xlint:deprecation']
|
||||||
|
compileJava.options.release.set Integer.parseInt(rootProject.ext.javaVersion)
|
||||||
javadoc.options.encoding = 'UTF-8'
|
javadoc.options.encoding = 'UTF-8'
|
||||||
javadoc.options.addStringOption('Xdoclint:none', '-quiet')
|
javadoc.options.addStringOption('Xdoclint:none', '-quiet')
|
||||||
|
|
||||||
@@ -73,6 +75,7 @@ allprojects {
|
|||||||
maven { url 'https://repo.william278.net/releases/' }
|
maven { url 'https://repo.william278.net/releases/' }
|
||||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
|
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
|
||||||
maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
|
maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
|
||||||
|
maven { url 'https://repo.papermc.io/repository/maven-public/' }
|
||||||
maven { url "https://repo.dmulloy2.net/repository/public/" }
|
maven { url "https://repo.dmulloy2.net/repository/public/" }
|
||||||
maven { url 'https://repo.codemc.io/repository/maven-public/' }
|
maven { url 'https://repo.codemc.io/repository/maven-public/' }
|
||||||
maven { url 'https://repo.minebench.de/' }
|
maven { url 'https://repo.minebench.de/' }
|
||||||
@@ -83,9 +86,9 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.3'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.4'
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.3'
|
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.11.4'
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.3'
|
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.11.4'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@@ -125,9 +128,9 @@ subprojects {
|
|||||||
archiveClassifier.set('')
|
archiveClassifier.set('')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append the Minecraft to the version for Fabric projects
|
// Append the compatible Minecraft version to the version
|
||||||
if (project.name == 'fabric') {
|
if (['bukkit', 'paper', 'fabric'].contains(project.name)) {
|
||||||
version += "+mc.${fabric_minecraft_version}"
|
version += "+mc.${minecraft_version}"
|
||||||
}
|
}
|
||||||
|
|
||||||
// API publishing
|
// API publishing
|
||||||
@@ -163,7 +166,7 @@ subprojects {
|
|||||||
mavenJavaBukkit(MavenPublication) {
|
mavenJavaBukkit(MavenPublication) {
|
||||||
groupId = 'net.william278.husksync'
|
groupId = 'net.william278.husksync'
|
||||||
artifactId = 'husksync-bukkit'
|
artifactId = 'husksync-bukkit'
|
||||||
version = "$rootProject.version"
|
version = "$rootProject.version+${minecraft_version}"
|
||||||
artifact shadowJar
|
artifact shadowJar
|
||||||
artifact sourcesJar
|
artifact sourcesJar
|
||||||
artifact javadocJar
|
artifact javadocJar
|
||||||
@@ -176,7 +179,7 @@ subprojects {
|
|||||||
mavenJavaFabric(MavenPublication) {
|
mavenJavaFabric(MavenPublication) {
|
||||||
groupId = 'net.william278.husksync'
|
groupId = 'net.william278.husksync'
|
||||||
artifactId = 'husksync-fabric'
|
artifactId = 'husksync-fabric'
|
||||||
version = "$rootProject.version+${fabric_minecraft_version}"
|
version = "$rootProject.version+${minecraft_version}"
|
||||||
artifact remapJar
|
artifact remapJar
|
||||||
artifact sourcesJar
|
artifact sourcesJar
|
||||||
artifact javadocJar
|
artifact javadocJar
|
||||||
@@ -190,10 +193,15 @@ subprojects {
|
|||||||
clean.delete "$rootDir/target"
|
clean.delete "$rootDir/target"
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.lifecycle("Building HuskSync ${version} by William278")
|
logger.lifecycle("Building HuskSync ${version} by William278 for Minecraft ${minecraft_version}")
|
||||||
|
|
||||||
@SuppressWarnings('GrMethodMayBeStatic')
|
@SuppressWarnings('GrMethodMayBeStatic')
|
||||||
def versionMetadata() {
|
def versionMetadata() {
|
||||||
|
// If the force-hide-version-meta environment variable is set, return ''
|
||||||
|
if (System.getProperty('force-hide-version-meta') != null) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
// Require grgit
|
// Require grgit
|
||||||
if (grgit == null) {
|
if (grgit == null) {
|
||||||
return '-unknown'
|
return '-unknown'
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation project(path: ':common')
|
implementation project(path: ':common')
|
||||||
|
|
||||||
implementation 'net.william278.uniform:uniform-bukkit:1.2.1'
|
implementation 'net.william278.uniform:uniform-bukkit:1.3'
|
||||||
implementation 'net.william278:mpdbdataconverter:1.0.1'
|
implementation 'net.william278:mpdbdataconverter:1.0.1'
|
||||||
implementation 'net.william278:hsldataconverter:1.0'
|
implementation 'net.william278:hsldataconverter:1.0'
|
||||||
implementation 'net.william278:mapdataapi:1.0.3'
|
implementation 'net.william278:mapdataapi:2.0'
|
||||||
implementation 'org.bstats:bstats-bukkit:3.0.2'
|
implementation 'org.bstats:bstats-bukkit:3.1.0'
|
||||||
implementation 'net.kyori:adventure-platform-bukkit:4.3.3'
|
implementation 'net.kyori:adventure-platform-bukkit:4.3.4'
|
||||||
implementation 'dev.triumphteam:triumph-gui:3.1.10'
|
implementation 'dev.triumphteam:triumph-gui:3.1.11'
|
||||||
implementation 'space.arim.morepaperlib:morepaperlib:0.4.4'
|
implementation 'space.arim.morepaperlib:morepaperlib:0.4.4'
|
||||||
implementation 'de.tr7zw:item-nbt-api:2.13.2'
|
implementation 'de.tr7zw:item-nbt-api:2.14.2-SNAPSHOT'
|
||||||
|
|
||||||
compileOnly 'org.spigotmc:spigot-api:1.17.1-R0.1-SNAPSHOT'
|
compileOnly "org.spigotmc:spigot-api:${bukkit_spigot_api}"
|
||||||
compileOnly 'com.github.retrooper.packetevents:spigot:2.3.0'
|
compileOnly 'com.github.retrooper:packetevents-spigot:2.7.0'
|
||||||
compileOnly 'com.comphenix.protocol:ProtocolLib:5.1.0'
|
compileOnly 'com.comphenix.protocol:ProtocolLib:5.3.0'
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.34'
|
compileOnly 'org.projectlombok:lombok:1.18.36'
|
||||||
compileOnly 'commons-io:commons-io:2.16.1'
|
compileOnly 'commons-io:commons-io:2.18.0'
|
||||||
compileOnly 'org.json:json:20240303'
|
compileOnly 'org.json:json:20250107'
|
||||||
compileOnly 'net.william278:minedown:1.8.2'
|
compileOnly 'net.william278:minedown:1.8.2'
|
||||||
compileOnly 'de.exlll:configlib-yaml:4.5.0'
|
compileOnly 'de.exlll:configlib-yaml:4.5.0'
|
||||||
compileOnly 'com.zaxxer:HikariCP:5.1.0'
|
compileOnly 'com.zaxxer:HikariCP:6.2.1'
|
||||||
compileOnly 'net.william278:DesertWell:2.0.4'
|
compileOnly 'net.william278:DesertWell:2.0.4'
|
||||||
compileOnly 'net.william278:AdvancementAPI:97a9583413'
|
compileOnly 'net.william278:AdvancementAPI:97a9583413'
|
||||||
compileOnly "redis.clients:jedis:$jedis_version"
|
compileOnly "redis.clients:jedis:$jedis_version"
|
||||||
|
|
||||||
annotationProcessor 'org.projectlombok:lombok:1.18.34'
|
annotationProcessor 'org.projectlombok:lombok:1.18.36'
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.google.common.collect.Lists;
|
|||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
import de.tr7zw.changeme.nbtapi.utils.DataFixerUtil;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@@ -78,6 +79,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.Supplier,
|
public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.Supplier,
|
||||||
BukkitEventDispatcher, BukkitMapPersister {
|
BukkitEventDispatcher, BukkitMapPersister {
|
||||||
|
|
||||||
@@ -127,6 +129,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
loadSettings();
|
loadSettings();
|
||||||
loadLocales();
|
loadLocales();
|
||||||
loadServer();
|
loadServer();
|
||||||
|
validateConfigFiles();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.eventListener = createEventListener();
|
this.eventListener = createEventListener();
|
||||||
@@ -137,6 +140,9 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
this.audiences = BukkitAudiences.create(this);
|
this.audiences = BukkitAudiences.create(this);
|
||||||
|
|
||||||
|
// Check compatibility
|
||||||
|
checkCompatibility();
|
||||||
|
|
||||||
// Register commands
|
// Register commands
|
||||||
initialize("commands", (plugin) -> getUniform().register(PluginCommand.Type.create(this)));
|
initialize("commands", (plugin) -> getUniform().register(PluginCommand.Type.create(this)));
|
||||||
|
|
||||||
@@ -327,6 +333,22 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
return Version.fromString(getServer().getBukkitVersion());
|
return Version.fromString(getServer().getBukkitVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getDataVersion(@NotNull Version mcVersion) {
|
||||||
|
return switch (mcVersion.toStringWithoutMetadata()) {
|
||||||
|
case "1.16", "1.16.1", "1.16.2", "1.16.3", "1.16.4", "1.16.5" -> DataFixerUtil.VERSION1_16_5;
|
||||||
|
case "1.17", "1.17.1" -> DataFixerUtil.VERSION1_17_1;
|
||||||
|
case "1.18", "1.18.1", "1.18.2" -> DataFixerUtil.VERSION1_18_2;
|
||||||
|
case "1.19", "1.19.1", "1.19.2" -> DataFixerUtil.VERSION1_19_2;
|
||||||
|
case "1.20", "1.20.1", "1.20.2" -> DataFixerUtil.VERSION1_20_2;
|
||||||
|
case "1.20.3", "1.20.4" -> DataFixerUtil.VERSION1_20_4;
|
||||||
|
case "1.20.5", "1.20.6" -> DataFixerUtil.VERSION1_20_5;
|
||||||
|
case "1.21", "1.21.1" -> DataFixerUtil.VERSION1_21;
|
||||||
|
case "1.21.2", "1.21.3" -> DataFixerUtil.VERSION1_21_2;
|
||||||
|
case "1.21.4" -> 4189/*DataFixerUtil.VERSION1_21_4*/;
|
||||||
|
default -> DataFixerUtil.getCurrentVersion();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public String getPlatformType() {
|
public String getPlatformType() {
|
||||||
|
|||||||
@@ -25,9 +25,7 @@ import com.google.gson.annotations.SerializedName;
|
|||||||
import de.tr7zw.changeme.nbtapi.NBTCompound;
|
import de.tr7zw.changeme.nbtapi.NBTCompound;
|
||||||
import de.tr7zw.changeme.nbtapi.NBTPersistentDataContainer;
|
import de.tr7zw.changeme.nbtapi.NBTPersistentDataContainer;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import net.kyori.adventure.util.TriState;
|
|
||||||
import net.william278.desertwell.util.ThrowingConsumer;
|
import net.william278.desertwell.util.ThrowingConsumer;
|
||||||
import net.william278.desertwell.util.Version;
|
|
||||||
import net.william278.husksync.BukkitHuskSync;
|
import net.william278.husksync.BukkitHuskSync;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.adapter.Adaptable;
|
import net.william278.husksync.adapter.Adaptable;
|
||||||
@@ -37,9 +35,10 @@ import org.bukkit.*;
|
|||||||
import org.bukkit.advancement.AdvancementProgress;
|
import org.bukkit.advancement.AdvancementProgress;
|
||||||
import org.bukkit.attribute.AttributeInstance;
|
import org.bukkit.attribute.AttributeInstance;
|
||||||
import org.bukkit.attribute.AttributeModifier;
|
import org.bukkit.attribute.AttributeModifier;
|
||||||
|
import org.bukkit.entity.EntityType;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.inventory.InventoryType;
|
import org.bukkit.event.inventory.InventoryType;
|
||||||
import org.bukkit.inventory.EquipmentSlot;
|
import org.bukkit.inventory.EquipmentSlotGroup;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.persistence.PersistentDataContainer;
|
import org.bukkit.persistence.PersistentDataContainer;
|
||||||
import org.bukkit.potion.PotionEffect;
|
import org.bukkit.potion.PotionEffect;
|
||||||
@@ -49,7 +48,6 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import org.jetbrains.annotations.Range;
|
import org.jetbrains.annotations.Range;
|
||||||
import org.jetbrains.annotations.Unmodifiable;
|
import org.jetbrains.annotations.Unmodifiable;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -157,20 +155,17 @@ public abstract class BukkitData implements Data {
|
|||||||
this.clearInventoryCraftingSlots(player);
|
this.clearInventoryCraftingSlots(player);
|
||||||
player.setItemOnCursor(null);
|
player.setItemOnCursor(null);
|
||||||
player.getInventory().setContents(plugin.setMapViews(getContents()));
|
player.getInventory().setContents(plugin.setMapViews(getContents()));
|
||||||
player.updateInventory();
|
|
||||||
player.getInventory().setHeldItemSlot(heldItemSlot);
|
player.getInventory().setHeldItemSlot(heldItemSlot);
|
||||||
|
//noinspection UnstableApiUsage
|
||||||
|
player.updateInventory();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearInventoryCraftingSlots(@NotNull Player player) {
|
private void clearInventoryCraftingSlots(@NotNull Player player) {
|
||||||
try {
|
final org.bukkit.inventory.Inventory inventory = player.getOpenInventory().getTopInventory();
|
||||||
final org.bukkit.inventory.Inventory inventory = player.getOpenInventory().getTopInventory();
|
if (inventory.getType() == InventoryType.CRAFTING) {
|
||||||
if (inventory.getType() == InventoryType.CRAFTING) {
|
for (int slot = 0; slot < 5; slot++) {
|
||||||
for (int slot = 0; slot < 5; slot++) {
|
inventory.setItem(slot, null);
|
||||||
inventory.setItem(slot, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
|
||||||
// Ignore any exceptions
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +278,7 @@ public abstract class BukkitData implements Data {
|
|||||||
public List<Effect> getActiveEffects() {
|
public List<Effect> getActiveEffects() {
|
||||||
return effects.stream()
|
return effects.stream()
|
||||||
.map(potionEffect -> new Effect(
|
.map(potionEffect -> new Effect(
|
||||||
potionEffect.getType().getName().toLowerCase(Locale.ENGLISH),
|
potionEffect.getType().getKey().toString(),
|
||||||
potionEffect.getAmplifier(),
|
potionEffect.getAmplifier(),
|
||||||
potionEffect.getDuration(),
|
potionEffect.getDuration(),
|
||||||
potionEffect.isAmbient(),
|
potionEffect.isAmbient(),
|
||||||
@@ -458,9 +453,10 @@ public abstract class BukkitData implements Data {
|
|||||||
Registry.STATISTIC.forEach(id -> {
|
Registry.STATISTIC.forEach(id -> {
|
||||||
switch (id.getType()) {
|
switch (id.getType()) {
|
||||||
case UNTYPED -> addStatistic(player, id, generic);
|
case UNTYPED -> addStatistic(player, id, generic);
|
||||||
case BLOCK -> addMaterialStatistic(player, id, blocks, true);
|
// Todo - Future - Use BLOCK and ITEM registries when API stabilizes
|
||||||
case ITEM -> addMaterialStatistic(player, id, items, false);
|
case BLOCK -> addStatistic(player, id, Registry.MATERIAL, blocks);
|
||||||
case ENTITY -> addEntityStatistic(player, id, entities);
|
case ITEM -> addStatistic(player, id, Registry.MATERIAL, items);
|
||||||
|
case ENTITY -> addStatistic(player, id, Registry.ENTITY_TYPE, entities);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return new BukkitData.Statistics(generic, blocks, items, entities);
|
return new BukkitData.Statistics(generic, blocks, items, entities);
|
||||||
@@ -481,43 +477,31 @@ public abstract class BukkitData implements Data {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addMaterialStatistic(@NotNull Player p, @NotNull Statistic id,
|
private static <R extends Keyed> void addStatistic(@NotNull Player p, @NotNull Statistic id,
|
||||||
@NotNull Map<String, Map<String, Integer>> map, boolean isBlock) {
|
@NotNull Registry<R> registry,
|
||||||
Registry.MATERIAL.forEach(material -> {
|
@NotNull Map<String, Map<String, Integer>> map) {
|
||||||
if ((material.isBlock() && !isBlock) || (material.isItem() && isBlock)) {
|
registry.forEach(i -> {
|
||||||
return;
|
try {
|
||||||
}
|
final int stat = i instanceof Material m ? p.getStatistic(id, m) :
|
||||||
final int stat = p.getStatistic(id, material);
|
(i instanceof EntityType e ? p.getStatistic(id, e) : -1);
|
||||||
if (stat != 0) {
|
if (stat != 0) {
|
||||||
map.computeIfAbsent(id.getKey().getKey(), k -> Maps.newHashMap())
|
map.compute(id.getKey().getKey(), (k, v) -> v == null ? Maps.newHashMap() : v)
|
||||||
.put(material.getKey().getKey(), stat);
|
.put(i.getKey().getKey(), stat);
|
||||||
}
|
}
|
||||||
});
|
} catch (IllegalStateException ignored) {
|
||||||
}
|
|
||||||
|
|
||||||
private static void addEntityStatistic(@NotNull Player p, @NotNull Statistic id,
|
|
||||||
@NotNull Map<String, Map<String, Integer>> map) {
|
|
||||||
Registry.ENTITY_TYPE.forEach(entity -> {
|
|
||||||
if (!entity.isAlive()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final int stat = p.getStatistic(id, entity);
|
|
||||||
if (stat != 0) {
|
|
||||||
map.computeIfAbsent(id.getKey().getKey(), k -> Maps.newHashMap())
|
|
||||||
.put(entity.getKey().getKey(), stat);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void apply(@NotNull BukkitUser user, @NotNull BukkitHuskSync plugin) {
|
public void apply(@NotNull BukkitUser user, @NotNull BukkitHuskSync p) {
|
||||||
genericStatistics.forEach((id, v) -> applyStat(user, id, Statistic.Type.UNTYPED, v));
|
genericStatistics.forEach((k, v) -> applyStat(p, user, k, Statistic.Type.UNTYPED, v));
|
||||||
blockStatistics.forEach((id, m) -> m.forEach((b, v) -> applyStat(user, id, Statistic.Type.BLOCK, v, b)));
|
blockStatistics.forEach((k, m) -> m.forEach((b, v) -> applyStat(p, user, k, Statistic.Type.BLOCK, v, b)));
|
||||||
itemStatistics.forEach((id, m) -> m.forEach((i, v) -> applyStat(user, id, Statistic.Type.ITEM, v, i)));
|
itemStatistics.forEach((k, m) -> m.forEach((i, v) -> applyStat(p, user, k, Statistic.Type.ITEM, v, i)));
|
||||||
entityStatistics.forEach((id, m) -> m.forEach((e, v) -> applyStat(user, id, Statistic.Type.ENTITY, v, e)));
|
entityStatistics.forEach((k, m) -> m.forEach((e, v) -> applyStat(p, user, k, Statistic.Type.ENTITY, v, e)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyStat(@NotNull UserDataHolder user, @NotNull String id,
|
private void applyStat(@NotNull HuskSync plugin, @NotNull UserDataHolder user, @NotNull String id,
|
||||||
@NotNull Statistic.Type type, int value, @NotNull String... key) {
|
@NotNull Statistic.Type type, int value, @NotNull String... key) {
|
||||||
final Player player = ((BukkitUser) user).getPlayer();
|
final Player player = ((BukkitUser) user).getPlayer();
|
||||||
final Statistic stat = matchStatistic(id);
|
final Statistic stat = matchStatistic(id);
|
||||||
@@ -531,7 +515,8 @@ public abstract class BukkitData implements Data {
|
|||||||
case BLOCK, ITEM -> player.setStatistic(stat, Objects.requireNonNull(matchMaterial(key[0])), value);
|
case BLOCK, ITEM -> player.setStatistic(stat, Objects.requireNonNull(matchMaterial(key[0])), value);
|
||||||
case ENTITY -> player.setStatistic(stat, Objects.requireNonNull(matchEntityType(key[0])), value);
|
case ENTITY -> player.setStatistic(stat, Objects.requireNonNull(matchEntityType(key[0])), value);
|
||||||
}
|
}
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable a) {
|
||||||
|
plugin.log(Level.WARNING, "Failed to apply statistic " + id, a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,13 +551,9 @@ public abstract class BukkitData implements Data {
|
|||||||
@Getter
|
@Getter
|
||||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
public static class Attributes extends BukkitData implements Data.Attributes, Adaptable {
|
public static class Attributes extends BukkitData implements Data.Attributes, Adaptable {
|
||||||
|
|
||||||
private static final String EQUIPMENT_SLOT_GROUP = "org.bukkit.inventory.EquipmentSlotGroup";
|
|
||||||
private static final String EQUIPMENT_SLOT_GROUP$ANY = "ANY";
|
|
||||||
private static final String EQUIPMENT_SLOT$getGroup = "getGroup";
|
|
||||||
private static TriState USE_KEYED_MODIFIERS = TriState.NOT_SET;
|
|
||||||
|
|
||||||
private List<Attribute> attributes;
|
private List<Attribute> attributes;
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -581,9 +562,8 @@ public abstract class BukkitData implements Data {
|
|||||||
final AttributeSettings settings = plugin.getSettings().getSynchronization().getAttributes();
|
final AttributeSettings settings = plugin.getSettings().getSynchronization().getAttributes();
|
||||||
Registry.ATTRIBUTE.forEach(id -> {
|
Registry.ATTRIBUTE.forEach(id -> {
|
||||||
final AttributeInstance instance = player.getAttribute(id);
|
final AttributeInstance instance = player.getAttribute(id);
|
||||||
if (instance == null || Double.compare(instance.getValue(), instance.getDefaultValue()) == 0
|
if (settings.isIgnoredAttribute(id.getKey().toString()) || instance == null) {
|
||||||
|| settings.isIgnoredAttribute(id.getKey().toString())) {
|
return; // We don't sync attributes not marked as to be synced
|
||||||
return; // We don't sync unmodified or disabled attributes
|
|
||||||
}
|
}
|
||||||
attributes.add(adapt(instance, settings));
|
attributes.add(adapt(instance, settings));
|
||||||
});
|
});
|
||||||
@@ -610,6 +590,7 @@ public abstract class BukkitData implements Data {
|
|||||||
instance.getBaseValue(),
|
instance.getBaseValue(),
|
||||||
instance.getModifiers().stream()
|
instance.getModifiers().stream()
|
||||||
.filter(modifier -> !settings.isIgnoredModifier(modifier.getName()))
|
.filter(modifier -> !settings.isIgnoredModifier(modifier.getName()))
|
||||||
|
.filter(modifier -> modifier.getSlotGroup() != EquipmentSlotGroup.ANY)
|
||||||
.map(BukkitData.Attributes::adapt).collect(Collectors.toSet())
|
.map(BukkitData.Attributes::adapt).collect(Collectors.toSet())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -617,89 +598,47 @@ public abstract class BukkitData implements Data {
|
|||||||
@NotNull
|
@NotNull
|
||||||
private static Modifier adapt(@NotNull AttributeModifier modifier) {
|
private static Modifier adapt(@NotNull AttributeModifier modifier) {
|
||||||
return new Modifier(
|
return new Modifier(
|
||||||
getModifierId(modifier),
|
modifier.getKey().toString(),
|
||||||
modifier.getName(),
|
|
||||||
modifier.getAmount(),
|
modifier.getAmount(),
|
||||||
modifier.getOperation().ordinal(),
|
modifier.getOperation().ordinal(),
|
||||||
modifier.getSlot() != null ? modifier.getSlot().ordinal() : -1
|
modifier.getSlotGroup().toString()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
private static void applyAttribute(@Nullable AttributeInstance instance, @Nullable Attribute attribute) {
|
||||||
private static UUID getModifierId(@NotNull AttributeModifier modifier) {
|
|
||||||
try {
|
|
||||||
return modifier.getUniqueId();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean useKeyedModifiers(@NotNull HuskSync plugin) {
|
|
||||||
if (USE_KEYED_MODIFIERS == TriState.NOT_SET) {
|
|
||||||
boolean is1_21 = plugin.getMinecraftVersion().compareTo(Version.fromString("1.21")) >= 0;
|
|
||||||
USE_KEYED_MODIFIERS = TriState.byBoolean(is1_21);
|
|
||||||
return is1_21;
|
|
||||||
}
|
|
||||||
return Boolean.TRUE.equals(USE_KEYED_MODIFIERS.toBoolean());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void applyAttribute(@Nullable AttributeInstance instance, @Nullable Attribute attribute,
|
|
||||||
@NotNull HuskSync plugin) {
|
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
instance.setBaseValue(attribute == null ? instance.getDefaultValue() : attribute.baseValue());
|
|
||||||
instance.getModifiers().forEach(instance::removeModifier);
|
instance.getModifiers().forEach(instance::removeModifier);
|
||||||
|
instance.setBaseValue(attribute == null ? instance.getValue() : attribute.baseValue());
|
||||||
if (attribute != null) {
|
if (attribute != null) {
|
||||||
attribute.modifiers().stream()
|
attribute.modifiers().stream()
|
||||||
.filter(mod -> instance.getModifiers().stream().map(AttributeModifier::getName)
|
.filter(mod -> instance.getModifiers().stream().map(AttributeModifier::getName)
|
||||||
.noneMatch(n -> n.equals(mod.name())))
|
.noneMatch(n -> n.equals(mod.name())))
|
||||||
.distinct()
|
.distinct().filter(mod -> !mod.hasUuid())
|
||||||
.filter(mod -> useKeyedModifiers(plugin) == !mod.hasUuid())
|
.forEach(mod -> instance.addModifier(adapt(mod)));
|
||||||
.forEach(mod -> instance.addModifier(adapt(mod, plugin)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("JavaReflectionMemberAccess")
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private static AttributeModifier adapt(@NotNull Modifier modifier, @NotNull HuskSync plugin) {
|
private static AttributeModifier adapt(@NotNull Modifier modifier) {
|
||||||
final int slotId = modifier.equipmentSlot();
|
|
||||||
if (useKeyedModifiers(plugin)) {
|
|
||||||
try {
|
|
||||||
// Reflexively create a modern keyed attribute modifier instance. Remove in favor of API long-term.
|
|
||||||
final EquipmentSlot slot = slotId != -1 ? EquipmentSlot.values()[slotId] : null;
|
|
||||||
final Class<?> slotGroup = Class.forName(EQUIPMENT_SLOT_GROUP);
|
|
||||||
final String modifierName = modifier.name() == null ? modifier.uuid().toString() : modifier.name();
|
|
||||||
final NamespacedKey modifierKey = Objects.requireNonNull(NamespacedKey.fromString(modifierName),
|
|
||||||
"Modifier key returned null");
|
|
||||||
final Constructor<AttributeModifier> constructor = AttributeModifier.class.getDeclaredConstructor(
|
|
||||||
NamespacedKey.class, double.class, AttributeModifier.Operation.class, slotGroup);
|
|
||||||
return constructor.newInstance(
|
|
||||||
modifierKey,
|
|
||||||
modifier.amount(),
|
|
||||||
AttributeModifier.Operation.values()[modifier.operationType()],
|
|
||||||
slot == null ? slotGroup.getField(EQUIPMENT_SLOT_GROUP$ANY).get(null)
|
|
||||||
: EquipmentSlot.class.getDeclaredMethod(EQUIPMENT_SLOT$getGroup).invoke(slot)
|
|
||||||
);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
plugin.log(Level.WARNING, "Error reflectively creating keyed attribute modifier", e);
|
|
||||||
USE_KEYED_MODIFIERS = TriState.FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new AttributeModifier(
|
return new AttributeModifier(
|
||||||
modifier.uuid(),
|
Objects.requireNonNull(NamespacedKey.fromString(modifier.name())),
|
||||||
modifier.name(),
|
|
||||||
modifier.amount(),
|
modifier.amount(),
|
||||||
AttributeModifier.Operation.values()[modifier.operationType()],
|
AttributeModifier.Operation.values()[modifier.operation()],
|
||||||
slotId != -1 ? EquipmentSlot.values()[slotId] : null
|
Optional.ofNullable(EquipmentSlotGroup.getByName(modifier.slotGroup())).orElse(EquipmentSlotGroup.ANY)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void apply(@NotNull BukkitUser user, @NotNull BukkitHuskSync plugin) throws IllegalStateException {
|
public void apply(@NotNull BukkitUser user, @NotNull BukkitHuskSync plugin) throws IllegalStateException {
|
||||||
Registry.ATTRIBUTE.forEach(id -> applyAttribute(
|
final AttributeSettings settings = plugin.getSettings().getSynchronization().getAttributes();
|
||||||
user.getPlayer().getAttribute(id), getAttribute(id).orElse(null), plugin
|
Registry.ATTRIBUTE.forEach(id -> {
|
||||||
));
|
if (settings.isIgnoredAttribute(id.getKey().toString())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applyAttribute(user.getPlayer().getAttribute(id), getAttribute(id).orElse(null));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,21 +153,11 @@ public class BukkitSerializer {
|
|||||||
@NotNull
|
@NotNull
|
||||||
private ReadWriteNBT upgradeItemData(@NotNull ReadWriteNBT tag, @NotNull Version mcVersion)
|
private ReadWriteNBT upgradeItemData(@NotNull ReadWriteNBT tag, @NotNull Version mcVersion)
|
||||||
throws NoSuchFieldException, IllegalAccessException {
|
throws NoSuchFieldException, IllegalAccessException {
|
||||||
return DataFixerUtil.fixUpItemData(tag, getDataVersion(mcVersion), DataFixerUtil.getCurrentVersion());
|
return DataFixerUtil.fixUpItemData(
|
||||||
}
|
tag,
|
||||||
|
getPlugin().getDataVersion(mcVersion),
|
||||||
private int getDataVersion(@NotNull Version mcVersion) {
|
DataFixerUtil.getCurrentVersion()
|
||||||
return switch (mcVersion.toStringWithoutMetadata()) {
|
);
|
||||||
case "1.16", "1.16.1", "1.16.2", "1.16.3", "1.16.4", "1.16.5" -> DataFixerUtil.VERSION1_16_5;
|
|
||||||
case "1.17", "1.17.1" -> DataFixerUtil.VERSION1_17_1;
|
|
||||||
case "1.18", "1.18.1", "1.18.2" -> DataFixerUtil.VERSION1_18_2;
|
|
||||||
case "1.19", "1.19.1", "1.19.2" -> DataFixerUtil.VERSION1_19_2;
|
|
||||||
case "1.20", "1.20.1", "1.20.2" -> DataFixerUtil.VERSION1_20_2;
|
|
||||||
case "1.20.3", "1.20.4" -> DataFixerUtil.VERSION1_20_4;
|
|
||||||
case "1.20.5", "1.20.6" -> DataFixerUtil.VERSION1_20_5;
|
|
||||||
case "1.21" -> DataFixerUtil.VERSION1_21;
|
|
||||||
default -> DataFixerUtil.getCurrentVersion();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -229,7 +219,7 @@ public class BukkitSerializer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BukkitData.PersistentData deserialize(@NotNull String serialized) throws DeserializationException {
|
public BukkitData.PersistentData deserialize(@NotNull String serialized) throws DeserializationException {
|
||||||
return BukkitData.PersistentData.from(new NBTContainer(serialized));
|
return BukkitData.PersistentData.from((NBTContainer) NBT.parseNBT(serialized));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
|||||||
@@ -40,12 +40,11 @@ public class BukkitPacketEventsLockedPacketListener extends BukkitLockedEventLis
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
public void onLoad() {
|
public void onLoad() {
|
||||||
super.onLoad();
|
super.onLoad();
|
||||||
PacketEvents.setAPI(SpigotPacketEventsBuilder.build(getPlugin()));
|
PacketEvents.setAPI(SpigotPacketEventsBuilder.build(getPlugin()));
|
||||||
PacketEvents.getAPI().getSettings().reEncodeByDefault(false)
|
PacketEvents.getAPI().getSettings().reEncodeByDefault(false).checkForUpdates(false);
|
||||||
.checkForUpdates(false)
|
|
||||||
.bStats(true);
|
|
||||||
PacketEvents.getAPI().load();
|
PacketEvents.getAPI().load();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +60,7 @@ public class BukkitPacketEventsLockedPacketListener extends BukkitLockedEventLis
|
|||||||
|
|
||||||
private static final Set<PacketType.Play.Client> ALLOWED_PACKETS = Set.of(
|
private static final Set<PacketType.Play.Client> ALLOWED_PACKETS = Set.of(
|
||||||
PacketType.Play.Client.KEEP_ALIVE, PacketType.Play.Client.PONG, PacketType.Play.Client.PLUGIN_MESSAGE, // Connection packets
|
PacketType.Play.Client.KEEP_ALIVE, PacketType.Play.Client.PONG, PacketType.Play.Client.PLUGIN_MESSAGE, // Connection packets
|
||||||
|
PacketType.Play.Client.PLAYER_LOADED, PacketType.Play.Client.CLIENT_TICK_END, // Connection packets
|
||||||
PacketType.Play.Client.CHAT_MESSAGE, PacketType.Play.Client.CHAT_COMMAND, PacketType.Play.Client.CHAT_SESSION_UPDATE, // Chat / command packets
|
PacketType.Play.Client.CHAT_MESSAGE, PacketType.Play.Client.CHAT_COMMAND, PacketType.Play.Client.CHAT_SESSION_UPDATE, // Chat / command packets
|
||||||
PacketType.Play.Client.PLAYER_POSITION, PacketType.Play.Client.PLAYER_POSITION_AND_ROTATION, PacketType.Play.Client.PLAYER_ROTATION, // Movement packets
|
PacketType.Play.Client.PLAYER_POSITION, PacketType.Play.Client.PLAYER_POSITION_AND_ROTATION, PacketType.Play.Client.PLAYER_ROTATION, // Movement packets
|
||||||
PacketType.Play.Client.HELD_ITEM_CHANGE, PacketType.Play.Client.ANIMATION, PacketType.Play.Client.TELEPORT_CONFIRM, // Animation packets
|
PacketType.Play.Client.HELD_ITEM_CHANGE, PacketType.Play.Client.ANIMATION, PacketType.Play.Client.TELEPORT_CONFIRM, // Animation packets
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public class BukkitProtocolLibLockedPacketListener extends BukkitLockedEventList
|
|||||||
|
|
||||||
private static class PlayerPacketAdapter extends PacketAdapter {
|
private static class PlayerPacketAdapter extends PacketAdapter {
|
||||||
|
|
||||||
// Packets we want the player to still be able to send/receiver to/from the server
|
// Packets we want the player to still be able to send/receiver to/from the server - //todo update 1.21.4
|
||||||
private static final Set<PacketType> ALLOWED_PACKETS = Set.of(
|
private static final Set<PacketType> ALLOWED_PACKETS = Set.of(
|
||||||
Client.KEEP_ALIVE, Client.PONG, Client.CUSTOM_PAYLOAD, // Connection packets
|
Client.KEEP_ALIVE, Client.PONG, Client.CUSTOM_PAYLOAD, // Connection packets
|
||||||
Client.CHAT_COMMAND, Client.CLIENT_COMMAND, Client.CHAT, Client.CHAT_SESSION_UPDATE, // Chat / command packets
|
Client.CHAT_COMMAND, Client.CLIENT_COMMAND, Client.CHAT, Client.CHAT_SESSION_UPDATE, // Chat / command packets
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ public class BukkitUser extends OnlineUser implements BukkitUserDataHolder {
|
|||||||
if (!editable) {
|
if (!editable) {
|
||||||
builder.disableAllInteractions();
|
builder.disableAllInteractions();
|
||||||
}
|
}
|
||||||
final StorageGui gui = builder.enableOtherActions()
|
final StorageGui gui = builder
|
||||||
.apply(a -> a.getInventory().setContents(contents))
|
.apply(a -> a.getInventory().setContents(contents))
|
||||||
.title(title.toComponent()).create();
|
.title(title.toComponent()).create();
|
||||||
gui.setCloseGuiAction((close) -> onClose.accept(BukkitData.Items.ItemArray.adapt(
|
gui.setCloseGuiAction((close) -> onClose.accept(BukkitData.Items.ItemArray.adapt(
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public final class BukkitKeyedAdapter {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static PotionEffectType matchEffectType(@NotNull String key) {
|
public static PotionEffectType matchEffectType(@NotNull String key) {
|
||||||
return PotionEffectType.getByName(key); // No registry for this in 1.17 API
|
return getRegistryValue(Registry.EFFECT, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T extends Keyed> T getRegistryValue(@NotNull Registry<T> registry, @NotNull String keyString) {
|
private static <T extends Keyed> T getRegistryValue(@NotNull Registry<T> registry, @NotNull String keyString) {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import net.william278.mapdataapi.MapData;
|
|||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.block.ShulkerBox;
|
import org.bukkit.block.Container;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.inventory.meta.BlockStateMeta;
|
import org.bukkit.inventory.meta.BlockStateMeta;
|
||||||
@@ -96,7 +96,7 @@ public interface BukkitMapPersister {
|
|||||||
}
|
}
|
||||||
if (item.getType() == Material.FILLED_MAP && item.hasItemMeta()) {
|
if (item.getType() == Material.FILLED_MAP && item.hasItemMeta()) {
|
||||||
items[i] = function.apply(item);
|
items[i] = function.apply(item);
|
||||||
} else if (item.getItemMeta() instanceof BlockStateMeta b && b.getBlockState() instanceof ShulkerBox box) {
|
} else if (item.getItemMeta() instanceof BlockStateMeta b && b.getBlockState() instanceof Container box) {
|
||||||
forEachMap(box.getInventory().getContents(), function);
|
forEachMap(box.getInventory().getContents(), function);
|
||||||
b.setBlockState(box);
|
b.setBlockState(box);
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,8 @@ public interface BukkitMapPersister {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render the map
|
// Render the map
|
||||||
final PersistentMapCanvas canvas = new PersistentMapCanvas(view);
|
final int dataVersion = getPlugin().getDataVersion(getPlugin().getMinecraftVersion());
|
||||||
|
final PersistentMapCanvas canvas = new PersistentMapCanvas(view, dataVersion);
|
||||||
for (MapRenderer renderer : view.getRenderers()) {
|
for (MapRenderer renderer : view.getRenderers()) {
|
||||||
renderer.render(view, canvas, delegateRenderer);
|
renderer.render(view, canvas, delegateRenderer);
|
||||||
getPlugin().debug(String.format("Rendered locked map canvas to view (#%s)", view.getId()));
|
getPlugin().debug(String.format("Rendered locked map canvas to view (#%s)", view.getId()));
|
||||||
@@ -140,6 +141,7 @@ public interface BukkitMapPersister {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private ItemStack applyMapView(@NotNull ItemStack map) {
|
private ItemStack applyMapView(@NotNull ItemStack map) {
|
||||||
|
final int dataVersion = getPlugin().getDataVersion(getPlugin().getMinecraftVersion());
|
||||||
final MapMeta meta = Objects.requireNonNull((MapMeta) map.getItemMeta());
|
final MapMeta meta = Objects.requireNonNull((MapMeta) map.getItemMeta());
|
||||||
NBT.get(map, nbt -> {
|
NBT.get(map, nbt -> {
|
||||||
if (!nbt.hasTag(MAP_DATA_KEY)) {
|
if (!nbt.hasTag(MAP_DATA_KEY)) {
|
||||||
@@ -178,8 +180,9 @@ public interface BukkitMapPersister {
|
|||||||
final MapData canvasData;
|
final MapData canvasData;
|
||||||
try {
|
try {
|
||||||
getPlugin().debug("Deserializing map data from NBT and generating view...");
|
getPlugin().debug("Deserializing map data from NBT and generating view...");
|
||||||
canvasData = MapData.fromByteArray(Objects.requireNonNull(mapData.getByteArray(MAP_PIXEL_DATA_KEY),
|
canvasData = MapData.fromByteArray(
|
||||||
"Map pixel data is null"));
|
dataVersion,
|
||||||
|
Objects.requireNonNull(mapData.getByteArray(MAP_PIXEL_DATA_KEY), "Pixel data null!"));
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
getPlugin().log(Level.WARNING, "Failed to deserialize map data from NBT", e);
|
getPlugin().log(Level.WARNING, "Failed to deserialize map data from NBT", e);
|
||||||
return;
|
return;
|
||||||
@@ -276,7 +279,7 @@ public interface BukkitMapPersister {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private static World getDefaultMapWorld() {
|
private static World getDefaultMapWorld() {
|
||||||
final World world = Bukkit.getWorlds().get(0);
|
final World world = Bukkit.getWorlds().getFirst();
|
||||||
if (world == null) {
|
if (world == null) {
|
||||||
throw new IllegalStateException("No worlds are loaded on the server!");
|
throw new IllegalStateException("No worlds are loaded on the server!");
|
||||||
}
|
}
|
||||||
@@ -294,6 +297,7 @@ public interface BukkitMapPersister {
|
|||||||
/**
|
/**
|
||||||
* A {@link MapRenderer} that can be used to render persistently serialized {@link MapData} to a {@link MapView}
|
* A {@link MapRenderer} that can be used to render persistently serialized {@link MapData} to a {@link MapView}
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
class PersistentMapRenderer extends MapRenderer {
|
class PersistentMapRenderer extends MapRenderer {
|
||||||
|
|
||||||
private final MapData canvasData;
|
private final MapData canvasData;
|
||||||
@@ -355,13 +359,16 @@ public interface BukkitMapPersister {
|
|||||||
/**
|
/**
|
||||||
* A {@link MapCanvas} implementation used for pre-rendering maps to be converted into {@link MapData}
|
* A {@link MapCanvas} implementation used for pre-rendering maps to be converted into {@link MapData}
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
class PersistentMapCanvas implements MapCanvas {
|
class PersistentMapCanvas implements MapCanvas {
|
||||||
|
|
||||||
|
private final int mapDataVersion;
|
||||||
private final MapView mapView;
|
private final MapView mapView;
|
||||||
private final int[][] pixels = new int[128][128];
|
private final int[][] pixels = new int[128][128];
|
||||||
private MapCursorCollection cursors;
|
private MapCursorCollection cursors;
|
||||||
|
|
||||||
private PersistentMapCanvas(@NotNull MapView mapView) {
|
private PersistentMapCanvas(@NotNull MapView mapView, int mapDataVersion) {
|
||||||
|
this.mapDataVersion = mapDataVersion;
|
||||||
this.mapView = mapView;
|
this.mapView = mapView;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,18 +390,38 @@ public interface BukkitMapPersister {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated
|
||||||
public void setPixel(int x, int y, byte color) {
|
public void setPixel(int x, int y, byte color) {
|
||||||
pixels[x][y] = color;
|
pixels[x][y] = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated
|
||||||
public byte getPixel(int x, int y) {
|
public byte getPixel(int x, int y) {
|
||||||
return (byte) pixels[x][y];
|
return (byte) pixels[x][y];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated
|
||||||
public byte getBasePixel(int x, int y) {
|
public byte getBasePixel(int x, int y) {
|
||||||
return getPixel(x, y);
|
return (byte) pixels[x][y];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPixelColor(int x, int y, @Nullable Color color) {
|
||||||
|
pixels[x][y] = color == null ? -1 : MapPalette.matchColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Color getPixelColor(int x, int y) {
|
||||||
|
return MapPalette.getColor((byte) pixels[x][y]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Color getBasePixelColor(int x, int y) {
|
||||||
|
return MapPalette.getColor((byte) pixels[x][y]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -424,25 +451,22 @@ public interface BukkitMapPersister {
|
|||||||
@NotNull
|
@NotNull
|
||||||
private MapData extractMapData() {
|
private MapData extractMapData() {
|
||||||
final List<MapBanner> banners = Lists.newArrayList();
|
final List<MapBanner> banners = Lists.newArrayList();
|
||||||
try {
|
final String BANNER_PREFIX = "banner_";
|
||||||
final String BANNER_PREFIX = "banner_";
|
for (int i = 0; i < getCursors().size(); i++) {
|
||||||
for (int i = 0; i < getCursors().size(); i++) {
|
final MapCursor cursor = getCursors().getCursor(i);
|
||||||
final MapCursor cursor = getCursors().getCursor(i);
|
final String type = cursor.getType().getKey().getKey();
|
||||||
final String type = cursor.getType().name().toLowerCase(Locale.ENGLISH);
|
if (type.startsWith(BANNER_PREFIX)) {
|
||||||
if (type.startsWith(BANNER_PREFIX)) {
|
banners.add(new MapBanner(
|
||||||
banners.add(new MapBanner(
|
type.replaceAll(BANNER_PREFIX, ""),
|
||||||
type.replaceAll(BANNER_PREFIX, ""),
|
cursor.getCaption() == null ? "" : cursor.getCaption(),
|
||||||
cursor.getCaption() == null ? "" : cursor.getCaption(),
|
cursor.getX(),
|
||||||
cursor.getX(),
|
mapView.getWorld() != null ? mapView.getWorld().getSeaLevel() : 128,
|
||||||
mapView.getWorld() != null ? mapView.getWorld().getSeaLevel() : 128,
|
cursor.getY()
|
||||||
cursor.getY()
|
));
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
}
|
||||||
return MapData.fromPixels(pixels, getDimension(), (byte) 2, banners, List.of());
|
return MapData.fromPixels(mapDataVersion, pixels, getDimension(), (byte) 2, banners, List.of());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,26 +3,26 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api 'commons-io:commons-io:2.16.1'
|
api 'commons-io:commons-io:2.18.0'
|
||||||
api 'org.apache.commons:commons-text:1.12.0'
|
api 'org.apache.commons:commons-text:1.13.0'
|
||||||
api 'net.william278:minedown:1.8.2'
|
api 'net.william278:minedown:1.8.2'
|
||||||
api 'org.json:json:20240303'
|
api 'org.json:json:20250107'
|
||||||
api 'com.google.code.gson:gson:2.11.0'
|
api 'com.google.code.gson:gson:2.11.0'
|
||||||
api 'com.fatboyindustrial.gson-javatime-serialisers:gson-javatime-serialisers:1.1.2'
|
api 'com.fatboyindustrial.gson-javatime-serialisers:gson-javatime-serialisers:1.1.2'
|
||||||
api 'de.exlll:configlib-yaml:4.5.0'
|
api 'de.exlll:configlib-yaml:4.5.0'
|
||||||
api 'net.william278:paginedown:1.1.2'
|
api 'net.william278:paginedown:1.1.2'
|
||||||
api 'net.william278:DesertWell:2.0.4'
|
api 'net.william278:DesertWell:2.0.4'
|
||||||
api('com.zaxxer:HikariCP:5.1.0') {
|
api('com.zaxxer:HikariCP:6.2.1') {
|
||||||
exclude module: 'slf4j-api'
|
exclude module: 'slf4j-api'
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOnly 'net.william278.uniform:uniform-common:1.2.1'
|
compileOnly 'net.william278.uniform:uniform-common:1.3'
|
||||||
compileOnly 'com.mojang:brigadier:1.1.8'
|
compileOnly 'com.mojang:brigadier:1.1.8'
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.34'
|
compileOnly 'org.projectlombok:lombok:1.18.36'
|
||||||
compileOnly 'org.jetbrains:annotations:24.1.0'
|
compileOnly 'org.jetbrains:annotations:26.0.1'
|
||||||
compileOnly 'net.kyori:adventure-api:4.17.0'
|
compileOnly 'net.kyori:adventure-api:4.18.0'
|
||||||
compileOnly 'net.kyori:adventure-platform-api:4.3.3'
|
compileOnly 'net.kyori:adventure-platform-api:4.3.4'
|
||||||
compileOnly 'com.google.guava:guava:33.2.1-jre'
|
compileOnly 'com.google.guava:guava:33.4.0-jre'
|
||||||
compileOnly 'com.github.plan-player-analytics:Plan:5.5.2272'
|
compileOnly 'com.github.plan-player-analytics:Plan:5.5.2272'
|
||||||
compileOnly "redis.clients:jedis:$jedis_version"
|
compileOnly "redis.clients:jedis:$jedis_version"
|
||||||
compileOnly "com.mysql:mysql-connector-j:$mysql_driver_version"
|
compileOnly "com.mysql:mysql-connector-j:$mysql_driver_version"
|
||||||
@@ -33,10 +33,10 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation "redis.clients:jedis:$jedis_version"
|
testImplementation "redis.clients:jedis:$jedis_version"
|
||||||
testImplementation "org.xerial.snappy:snappy-java:$snappy_version"
|
testImplementation "org.xerial.snappy:snappy-java:$snappy_version"
|
||||||
testImplementation 'com.google.guava:guava:33.2.1-jre'
|
testImplementation 'com.google.guava:guava:33.4.0-jre'
|
||||||
testImplementation 'com.github.plan-player-analytics:Plan:5.5.2272'
|
testImplementation 'com.github.plan-player-analytics:Plan:5.5.2272'
|
||||||
testCompileOnly 'de.exlll:configlib-yaml:4.5.0'
|
testCompileOnly 'de.exlll:configlib-yaml:4.5.0'
|
||||||
testCompileOnly 'org.jetbrains:annotations:24.1.0'
|
testCompileOnly 'org.jetbrains:annotations:26.0.1'
|
||||||
|
|
||||||
annotationProcessor 'org.projectlombok:lombok:1.18.34'
|
annotationProcessor 'org.projectlombok:lombok:1.18.36'
|
||||||
}
|
}
|
||||||
@@ -39,6 +39,7 @@ import net.william278.husksync.redis.RedisManager;
|
|||||||
import net.william278.husksync.sync.DataSyncer;
|
import net.william278.husksync.sync.DataSyncer;
|
||||||
import net.william278.husksync.user.ConsoleUser;
|
import net.william278.husksync.user.ConsoleUser;
|
||||||
import net.william278.husksync.user.OnlineUser;
|
import net.william278.husksync.user.OnlineUser;
|
||||||
|
import net.william278.husksync.util.CompatibilityChecker;
|
||||||
import net.william278.husksync.util.LegacyConverter;
|
import net.william278.husksync.util.LegacyConverter;
|
||||||
import net.william278.husksync.util.Task;
|
import net.william278.husksync.util.Task;
|
||||||
import net.william278.uniform.Uniform;
|
import net.william278.uniform.Uniform;
|
||||||
@@ -52,7 +53,8 @@ import java.util.logging.Level;
|
|||||||
/**
|
/**
|
||||||
* Abstract implementation of the HuskSync plugin.
|
* Abstract implementation of the HuskSync plugin.
|
||||||
*/
|
*/
|
||||||
public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider, SerializerRegistry {
|
public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider, SerializerRegistry,
|
||||||
|
CompatibilityChecker {
|
||||||
|
|
||||||
int SPIGOT_RESOURCE_ID = 97144;
|
int SPIGOT_RESOURCE_ID = 97144;
|
||||||
|
|
||||||
@@ -247,6 +249,14 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider
|
|||||||
@NotNull
|
@NotNull
|
||||||
Version getMinecraftVersion();
|
Version getMinecraftVersion();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data version for a Minecraft version
|
||||||
|
*
|
||||||
|
* @param minecraftVersion the Minecraft version
|
||||||
|
* @return the data version int
|
||||||
|
*/
|
||||||
|
int getDataVersion(@NotNull Version minecraftVersion);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the platform type
|
* Returns the platform type
|
||||||
*
|
*
|
||||||
@@ -338,7 +348,11 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider
|
|||||||
|
|
||||||
Caused by: %s""";
|
Caused by: %s""";
|
||||||
|
|
||||||
FailedToLoadException(@NotNull String message, @NotNull Throwable cause) {
|
public FailedToLoadException(@NotNull String message) {
|
||||||
|
super(String.format(FORMAT, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public FailedToLoadException(@NotNull String message, @NotNull Throwable cause) {
|
||||||
super(String.format(FORMAT, message), cause);
|
super(String.format(FORMAT, message), cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,6 +99,18 @@ public class HuskSyncAPI {
|
|||||||
return plugin.supplyAsync(() -> plugin.getDatabase().getUser(uuid));
|
return plugin.supplyAsync(() -> plugin.getDatabase().getUser(uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an {@link OnlineUser} by their UUID
|
||||||
|
*
|
||||||
|
* @param uuid the UUID of the user to get
|
||||||
|
* @return The {@link OnlineUser} wrapped in an optional, if they are online on <i>this</i> server.
|
||||||
|
* @since 3.7.2
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public Optional<OnlineUser> getOnlineUser(@NotNull UUID uuid) {
|
||||||
|
return plugin.getOnlineUser(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a {@link User} by their username
|
* Get a {@link User} by their username
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -30,9 +30,11 @@ import net.kyori.adventure.text.format.TextColor;
|
|||||||
import net.william278.desertwell.about.AboutMenu;
|
import net.william278.desertwell.about.AboutMenu;
|
||||||
import net.william278.desertwell.util.UpdateChecker;
|
import net.william278.desertwell.util.UpdateChecker;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.data.DataSnapshot;
|
||||||
import net.william278.husksync.database.Database;
|
import net.william278.husksync.database.Database;
|
||||||
import net.william278.husksync.migrator.Migrator;
|
import net.william278.husksync.migrator.Migrator;
|
||||||
import net.william278.husksync.user.CommandUser;
|
import net.william278.husksync.user.CommandUser;
|
||||||
|
import net.william278.husksync.util.LegacyConverter;
|
||||||
import net.william278.uniform.BaseCommand;
|
import net.william278.uniform.BaseCommand;
|
||||||
import net.william278.uniform.CommandProvider;
|
import net.william278.uniform.CommandProvider;
|
||||||
import net.william278.uniform.Permission;
|
import net.william278.uniform.Permission;
|
||||||
@@ -40,8 +42,10 @@ import net.william278.uniform.element.ArgumentElement;
|
|||||||
import org.apache.commons.text.WordUtils;
|
import org.apache.commons.text.WordUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -96,6 +100,7 @@ public class HuskSyncCommand extends PluginCommand {
|
|||||||
command.addSubCommand("status", needsOp("status"), status());
|
command.addSubCommand("status", needsOp("status"), status());
|
||||||
command.addSubCommand("reload", needsOp("reload"), reload());
|
command.addSubCommand("reload", needsOp("reload"), reload());
|
||||||
command.addSubCommand("update", needsOp("update"), update());
|
command.addSubCommand("update", needsOp("update"), update());
|
||||||
|
command.addSubCommand("forceupgrade", forceUpgrade());
|
||||||
command.addSubCommand("migrate", migrate());
|
command.addSubCommand("migrate", migrate());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,6 +187,35 @@ public class HuskSyncCommand extends PluginCommand {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private CommandProvider forceUpgrade() {
|
||||||
|
return (sub) -> {
|
||||||
|
sub.setCondition((ctx) -> sub.getUser(ctx).isConsole());
|
||||||
|
sub.setDefaultExecutor((ctx) -> {
|
||||||
|
final LegacyConverter converter = plugin.getLegacyConverter().orElse(null);
|
||||||
|
if (converter == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.runAsync(() -> {
|
||||||
|
final Database database = plugin.getDatabase();
|
||||||
|
plugin.log(Level.INFO, "Beginning forced legacy data upgrade for all users...");
|
||||||
|
database.getAllUsers().forEach(user -> database.getLatestSnapshot(user).ifPresent(snapshot -> {
|
||||||
|
final DataSnapshot.Packed upgraded = converter.convert(
|
||||||
|
snapshot.asBytes(plugin),
|
||||||
|
UUID.randomUUID(),
|
||||||
|
OffsetDateTime.now()
|
||||||
|
);
|
||||||
|
upgraded.setSaveCause(DataSnapshot.SaveCause.CONVERTED_FROM_V2);
|
||||||
|
plugin.getDatabase().addSnapshot(user, upgraded);
|
||||||
|
plugin.getRedisManager().clearUserData(user);
|
||||||
|
}));
|
||||||
|
plugin.log(Level.INFO, "Legacy data upgrade complete!");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private <S> ArgumentElement<S, Migrator> migrator() {
|
private <S> ArgumentElement<S, Migrator> migrator() {
|
||||||
return new ArgumentElement<>("migrator", reader -> {
|
return new ArgumentElement<>("migrator", reader -> {
|
||||||
|
|||||||
@@ -131,6 +131,15 @@ public interface ConfigProvider {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default void validateConfigFiles() {
|
||||||
|
// Validate server name is default
|
||||||
|
if (getServerName().equals("server")) {
|
||||||
|
getPlugin().log(Level.WARNING, "The server name set in ~/plugins/HuskSync/server.yml appears to" +
|
||||||
|
"be unchanged from the default (currently set to: \"server\"). Please check that this value has" +
|
||||||
|
"been updated to match the case-sensitive ID of this server in your proxy config file!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a plugin resource
|
* Get a plugin resource
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -141,6 +141,9 @@ public class Settings {
|
|||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
private Map<String, String> tableNames = Database.TableName.getDefaults();
|
private Map<String, String> tableNames = Database.TableName.getDefaults();
|
||||||
|
|
||||||
|
@Comment("Whether to run the creation SQL on the database when the server starts. Don't modify this unless you know what you're doing!")
|
||||||
|
private boolean createTables = true;
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public String getTableName(@NotNull Database.TableName tableName) {
|
public String getTableName(@NotNull Database.TableName tableName) {
|
||||||
return tableNames.getOrDefault(tableName.name().toLowerCase(Locale.ENGLISH), tableName.getDefaultName());
|
return tableNames.getOrDefault(tableName.name().toLowerCase(Locale.ENGLISH), tableName.getDefaultName());
|
||||||
@@ -156,7 +159,7 @@ public class Settings {
|
|||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public static class RedisSettings {
|
public static class RedisSettings {
|
||||||
|
|
||||||
@Comment("Specify the credentials of your Redis database here. Set \"password\" to '' if you don't have one")
|
@Comment("Specify the credentials of your Redis server here. Set \"password\" to '' if you don't have one")
|
||||||
private RedisCredentials credentials = new RedisCredentials();
|
private RedisCredentials credentials = new RedisCredentials();
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@@ -275,12 +278,19 @@ public class Settings {
|
|||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public static class AttributeSettings {
|
public static class AttributeSettings {
|
||||||
|
|
||||||
@Comment({"Which attributes should not be saved when syncing users. Supports wildcard matching.",
|
@Comment({"Which attribute types should be saved as part of attribute syncing. Supports wildcard matching.",
|
||||||
"(e.g. ['minecraft:generic.max_health', 'minecraft:generic.*'])"})
|
"(e.g. ['minecraft:generic.max_health', 'minecraft:generic.*'])"})
|
||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
private List<String> ignoredAttributes = new ArrayList<>(List.of(""));
|
private List<String> syncedAttributes = new ArrayList<>(List.of(
|
||||||
|
"minecraft:generic.max_health", "minecraft:max_health",
|
||||||
|
"minecraft:generic.max_absorption", "minecraft:max_absorption",
|
||||||
|
"minecraft:generic.luck", "minecraft:luck",
|
||||||
|
"minecraft:generic.scale", "minecraft:scale",
|
||||||
|
"minecraft:generic.step_height", "minecraft:step_height",
|
||||||
|
"minecraft:generic.gravity", "minecraft:gravity"
|
||||||
|
));
|
||||||
|
|
||||||
@Comment({"Which modifiers should not be saved when syncing users. Supports wildcard matching.",
|
@Comment({"Which attribute modifiers should be saved. Supports wildcard matching.",
|
||||||
"(e.g. ['minecraft:effect.speed', 'minecraft:effect.*'])"})
|
"(e.g. ['minecraft:effect.speed', 'minecraft:effect.*'])"})
|
||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
private List<String> ignoredModifiers = new ArrayList<>(List.of(
|
private List<String> ignoredModifiers = new ArrayList<>(List.of(
|
||||||
@@ -298,7 +308,7 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isIgnoredAttribute(@NotNull String attribute) {
|
public boolean isIgnoredAttribute(@NotNull String attribute) {
|
||||||
return ignoredAttributes.stream().anyMatch(wildcard -> matchesWildcard(wildcard, attribute));
|
return syncedAttributes.stream().noneMatch(wildcard -> matchesWildcard(wildcard, attribute));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isIgnoredModifier(@NotNull String modifier) {
|
public boolean isIgnoredModifier(@NotNull String modifier) {
|
||||||
|
|||||||
@@ -22,9 +22,8 @@ package net.william278.husksync.data;
|
|||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import net.kyori.adventure.key.Key;
|
import net.kyori.adventure.key.Key;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
@@ -132,7 +131,7 @@ public interface Data {
|
|||||||
/**
|
/**
|
||||||
* Represents a potion effect
|
* Represents a potion effect
|
||||||
*
|
*
|
||||||
* @param type the type of potion effect
|
* @param type the key of potion effect
|
||||||
* @param amplifier the amplifier of the potion effect
|
* @param amplifier the amplifier of the potion effect
|
||||||
* @param duration the duration of the potion effect
|
* @param duration the duration of the potion effect
|
||||||
* @param isAmbient whether the potion effect is ambient
|
* @param isAmbient whether the potion effect is ambient
|
||||||
@@ -341,42 +340,60 @@ public interface Data {
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Accessors(fluent = true)
|
@Accessors(fluent = true)
|
||||||
@AllArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@NoArgsConstructor
|
|
||||||
final class Modifier {
|
final class Modifier {
|
||||||
|
final static String ANY_EQUIPMENT_SLOT_GROUP = "any";
|
||||||
|
|
||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
@Nullable
|
@Nullable
|
||||||
@SerializedName("uuid")
|
@SerializedName("uuid")
|
||||||
private UUID uuid;
|
private UUID uuid = null;
|
||||||
|
|
||||||
|
// Since 1.21.1: Name, amount, operation, slotGroup
|
||||||
@SerializedName("name")
|
@SerializedName("name")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@SerializedName("amount")
|
@SerializedName("amount")
|
||||||
private double amount;
|
private double amount;
|
||||||
|
|
||||||
@SerializedName("operation")
|
@SerializedName("operation")
|
||||||
private int operationType;
|
private int operation;
|
||||||
|
|
||||||
@SerializedName("equipment_slot")
|
@SerializedName("equipment_slot")
|
||||||
|
@Deprecated(since = "3.7")
|
||||||
private int equipmentSlot;
|
private int equipmentSlot;
|
||||||
|
|
||||||
public Modifier(@NotNull String name, double amount, int operationType, int equipmentSlot) {
|
@SerializedName("equipment_slot_group")
|
||||||
|
private String slotGroup = ANY_EQUIPMENT_SLOT_GROUP;
|
||||||
|
|
||||||
|
public Modifier(@NotNull String name, double amount, int operation, @NotNull String slotGroup) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
this.operationType = operationType;
|
this.operation = operation;
|
||||||
|
this.slotGroup = slotGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated(since = "3.7")
|
||||||
|
public Modifier(@NotNull UUID uuid, @NotNull String name, double amount, int operation, int equipmentSlot) {
|
||||||
|
this.name = name;
|
||||||
|
this.amount = amount;
|
||||||
|
this.operation = operation;
|
||||||
this.equipmentSlot = equipmentSlot;
|
this.equipmentSlot = equipmentSlot;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj instanceof Modifier other) {
|
if (obj instanceof Modifier other) {
|
||||||
if (uuid == null || other.uuid == null) {
|
if (uuid != null && other.uuid != null) {
|
||||||
return name.equals(other.name);
|
return uuid.equals(other.uuid);
|
||||||
}
|
}
|
||||||
return uuid.equals(other.uuid);
|
return name.equals(other.name);
|
||||||
}
|
}
|
||||||
return super.equals(obj);
|
return super.equals(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public double modify(double value) {
|
public double modify(double value) {
|
||||||
return switch (operationType) {
|
return switch (operation) {
|
||||||
case 0 -> value + amount;
|
case 0 -> value + amount;
|
||||||
case 1 -> value * amount;
|
case 1 -> value * amount;
|
||||||
case 2 -> value * (1 + amount);
|
case 2 -> value * (1 + amount);
|
||||||
|
|||||||
@@ -913,6 +913,8 @@ public class DataSnapshot {
|
|||||||
|
|
||||||
private final boolean fireDataSaveEvent;
|
private final boolean fireDataSaveEvent;
|
||||||
|
|
||||||
|
private static Map<String, SaveCause> registry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or create a {@link SaveCause} from a name
|
* Get or create a {@link SaveCause} from a name
|
||||||
*
|
*
|
||||||
@@ -921,7 +923,7 @@ public class DataSnapshot {
|
|||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
public static SaveCause of(@NotNull String name) {
|
public static SaveCause of(@NotNull String name) {
|
||||||
return new SaveCause(name.length() > 32 ? name.substring(0, 31) : name, true);
|
return of(name,true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -933,7 +935,14 @@ public class DataSnapshot {
|
|||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
public static SaveCause of(@NotNull String name, boolean firesSaveEvent) {
|
public static SaveCause of(@NotNull String name, boolean firesSaveEvent) {
|
||||||
return new SaveCause(name.length() > 32 ? name.substring(0, 31) : name, firesSaveEvent);
|
name = name.length() > 32 ? name.substring(0, 31) : name;
|
||||||
|
|
||||||
|
if (registry == null) registry = new HashMap<>();
|
||||||
|
if (registry.containsKey(name)) return registry.get(name);
|
||||||
|
|
||||||
|
SaveCause cause = new SaveCause(name, firesSaveEvent);
|
||||||
|
registry.put(cause.name(), cause);
|
||||||
|
return cause;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -944,11 +953,10 @@ public class DataSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@ApiStatus.Obsolete
|
||||||
public static SaveCause[] values() {
|
public static SaveCause[] values() {
|
||||||
return new SaveCause[]{
|
if (registry == null) registry = new HashMap<>();
|
||||||
DISCONNECT, WORLD_SAVE, DEATH, SERVER_SHUTDOWN, INVENTORY_COMMAND, ENDERCHEST_COMMAND,
|
return registry.values().toArray(new SaveCause[0]);
|
||||||
BACKUP_RESTORE, API, MPDB_MIGRATION, LEGACY_MIGRATION, CONVERTED_FROM_V2
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import net.kyori.adventure.key.Key;
|
|||||||
import org.intellij.lang.annotations.Subst;
|
import org.intellij.lang.annotations.Subst;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
@@ -229,11 +230,8 @@ public class Identifier {
|
|||||||
* @return {@code true} if the given object is an identifier with the same key as this identifier
|
* @return {@code true} if the given object is an identifier with the same key as this identifier
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(@Nullable Object obj) {
|
||||||
if (obj instanceof Identifier other) {
|
return obj instanceof Identifier other ? toString().equals(other.toString()) : super.equals(obj);
|
||||||
return key.equals(other.key);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the config entry for the identifier
|
// Get the config entry for the identifier
|
||||||
|
|||||||
@@ -107,6 +107,14 @@ public abstract class Database {
|
|||||||
@Blocking
|
@Blocking
|
||||||
public abstract Optional<User> getUserByName(@NotNull String username);
|
public abstract Optional<User> getUserByName(@NotNull String username);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all users
|
||||||
|
*
|
||||||
|
* @return A list of all users
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
@Blocking
|
||||||
|
public abstract List<User> getAllUsers();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the latest data snapshot for a user.
|
* Get the latest data snapshot for a user.
|
||||||
|
|||||||
@@ -57,11 +57,6 @@ public class MongoDbDatabase extends Database {
|
|||||||
this.userDataTable = plugin.getSettings().getDatabase().getTableName(TableName.USER_DATA);
|
this.userDataTable = plugin.getSettings().getDatabase().getTableName(TableName.USER_DATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the database and ensure tables are present; create tables if they do not exist.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException if the database could not be initialized
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() throws IllegalStateException {
|
public void initialize() throws IllegalStateException {
|
||||||
final Settings.DatabaseSettings.DatabaseCredentials credentials = plugin.getSettings().getDatabase().getCredentials();
|
final Settings.DatabaseSettings.DatabaseCredentials credentials = plugin.getSettings().getDatabase().getCredentials();
|
||||||
@@ -69,6 +64,10 @@ public class MongoDbDatabase extends Database {
|
|||||||
ConnectionString URI = createConnectionURI(credentials);
|
ConnectionString URI = createConnectionURI(credentials);
|
||||||
mongoConnectionHandler = new MongoConnectionHandler(URI, credentials.getDatabase());
|
mongoConnectionHandler = new MongoConnectionHandler(URI, credentials.getDatabase());
|
||||||
mongoCollectionHelper = new MongoCollectionHelper(mongoConnectionHandler);
|
mongoCollectionHelper = new MongoCollectionHelper(mongoConnectionHandler);
|
||||||
|
|
||||||
|
// Check config for if tables should be created
|
||||||
|
if (!plugin.getSettings().getDatabase().isCreateTables()) return;
|
||||||
|
|
||||||
if (mongoCollectionHelper.getCollection(usersTable) == null) {
|
if (mongoCollectionHelper.getCollection(usersTable) == null) {
|
||||||
mongoCollectionHelper.createCollection(usersTable);
|
mongoCollectionHelper.createCollection(usersTable);
|
||||||
}
|
}
|
||||||
@@ -94,11 +93,6 @@ public class MongoDbDatabase extends Database {
|
|||||||
return new ConnectionString(baseURI);
|
return new ConnectionString(baseURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure a {@link User} has an entry in the database and that their username is up-to-date
|
|
||||||
*
|
|
||||||
* @param user The {@link User} to ensure
|
|
||||||
*/
|
|
||||||
@Blocking
|
@Blocking
|
||||||
@Override
|
@Override
|
||||||
public void ensureUser(@NotNull User user) {
|
public void ensureUser(@NotNull User user) {
|
||||||
@@ -136,12 +130,6 @@ public class MongoDbDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a player by their Minecraft account {@link UUID}
|
|
||||||
*
|
|
||||||
* @param uuid Minecraft account {@link UUID} of the {@link User} to get
|
|
||||||
* @return An optional with the {@link User} present if they exist
|
|
||||||
*/
|
|
||||||
@Blocking
|
@Blocking
|
||||||
@Override
|
@Override
|
||||||
public Optional<User> getUser(@NotNull UUID uuid) {
|
public Optional<User> getUser(@NotNull UUID uuid) {
|
||||||
@@ -158,12 +146,6 @@ public class MongoDbDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a user by their username (<i>case-insensitive</i>)
|
|
||||||
*
|
|
||||||
* @param username Username of the {@link User} to get (<i>case-insensitive</i>)
|
|
||||||
* @return An optional with the {@link User} present if they exist
|
|
||||||
*/
|
|
||||||
@Blocking
|
@Blocking
|
||||||
@Override
|
@Override
|
||||||
public Optional<User> getUserByName(@NotNull String username) {
|
public Optional<User> getUserByName(@NotNull String username) {
|
||||||
@@ -181,12 +163,24 @@ public class MongoDbDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Get the latest data snapshot for a user.
|
@NotNull
|
||||||
*
|
public List<User> getAllUsers() {
|
||||||
* @param user The user to get data for
|
final List<User> users = Lists.newArrayList();
|
||||||
* @return an optional containing the {@link DataSnapshot}, if it exists, or an empty optional if it does not
|
try {
|
||||||
*/
|
final FindIterable<Document> doc = mongoCollectionHelper.getCollection(usersTable).find();
|
||||||
|
for (Document document : doc) {
|
||||||
|
users.add(new User(
|
||||||
|
UUID.fromString(document.getString("uuid")),
|
||||||
|
document.getString("username")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} catch (MongoException e) {
|
||||||
|
plugin.log(Level.SEVERE, "Failed to get all users from the database", e);
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
@Blocking
|
@Blocking
|
||||||
@Override
|
@Override
|
||||||
public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) {
|
public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) {
|
||||||
@@ -209,12 +203,6 @@ public class MongoDbDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all {@link DataSnapshot} entries for a user from the database.
|
|
||||||
*
|
|
||||||
* @param user The user to get data for
|
|
||||||
* @return The list of a user's {@link DataSnapshot} entries
|
|
||||||
*/
|
|
||||||
@Blocking
|
@Blocking
|
||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -238,13 +226,6 @@ public class MongoDbDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a specific {@link DataSnapshot} entry for a user from the database, by its UUID.
|
|
||||||
*
|
|
||||||
* @param user The user to get data for
|
|
||||||
* @param versionUuid The UUID of the {@link DataSnapshot} entry to get
|
|
||||||
* @return An optional containing the {@link DataSnapshot}, if it exists
|
|
||||||
*/
|
|
||||||
@Blocking
|
@Blocking
|
||||||
@Override
|
@Override
|
||||||
public Optional<DataSnapshot.Packed> getSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
|
public Optional<DataSnapshot.Packed> getSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
|
||||||
@@ -266,12 +247,6 @@ public class MongoDbDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* <b>(Internal)</b> Prune user data for a given user to the maximum value as configured.
|
|
||||||
*
|
|
||||||
* @param user The user to prune data for
|
|
||||||
* @implNote Data snapshots marked as {@code pinned} are exempt from rotation
|
|
||||||
*/
|
|
||||||
@Blocking
|
@Blocking
|
||||||
@Override
|
@Override
|
||||||
protected void rotateSnapshots(@NotNull User user) {
|
protected void rotateSnapshots(@NotNull User user) {
|
||||||
@@ -297,12 +272,6 @@ public class MongoDbDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a specific {@link DataSnapshot} entry for a user from the database, by its UUID.
|
|
||||||
*
|
|
||||||
* @param user The user to get data for
|
|
||||||
* @param versionUuid The UUID of the {@link DataSnapshot} entry to delete
|
|
||||||
*/
|
|
||||||
@Blocking
|
@Blocking
|
||||||
@Override
|
@Override
|
||||||
public boolean deleteSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
|
public boolean deleteSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
|
||||||
@@ -320,14 +289,6 @@ public class MongoDbDatabase extends Database {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the most recent data snapshot by the given {@link User user}
|
|
||||||
* The snapshot must have been created after {@link OffsetDateTime time} and NOT be pinned
|
|
||||||
* Facilities the backup frequency feature, reducing redundant snapshots from being saved longer than needed
|
|
||||||
*
|
|
||||||
* @param user The user to delete a snapshot for
|
|
||||||
* @param within The time to delete a snapshot after
|
|
||||||
*/
|
|
||||||
@Blocking
|
@Blocking
|
||||||
@Override
|
@Override
|
||||||
protected void rotateLatestSnapshot(@NotNull User user, @NotNull OffsetDateTime within) {
|
protected void rotateLatestSnapshot(@NotNull User user, @NotNull OffsetDateTime within) {
|
||||||
@@ -352,12 +313,6 @@ public class MongoDbDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* <b>Internal</b> - Create user data in the database
|
|
||||||
*
|
|
||||||
* @param user The user to add data for
|
|
||||||
* @param data The {@link DataSnapshot} to set.
|
|
||||||
*/
|
|
||||||
@Blocking
|
@Blocking
|
||||||
@Override
|
@Override
|
||||||
protected void createSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
protected void createSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
||||||
@@ -374,12 +329,6 @@ public class MongoDbDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a saved {@link DataSnapshot} by given version UUID
|
|
||||||
*
|
|
||||||
* @param user The user whose data snapshot
|
|
||||||
* @param data The {@link DataSnapshot} to update
|
|
||||||
*/
|
|
||||||
@Blocking
|
@Blocking
|
||||||
@Override
|
@Override
|
||||||
public void updateSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
public void updateSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
||||||
@@ -396,10 +345,6 @@ public class MongoDbDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Wipes <b>all</b> {@link User} entries from the database.
|
|
||||||
* <b>This should only be used when preparing tables for a data migration.</b>
|
|
||||||
*/
|
|
||||||
@Blocking
|
@Blocking
|
||||||
@Override
|
@Override
|
||||||
public void wipeDatabase() {
|
public void wipeDatabase() {
|
||||||
@@ -410,9 +355,6 @@ public class MongoDbDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the database connection
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void terminate() {
|
public void terminate() {
|
||||||
if (mongoConnectionHandler != null) {
|
if (mongoConnectionHandler != null) {
|
||||||
|
|||||||
@@ -115,6 +115,9 @@ public class MySqlDatabase extends Database {
|
|||||||
);
|
);
|
||||||
dataSource.setDataSourceProperties(properties);
|
dataSource.setDataSourceProperties(properties);
|
||||||
|
|
||||||
|
// Check config for if tables should be created
|
||||||
|
if (!plugin.getSettings().getDatabase().isCreateTables()) return;
|
||||||
|
|
||||||
// Prepare database schema; make tables if they don't exist
|
// Prepare database schema; make tables if they don't exist
|
||||||
try (Connection connection = dataSource.getConnection()) {
|
try (Connection connection = dataSource.getConnection()) {
|
||||||
final String[] databaseSchema = getSchemaStatements(String.format("database/%s_schema.sql", flavor));
|
final String[] databaseSchema = getSchemaStatements(String.format("database/%s_schema.sql", flavor));
|
||||||
@@ -218,6 +221,27 @@ public class MySqlDatabase extends Database {
|
|||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public List<User> getAllUsers() {
|
||||||
|
final List<User> users = Lists.newArrayList();
|
||||||
|
try (Connection connection = getConnection()) {
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||||
|
SELECT `uuid`, `username`
|
||||||
|
FROM `%users_table%`;
|
||||||
|
"""))) {
|
||||||
|
final ResultSet resultSet = statement.executeQuery();
|
||||||
|
while (resultSet.next()) {
|
||||||
|
users.add(new User(UUID.fromString(resultSet.getString("uuid")),
|
||||||
|
resultSet.getString("username")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.log(Level.SEVERE, "Failed to fetch a user by name from the database", e);
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
@Blocking
|
@Blocking
|
||||||
@Override
|
@Override
|
||||||
public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) {
|
public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) {
|
||||||
|
|||||||
@@ -51,12 +51,6 @@ public class PostgresDatabase extends Database {
|
|||||||
this.driverClass = "org.postgresql.Driver";
|
this.driverClass = "org.postgresql.Driver";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch the auto-closeable connection from the hikariDataSource
|
|
||||||
*
|
|
||||||
* @return The {@link Connection} to the MySQL database
|
|
||||||
* @throws SQLException if the connection fails for some reason
|
|
||||||
*/
|
|
||||||
@Blocking
|
@Blocking
|
||||||
@NotNull
|
@NotNull
|
||||||
private Connection getConnection() throws SQLException {
|
private Connection getConnection() throws SQLException {
|
||||||
@@ -114,6 +108,9 @@ public class PostgresDatabase extends Database {
|
|||||||
);
|
);
|
||||||
dataSource.setDataSourceProperties(properties);
|
dataSource.setDataSourceProperties(properties);
|
||||||
|
|
||||||
|
// Check config for if tables should be created
|
||||||
|
if (!plugin.getSettings().getDatabase().isCreateTables()) return;
|
||||||
|
|
||||||
// Prepare database schema; make tables if they don't exist
|
// Prepare database schema; make tables if they don't exist
|
||||||
try (Connection connection = dataSource.getConnection()) {
|
try (Connection connection = dataSource.getConnection()) {
|
||||||
final String[] databaseSchema = getSchemaStatements(String.format("database/%s_schema.sql", flavor));
|
final String[] databaseSchema = getSchemaStatements(String.format("database/%s_schema.sql", flavor));
|
||||||
@@ -140,9 +137,9 @@ public class PostgresDatabase extends Database {
|
|||||||
// Update a user's name if it has changed in the database
|
// Update a user's name if it has changed in the database
|
||||||
try (Connection connection = getConnection()) {
|
try (Connection connection = getConnection()) {
|
||||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||||
UPDATE "%users_table%"
|
UPDATE %users_table%
|
||||||
SET "username"=?
|
SET username=?
|
||||||
WHERE "uuid"=?"""))) {
|
WHERE uuid=?;"""))) {
|
||||||
|
|
||||||
statement.setString(1, user.getUsername());
|
statement.setString(1, user.getUsername());
|
||||||
statement.setObject(2, existingUser.getUuid());
|
statement.setObject(2, existingUser.getUuid());
|
||||||
@@ -158,7 +155,7 @@ public class PostgresDatabase extends Database {
|
|||||||
// Insert new player data into the database
|
// Insert new player data into the database
|
||||||
try (Connection connection = getConnection()) {
|
try (Connection connection = getConnection()) {
|
||||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||||
INSERT INTO "%users_table%" ("uuid","username")
|
INSERT INTO %users_table% (uuid,username)
|
||||||
VALUES (?,?);"""))) {
|
VALUES (?,?);"""))) {
|
||||||
|
|
||||||
statement.setObject(1, user.getUuid());
|
statement.setObject(1, user.getUuid());
|
||||||
@@ -177,9 +174,9 @@ public class PostgresDatabase extends Database {
|
|||||||
public Optional<User> getUser(@NotNull UUID uuid) {
|
public Optional<User> getUser(@NotNull UUID uuid) {
|
||||||
try (Connection connection = getConnection()) {
|
try (Connection connection = getConnection()) {
|
||||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||||
SELECT "uuid", "username"
|
SELECT uuid, username
|
||||||
FROM "%users_table%"
|
FROM %users_table%
|
||||||
WHERE "uuid"=?"""))) {
|
WHERE uuid=?;"""))) {
|
||||||
|
|
||||||
statement.setObject(1, uuid);
|
statement.setObject(1, uuid);
|
||||||
|
|
||||||
@@ -200,9 +197,9 @@ public class PostgresDatabase extends Database {
|
|||||||
public Optional<User> getUserByName(@NotNull String username) {
|
public Optional<User> getUserByName(@NotNull String username) {
|
||||||
try (Connection connection = getConnection()) {
|
try (Connection connection = getConnection()) {
|
||||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||||
SELECT "uuid", "username"
|
SELECT uuid, username
|
||||||
FROM "%users_table%"
|
FROM %users_table%
|
||||||
WHERE "username"=?"""))) {
|
WHERE username=?;"""))) {
|
||||||
statement.setString(1, username);
|
statement.setString(1, username);
|
||||||
|
|
||||||
final ResultSet resultSet = statement.executeQuery();
|
final ResultSet resultSet = statement.executeQuery();
|
||||||
@@ -217,15 +214,37 @@ public class PostgresDatabase extends Database {
|
|||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public List<User> getAllUsers() {
|
||||||
|
final List<User> users = Lists.newArrayList();
|
||||||
|
try (Connection connection = getConnection()) {
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||||
|
SELECT uuid, username
|
||||||
|
FROM %users_table%;
|
||||||
|
"""))) {
|
||||||
|
final ResultSet resultSet = statement.executeQuery();
|
||||||
|
while (resultSet.next()) {
|
||||||
|
users.add(new User(UUID.fromString(resultSet.getString("uuid")),
|
||||||
|
resultSet.getString("username")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
plugin.log(Level.SEVERE, "Failed to fetch a user by name from the database", e);
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
@Blocking
|
@Blocking
|
||||||
@Override
|
@Override
|
||||||
public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) {
|
public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) {
|
||||||
try (Connection connection = getConnection()) {
|
try (Connection connection = getConnection()) {
|
||||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||||
SELECT "version_uuid", "timestamp", "data"
|
SELECT version_uuid, timestamp, data
|
||||||
FROM "%user_data_table%"
|
FROM %user_data_table%
|
||||||
WHERE "player_uuid"=?
|
WHERE player_uuid=?
|
||||||
ORDER BY "timestamp" DESC
|
ORDER BY timestamp DESC
|
||||||
LIMIT 1;"""))) {
|
LIMIT 1;"""))) {
|
||||||
statement.setObject(1, user.getUuid());
|
statement.setObject(1, user.getUuid());
|
||||||
final ResultSet resultSet = statement.executeQuery();
|
final ResultSet resultSet = statement.executeQuery();
|
||||||
@@ -251,10 +270,10 @@ public class PostgresDatabase extends Database {
|
|||||||
final List<DataSnapshot.Packed> retrievedData = Lists.newArrayList();
|
final List<DataSnapshot.Packed> retrievedData = Lists.newArrayList();
|
||||||
try (Connection connection = getConnection()) {
|
try (Connection connection = getConnection()) {
|
||||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||||
SELECT "version_uuid", "timestamp", "data"
|
SELECT version_uuid, timestamp, data
|
||||||
FROM "%user_data_table%"
|
FROM %user_data_table%
|
||||||
WHERE "player_uuid"=?
|
WHERE player_uuid=?
|
||||||
ORDER BY "timestamp" DESC;"""))) {
|
ORDER BY timestamp DESC;"""))) {
|
||||||
statement.setObject(1, user.getUuid());
|
statement.setObject(1, user.getUuid());
|
||||||
final ResultSet resultSet = statement.executeQuery();
|
final ResultSet resultSet = statement.executeQuery();
|
||||||
while (resultSet.next()) {
|
while (resultSet.next()) {
|
||||||
@@ -278,10 +297,10 @@ public class PostgresDatabase extends Database {
|
|||||||
public Optional<DataSnapshot.Packed> getSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
|
public Optional<DataSnapshot.Packed> getSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
|
||||||
try (Connection connection = getConnection()) {
|
try (Connection connection = getConnection()) {
|
||||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||||
SELECT "version_uuid", "timestamp", "data"
|
SELECT version_uuid, timestamp, data
|
||||||
FROM "%user_data_table%"
|
FROM %user_data_table%
|
||||||
WHERE "player_uuid"=? AND "version_uuid"=?
|
WHERE player_uuid=? AND version_uuid=?
|
||||||
ORDER BY "timestamp" DESC
|
ORDER BY timestamp DESC
|
||||||
LIMIT 1;"""))) {
|
LIMIT 1;"""))) {
|
||||||
statement.setObject(1, user.getUuid());
|
statement.setObject(1, user.getUuid());
|
||||||
statement.setObject(2, versionUuid);
|
statement.setObject(2, versionUuid);
|
||||||
@@ -309,11 +328,16 @@ public class PostgresDatabase extends Database {
|
|||||||
if (unpinnedUserData.size() > maxSnapshots) {
|
if (unpinnedUserData.size() > maxSnapshots) {
|
||||||
try (Connection connection = getConnection()) {
|
try (Connection connection = getConnection()) {
|
||||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||||
DELETE FROM "%user_data_table%"
|
WITH cte AS (
|
||||||
WHERE "player_uuid"=?
|
SELECT version_uuid
|
||||||
AND "pinned" = FALSE
|
FROM %user_data_table%
|
||||||
ORDER BY "timestamp" ASC
|
WHERE player_uuid=?
|
||||||
LIMIT %entry_count%;""".replace("%entry_count%",
|
AND pinned=FALSE
|
||||||
|
ORDER BY timestamp ASC
|
||||||
|
LIMIT %entry_count%
|
||||||
|
)
|
||||||
|
DELETE FROM %user_data_table%
|
||||||
|
WHERE version_uuid IN (SELECT version_uuid FROM cte);""".replace("%entry_count%",
|
||||||
Integer.toString(unpinnedUserData.size() - maxSnapshots))))) {
|
Integer.toString(unpinnedUserData.size() - maxSnapshots))))) {
|
||||||
statement.setObject(1, user.getUuid());
|
statement.setObject(1, user.getUuid());
|
||||||
statement.executeUpdate();
|
statement.executeUpdate();
|
||||||
@@ -329,11 +353,10 @@ public class PostgresDatabase extends Database {
|
|||||||
public boolean deleteSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
|
public boolean deleteSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
|
||||||
try (Connection connection = getConnection()) {
|
try (Connection connection = getConnection()) {
|
||||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||||
DELETE FROM "%user_data_table%"
|
DELETE FROM %user_data_table%
|
||||||
WHERE "player_uuid"=? AND "version_uuid"=?
|
WHERE player_uuid=? AND version_uuid=?;"""))) {
|
||||||
LIMIT 1;"""))) {
|
|
||||||
statement.setObject(1, user.getUuid());
|
statement.setObject(1, user.getUuid());
|
||||||
statement.setString(2, versionUuid.toString());
|
statement.setObject(2, versionUuid);
|
||||||
return statement.executeUpdate() > 0;
|
return statement.executeUpdate() > 0;
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
@@ -347,12 +370,12 @@ public class PostgresDatabase extends Database {
|
|||||||
protected void rotateLatestSnapshot(@NotNull User user, @NotNull OffsetDateTime within) {
|
protected void rotateLatestSnapshot(@NotNull User user, @NotNull OffsetDateTime within) {
|
||||||
try (Connection connection = getConnection()) {
|
try (Connection connection = getConnection()) {
|
||||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||||
DELETE FROM "%user_data_table%"
|
DELETE FROM %user_data_table%
|
||||||
WHERE "player_uuid"=? AND "timestamp" = (
|
WHERE player_uuid=? AND timestamp = (
|
||||||
SELECT "timestamp"
|
SELECT timestamp
|
||||||
FROM "%user_data_table%"
|
FROM %user_data_table%
|
||||||
WHERE "player_uuid"=? AND "timestamp" > ? AND "pinned" = FALSE
|
WHERE player_uuid=? AND timestamp > ? AND pinned=FALSE
|
||||||
ORDER BY "timestamp" ASC
|
ORDER BY timestamp ASC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
);"""))) {
|
);"""))) {
|
||||||
statement.setObject(1, user.getUuid());
|
statement.setObject(1, user.getUuid());
|
||||||
@@ -370,8 +393,8 @@ public class PostgresDatabase extends Database {
|
|||||||
protected void createSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
protected void createSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
||||||
try (Connection connection = getConnection()) {
|
try (Connection connection = getConnection()) {
|
||||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||||
INSERT INTO "%user_data_table%"
|
INSERT INTO %user_data_table%
|
||||||
("player_uuid","version_uuid","timestamp","save_cause","pinned","data")
|
(player_uuid,version_uuid,timestamp,save_cause,pinned,data)
|
||||||
VALUES (?,?,?,?,?,?);"""))) {
|
VALUES (?,?,?,?,?,?);"""))) {
|
||||||
statement.setObject(1, user.getUuid());
|
statement.setObject(1, user.getUuid());
|
||||||
statement.setObject(2, data.getId());
|
statement.setObject(2, data.getId());
|
||||||
@@ -391,10 +414,10 @@ public class PostgresDatabase extends Database {
|
|||||||
public void updateSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
public void updateSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
||||||
try (Connection connection = getConnection()) {
|
try (Connection connection = getConnection()) {
|
||||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||||
UPDATE "%user_data_table%"
|
UPDATE %user_data_table%
|
||||||
SET "save_cause"=?,"pinned"=?,"data"=?
|
SET save_cause=?,pinned=?,data=?
|
||||||
WHERE "player_uuid"=? AND "version_uuid"=?
|
WHERE player_uuid=? AND version_uuid=?;
|
||||||
LIMIT 1;"""))) {
|
"""))) {
|
||||||
statement.setString(1, data.getSaveCause().name());
|
statement.setString(1, data.getSaveCause().name());
|
||||||
statement.setBoolean(2, data.isPinned());
|
statement.setBoolean(2, data.isPinned());
|
||||||
statement.setBytes(3, data.asBytes(plugin));
|
statement.setBytes(3, data.asBytes(plugin));
|
||||||
@@ -411,7 +434,7 @@ public class PostgresDatabase extends Database {
|
|||||||
public void wipeDatabase() {
|
public void wipeDatabase() {
|
||||||
try (Connection connection = getConnection()) {
|
try (Connection connection = getConnection()) {
|
||||||
try (Statement statement = connection.createStatement()) {
|
try (Statement statement = connection.createStatement()) {
|
||||||
statement.executeUpdate(formatStatementTables("DELETE FROM \"%user_data_table%\";"));
|
statement.executeUpdate(formatStatementTables("DELETE FROM %user_data_table%;"));
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
plugin.log(Level.SEVERE, "Failed to wipe the database", e);
|
plugin.log(Level.SEVERE, "Failed to wipe the database", e);
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ public class RedisManager extends JedisPubSub {
|
|||||||
switch (messageType) {
|
switch (messageType) {
|
||||||
case UPDATE_USER_DATA -> plugin.getOnlineUser(redisMessage.getTargetUuid()).ifPresent(
|
case UPDATE_USER_DATA -> plugin.getOnlineUser(redisMessage.getTargetUuid()).ifPresent(
|
||||||
user -> {
|
user -> {
|
||||||
|
plugin.lockPlayer(user.getUuid());
|
||||||
try {
|
try {
|
||||||
final DataSnapshot.Packed data = DataSnapshot.deserialize(plugin, redisMessage.getPayload());
|
final DataSnapshot.Packed data = DataSnapshot.deserialize(plugin, redisMessage.getPayload());
|
||||||
user.applySnapshot(data, DataSnapshot.UpdateCause.UPDATED);
|
user.applySnapshot(data, DataSnapshot.UpdateCause.UPDATED);
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ package net.william278.husksync.redis;
|
|||||||
|
|
||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.adapter.Adaptable;
|
import net.william278.husksync.adapter.Adaptable;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -34,6 +36,8 @@ public class RedisMessage implements Adaptable {
|
|||||||
|
|
||||||
@SerializedName("target_uuid")
|
@SerializedName("target_uuid")
|
||||||
private UUID targetUuid;
|
private UUID targetUuid;
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
@SerializedName("payload")
|
@SerializedName("payload")
|
||||||
private byte[] payload;
|
private byte[] payload;
|
||||||
|
|
||||||
@@ -72,14 +76,6 @@ public class RedisMessage implements Adaptable {
|
|||||||
this.targetUuid = targetUuid;
|
this.targetUuid = targetUuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getPayload() {
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPayload(byte[] payload) {
|
|
||||||
this.payload = payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Type {
|
public enum Type {
|
||||||
|
|
||||||
UPDATE_USER_DATA,
|
UPDATE_USER_DATA,
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.util;
|
||||||
|
|
||||||
|
import de.exlll.configlib.Configuration;
|
||||||
|
import de.exlll.configlib.YamlConfigurationProperties;
|
||||||
|
import de.exlll.configlib.YamlConfigurationStore;
|
||||||
|
import net.william278.desertwell.util.Version;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import static net.william278.husksync.config.ConfigProvider.YAML_CONFIGURATION_PROPERTIES;
|
||||||
|
|
||||||
|
public interface CompatibilityChecker {
|
||||||
|
|
||||||
|
String COMPATIBILITY_FILE = "compatibility.yml";
|
||||||
|
|
||||||
|
default void checkCompatibility() throws HuskSync.FailedToLoadException {
|
||||||
|
final YamlConfigurationProperties p = YAML_CONFIGURATION_PROPERTIES.build();
|
||||||
|
final Version compatible;
|
||||||
|
|
||||||
|
// Load compatibility file
|
||||||
|
try (InputStream input = getResource(COMPATIBILITY_FILE)) {
|
||||||
|
final CompatibilityConfig compat = new YamlConfigurationStore<>(CompatibilityConfig.class, p).read(input);
|
||||||
|
compatible = Objects.requireNonNull(compat.getCompatibleWith());
|
||||||
|
} catch (Throwable e) {
|
||||||
|
getPlugin().log(Level.WARNING, "Failed to load compatibility config, skipping check.", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check compatibility
|
||||||
|
if (compatible.compareTo(getPlugin().getMinecraftVersion()) != 0) {
|
||||||
|
throw new HuskSync.FailedToLoadException("""
|
||||||
|
Incompatible Minecraft version. This version of HuskSync is designed for Minecraft %s.
|
||||||
|
Please download the correct version of HuskSync for your server's Minecraft version (%s)."""
|
||||||
|
.formatted(compatible.toString(), getPlugin().getMinecraftVersion().toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream getResource(@NotNull String name);
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
HuskSync getPlugin();
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
record CompatibilityConfig(@NotNull String minecraftVersion) {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public Version getCompatibleWith() {
|
||||||
|
return Version.fromString(minecraftVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@@ -82,7 +83,7 @@ public class DataDumper {
|
|||||||
@NotNull
|
@NotNull
|
||||||
public String toWeb() {
|
public String toWeb() {
|
||||||
try {
|
try {
|
||||||
final URL url = new URL(LOGS_SITE_ENDPOINT);
|
final URL url = URI.create(LOGS_SITE_ENDPOINT).toURL();
|
||||||
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
connection.setRequestMethod("POST");
|
connection.setRequestMethod("POST");
|
||||||
connection.setDoOutput(true);
|
connection.setDoOutput(true);
|
||||||
|
|||||||
2
common/src/main/resources/compatibility.yml
Normal file
2
common/src/main/resources/compatibility.yml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# File used for checking Minecraft server compatibility with this version of HuskSync
|
||||||
|
minecraft_version: '${minecraft_version}'
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
> **Warning:** API v2 is no longer supported or compatible with HuskSync v3.0. See [[Data Snapshot API]] for the equivalent v3 API. 🚨
|
|
||||||
|
|
||||||
HuskSync v2.0 provides an API for fetching and retrieving `UserData`; a snapshot of a user's synchronization.
|
HuskSync v2.0 provides an API for fetching and retrieving `UserData`; a snapshot of a user's synchronization.
|
||||||
|
|
||||||
|
> **Warning:** API v2 is no longer supported or compatible with HuskSync v3.0. See [[Data Snapshot API]] for the equivalent v3 API. 🚨
|
||||||
|
|
||||||
This page assumes you've read the general [[API]] introduction and imported HuskSync (v2.x) into your project, and added it as a dependency.
|
This page assumes you've read the general [[API]] introduction and imported HuskSync (v2.x) into your project, and added it as a dependency.
|
||||||
|
|
||||||
🚨 HuskSync API v2 only targets HuskSync v2.0-2.2.8. It is **not compatible with HuskSync v3.0+**. The equivalent API for HuskSync v3 is the [[Data Snapshot API]].
|
🚨 HuskSync API v2 only targets HuskSync v2.0-2.2.8. It is **not compatible with HuskSync v3.0+**. The equivalent API for HuskSync v3 is the [[Data Snapshot API]].
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ The HuskSync API is available for the following platforms:
|
|||||||
* `fabric` - Fabric API for Minecraft. Provides Fabric API event listeners and adapters to `net.minecraft` objects.
|
* `fabric` - Fabric API for Minecraft. Provides Fabric API event listeners and adapters to `net.minecraft` objects.
|
||||||
* `common` - Common API for all platforms.
|
* `common` - Common API for all platforms.
|
||||||
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Targeting older versions</summary>
|
<summary>Targeting older versions</summary>
|
||||||
|
|
||||||
@@ -53,12 +52,12 @@ Add the repository to your `pom.xml` as per below. You can alternatively specify
|
|||||||
</repository>
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
```
|
```
|
||||||
Add the dependency to your `pom.xml` as per below. Replace `VERSION` with the latest version of HuskSync (without the v): . Note for Fabric you must append the target Minecraft version to the version number (e.g. `3.6.1+1.20.1`).
|
Add the dependency to your `pom.xml` as per below. Replace `HUSKSYNC_VERSION` with the latest version of HuskSync (without the v): . and `MINECRAFT_VERSION` with the version of Minecraft you want to target (e.g. `1.20.1`). A correctly formed version target should look like: `3.7+1.20.1`. Omit the plus symbol and Minecraft version if you are targeting the `common` platform.
|
||||||
```xml
|
```xml
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.william278.husksync</groupId>
|
<groupId>net.william278.husksync</groupId>
|
||||||
<artifactId>husksync-PLATFORM</artifactId>
|
<artifactId>husksync-PLATFORM</artifactId>
|
||||||
<version>VERSION</version>
|
<version>HUSKSYNC_VERSION+MINECRAFT_VERSION</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
@@ -76,11 +75,11 @@ allprojects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Add the dependency as per below. Replace `VERSION` with the latest version of HuskSync (without the v): . Note for Fabric you must append the target Minecraft version to the version number (e.g. `3.6.1+1.20.1`).
|
Add the dependency as per below. Replace `HUSKSYNC_VERSION` with the latest version of HuskSync (without the v): . and `MINECRAFT_VERSION` with the version of Minecraft you want to target (e.g. `1.20.1`). A correctly formed version target should look like: `3.7+1.20.1`. Omit the plus symbol and Minecraft version if you are targeting the `common` platform.
|
||||||
|
|
||||||
```groovy
|
```groovy
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly 'net.william278.husksync:husksync-PLATFORM:VERSION'
|
compileOnly 'net.william278.husksync:husksync-PLATFORM:HUSKSYNC_VERSION+MINECRAFT_VERSION'
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
35
docs/Compatibility.md
Normal file
35
docs/Compatibility.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
HuskSync supports the following versions of Minecraft. Since v3.7, you must download the correct version of HuskSync for your server:
|
||||||
|
|
||||||
|
| Minecraft | Latest HuskSync | Java Version | Platforms | Support Status |
|
||||||
|
|:---------------:|:---------------:|:------------:|:--------------|:-----------------------------|
|
||||||
|
| 1.21.4 | _latest_ | 21 | Paper, Fabric | ✅ **Active Release** |
|
||||||
|
| 1.21.3 | 3.7.1 | 21 | Paper, Fabric | 🗃️ Archived (December 2024) |
|
||||||
|
| 1.21.1 | _latest_ | 21 | Paper, Fabric | ✅ **November 2025** (LTS) |
|
||||||
|
| 1.20.6 | 3.6.8 | 17 | Paper | 🗃️ Archived (October 2024) |
|
||||||
|
| 1.20.4 | 3.6.8 | 17 | Paper | 🗃️ Archived (July 2024) |
|
||||||
|
| 1.20.1 | _latest_ | 17 | Paper, Fabric | ✅ **November 2025** (LTS) |
|
||||||
|
| 1.17.1 - 1.19.4 | 3.6.8 | 17 | Paper | 🗃️ Archived |
|
||||||
|
| 1.16.5 | 3.2.1 | 16 | Paper | 🗃️ Archived |
|
||||||
|
|
||||||
|
HuskSync is primarily developed against the latest release. Old Minecraft versions are allocated a support channel based on popularity, mod support, etc:
|
||||||
|
|
||||||
|
* Long Term Support (LTS) – Supported for up to 12-18 months
|
||||||
|
* Non-Long Term Support (Non-LTS) – Supported for 3-6 months
|
||||||
|
|
||||||
|
## Incompatible versions
|
||||||
|
This plugin does not support the following software-Minecraft version combinations. The plugin will fail to load if you attempt to run it with these versions. Apologies for the inconvenience.
|
||||||
|
|
||||||
|
| Minecraft | Server Software | Notes |
|
||||||
|
|-------------------|-------------------------------------------|----------------------------------------|
|
||||||
|
| 1.19.4 | Only: `Purpur, Pufferfish`† | Older Paper builds also not supported. |
|
||||||
|
| 1.19.3 | Only: `Paper, Purpur, Pufferfish`† | Upgrade to 1.19.4 or use Spigot |
|
||||||
|
| 1.16.5 | _All_ | Please use v3.3.1 or lower |
|
||||||
|
| below 1.16.5 | _All_ | Upgrade to Minecraft 1.16.5 |
|
||||||
|
|
||||||
|
†Further downstream forks of this server software are also affected.
|
||||||
|
|
||||||
|
## Incompatible plugins / mods
|
||||||
|
Please note the following plugins / mods can cause issues with HuskSync:
|
||||||
|
|
||||||
|
* Restart plugins / mods are not supported. These will cause [player data to not save correctly when your server restarts](troubleshooting#issues-with-player-data-going-out-of-sync-during-a-server-restart) due to the way these plugins utilise bash scripts. It's important to understand that restart plugins don't actually restart yur server, they just trigger some (often unstable) process-killing scripting logic to occur!
|
||||||
|
* Combat logging plugins / mods are not supported. Some have built-in support for HuskSync and should work as expected, but for others you may wish to modify the [[Event Priorities]]
|
||||||
@@ -65,7 +65,7 @@ database:
|
|||||||
user_data: husksync_user_data
|
user_data: husksync_user_data
|
||||||
# Redis settings
|
# Redis settings
|
||||||
redis:
|
redis:
|
||||||
# Specify the credentials of your Redis database here. Set "password" to '' if you don't have one
|
# Specify the credentials of your Redis server here. Set "password" to '' if you don't have one
|
||||||
credentials:
|
credentials:
|
||||||
host: localhost
|
host: localhost
|
||||||
port: 6379
|
port: 6379
|
||||||
@@ -136,10 +136,22 @@ synchronization:
|
|||||||
- '*'
|
- '*'
|
||||||
# Configuration for how to sync attributes
|
# Configuration for how to sync attributes
|
||||||
attributes:
|
attributes:
|
||||||
# Which attributes should not be saved when syncing users. Supports wildcard matching.
|
# Which attribute types should be saved as part of attribute syncing. Supports wildcard matching.
|
||||||
# (e.g. ['minecraft:generic.max_health', 'minecraft:generic.*'])
|
# (e.g. ['minecraft:generic.max_health', 'minecraft:generic.*'])
|
||||||
ignored_attributes: []
|
synced_attributes:
|
||||||
# Which modifiers should not be saved when syncing users. Supports wildcard matching.
|
- "minecraft:generic.max_health"
|
||||||
|
- "minecraft:max_health"
|
||||||
|
- "minecraft:generic.max_absorption"
|
||||||
|
- "minecraft:max_absorption"
|
||||||
|
- "minecraft:generic.luck"
|
||||||
|
- "minecraft:luck"
|
||||||
|
- "minecraft:generic.scale"
|
||||||
|
- "minecraft:scale"
|
||||||
|
- "minecraft:generic.step_height"
|
||||||
|
- "minecraft:step_height"
|
||||||
|
- "minecraft:generic.gravity"
|
||||||
|
- "minecraft:gravity"
|
||||||
|
# Which attribute modifiers should not be saved when syncing users. Supports wildcard matching.
|
||||||
# (e.g. ['minecraft:effect.speed', 'minecraft:effect.*'])
|
# (e.g. ['minecraft:effect.speed', 'minecraft:effect.*'])
|
||||||
ignored_modifiers: ['minecraft:effect.*', 'minecraft:creative_mode_*']
|
ignored_modifiers: ['minecraft:effect.*', 'minecraft:creative_mode_*']
|
||||||
# Event priorities for listeners (HIGHEST, NORMAL, LOWEST). Change if you encounter plugin conflicts
|
# Event priorities for listeners (HIGHEST, NORMAL, LOWEST). Change if you encounter plugin conflicts
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ huskSyncAPI.getUser(uuid).thenAccept(optionalUser -> {
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
* If you have an online `org.bukkit.Player` object, you can use `BukkitPlayer#adapt(player)` to get an `OnlineUser` (extends `User`), representing a logged-in user.
|
* If you have an online `org.bukkit.Player` object, you can use `BukkitPlayer#adapt(player)` to get an `OnlineUser` (extends `User`), representing a logged-in user.
|
||||||
|
* You can also use `#getOnlineUser(UUID)` to get an OnlineUser by their UUID - note this only works for players online on the server the logic is called from, however.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Code Example — Getting an online user</summary>
|
<summary>Code Example — Getting an online user</summary>
|
||||||
|
|||||||
73
docs/Database.md
Normal file
73
docs/Database.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
HuskSync persists player data and snapshots in a database of your choice. This is separate from a [[Redis]] server, which HuskSync uses for caching and inter-server messaging, which is also required to use HuskSync.
|
||||||
|
|
||||||
|
## Database types
|
||||||
|
> **Warning:** There is no automatic way of migrating between _database_ types. Changing the database type will cause data to be lost.
|
||||||
|
|
||||||
|
| Type | Database Software |
|
||||||
|
|:--------------------------|:--------------------------|
|
||||||
|
| `MYSQL` | MySQL 8.0 or newer |
|
||||||
|
| `MARIADB` | MariaDB 5.0 or newer |
|
||||||
|
| `POSTGRES` | PostgreSQL |
|
||||||
|
| [`MONGO`](#mongodb-setup) | MongoDB |
|
||||||
|
|
||||||
|
## Configuring
|
||||||
|
To change the database type, navigate to your [`config.yml`](Config-File) file and modify the properties under `database`.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Database options (config.yml)</summary>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Database settings
|
||||||
|
database:
|
||||||
|
# Type of database to use (MYSQL, MARIADB, POSTGRES, MONGO)
|
||||||
|
type: MYSQL
|
||||||
|
# Specify credentials here for your MYSQL, MARIADB, POSTGRES OR MONGO database
|
||||||
|
credentials:
|
||||||
|
host: localhost
|
||||||
|
port: 3306
|
||||||
|
database: minecraft
|
||||||
|
username: root
|
||||||
|
password: ''
|
||||||
|
# Only change this if you're using MARIADB or POSTGRES
|
||||||
|
parameters: ?autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8
|
||||||
|
# MYSQL, MARIADB, POSTGRES database Hikari connection pool properties. Don't modify this unless you know what you're doing!
|
||||||
|
connection_pool:
|
||||||
|
maximum_pool_size: 10
|
||||||
|
minimum_idle: 10
|
||||||
|
maximum_lifetime: 1800000
|
||||||
|
keepalive_time: 0
|
||||||
|
connection_timeout: 5000
|
||||||
|
# Advanced MongoDB settings. Don't modify unless you know what you're doing!
|
||||||
|
mongo_settings:
|
||||||
|
using_atlas: false
|
||||||
|
parameters: ?retryWrites=true&w=majority&authSource=HuskSync
|
||||||
|
# Names of tables to use on your database. Don't modify this unless you know what you're doing!
|
||||||
|
table_names:
|
||||||
|
users: husksync_users
|
||||||
|
user_data: husksync_user_data
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### Credentials
|
||||||
|
You will need to specify the credentials (hostname, port, username, password and the database). These credentials are used to connect to your database server.
|
||||||
|
|
||||||
|
If your database server account doesn't have a password (not recommended), leave the password field blank (`password: ''`') and the plugin will attempt to connect without a password.
|
||||||
|
|
||||||
|
### Connection Pool properties
|
||||||
|
If you're using MySQL, MariaDB, or PostgreSQL as your database type, you can modify the HikariCP connection pool properties if you know what you're doing.
|
||||||
|
|
||||||
|
Please note that modifying these values can cause issues if you don't know what you're doing. The default values should be fine for most users.
|
||||||
|
|
||||||
|
## MongoDB Setup
|
||||||
|
If you're using a MongoDB database, in addition to setting the database type to `MONGO`, you'll need to perform slightly different configuration steps.
|
||||||
|
|
||||||
|
- Under `credentials` in the `database` section, enter the credentials of your MongoDB Database. You shouldn't touch the `connection_pool` properties.
|
||||||
|
- Under `parameters` in the `mongo_settings` section, ensure the specified `&authSource=` matches the database you are using (default is `HuskSync`).
|
||||||
|
|
||||||
|
### MongoDB Atlas setup
|
||||||
|
If you're using a MongoDB Atlas database, you'll also need to set the Atlas settings and adjust the connection parameters string.
|
||||||
|
|
||||||
|
- Set `using_atlas` in the `mongo_settings` section to `true`.
|
||||||
|
- Remove `&authSource=HuskSync` from `parameters` in the `mongo_settings`.
|
||||||
|
|
||||||
|
Note that the `port` setting in `credentials` is ignored when using Atlas.
|
||||||
55
docs/FAQs.md
55
docs/FAQs.md
@@ -1,9 +1,9 @@
|
|||||||
This page addresses a number of frequently asked questions about the plugin.
|
This page addresses a number of frequently asked questions about HuskSync.
|
||||||
|
|
||||||
## Frequently Asked Questions
|
## Frequently Asked Questions
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary> <b>What data can be synchronized?</b></summary>
|
<summary> <b>What data can be synced?</b></summary>
|
||||||
|
|
||||||
HuskSync supports synchronising a wide range of different data elements, each of which can be toggled to your liking. Please check out the [[Sync Features]] page for a full list.
|
HuskSync supports synchronising a wide range of different data elements, each of which can be toggled to your liking. Please check out the [[Sync Features]] page for a full list.
|
||||||
|
|
||||||
@@ -30,16 +30,56 @@ Please note we cannot guarantee compatibility with everything — test thoro
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary> <b>Is Redis required? What is Redis?</b></summary>
|
<summary> <b>What versions of Minecraft does HuskSync support?</b></summary>
|
||||||
|
|
||||||
Yes! HuskSync requires Redis to operate (for reasons demonstrated below).
|
Check the [[Compatibility]] table. In addition to the latest release of Minecraft, the latest version of HuskSync will support specific older versions based on popularity and mod support.
|
||||||
|
|
||||||
Redis is an in-memory database server used for caching data at scale and sending messages across a network. You have a Redis server in a similar fashion to the way you have a MySQL database server. If you're using a Minecraft hosting company, you'll want to contact their support and ask if they offer Redis. If you're looking for a host, I have a list of some popular hosts and whether they support Redis [available to read here.](https://william278.net/redis-hosts)
|
If your server's version of Minecraft isn't supported by the latest release, there's plenty of older, stable versions of HuskSync you can download, though note support for these versions will be limited.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary> <b>How does the plugin synchronize data?</b></summary>
|
<summary> <b>What do I need to run HuskSync?</b></summary>
|
||||||
|
|
||||||
|
See the [Requirements](setup#requirements) section under Setup.
|
||||||
|
|
||||||
|
You need a [[Database]] server, a [[Redis]] server, and [compatible Minecraft servers](compatibility).
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> <b>Is Redis required? What is Redis?</b></summary>
|
||||||
|
|
||||||
|
Yes, HuskSync requires a [[Redis]] server **in addition to a [[Database]] server** to operate.
|
||||||
|
|
||||||
|
Redis is an in-memory database server used for caching data at scale and sending messages across a network. You have a Redis server in a similar fashion to the way you have a MySQL database server. If you're using a Minecraft hosting company, you'll want to contact their support and ask if they offer Redis. If you're looking for a host, I have a list of some popular hosts and whether they support Redis [available to view here.](https://william278.net/docs/website/redis-hosts)
|
||||||
|
|
||||||
|
For more information, check our [Redis setup instructions](redis).
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> <b>How much RAM does my Redis server need?</b></summary>
|
||||||
|
|
||||||
|
We recommend your Redis server has 1GB of RAM, and that your Redis server is installed locally (on the same server as your game servers, or at least on the server running your Velocity/BungeeCord/Waterfall proxy).
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> <b>Is a Database required? What Databases are supported?</b></summary>
|
||||||
|
|
||||||
|
Yes. HuskSync requires both a [[Database]] server and a [[Redis]] server to operate.
|
||||||
|
|
||||||
|
HuskSync supports the following database types:
|
||||||
|
* MySQL v8.0+
|
||||||
|
* MariaDB v5.0+
|
||||||
|
* PostgreSQL
|
||||||
|
* MongoDB
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary> <b>How does data syncing work?</b></summary>
|
||||||
|
|
||||||
HuskSync makes use of both MySQL and Redis for optimal data synchronization. You have the option of using one of two [[Sync Modes]], which synchronize data between servers (`DELAY` or `LOCKSTEP`)
|
HuskSync makes use of both MySQL and Redis for optimal data synchronization. You have the option of using one of two [[Sync Modes]], which synchronize data between servers (`DELAY` or `LOCKSTEP`)
|
||||||
|
|
||||||
@@ -71,9 +111,10 @@ Indeed, there exist economy plugins — such as [XConomy](https://github.com
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary> <b>Is this better than MySQLPlayerDataBridge?</b></summary>
|
<summary> <b>Is HuskSync better than MySQLPlayerDataBridge?</b></summary>
|
||||||
|
|
||||||
I can't provide a fair answer to this question! What I can say is that your mileage will of course vary.
|
I can't provide a fair answer to this question! What I can say is that your mileage will of course vary.
|
||||||
|
|
||||||
The performance improvements offered by HuskSync's synchronization method will depend on your network environment and the economies of scale that come with your player count. In terms of featureset, HuskSync does feature greater rollback and snapshot backup/management features if this is something you are looking for.
|
The performance improvements offered by HuskSync's synchronization method will depend on your network environment and the economies of scale that come with your player count. In terms of featureset, HuskSync does feature greater rollback and snapshot backup/management features if this is something you are looking for.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
23
docs/Home.md
23
docs/Home.md
@@ -1,24 +1,31 @@
|
|||||||
# [](https://github.com/WiIIiam278/HuskSync)
|
# [](https://github.com/WiIIiam278/HuskSync)
|
||||||
Welcome! This is the plugin documentation for HuskSync v3.x+. Please click through to the topic you'd like to read about.
|
Welcome! This is the plugin documentation for HuskSync v3.x+. Please click through to the topic you'd like to read about.
|
||||||
|
|
||||||
## Guides
|
## Setup
|
||||||
* 📚 [[Setup]]
|
* 📚 [[Setup]]
|
||||||
|
* 💾 [[Database]]
|
||||||
|
* ✨ [[Redis]]
|
||||||
|
* ⚠️ [[Compatibility]]
|
||||||
* 📄 [[Config File]]
|
* 📄 [[Config File]]
|
||||||
* 🔗 [[Troubleshooting]]
|
* 🔗 [[Troubleshooting]]
|
||||||
* ↪️ [[Data Rotation]]
|
|
||||||
* ↗️ [[Legacy Migration]]
|
|
||||||
* ✨ [[MPDB Migration]]
|
|
||||||
* 🎏 [[Translations]]
|
|
||||||
* ❓ [[FAQs]]
|
|
||||||
|
|
||||||
## Documentation
|
## Features
|
||||||
* 🖥️ [[Commands]]
|
* 🖥️ [[Commands]]
|
||||||
* ✅ [[Sync Features]]
|
* ✅ [[Sync Features]]
|
||||||
* ⚙️ [[Sync Modes]]
|
* ⚙️ [[Sync Modes]]
|
||||||
* 🟩 [[Plan Hook]]
|
* ↪️ [[Data Rotation]]
|
||||||
|
* ❓ [[FAQs]]
|
||||||
|
|
||||||
|
## Guides
|
||||||
|
* ↗️ [[Legacy Migration]]
|
||||||
|
* ✨ [[MPDB Migration]]
|
||||||
* ☂️ [[Dumping UserData]]
|
* ☂️ [[Dumping UserData]]
|
||||||
|
* 🟩 [[Plan Hook]]
|
||||||
* 📋 [[Event Priorities]]
|
* 📋 [[Event Priorities]]
|
||||||
* ⚔️ [[Keep Inventory]]
|
* ⚔️ [[Keep Inventory]]
|
||||||
|
* 🎏 [[Translations]]
|
||||||
|
|
||||||
|
## Developers
|
||||||
* 📦 [[API]] v3
|
* 📦 [[API]] v3
|
||||||
* 📝 [[Data Snapshot API]]
|
* 📝 [[Data Snapshot API]]
|
||||||
* 📝 [[Custom Data API]]
|
* 📝 [[Custom Data API]]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
If your server uses the `keepInventory` gamerule, where players keep the contents of their inventory after dying, HuskSync's built-in snapshot-on-death and dead-player synchronization features can saveCause a conflict leading to synchronization issues.
|
If your server uses the [`keepInventory` game rule](https://minecraft.wiki/w/Keep_inventory), where players keep the contents of their inventory after dying, HuskSync's built-in snapshot-on-death and dead-player synchronization features can saveCause a conflict leading to synchronization issues.
|
||||||
|
|
||||||
To solve this issue, you will need to adjust three settings in your `config.yml` file, as described below.
|
To solve this issue, you will need to adjust three settings in your `config.yml` file, as described below.
|
||||||
|
|
||||||
|
|||||||
80
docs/Redis.md
Normal file
80
docs/Redis.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
Redis is a piece of server used for data caching and cross-server messaging. A Redis server running Redis v5.0+ is **required** in addition to a compatible [[Database]] to use HuskSync. There are a number of ways of [installing or getting a Redis server](#getting-a-redis-server).
|
||||||
|
|
||||||
|
For the best results, we recommend a Redis server with 1GB of RAM, hosted locally (on the same machine as all your other servers). If your setup has multiple machines, install Redis on the machine with your Velocity/BungeeCord/Waterfall proxy server and ensure lockstep syncing mode is in use.
|
||||||
|
|
||||||
|
## What is Redis?
|
||||||
|
[Redis](http://redis.io/) (**RE**mote **DI**ctionary **S**erver) is an open-source, in-memory data store server that can be used as a cache, message broker, streaming engine, or database.
|
||||||
|
|
||||||
|
HuskSync requires Redis and uses it for caching player data when they change server, and for pub/sub messaging to facilitate cross-server admin actions (such as the [`/invsee` command](Commands) to update a player's data on other servers). Check the [[FAQs]] for more details.
|
||||||
|
|
||||||
|
## Configuring
|
||||||
|
To configure Redis, navigate to your [`config.yml`](Config-File) file and modify the properties under `redis`.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Database options (config.yml)</summary>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Redis settings
|
||||||
|
redis:
|
||||||
|
# Specify the credentials of your Redis server here. Set "password" to '' if you don't have one
|
||||||
|
credentials:
|
||||||
|
host: localhost
|
||||||
|
port: 6379
|
||||||
|
password: ''
|
||||||
|
use_ssl: false
|
||||||
|
# Options for if you're using Redis sentinel. Don't modify this unless you know what you're doing!
|
||||||
|
sentinel:
|
||||||
|
# The master set name for the Redis sentinel.
|
||||||
|
master: ''
|
||||||
|
# List of host:port pairs
|
||||||
|
nodes: []
|
||||||
|
password: ''
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### Credentials
|
||||||
|
Enter the hostname, port, and default user password of your Redis server.
|
||||||
|
|
||||||
|
If your Redis default user doesn't have a password, leave the password field blank (`password: ''`') and the plugin will attempt to connect without a password.
|
||||||
|
|
||||||
|
### Default user password
|
||||||
|
Depending on the version of Redis you've installed, Redis may or may not set a random default user password. Please check this in your Redis server config. You can clear the password of the default user with the below command in `redis-cli`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
requirepass thepassword
|
||||||
|
user default on nopass ~* &* +@all
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Redis Sentinel
|
||||||
|
If you're using [Redis Sentinel](https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel/), set this up by filling out the properties under the `sentinel` subsection.
|
||||||
|
|
||||||
|
You'll need to supply your master set name, your sentinel password, and a list of hosts/ports in the format `host:port`.
|
||||||
|
|
||||||
|
## Getting a Redis Server
|
||||||
|
HuskSync requires a Redis server. Instructions for getting Redis on different servers are detailed below. HuskSync is tested for the official Redis package, but should also work with Redis forks or other compatible software.
|
||||||
|
|
||||||
|
For the best results, we recommend a Redis server with 1GB of RAM, hosted locally (on the same machine as all your other servers). If your setup has multiple machines, install Redis on the machine with your Velocity/BungeeCord/Waterfall proxy server and ensure lockstep syncing mode is in use.
|
||||||
|
|
||||||
|
### If you're using a Minecraft server hosting provider
|
||||||
|
Please contact your host's customer support and request Redis. You can direct them to this page if you wish. Looking for a Minecraft Server host that supports Redis? We maintain a list of [server hosts which offer Redis](https://william278.net/docs/website/redis-hosts).
|
||||||
|
|
||||||
|
If your host doesn't offer Redis, you should consider whether HuskSync is the right plugin for you. If you still want to use HuskSync, you could choose to rent a cheap Redis server externally from a provider such as DigitalOcean, though note we don't recommend this as it increases the latency between your game servers and cache, which will impact syncing performance.
|
||||||
|
|
||||||
|
### Redis on Linux or macOS
|
||||||
|
You can [install Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-linux/) on your distribution of Linux. Redis is widely available on most package manager repositories.
|
||||||
|
|
||||||
|
You can also [install Redis](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-mac-os/) on your macOS server.
|
||||||
|
|
||||||
|
### Redis on Windows
|
||||||
|
Redis isn't officially supported on Windows, but there's a number of [unofficial ports](https://github.com/tporadowski/redis/releases) you can install which work great and run Redis as a Windows service.
|
||||||
|
|
||||||
|
You can also [install Redis via WSL](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/install-redis-on-windows/) if you prefer.
|
||||||
|
|
||||||
|
### Pterodactyl / Pelican panel hosts
|
||||||
|
If you're self-hosting your server on a Pterodactyl or Pelican panel, you will already have Redis installed and can use this server for HuskSync, too.
|
||||||
|
|
||||||
|
If you are hosting your Redis server on the same node as your servers, you need to use `172.18.0.1` as your host (or equivalent if you changed your network settings), and bind it in the Redis config `nano /etc/redis/redis.conf`.
|
||||||
|
|
||||||
|
You will also need to uncomment the `requirepass` directive and set a password to allow outside connections, or disable `protected-mode`. Once a password is set and Redis is restarted `systemctl restart redis`, you will also need to update the password in your pterodactyl `.env` (`nano /var/www/pterodactyl/.env`) and refresh the cache `cd /var/www/pterodactyl && php artisan config:clear`.
|
||||||
|
|
||||||
|
You may also need to allow connections from your firewall depending on your Linux distribution.
|
||||||
@@ -1,19 +1,18 @@
|
|||||||
> **Warning:** Fabric support is currently in beta and is not production ready yet. Customers can get in touch on Discord to request the Fabric build, or you can self-compile.
|
This will walk you through installing HuskSync on your network of Spigot or Fabric servers. Please check your server's [[Compatibility]] and download the correct version of HuskSync for your server.
|
||||||
|
|
||||||
This will walk you through installing HuskSync on your network of Spigot or Fabric servers.
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
> **Warning:** Mixing and matching Fabric/Spigot servers is not supported, and all servers must be running the same Minecraft version.
|
HuskSync requires a Database server, a Redis server, and any number of compatible Minecraft servers:
|
||||||
|
|
||||||
> **Note:** Please also note some specific legacy Paper/Purpur versions are [not compatible](Unsupported-Versions) with HuskSync.
|
* Any number of [compatible Fabric or Spigot/Paper-based](Compatibility) servers
|
||||||
|
* Each server must be running the same exact version of Minecraft
|
||||||
* A MySQL Database (v8.0+)
|
* It is not possible to sync data between a mixture of Fabric and Spigot servers
|
||||||
* **OR** a MariaDB, PostrgreSQL or MongoDB database, which are also supported
|
* HuskSync should not be installed on your Velocity, BungeeCord, or Waterfall proxy
|
||||||
* A Redis Database (v5.0+) — see [[FAQs]] for more details.
|
* A [[Database]] server running MySQL v8.0+, MariaDB v5.0+, PostgreSQL or MongoDB
|
||||||
* Any number of Spigot servers, connected by a BungeeCord or Velocity-based proxy (Minecraft v1.17.1+, running Java 17+)
|
* A [[Redis]] server running Redis v5.0+
|
||||||
* **OR** a network of Fabric servers, connected by a Fabric proxy (Minecraft v1.20.1, running Java 17+)
|
|
||||||
|
|
||||||
## Setup Instructions
|
## Setup Instructions
|
||||||
|
Before you begin, switch off all servers on your network. It is recommended that you also take a backup.
|
||||||
|
|
||||||
### 1. Install the jar
|
### 1. Install the jar
|
||||||
- Place the plugin jar file in the `/plugins/` or `/mods/` directory of each Spigot/Fabric server respectively.
|
- Place the plugin jar file in the `/plugins/` or `/mods/` directory of each Spigot/Fabric server respectively.
|
||||||
- You do not need to install HuskSync as a proxy plugin.
|
- You do not need to install HuskSync as a proxy plugin.
|
||||||
@@ -24,10 +23,10 @@ This will walk you through installing HuskSync on your network of Spigot or Fabr
|
|||||||
- Start, then stop every server to let HuskSync generate the [[config file]].
|
- Start, then stop every server to let HuskSync generate the [[config file]].
|
||||||
- HuskSync will throw an error in the console and disable itself as it is unable to connect to the database. You haven't set the credentials yet, so this is expected.
|
- HuskSync will throw an error in the console and disable itself as it is unable to connect to the database. You haven't set the credentials yet, so this is expected.
|
||||||
|
|
||||||
### 3. Enter Mysql & Redis database credentials
|
### 3. Enter Database & Redis server credentials
|
||||||
- Navigate to the new config file on each server (`~/plugins/HuskSync/config.yml` on Spigot, `~/config/husksync/config.yml` on Fabric)
|
- Navigate to the new config file on each server (`~/plugins/HuskSync/config.yml` on Spigot, `~/config/husksync/config.yml` on Fabric)
|
||||||
- Under `credentials` in the `database` section, enter the credentials of your (MySQL/MariaDB/MongoDB/PostgreSQL) Database. You shouldn't touch the `connection_pool` properties.
|
- Under `credentials` in the [`database`](Database) section, enter your database credentials. If you're using a Mongo database, [follow the instructions](database#mongodb-setup) here. You shouldn't need to modify the `connection_pool` properties.
|
||||||
- Under `credentials` in the `redis` section, enter the credentials of your Redis Database. If your Redis server doesn't have a password, leave the password blank as it is.
|
- Under `credentials` in the [`redis`](Redis) section, enter the credentials of your Redis server. If your Redis server doesn't have a password, leave the password blank as it is.
|
||||||
- Unless you want to have multiple clusters of servers within your network, each with separate user data, you should not change the value of `cluster_id`.
|
- Unless you want to have multiple clusters of servers within your network, each with separate user data, you should not change the value of `cluster_id`.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -46,15 +45,7 @@ This will walk you through installing HuskSync on your network of Spigot or Fabr
|
|||||||
(The `port` setting in `credentials` is disregarded when using Atlas.)
|
(The `port` setting in `credentials` is disregarded when using Atlas.)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Pterodactyl self-hosts — Redis setup instructions</summary>
|
|
||||||
|
|
||||||
If you are hosting your Redis server on the same node as your servers, you need to use `172.18.0.1` as your host (or equivalent if you changed your network settings), and bind it in the Redis config `nano /etc/redis/redis.conf`.
|
|
||||||
|
|
||||||
You will also need to uncomment the `requirepass` directive and set a password to allow outside connections, or disable `protected-mode`. Once a password is set and Redis is restarted `systemctl restart redis`, you will also need to update the password in your pterodactyl `.env` (`nano /var/www/pterodactyl/.env`) and refresh the cache `cd /var/www/pterodactyl && php artisan config:clear`.
|
|
||||||
|
|
||||||
You may also need to allow connections from your firewall depending on your distribution.
|
|
||||||
</details>
|
|
||||||
|
|
||||||
### 4. Set server names in server.yml files
|
### 4. Set server names in server.yml files
|
||||||
- Navigate to the server name file on each server (`~/plugins/HuskSync/server.yml` on Spigot, `~/config/husksync/server.yml` on Fabric)
|
- Navigate to the server name file on each server (`~/plugins/HuskSync/server.yml` on Spigot, `~/config/husksync/server.yml` on Fabric)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
This page contains a list of the features HuskSync is and isn't able to syncrhonise on your server.
|
This page contains a list of the features HuskSync is and isn't able to synchronise on your server.
|
||||||
|
|
||||||
You can customise how much data HuskSync saves about a player by [turning each synchronization feature on or off](#toggling-sync-features). When a synchronization feature is turned off, HuskSync won't touch that part of a player's profile; in other words, the data they will inherit when changing servers will be read from their player data file on the local server.
|
You can customise how much data HuskSync saves about a player by [turning each synchronization feature on or off](#toggling-sync-features). When a synchronization feature is turned off, HuskSync won't touch that part of a player's profile; in other words, the data they will inherit when changing servers will be read from their player data file on the local server.
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,41 @@
|
|||||||
This page contains a number of common issues and how you can troubleshoot and resolve them.
|
This page contains a number of common issues when using HuskSync and how you can troubleshoot and resolve them.
|
||||||
|
|
||||||
## Topics
|
## Topics
|
||||||
### Duplicate UUIDs in database
|
### Duplicate UUIDs in database
|
||||||
This is most frequently caused by running a cracked "offline mode" network of servers. We [don't provide support](https://william278.net/terms) for problems caused by cracked servers and the most advice we can offer you is:
|
This is most frequently caused by running a cracked "offline mode" network of servers. We [don't provide support](https://william278.net/terms) for problems caused by cracked servers and the most advice we can offer you is:
|
||||||
- Ensure `bungee_online_mode` is set to the correct value in the `paper.yml` config file on each of your Bukkit servers
|
- Ensure `bungee_online_mode` is set to the correct value in the `paper.yml` config file on each of your Bukkit servers
|
||||||
- Ensure your authenticator plugin is passing valid, unique IDs to each backend Spigot server.
|
- Ensure your authenticator plugin is passing valid, unique IDs to each backend Spigot/Fabric server.
|
||||||
|
|
||||||
### Cannot set data with newer Minecraft version than the server
|
### Cannot set data with newer Minecraft version than the server
|
||||||
This is caused when you attempt to downgrade user data from a newer version of Minecraft to an older one, or when your Spigot servers are running mismatched Minecraft versions.
|
This is caused when you attempt to downgrade user data from a newer version of Minecraft to an older one, or when your Spigot/Fabric servers are running mismatched Minecraft versions.
|
||||||
|
|
||||||
HuskSync will identify this and safely prevent the synchronization from occuring. Your Spigot servers must be running the same version of both Minecraft and HuskSync.
|
HuskSync will identify this and safely prevent the synchronization from occurring. Your Spigot/Fabric servers must be running the same version of both Minecraft and HuskSync.
|
||||||
|
|
||||||
### User data failing to synchronize
|
### User data failing to synchronize
|
||||||
This can occur due to misaligned timings between your Spigot servers and your Redis server. HuskSync has a built in way of tuning this. Try continously increasing the `network_latency_milliseconds` option in your config to a higher value.
|
This can occur due to misaligned timings between your Spigot/Fabric servers and your Redis server. HuskSync has a built in way of tuning this. Try continously increasing the `network_latency_milliseconds` option in your config to a higher value.
|
||||||
|
|
||||||
### Synchronization issues with Keep Inventory enabled
|
### Synchronization issues with Keep Inventory enabled
|
||||||
On servers that use Keep Inventory move (where players keep their items when they die), you can run into synchronization issues. See [[Keep Inventory]] for details on why this happens and how to resolve it.
|
On servers that use [[Keep Inventory]] (where players keep their items when they die), you can run into synchronization issues. See [[Keep Inventory]] for details on why this happens and how to resolve it.
|
||||||
|
|
||||||
### Exceptions when compressing data via Snappy (lightweight Linux distros)
|
### Exceptions when compressing data via Snappy (lightweight Linux distros)
|
||||||
Some lightweight Linux distros such as Alpine Linux (used on Pterodactyl) might not have the dependencies needed for the [Snappy](https://github.com/xerial/snappy-java) compressor. It's possible to disable data compression by changing `compress_data` to false in your config. Note that after changing this setting you will need to reset your database. Alternatively, find the right libraries for your distro!
|
Some lightweight Linux distros such as Alpine Linux (used on Pterodactyl) might not have the dependencies needed for the [Snappy](https://github.com/xerial/snappy-java) compressor. It's possible to disable data compression by changing `compress_data` to false in your config. Note that after changing this setting you will need to reset your database. Alternatively, find the right libraries for your distro!
|
||||||
|
|
||||||
### Redis connection problems on Pterodactyl
|
### Redis connection problems on Pterodactyl / Pelican
|
||||||
If you are hosting your Redis server on the same node as your servers, you need to use 172.18.0.1 (or equivelant if you changed your network settings) as your host. You may also need to [allow connections from your firewall](https://pterodactyl.io/community/games/minecraft.html#firewalls) depending on your distribution.
|
If you are hosting your [[Redis]] server on the same node as your servers, you need to use 172.18.0.1 (or equivelant if you changed your network settings) as your host. You may also need to [allow connections from your firewall](https://pterodactyl.io/community/games/minecraft.html#firewalls) depending on your distribution. See our tips for running [Redis on a Pterodactyl or Pelican panel](Redis#pterodactyl--pelican-panel-hosts)
|
||||||
|
|
||||||
### MySQL connection problems on Pterodactyl
|
### Database connection problems on Pterodactyl / Pelican
|
||||||
If you have more than one MySQL server connected to your panel, you may need to set `useSSL=true` in the parameters.
|
If you have more than one [[Database]] server connected to your panel, you may need to set `useSSL=true` in the parameters.
|
||||||
|
|
||||||
|
### Issues with player data going out of sync during a server restart
|
||||||
|
This can happen due to the way in which your server restarts. If your server uses either:
|
||||||
|
|
||||||
|
* `/restart` (this is a weird Spigot/Fabric command that uses legacy bash scripting)
|
||||||
|
* ANY restart plugin, e.g. UltimateAutoRestart (these basically execute an API-called restart using the same legacy bash logic as per above)
|
||||||
|
|
||||||
|
These are **not compatible** with HuskSync in most cases due to the way in which this causes restart servers causing shutdown logic to process in strange and unpredictable orders, usually before HuskSync has had a chance to scan and perform its shutdown logic. To safely restart your server, please use:
|
||||||
|
|
||||||
|
* A Pterodactyl task to perform a Restart. This executes the Power Action program stopcode (and then execute the startup command when the container has terminated)
|
||||||
|
* A cronjob to send a stop command / Power Action program stopcode, listen for the service to fully terminate, and then execute your startup command
|
||||||
|
* For manual restarts, executing `/stop` and starting your server up with the startup command is totally fine.
|
||||||
|
|
||||||
|
It's not a great idea to use a plugin to handle restarts. Plugins are only able to operate when your server is turned on and must rely on scripts which don't safely shutdown servers when restarting.
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
This plugin does not support the following software-Minecraft version combinations. The plugin will fail to load if you attempt to run it with these versions. Apologies for the inconvenience.
|
|
||||||
|
|
||||||
## Incompatibility table
|
|
||||||
| Minecraft Versions | Server Software | Notes |
|
|
||||||
|--------------------|-------------------------------------------|----------------------------------------|
|
|
||||||
| 1.19.4 | Only: `Purpur, Pufferfish`† | Older Paper builds also not supported. |
|
|
||||||
| 1.19.3 | Only: `Paper, Purpur, Pufferfish`† | Upgrade to 1.19.4 or use Spigot |
|
|
||||||
| 1.16.5 | _All_ | Please use v3.3.1 or lower |
|
|
||||||
| below 1.16.5 | _All_ | Upgrade Minecraft 1.16.5 |
|
|
||||||
|
|
||||||
†Further downstream forks of this server software are also affected.
|
|
||||||
@@ -1,21 +1,28 @@
|
|||||||
## Guides
|
## Setup
|
||||||
* 📚 [[Setup]]
|
* 📚 [[Setup]]
|
||||||
|
* 💾 [[Database]]
|
||||||
|
* ✨ [[Redis]]
|
||||||
|
* ⚠️ [[Compatibility]]
|
||||||
* 📄 [[Config File]]
|
* 📄 [[Config File]]
|
||||||
* 🔗 [[Troubleshooting]]
|
* 🔗 [[Troubleshooting]]
|
||||||
* ↪️ [[Data Rotation]]
|
|
||||||
* ↗️ [[Legacy Migration]]
|
|
||||||
* ✨ [[MPDB Migration]]
|
|
||||||
* 🎏 [[Translations]]
|
|
||||||
* ❓ [[FAQs]]
|
|
||||||
|
|
||||||
## Documentation
|
## Features
|
||||||
* 🖥️ [[Commands]]
|
* 🖥️ [[Commands]]
|
||||||
* ✅ [[Sync Features]]
|
* ✅ [[Sync Features]]
|
||||||
* ⚙️ [[Sync Modes]]
|
* ⚙️ [[Sync Modes]]
|
||||||
* 🟩 [[Plan Hook]]
|
* ↪️ [[Data Rotation]]
|
||||||
|
* ❓ [[FAQs]]
|
||||||
|
|
||||||
|
## Guides
|
||||||
|
* ↗️ [[Legacy Migration]]
|
||||||
|
* ✨ [[MPDB Migration]]
|
||||||
* ☂️ [[Dumping UserData]]
|
* ☂️ [[Dumping UserData]]
|
||||||
|
* 🟩 [[Plan Hook]]
|
||||||
* 📋 [[Event Priorities]]
|
* 📋 [[Event Priorities]]
|
||||||
* ⚔️ [[Keep Inventory]]
|
* ⚔️ [[Keep Inventory]]
|
||||||
|
* 🎏 [[Translations]]
|
||||||
|
|
||||||
|
## Developers
|
||||||
* 📦 [[API]] v3
|
* 📦 [[API]] v3
|
||||||
* 📝 [[Data Snapshot API]]
|
* 📝 [[Data Snapshot API]]
|
||||||
* 📝 [[Custom Data API]]
|
* 📝 [[Custom Data API]]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'fabric-loom' version '1.7-SNAPSHOT'
|
id 'fabric-loom' version "$fabric_loom_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'fabric-loom'
|
apply plugin: 'fabric-loom'
|
||||||
@@ -11,14 +11,14 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
minecraft "com.mojang:minecraft:${fabric_minecraft_version}"
|
minecraft "com.mojang:minecraft:${minecraft_version}"
|
||||||
mappings "net.fabricmc:yarn:${fabric_yarn_mappings}:v2"
|
mappings "net.fabricmc:yarn:${fabric_yarn_mappings}:v2"
|
||||||
modImplementation "net.fabricmc:fabric-loader:${fabric_loader_version}"
|
modImplementation "net.fabricmc:fabric-loader:${fabric_loader_version}"
|
||||||
|
|
||||||
modImplementation include("net.kyori:adventure-platform-fabric:${adventure_platform_fabric_version}")
|
modImplementation include("net.kyori:adventure-platform-fabric:${fabric_adventure_platform_version}")
|
||||||
modImplementation include("me.lucko:fabric-permissions-api:${fabric_permissions_api_version}")
|
modImplementation include("me.lucko:fabric-permissions-api:${fabric_permissions_api_version}")
|
||||||
modImplementation include("eu.pb4:sgui:${sgui_version}")
|
modImplementation include("eu.pb4:sgui:${fabric_sgui_version}")
|
||||||
modImplementation include('net.william278.uniform:uniform-fabric:1.2.1+1.20.1')
|
modImplementation include("net.william278.uniform:uniform-fabric:1.3+${minecraft_version}")
|
||||||
modCompileOnly "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}"
|
modCompileOnly "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}"
|
||||||
|
|
||||||
implementation include('org.apache.commons:commons-pool2:2.12.0')
|
implementation include('org.apache.commons:commons-pool2:2.12.0')
|
||||||
@@ -28,11 +28,11 @@ dependencies {
|
|||||||
implementation include("org.postgresql:postgresql:$postgres_driver_version")
|
implementation include("org.postgresql:postgresql:$postgres_driver_version")
|
||||||
implementation include("org.xerial.snappy:snappy-java:$snappy_version")
|
implementation include("org.xerial.snappy:snappy-java:$snappy_version")
|
||||||
|
|
||||||
compileOnly 'org.jetbrains:annotations:24.1.0'
|
compileOnly 'org.jetbrains:annotations:26.0.1'
|
||||||
compileOnly 'net.william278:DesertWell:2.0.4'
|
compileOnly 'net.william278:DesertWell:2.0.4'
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.34'
|
compileOnly 'org.projectlombok:lombok:1.18.36'
|
||||||
|
|
||||||
annotationProcessor 'org.projectlombok:lombok:1.18.34'
|
annotationProcessor 'org.projectlombok:lombok:1.18.36'
|
||||||
|
|
||||||
shadow project(path: ":common")
|
shadow project(path: ":common")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
|||||||
import net.fabricmc.loader.api.FabricLoader;
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
import net.fabricmc.loader.api.ModContainer;
|
import net.fabricmc.loader.api.ModContainer;
|
||||||
import net.kyori.adventure.platform.AudienceProvider;
|
import net.kyori.adventure.platform.AudienceProvider;
|
||||||
import net.kyori.adventure.platform.fabric.FabricServerAudiences;
|
import net.kyori.adventure.platform.modcommon.MinecraftServerAudiences;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.william278.desertwell.util.Version;
|
import net.william278.desertwell.util.Version;
|
||||||
import net.william278.husksync.adapter.DataAdapter;
|
import net.william278.husksync.adapter.DataAdapter;
|
||||||
@@ -78,11 +78,25 @@ import java.util.logging.Level;
|
|||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync, FabricTask.Supplier,
|
public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync, FabricTask.Supplier,
|
||||||
FabricEventDispatcher {
|
FabricEventDispatcher {
|
||||||
|
|
||||||
private static final String PLATFORM_TYPE_ID = "fabric";
|
private static final String PLATFORM_TYPE_ID = "fabric";
|
||||||
|
|
||||||
|
private static final int VERSION1_16_5 = 2586;
|
||||||
|
private static final int VERSION1_17_1 = 2730;
|
||||||
|
private static final int VERSION1_18_2 = 2975;
|
||||||
|
private static final int VERSION1_19_2 = 3120;
|
||||||
|
private static final int VERSION1_19_4 = 3337;
|
||||||
|
private static final int VERSION1_20_1 = 3465;
|
||||||
|
private static final int VERSION1_20_2 = 3578;
|
||||||
|
private static final int VERSION1_20_4 = 3700;
|
||||||
|
private static final int VERSION1_20_5 = 3837;
|
||||||
|
private static final int VERSION1_21_1 = 3955;
|
||||||
|
private static final int VERSION1_21_3 = 4082;
|
||||||
|
private static final int VERSION1_21_4 = 4189; // Current
|
||||||
|
|
||||||
private final TreeMap<Identifier, Serializer<? extends Data>> serializers = Maps.newTreeMap(
|
private final TreeMap<Identifier, Serializer<? extends Data>> serializers = Maps.newTreeMap(
|
||||||
SerializerRegistry.DEPENDENCY_ORDER_COMPARATOR
|
SerializerRegistry.DEPENDENCY_ORDER_COMPARATOR
|
||||||
);
|
);
|
||||||
@@ -125,6 +139,7 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
loadSettings();
|
loadSettings();
|
||||||
loadLocales();
|
loadLocales();
|
||||||
loadServer();
|
loadServer();
|
||||||
|
validateConfigFiles();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Register commands
|
// Register commands
|
||||||
@@ -142,7 +157,10 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
|
|
||||||
private void onEnable() {
|
private void onEnable() {
|
||||||
// Initial plugin setup
|
// Initial plugin setup
|
||||||
this.audiences = FabricServerAudiences.of(minecraftServer);
|
this.audiences = MinecraftServerAudiences.of(minecraftServer);
|
||||||
|
|
||||||
|
// Check compatibility
|
||||||
|
checkCompatibility();
|
||||||
|
|
||||||
// Prepare data adapter
|
// Prepare data adapter
|
||||||
initialize("data adapter", (plugin) -> {
|
initialize("data adapter", (plugin) -> {
|
||||||
@@ -208,14 +226,6 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
// Check for updates
|
// Check for updates
|
||||||
this.checkForUpdates();
|
this.checkForUpdates();
|
||||||
|
|
||||||
log(Level.WARNING, """
|
|
||||||
**************
|
|
||||||
WARNING:
|
|
||||||
|
|
||||||
HuskSync for Fabric is still in an alpha state and is
|
|
||||||
not considered production ready.
|
|
||||||
**************""");
|
|
||||||
|
|
||||||
ModLoadedCallback.EVENT.invoker().post(FabricHuskSyncAPI.getInstance());
|
ModLoadedCallback.EVENT.invoker().post(FabricHuskSyncAPI.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,6 +281,15 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
return FabricUniform.getInstance(mod.getMetadata().getId());
|
return FabricUniform.getInstance(mod.getMetadata().getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Map<Identifier, Data> getPlayerCustomDataStore(@NotNull OnlineUser user) {
|
||||||
|
return playerCustomDataStore.compute(
|
||||||
|
user.getUuid(),
|
||||||
|
(uuid, data) -> data == null ? Maps.newHashMap() : data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public InputStream getResource(@NotNull String name) {
|
public InputStream getResource(@NotNull String name) {
|
||||||
@@ -333,6 +352,25 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
return Version.fromString(minecraftServer.getVersion());
|
return Version.fromString(minecraftServer.getVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public int getDataVersion(@NotNull Version mcVersion) {
|
||||||
|
return switch (mcVersion.toStringWithoutMetadata()) {
|
||||||
|
case "1.16", "1.16.1", "1.16.2", "1.16.3", "1.16.4", "1.16.5" -> VERSION1_16_5;
|
||||||
|
case "1.17", "1.17.1" -> VERSION1_17_1;
|
||||||
|
case "1.18", "1.18.1", "1.18.2" -> VERSION1_18_2;
|
||||||
|
case "1.19", "1.19.1", "1.19.2" -> VERSION1_19_2;
|
||||||
|
case "1.19.4" -> VERSION1_19_4;
|
||||||
|
case "1.20", "1.20.1" -> VERSION1_20_1;
|
||||||
|
case "1.20.2" -> VERSION1_20_2;
|
||||||
|
case "1.20.4" -> VERSION1_20_4;
|
||||||
|
case "1.20.5", "1.20.6" -> VERSION1_20_5;
|
||||||
|
case "1.21", "1.21.1" -> VERSION1_21_1;
|
||||||
|
case "1.21.2", "1.21.3" -> VERSION1_21_3;
|
||||||
|
case "1.21.4" -> VERSION1_21_4;
|
||||||
|
default -> VERSION1_21_4; // Current supported ver
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public String getPlatformType() {
|
public String getPlatformType() {
|
||||||
|
|||||||
@@ -24,10 +24,9 @@ import com.google.common.collect.Maps;
|
|||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import net.fabricmc.fabric.api.dimension.v1.FabricDimensions;
|
|
||||||
import net.minecraft.advancement.AdvancementProgress;
|
import net.minecraft.advancement.AdvancementProgress;
|
||||||
import net.minecraft.advancement.PlayerAdvancementTracker;
|
import net.minecraft.advancement.PlayerAdvancementTracker;
|
||||||
import net.minecraft.enchantment.EnchantmentHelper;
|
import net.minecraft.component.DataComponentTypes;
|
||||||
import net.minecraft.entity.attribute.EntityAttribute;
|
import net.minecraft.entity.attribute.EntityAttribute;
|
||||||
import net.minecraft.entity.attribute.EntityAttributeInstance;
|
import net.minecraft.entity.attribute.EntityAttributeInstance;
|
||||||
import net.minecraft.entity.attribute.EntityAttributeModifier;
|
import net.minecraft.entity.attribute.EntityAttributeModifier;
|
||||||
@@ -35,20 +34,22 @@ import net.minecraft.entity.effect.StatusEffect;
|
|||||||
import net.minecraft.entity.effect.StatusEffectInstance;
|
import net.minecraft.entity.effect.StatusEffectInstance;
|
||||||
import net.minecraft.entity.player.HungerManager;
|
import net.minecraft.entity.player.HungerManager;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.item.ItemStack;
|
||||||
import net.minecraft.nbt.NbtCompound;
|
|
||||||
import net.minecraft.registry.Registries;
|
import net.minecraft.registry.Registries;
|
||||||
import net.minecraft.registry.Registry;
|
import net.minecraft.registry.Registry;
|
||||||
|
import net.minecraft.registry.entry.RegistryEntry;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
import net.minecraft.stat.StatType;
|
import net.minecraft.stat.StatType;
|
||||||
import net.minecraft.stat.Stats;
|
import net.minecraft.stat.Stats;
|
||||||
|
import net.minecraft.text.Text;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
import net.minecraft.util.math.Vec3d;
|
|
||||||
import net.minecraft.world.TeleportTarget;
|
import net.minecraft.world.TeleportTarget;
|
||||||
import net.william278.desertwell.util.ThrowingConsumer;
|
import net.william278.desertwell.util.ThrowingConsumer;
|
||||||
import net.william278.husksync.FabricHuskSync;
|
import net.william278.husksync.FabricHuskSync;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.adapter.Adaptable;
|
import net.william278.husksync.adapter.Adaptable;
|
||||||
|
import net.william278.husksync.config.Settings.SynchronizationSettings.AttributeSettings;
|
||||||
|
import net.william278.husksync.mixins.HungerManagerMixin;
|
||||||
import net.william278.husksync.user.FabricUser;
|
import net.william278.husksync.user.FabricUser;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -87,13 +88,10 @@ public abstract class FabricData implements Data {
|
|||||||
stack.getItem().toString(),
|
stack.getItem().toString(),
|
||||||
stack.getCount(),
|
stack.getCount(),
|
||||||
stack.getName().getString(),
|
stack.getName().getString(),
|
||||||
Optional.ofNullable(stack.getSubNbt(ItemStack.DISPLAY_KEY))
|
stack.getComponents().get(DataComponentTypes.LORE).lines().stream().map(Text::getString).toList(),
|
||||||
.flatMap(display -> Optional.ofNullable(display.get(ItemStack.LORE_KEY))
|
stack.getEnchantments().getEnchantments().stream()
|
||||||
.map(lore -> ((List<String>) lore).stream().toList())) //todo check this is ok
|
.map(RegistryEntry::getIdAsString)
|
||||||
.orElse(null),
|
.filter(Objects::nonNull)
|
||||||
stack.getEnchantments().stream()
|
|
||||||
.map(element -> EnchantmentHelper.getIdFromNbt((NbtCompound) element))
|
|
||||||
.filter(Objects::nonNull).map(Identifier::toString)
|
|
||||||
.toList()
|
.toList()
|
||||||
) : null)
|
) : null)
|
||||||
.toArray(Stack[]::new);
|
.toArray(Stack[]::new);
|
||||||
@@ -160,7 +158,7 @@ public abstract class FabricData implements Data {
|
|||||||
@Override
|
@Override
|
||||||
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||||
final ServerPlayerEntity player = user.getPlayer();
|
final ServerPlayerEntity player = user.getPlayer();
|
||||||
player.playerScreenHandler.clearCraftingSlots();
|
player.playerScreenHandler.getCraftingInput().clear();
|
||||||
player.currentScreenHandler.setCursorStack(ItemStack.EMPTY);
|
player.currentScreenHandler.setCursorStack(ItemStack.EMPTY);
|
||||||
final ItemStack[] items = getContents();
|
final ItemStack[] items = getContents();
|
||||||
for (int slot = 0; slot < player.getInventory().size(); slot++) {
|
for (int slot = 0; slot < player.getInventory().size(); slot++) {
|
||||||
@@ -248,7 +246,7 @@ public abstract class FabricData implements Data {
|
|||||||
.map(effect -> {
|
.map(effect -> {
|
||||||
final StatusEffect type = matchEffectType(effect.type());
|
final StatusEffect type = matchEffectType(effect.type());
|
||||||
return type != null ? new StatusEffectInstance(
|
return type != null ? new StatusEffectInstance(
|
||||||
type,
|
RegistryEntry.of(type),
|
||||||
effect.duration(),
|
effect.duration(),
|
||||||
effect.amplifier(),
|
effect.amplifier(),
|
||||||
effect.isAmbient(),
|
effect.isAmbient(),
|
||||||
@@ -270,9 +268,10 @@ public abstract class FabricData implements Data {
|
|||||||
@Override
|
@Override
|
||||||
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||||
final ServerPlayerEntity player = user.getPlayer();
|
final ServerPlayerEntity player = user.getPlayer();
|
||||||
final List<StatusEffect> effectsToRemove = player.getActiveStatusEffects().entrySet().stream()
|
//todo ambient check
|
||||||
.filter(e -> !e.getValue().isAmbient()).map(Map.Entry::getKey).toList();
|
List<StatusEffect> effectsToRemove = new ArrayList<>(player.getActiveStatusEffects().keySet().stream()
|
||||||
effectsToRemove.forEach(player::removeStatusEffect);
|
.map(RegistryEntry::value).toList());
|
||||||
|
effectsToRemove.forEach(effect -> player.removeStatusEffect(RegistryEntry.of(effect)));
|
||||||
getEffects().forEach(player::addStatusEffect);
|
getEffects().forEach(player::addStatusEffect);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +281,7 @@ public abstract class FabricData implements Data {
|
|||||||
public List<Effect> getActiveEffects() {
|
public List<Effect> getActiveEffects() {
|
||||||
return effects.stream()
|
return effects.stream()
|
||||||
.map(potionEffect -> {
|
.map(potionEffect -> {
|
||||||
final String key = getEffectId(potionEffect.getEffectType());
|
final String key = getEffectId(potionEffect.getEffectType().value());
|
||||||
return key != null ? new Effect(
|
return key != null ? new Effect(
|
||||||
key,
|
key,
|
||||||
potionEffect.getAmplifier(),
|
potionEffect.getAmplifier(),
|
||||||
@@ -310,16 +309,16 @@ public abstract class FabricData implements Data {
|
|||||||
public static FabricData.Advancements adapt(@NotNull ServerPlayerEntity player) {
|
public static FabricData.Advancements adapt(@NotNull ServerPlayerEntity player) {
|
||||||
final MinecraftServer server = Objects.requireNonNull(player.getServer(), "Server is null");
|
final MinecraftServer server = Objects.requireNonNull(player.getServer(), "Server is null");
|
||||||
final List<Advancement> advancements = Lists.newArrayList();
|
final List<Advancement> advancements = Lists.newArrayList();
|
||||||
forEachAdvancement(server, advancement -> {
|
forEachAdvancementEntry(server, advancementEntry -> {
|
||||||
final AdvancementProgress advancementProgress = player.getAdvancementTracker().getProgress(advancement);
|
final AdvancementProgress advancementProgress = player.getAdvancementTracker().getProgress(advancementEntry);
|
||||||
final Map<String, Date> awardedCriteria = Maps.newHashMap();
|
final Map<String, Date> awardedCriteria = Maps.newHashMap();
|
||||||
|
|
||||||
advancementProgress.getObtainedCriteria().forEach((criteria) -> awardedCriteria.put(criteria,
|
advancementProgress.getObtainedCriteria().forEach((criteria) -> awardedCriteria.put(criteria,
|
||||||
advancementProgress.getEarliestProgressObtainDate()));
|
Date.from(advancementProgress.getEarliestProgressObtainDate())));
|
||||||
|
|
||||||
// Only save the advancement if criteria has been completed
|
// Only save the advancement if criteria has been completed
|
||||||
if (!awardedCriteria.isEmpty()) {
|
if (!awardedCriteria.isEmpty()) {
|
||||||
advancements.add(Advancement.adapt(advancement.getId().toString(), awardedCriteria));
|
advancements.add(Advancement.adapt(advancementEntry.id().asString(), awardedCriteria));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return new FabricData.Advancements(advancements);
|
return new FabricData.Advancements(advancements);
|
||||||
@@ -334,10 +333,10 @@ public abstract class FabricData implements Data {
|
|||||||
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||||
final ServerPlayerEntity player = user.getPlayer();
|
final ServerPlayerEntity player = user.getPlayer();
|
||||||
final MinecraftServer server = Objects.requireNonNull(player.getServer(), "Server is null");
|
final MinecraftServer server = Objects.requireNonNull(player.getServer(), "Server is null");
|
||||||
plugin.runAsync(() -> forEachAdvancement(server, advancement -> {
|
plugin.runAsync(() -> forEachAdvancementEntry(server, advancementEntry -> {
|
||||||
final AdvancementProgress progress = player.getAdvancementTracker().getProgress(advancement);
|
final AdvancementProgress progress = player.getAdvancementTracker().getProgress(advancementEntry);
|
||||||
final Optional<Advancement> record = completed.stream()
|
final Optional<Advancement> record = completed.stream()
|
||||||
.filter(r -> r.getKey().equals(advancement.getId().toString()))
|
.filter(r -> r.getKey().equals(advancementEntry.id().toString()))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
if (record.isEmpty()) {
|
if (record.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
@@ -346,7 +345,7 @@ public abstract class FabricData implements Data {
|
|||||||
final Map<String, Date> criteria = record.get().getCompletedCriteria();
|
final Map<String, Date> criteria = record.get().getCompletedCriteria();
|
||||||
final List<String> awarded = Lists.newArrayList(progress.getObtainedCriteria());
|
final List<String> awarded = Lists.newArrayList(progress.getObtainedCriteria());
|
||||||
this.setAdvancement(
|
this.setAdvancement(
|
||||||
plugin, advancement, player, user,
|
plugin, advancementEntry, player, user,
|
||||||
criteria.keySet().stream().filter(key -> !awarded.contains(key)).toList(),
|
criteria.keySet().stream().filter(key -> !awarded.contains(key)).toList(),
|
||||||
awarded.stream().filter(key -> !criteria.containsKey(key)).toList()
|
awarded.stream().filter(key -> !criteria.containsKey(key)).toList()
|
||||||
);
|
);
|
||||||
@@ -354,7 +353,7 @@ public abstract class FabricData implements Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setAdvancement(@NotNull FabricHuskSync plugin,
|
private void setAdvancement(@NotNull FabricHuskSync plugin,
|
||||||
@NotNull net.minecraft.advancement.Advancement advancement,
|
@NotNull net.minecraft.advancement.AdvancementEntry advancementEntry,
|
||||||
@NotNull ServerPlayerEntity player,
|
@NotNull ServerPlayerEntity player,
|
||||||
@NotNull FabricUser user,
|
@NotNull FabricUser user,
|
||||||
@NotNull List<String> toAward,
|
@NotNull List<String> toAward,
|
||||||
@@ -366,8 +365,8 @@ public abstract class FabricData implements Data {
|
|||||||
|
|
||||||
// Award and revoke advancement criteria
|
// Award and revoke advancement criteria
|
||||||
final PlayerAdvancementTracker progress = player.getAdvancementTracker();
|
final PlayerAdvancementTracker progress = player.getAdvancementTracker();
|
||||||
toAward.forEach(a -> progress.grantCriterion(advancement, a));
|
toAward.forEach(a -> progress.grantCriterion(advancementEntry, a));
|
||||||
toRevoke.forEach(r -> progress.revokeCriterion(advancement, r));
|
toRevoke.forEach(r -> progress.revokeCriterion(advancementEntry, r));
|
||||||
|
|
||||||
// Restore player exp level & progress
|
// Restore player exp level & progress
|
||||||
if (!toAward.isEmpty()
|
if (!toAward.isEmpty()
|
||||||
@@ -378,9 +377,9 @@ public abstract class FabricData implements Data {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Performs a consuming function for every advancement registered on the server
|
// Performs a consuming function for every advancement entry registered on the server
|
||||||
private static void forEachAdvancement(@NotNull MinecraftServer server,
|
private static void forEachAdvancementEntry(@NotNull MinecraftServer server,
|
||||||
@NotNull ThrowingConsumer<net.minecraft.advancement.Advancement> con) {
|
@NotNull ThrowingConsumer<net.minecraft.advancement.AdvancementEntry> con) {
|
||||||
server.getAdvancementLoader().getAdvancements().forEach(con);
|
server.getAdvancementLoader().getAdvancements().forEach(con);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,9 +422,9 @@ public abstract class FabricData implements Data {
|
|||||||
player.getWorld(), "World is null"
|
player.getWorld(), "World is null"
|
||||||
).getRegistryKey().getValue().toString(),
|
).getRegistryKey().getValue().toString(),
|
||||||
UUID.nameUUIDFromBytes(
|
UUID.nameUUIDFromBytes(
|
||||||
player.getWorld().getDimensionKey().getValue().toString().getBytes()
|
player.getWorld().getDimensionEntry().getIdAsString().getBytes()
|
||||||
),
|
),
|
||||||
player.getWorld().getDimensionKey().getValue().toString()
|
player.getWorld().getDimensionEntry().getIdAsString()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -436,18 +435,15 @@ public abstract class FabricData implements Data {
|
|||||||
final MinecraftServer server = plugin.getMinecraftServer();
|
final MinecraftServer server = plugin.getMinecraftServer();
|
||||||
try {
|
try {
|
||||||
player.dismountVehicle();
|
player.dismountVehicle();
|
||||||
FabricDimensions.teleport(
|
player.teleportTo(
|
||||||
player,
|
|
||||||
server.getWorld(server.getWorldRegistryKeys().stream()
|
|
||||||
.filter(key -> key.getValue().equals(Identifier.tryParse(world.name())))
|
|
||||||
.findFirst().orElseThrow(
|
|
||||||
() -> new IllegalStateException("Invalid world")
|
|
||||||
)),
|
|
||||||
new TeleportTarget(
|
new TeleportTarget(
|
||||||
new Vec3d(x, y, z),
|
server.getWorld(server.getWorldRegistryKeys().stream()
|
||||||
Vec3d.ZERO,
|
.filter(key -> key.getValue().equals(Identifier.tryParse(world.name())))
|
||||||
yaw,
|
.findFirst().orElseThrow(
|
||||||
pitch
|
() -> new IllegalStateException("Invalid world")
|
||||||
|
)),
|
||||||
|
player,
|
||||||
|
TeleportTarget.NO_OP
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
@@ -576,19 +572,19 @@ public abstract class FabricData implements Data {
|
|||||||
@NotNull
|
@NotNull
|
||||||
public static FabricData.Attributes adapt(@NotNull ServerPlayerEntity player, @NotNull HuskSync plugin) {
|
public static FabricData.Attributes adapt(@NotNull ServerPlayerEntity player, @NotNull HuskSync plugin) {
|
||||||
final List<Attribute> attributes = Lists.newArrayList();
|
final List<Attribute> attributes = Lists.newArrayList();
|
||||||
|
final AttributeSettings settings = plugin.getSettings().getSynchronization().getAttributes();
|
||||||
Registries.ATTRIBUTE.forEach(id -> {
|
Registries.ATTRIBUTE.forEach(id -> {
|
||||||
final EntityAttributeInstance instance = player.getAttributeInstance(id);
|
final EntityAttributeInstance instance = player.getAttributeInstance(RegistryEntry.of(id));
|
||||||
final Identifier key = Registries.ATTRIBUTE.getId(id);
|
final Identifier key = Registries.ATTRIBUTE.getId(id);
|
||||||
if (instance == null || key == null) {
|
if (instance == null || key == null || settings.isIgnoredAttribute(key.asString())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Set<Modifier> modifiers = Sets.newHashSet();
|
final Set<Modifier> modifiers = Sets.newHashSet();
|
||||||
instance.getModifiers().forEach(modifier -> modifiers.add(new Modifier(
|
instance.getModifiers().forEach(modifier -> modifiers.add(new Modifier(
|
||||||
modifier.getId(),
|
modifier.id().toString(),
|
||||||
modifier.getName(),
|
modifier.value(),
|
||||||
modifier.getValue(),
|
modifier.operation().getId(),
|
||||||
modifier.getOperation().getId(),
|
Modifier.ANY_EQUIPMENT_SLOT_GROUP
|
||||||
-1
|
|
||||||
)));
|
)));
|
||||||
attributes.add(new Attribute(
|
attributes.add(new Attribute(
|
||||||
key.toString(),
|
key.toString(),
|
||||||
@@ -615,10 +611,17 @@ public abstract class FabricData implements Data {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) {
|
protected void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) {
|
||||||
Registries.ATTRIBUTE.forEach(id -> applyAttribute(
|
final AttributeSettings settings = plugin.getSettings().getSynchronization().getAttributes();
|
||||||
user.getPlayer().getAttributeInstance(id),
|
Registries.ATTRIBUTE.forEach(id -> {
|
||||||
getAttribute(id).orElse(null)
|
final Identifier key = Registries.ATTRIBUTE.getId(id);
|
||||||
));
|
if (key == null || settings.isIgnoredAttribute(key.toString())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applyAttribute(
|
||||||
|
user.getPlayer().getAttributeInstance(RegistryEntry.of(id)),
|
||||||
|
getAttribute(id).orElse(null)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -627,14 +630,13 @@ public abstract class FabricData implements Data {
|
|||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
instance.setBaseValue(attribute == null ? instance.getAttribute().getDefaultValue() : attribute.baseValue());
|
|
||||||
instance.getModifiers().forEach(instance::removeModifier);
|
instance.getModifiers().forEach(instance::removeModifier);
|
||||||
|
instance.setBaseValue(attribute == null ? instance.getValue() : attribute.baseValue());
|
||||||
if (attribute != null) {
|
if (attribute != null) {
|
||||||
attribute.modifiers().forEach(modifier -> instance.addPersistentModifier(new EntityAttributeModifier(
|
attribute.modifiers().forEach(modifier -> instance.addTemporaryModifier(new EntityAttributeModifier(
|
||||||
modifier.uuid(),
|
Identifier.of(modifier.uuid().toString()),
|
||||||
modifier.name(),
|
|
||||||
modifier.amount(),
|
modifier.amount(),
|
||||||
EntityAttributeModifier.Operation.fromId(modifier.operationType())
|
EntityAttributeModifier.Operation.ID_TO_VALUE.apply(modifier.operation())
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -692,7 +694,7 @@ public abstract class FabricData implements Data {
|
|||||||
@NotNull
|
@NotNull
|
||||||
public static FabricData.Hunger adapt(@NotNull ServerPlayerEntity player) {
|
public static FabricData.Hunger adapt(@NotNull ServerPlayerEntity player) {
|
||||||
final HungerManager hunger = player.getHungerManager();
|
final HungerManager hunger = player.getHungerManager();
|
||||||
return from(hunger.getFoodLevel(), hunger.getSaturationLevel(), hunger.getExhaustion());
|
return from(hunger.getFoodLevel(), hunger.getSaturationLevel(), ((HungerManagerMixin) hunger).getExhaustion());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -706,7 +708,7 @@ public abstract class FabricData implements Data {
|
|||||||
final HungerManager hunger = player.getHungerManager();
|
final HungerManager hunger = player.getHungerManager();
|
||||||
hunger.setFoodLevel(foodLevel);
|
hunger.setFoodLevel(foodLevel);
|
||||||
hunger.setSaturationLevel(saturation);
|
hunger.setSaturationLevel(saturation);
|
||||||
hunger.setExhaustion(exhaustion);
|
((HungerManagerMixin) hunger).setExhaustion(exhaustion);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import lombok.AllArgsConstructor;
|
|||||||
import net.minecraft.datafixer.TypeReferences;
|
import net.minecraft.datafixer.TypeReferences;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.item.ItemStack;
|
||||||
import net.minecraft.nbt.*;
|
import net.minecraft.nbt.*;
|
||||||
|
import net.minecraft.registry.DynamicRegistryManager;
|
||||||
import net.william278.desertwell.util.Version;
|
import net.william278.desertwell.util.Version;
|
||||||
import net.william278.husksync.FabricHuskSync;
|
import net.william278.husksync.FabricHuskSync;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
@@ -94,7 +95,7 @@ public abstract class FabricSerializer {
|
|||||||
public String serialize(@NotNull FabricData.Items.Inventory data) throws SerializationException {
|
public String serialize(@NotNull FabricData.Items.Inventory data) throws SerializationException {
|
||||||
try {
|
try {
|
||||||
final NbtCompound root = new NbtCompound();
|
final NbtCompound root = new NbtCompound();
|
||||||
root.put(ITEMS_TAG, serializeItemArray(data.getContents()));
|
root.put(ITEMS_TAG, serializeItemArray(data.getContents(), (FabricHuskSync) getPlugin()));
|
||||||
root.putInt(HELD_ITEM_SLOT_TAG, data.getHeldItemSlot());
|
root.putInt(HELD_ITEM_SLOT_TAG, data.getHeldItemSlot());
|
||||||
return root.toString();
|
return root.toString();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
@@ -132,7 +133,7 @@ public abstract class FabricSerializer {
|
|||||||
@Override
|
@Override
|
||||||
public String serialize(@NotNull FabricData.Items.EnderChest data) throws SerializationException {
|
public String serialize(@NotNull FabricData.Items.EnderChest data) throws SerializationException {
|
||||||
try {
|
try {
|
||||||
return serializeItemArray(data.getContents()).toString();
|
return serializeItemArray(data.getContents(), (FabricHuskSync) getPlugin()).toString();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
throw new SerializationException("Failed to serialize ender chest item NBT to string", e);
|
throw new SerializationException("Failed to serialize ender chest item NBT to string", e);
|
||||||
}
|
}
|
||||||
@@ -141,17 +142,6 @@ public abstract class FabricSerializer {
|
|||||||
|
|
||||||
private interface ItemDeserializer {
|
private interface ItemDeserializer {
|
||||||
|
|
||||||
int VERSION1_16_5 = 2586;
|
|
||||||
int VERSION1_17_1 = 2730;
|
|
||||||
int VERSION1_18_2 = 2975;
|
|
||||||
int VERSION1_19_2 = 3120;
|
|
||||||
int VERSION1_19_4 = 3337;
|
|
||||||
int VERSION1_20_1 = 3465;
|
|
||||||
int VERSION1_20_2 = 3578; // Future
|
|
||||||
int VERSION1_20_4 = 3700; // Future
|
|
||||||
int VERSION1_20_5 = 3837; // Future
|
|
||||||
int VERSION1_21 = 3953; // Future
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
default ItemStack[] getItems(@NotNull NbtCompound tag, @NotNull Version mcVersion, @NotNull FabricHuskSync plugin) {
|
default ItemStack[] getItems(@NotNull NbtCompound tag, @NotNull Version mcVersion, @NotNull FabricHuskSync plugin) {
|
||||||
try {
|
try {
|
||||||
@@ -161,9 +151,10 @@ public abstract class FabricSerializer {
|
|||||||
|
|
||||||
final ItemStack[] contents = new ItemStack[tag.getInt("size")];
|
final ItemStack[] contents = new ItemStack[tag.getInt("size")];
|
||||||
final NbtList itemList = tag.getList("items", NbtElement.COMPOUND_TYPE);
|
final NbtList itemList = tag.getList("items", NbtElement.COMPOUND_TYPE);
|
||||||
|
final DynamicRegistryManager registryManager = plugin.getMinecraftServer().getRegistryManager();
|
||||||
itemList.forEach(element -> {
|
itemList.forEach(element -> {
|
||||||
final NbtCompound compound = (NbtCompound) element;
|
final NbtCompound compound = (NbtCompound) element;
|
||||||
contents[compound.getInt("Slot")] = ItemStack.fromNbt(compound);
|
contents[compound.getInt("Slot")] = ItemStack.fromNbt(registryManager, element).get();
|
||||||
});
|
});
|
||||||
plugin.debug(Arrays.toString(contents));
|
plugin.debug(Arrays.toString(contents));
|
||||||
return contents;
|
return contents;
|
||||||
@@ -174,18 +165,18 @@ public abstract class FabricSerializer {
|
|||||||
|
|
||||||
// Serialize items slot-by-slot
|
// Serialize items slot-by-slot
|
||||||
@NotNull
|
@NotNull
|
||||||
default NbtCompound serializeItemArray(@Nullable ItemStack @NotNull [] items) {
|
default NbtCompound serializeItemArray(@Nullable ItemStack @NotNull [] items, @NotNull FabricHuskSync plugin) {
|
||||||
final NbtCompound container = new NbtCompound();
|
final NbtCompound container = new NbtCompound();
|
||||||
container.putInt("size", items.length);
|
container.putInt("size", items.length);
|
||||||
final NbtList itemList = new NbtList();
|
final NbtList itemList = new NbtList();
|
||||||
|
final DynamicRegistryManager registryManager = plugin.getMinecraftServer().getRegistryManager();
|
||||||
for (int i = 0; i < items.length; i++) {
|
for (int i = 0; i < items.length; i++) {
|
||||||
final ItemStack item = items[i];
|
final ItemStack item = items[i];
|
||||||
if (item == null || item.isEmpty()) {
|
if (item == null || item.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
NbtCompound entry = new NbtCompound();
|
NbtCompound entry = (NbtCompound) item.toNbt(registryManager);
|
||||||
entry.putInt("Slot", i);
|
entry.putInt("Slot", i);
|
||||||
item.writeNbt(entry);
|
|
||||||
itemList.add(entry);
|
itemList.add(entry);
|
||||||
}
|
}
|
||||||
container.put(ITEMS_TAG, itemList);
|
container.put(ITEMS_TAG, itemList);
|
||||||
@@ -198,6 +189,7 @@ public abstract class FabricSerializer {
|
|||||||
final int size = items.getInt("size");
|
final int size = items.getInt("size");
|
||||||
final NbtList list = items.getList("items", NbtElement.COMPOUND_TYPE);
|
final NbtList list = items.getList("items", NbtElement.COMPOUND_TYPE);
|
||||||
final ItemStack[] itemStacks = new ItemStack[size];
|
final ItemStack[] itemStacks = new ItemStack[size];
|
||||||
|
final DynamicRegistryManager registryManager = plugin.getMinecraftServer().getRegistryManager();
|
||||||
Arrays.fill(itemStacks, ItemStack.EMPTY);
|
Arrays.fill(itemStacks, ItemStack.EMPTY);
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
if (list.getCompound(i) == null) {
|
if (list.getCompound(i) == null) {
|
||||||
@@ -205,38 +197,21 @@ public abstract class FabricSerializer {
|
|||||||
}
|
}
|
||||||
final NbtCompound compound = list.getCompound(i);
|
final NbtCompound compound = list.getCompound(i);
|
||||||
final int slot = compound.getInt("Slot");
|
final int slot = compound.getInt("Slot");
|
||||||
itemStacks[slot] = ItemStack.fromNbt(upgradeItemData(list.getCompound(i), mcVersion, plugin));
|
itemStacks[slot] = ItemStack.fromNbt(registryManager, upgradeItemData(list.getCompound(i), mcVersion, plugin)).get();
|
||||||
}
|
}
|
||||||
return itemStacks;
|
return itemStacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@SuppressWarnings({"rawtypes", "unchecked"}) // For NBTOps lookup
|
@SuppressWarnings({"rawtypes", "unchecked"}) // For NBTOps lookup
|
||||||
private NbtCompound upgradeItemData(@NotNull NbtCompound tag, @NotNull Version mcVersion,
|
private NbtCompound upgradeItemData(@NotNull NbtCompound tag, @NotNull Version mcVersion,
|
||||||
@NotNull FabricHuskSync plugin) {
|
@NotNull FabricHuskSync plugin) {
|
||||||
return (NbtCompound) plugin.getMinecraftServer().getDataFixer().update(
|
return (NbtCompound) plugin.getMinecraftServer().getDataFixer().update(
|
||||||
TypeReferences.ITEM_STACK, new Dynamic<Object>((DynamicOps) NbtOps.INSTANCE, tag),
|
TypeReferences.ITEM_STACK, new Dynamic<Object>((DynamicOps) NbtOps.INSTANCE, tag),
|
||||||
getDataVersion(mcVersion), getDataVersion(plugin.getMinecraftVersion())
|
plugin.getDataVersion(mcVersion), plugin.getDataVersion(plugin.getMinecraftVersion())
|
||||||
).getValue();
|
).getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getDataVersion(@NotNull Version mcVersion) {
|
|
||||||
return switch (mcVersion.toStringWithoutMetadata()) {
|
|
||||||
case "1.16", "1.16.1", "1.16.2", "1.16.3", "1.16.4", "1.16.5" -> VERSION1_16_5;
|
|
||||||
case "1.17", "1.17.1" -> VERSION1_17_1;
|
|
||||||
case "1.18", "1.18.1", "1.18.2" -> VERSION1_18_2;
|
|
||||||
case "1.19", "1.19.1", "1.19.2" -> VERSION1_19_2;
|
|
||||||
case "1.19.4" -> VERSION1_19_4;
|
|
||||||
case "1.20", "1.20.1" -> VERSION1_20_1;
|
|
||||||
case "1.20.2" -> VERSION1_20_2; // Future
|
|
||||||
case "1.20.4" -> VERSION1_20_4; // Future
|
|
||||||
case "1.20.5", "1.20.6" -> VERSION1_20_5; // Future
|
|
||||||
case "1.21" -> VERSION1_21; // Future
|
|
||||||
default -> VERSION1_20_1; // Current supported ver
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class PotionEffects extends FabricSerializer implements Serializer<FabricData.PotionEffects> {
|
public static class PotionEffects extends FabricSerializer implements Serializer<FabricData.PotionEffects> {
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ public interface FabricUserDataHolder extends UserDataHolder {
|
|||||||
@Override
|
@Override
|
||||||
default Optional<Data.Items.EnderChest> getEnderChest() {
|
default Optional<Data.Items.EnderChest> getEnderChest() {
|
||||||
return Optional.of(FabricData.Items.EnderChest.adapt(
|
return Optional.of(FabricData.Items.EnderChest.adapt(
|
||||||
getPlayer().getEnderChestInventory().stacks
|
getPlayer().getEnderChestInventory().getHeldStacks()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import net.minecraft.server.network.ServerPlayerEntity;
|
|||||||
import net.minecraft.server.world.ServerWorld;
|
import net.minecraft.server.world.ServerWorld;
|
||||||
import net.minecraft.util.ActionResult;
|
import net.minecraft.util.ActionResult;
|
||||||
import net.minecraft.util.Hand;
|
import net.minecraft.util.Hand;
|
||||||
import net.minecraft.util.TypedActionResult;
|
|
||||||
import net.minecraft.util.hit.BlockHitResult;
|
import net.minecraft.util.hit.BlockHitResult;
|
||||||
import net.minecraft.util.hit.EntityHitResult;
|
import net.minecraft.util.hit.EntityHitResult;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
@@ -124,9 +123,8 @@ public class FabricEventListener extends EventListener implements LockedHandler
|
|||||||
return (cancelPlayerEvent(player.getUuid())) ? ActionResult.FAIL : ActionResult.PASS;
|
return (cancelPlayerEvent(player.getUuid())) ? ActionResult.FAIL : ActionResult.PASS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypedActionResult<ItemStack> handleItemInteract(PlayerEntity player, World world, Hand hand) {
|
private ActionResult handleItemInteract(PlayerEntity player, World world, Hand hand) {
|
||||||
ItemStack stackInHand = player.getStackInHand(hand);
|
return (cancelPlayerEvent(player.getUuid())) ? ActionResult.FAIL : ActionResult.PASS;
|
||||||
return (cancelPlayerEvent(player.getUuid())) ? TypedActionResult.fail(stackInHand) : TypedActionResult.pass(stackInHand);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleBlockBreak(World world, PlayerEntity player, BlockPos blockPos, BlockState blockState, BlockEntity blockEntity) {
|
private boolean handleBlockBreak(World world, PlayerEntity player, BlockPos blockPos, BlockState blockState, BlockEntity blockEntity) {
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.mixins;
|
||||||
|
|
||||||
|
import net.minecraft.entity.player.HungerManager;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
|
||||||
|
@Mixin(HungerManager.class)
|
||||||
|
public interface HungerManagerMixin {
|
||||||
|
|
||||||
|
@Accessor
|
||||||
|
float getExhaustion();
|
||||||
|
|
||||||
|
@Accessor("exhaustion")
|
||||||
|
void setExhaustion(float exhaustion);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -20,9 +20,11 @@
|
|||||||
package net.william278.husksync.mixins;
|
package net.william278.husksync.mixins;
|
||||||
|
|
||||||
import net.minecraft.enchantment.EnchantmentHelper;
|
import net.minecraft.enchantment.EnchantmentHelper;
|
||||||
|
import net.minecraft.enchantment.Enchantments;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
import net.minecraft.entity.player.PlayerInventory;
|
import net.minecraft.entity.player.PlayerInventory;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.registry.tag.TagKey;
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
import net.william278.husksync.event.PlayerDeathDropsCallback;
|
import net.william278.husksync.event.PlayerDeathDropsCallback;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -54,7 +56,7 @@ public class PlayerEntityMixin {
|
|||||||
final @Nullable ItemStack @NotNull [] toKeep = new ItemStack[inventory.size()];
|
final @Nullable ItemStack @NotNull [] toKeep = new ItemStack[inventory.size()];
|
||||||
for (int i = 0; i < inventory.size(); ++i) {
|
for (int i = 0; i < inventory.size(); ++i) {
|
||||||
ItemStack itemStack = inventory.getStack(i);
|
ItemStack itemStack = inventory.getStack(i);
|
||||||
if (!itemStack.isEmpty() && EnchantmentHelper.hasVanishingCurse(itemStack)) {
|
if (!itemStack.isEmpty() && EnchantmentHelper.hasAnyEnchantmentsIn(itemStack, TagKey.of(Enchantments.VANISHING_CURSE.getRegistryRef(), Enchantments.VANISHING_CURSE.getValue()))) {
|
||||||
toKeep[i] = null;
|
toKeep[i] = null;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -69,7 +71,7 @@ public class PlayerEntityMixin {
|
|||||||
final @Nullable ItemStack @NotNull [] toDrop = new ItemStack[inventory.size()];
|
final @Nullable ItemStack @NotNull [] toDrop = new ItemStack[inventory.size()];
|
||||||
for (int i = 0; i < inventory.size(); ++i) {
|
for (int i = 0; i < inventory.size(); ++i) {
|
||||||
ItemStack itemStack = inventory.getStack(i);
|
ItemStack itemStack = inventory.getStack(i);
|
||||||
if (!itemStack.isEmpty() && EnchantmentHelper.hasVanishingCurse(itemStack)) {
|
if (!itemStack.isEmpty() && EnchantmentHelper.hasAnyEnchantmentsIn(itemStack, TagKey.of(Enchantments.VANISHING_CURSE.getRegistryRef(), Enchantments.VANISHING_CURSE.getValue()))) {
|
||||||
toDrop[i] = itemStack;
|
toDrop[i] = itemStack;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import net.william278.husksync.event.ItemDropCallback;
|
|||||||
import net.william278.husksync.event.PlayerCommandCallback;
|
import net.william278.husksync.event.PlayerCommandCallback;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
@@ -44,19 +45,21 @@ public abstract class ServerPlayNetworkHandlerMixin {
|
|||||||
@Shadow
|
@Shadow
|
||||||
public ServerPlayerEntity player;
|
public ServerPlayerEntity player;
|
||||||
|
|
||||||
@Shadow
|
@Unique
|
||||||
public abstract void sendPacket(Packet<?> packet);
|
private void sendToPlayer(Packet<?> packet) {
|
||||||
|
this.player.networkHandler.sendPacket(packet);
|
||||||
|
}
|
||||||
|
|
||||||
@Inject(method = "onPlayerAction", at = @At("HEAD"), cancellable = true)
|
@Inject(method = "onPlayerAction", at = @At("HEAD"), cancellable = true)
|
||||||
public void onPlayerAction(PlayerActionC2SPacket packet, CallbackInfo ci) {
|
public void onPlayerAction(PlayerActionC2SPacket packet, CallbackInfo ci) {
|
||||||
if (packet.getAction() == PlayerActionC2SPacket.Action.DROP_ITEM
|
if (packet.getAction() == PlayerActionC2SPacket.Action.DROP_ITEM
|
||||||
|| packet.getAction() == PlayerActionC2SPacket.Action.DROP_ALL_ITEMS) {
|
|| packet.getAction() == PlayerActionC2SPacket.Action.DROP_ALL_ITEMS) {
|
||||||
ItemStack stack = player.getStackInHand(Hand.MAIN_HAND);
|
ItemStack stack = player.getStackInHand(Hand.MAIN_HAND);
|
||||||
ActionResult result = ItemDropCallback.EVENT.invoker().interact(player, stack);
|
ActionResult result = ItemDropCallback.EVENT.invoker().interact(player, stack);
|
||||||
|
|
||||||
if (result == ActionResult.FAIL) {
|
if (result == ActionResult.FAIL) {
|
||||||
ci.cancel();
|
ci.cancel();
|
||||||
this.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(
|
sendToPlayer(new ScreenHandlerSlotUpdateS2CPacket(
|
||||||
-2,
|
-2,
|
||||||
1,
|
1,
|
||||||
player.getInventory().getSlotWithStack(stack),
|
player.getInventory().getSlotWithStack(stack),
|
||||||
@@ -69,30 +72,34 @@ public abstract class ServerPlayNetworkHandlerMixin {
|
|||||||
@Inject(method = "onClickSlot", at = @At("HEAD"), cancellable = true)
|
@Inject(method = "onClickSlot", at = @At("HEAD"), cancellable = true)
|
||||||
public void onClickSlot(ClickSlotC2SPacket packet, CallbackInfo ci) {
|
public void onClickSlot(ClickSlotC2SPacket packet, CallbackInfo ci) {
|
||||||
int slot = packet.getSlot();
|
int slot = packet.getSlot();
|
||||||
if (slot < 0) return;
|
if (slot < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ItemStack stack = this.player.getInventory().getStack(slot);
|
ItemStack stack = this.player.getInventory().getStack(slot);
|
||||||
ActionResult result = ItemDropCallback.EVENT.invoker().interact(player, stack);
|
ActionResult result = ItemDropCallback.EVENT.invoker().interact(player, stack);
|
||||||
|
|
||||||
if (result == ActionResult.FAIL) {
|
if (result == ActionResult.FAIL) {
|
||||||
ci.cancel();
|
ci.cancel();
|
||||||
this.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(-2, 1, slot, stack));
|
sendToPlayer(new ScreenHandlerSlotUpdateS2CPacket(-2, 1, slot, stack));
|
||||||
this.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(-1, 1, -1, ItemStack.EMPTY));
|
sendToPlayer(new ScreenHandlerSlotUpdateS2CPacket(-1, 1, -1, ItemStack.EMPTY));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject(method = "onCreativeInventoryAction", at = @At("HEAD"), cancellable = true)
|
@Inject(method = "onCreativeInventoryAction", at = @At("HEAD"), cancellable = true)
|
||||||
public void onCreativeInventoryAction(CreativeInventoryActionC2SPacket packet, CallbackInfo ci) {
|
public void onCreativeInventoryAction(CreativeInventoryActionC2SPacket packet, CallbackInfo ci) {
|
||||||
int slot = packet.getSlot();
|
int slot = packet.slot();
|
||||||
if (slot < 0) return;
|
if (slot < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ItemStack stack = this.player.getInventory().getStack(slot);
|
ItemStack stack = this.player.getInventory().getStack(slot);
|
||||||
ActionResult result = ItemDropCallback.EVENT.invoker().interact(player, stack);
|
ActionResult result = ItemDropCallback.EVENT.invoker().interact(player, stack);
|
||||||
|
|
||||||
if (result == ActionResult.FAIL) {
|
if (result == ActionResult.FAIL) {
|
||||||
ci.cancel();
|
ci.cancel();
|
||||||
this.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(-2, 1, slot, stack));
|
sendToPlayer(new ScreenHandlerSlotUpdateS2CPacket(-2, 1, slot, stack));
|
||||||
this.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(-1, 1, -1, ItemStack.EMPTY));
|
sendToPlayer(new ScreenHandlerSlotUpdateS2CPacket(-1, 1, -1, ItemStack.EMPTY));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public class ServerWorldMixin {
|
|||||||
@Shadow
|
@Shadow
|
||||||
private MinecraftServer server;
|
private MinecraftServer server;
|
||||||
|
|
||||||
@Inject(method = "saveLevel", at = @At("HEAD"))
|
@Inject(method = "savePersistentState", at = @At("HEAD"))
|
||||||
public void saveLevel(CallbackInfo ci) {
|
public void saveLevel(CallbackInfo ci) {
|
||||||
if (server.isStopping() || server.isStopped()) {
|
if (server.isStopping() || server.isStopped()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import eu.pb4.sgui.api.elements.GuiElementInterface;
|
|||||||
import eu.pb4.sgui.api.gui.SimpleGui;
|
import eu.pb4.sgui.api.gui.SimpleGui;
|
||||||
import me.lucko.fabric.api.permissions.v0.Permissions;
|
import me.lucko.fabric.api.permissions.v0.Permissions;
|
||||||
import net.kyori.adventure.audience.Audience;
|
import net.kyori.adventure.audience.Audience;
|
||||||
import net.kyori.adventure.platform.fabric.FabricServerAudiences;
|
import net.kyori.adventure.platform.modcommon.MinecraftServerAudiences;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.item.ItemStack;
|
||||||
import net.minecraft.screen.GenericContainerScreenHandler;
|
import net.minecraft.screen.GenericContainerScreenHandler;
|
||||||
import net.minecraft.screen.ScreenHandlerType;
|
import net.minecraft.screen.ScreenHandlerType;
|
||||||
@@ -102,7 +102,7 @@ public class FabricUser extends OnlineUser implements FabricUserDataHolder {
|
|||||||
this.editable = editable;
|
this.editable = editable;
|
||||||
|
|
||||||
// Set title, items
|
// Set title, items
|
||||||
this.setTitle(((FabricServerAudiences) plugin.getAudiences()).toNative(title.toComponent()));
|
this.setTitle(((MinecraftServerAudiences) plugin.getAudiences()).asNative(title.toComponent()));
|
||||||
this.setLockPlayerInventory(!editable);
|
this.setLockPlayerInventory(!editable);
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
final ItemStack item = items.getContents()[i];
|
final ItemStack item = items.getContents()[i];
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"depends": {
|
"depends": {
|
||||||
"fabricloader": ">=${fabric_loader_version}",
|
"fabricloader": ">=${fabric_loader_version}",
|
||||||
"minecraft": ">=${fabric_minecraft_version}",
|
"minecraft": ">=${minecraft_version}",
|
||||||
"fabric-api": "*"
|
"fabric-api": "*"
|
||||||
},
|
},
|
||||||
"suggests": {
|
"suggests": {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"package": "net.william278.husksync.mixins",
|
"package": "net.william278.husksync.mixins",
|
||||||
"compatibilityLevel": "JAVA_17",
|
"compatibilityLevel": "JAVA_17",
|
||||||
"server": [
|
"server": [
|
||||||
|
"HungerManagerMixin",
|
||||||
"ItemEntityMixin",
|
"ItemEntityMixin",
|
||||||
"PlayerEntityMixin",
|
"PlayerEntityMixin",
|
||||||
"ServerPlayerEntityMixin",
|
"ServerPlayerEntityMixin",
|
||||||
|
|||||||
@@ -1,23 +1,31 @@
|
|||||||
|
# Gradle settings
|
||||||
org.gradle.jvmargs='-Dfile.encoding=UTF-8'
|
org.gradle.jvmargs='-Dfile.encoding=UTF-8'
|
||||||
|
|
||||||
org.gradle.daemon=true
|
org.gradle.daemon=true
|
||||||
javaVersion=17
|
javaVersion=21
|
||||||
|
|
||||||
plugin_version=3.6.8
|
# Plugin settings
|
||||||
|
plugin_version=3.7.3
|
||||||
|
minecraft_version=1.21.4
|
||||||
plugin_archive=husksync
|
plugin_archive=husksync
|
||||||
plugin_description=A modern, cross-server player data synchronization system
|
plugin_description=A modern, cross-server player data synchronization system
|
||||||
|
|
||||||
jedis_version=5.1.4
|
# Drivers
|
||||||
mysql_driver_version=9.0.0
|
jedis_version=5.2.0
|
||||||
mariadb_driver_version=3.4.1
|
mysql_driver_version=9.2.0
|
||||||
postgres_driver_version=42.7.3
|
mariadb_driver_version=3.5.1
|
||||||
mongodb_driver_version=5.1.2
|
postgres_driver_version=42.7.5
|
||||||
snappy_version=1.1.10.6
|
mongodb_driver_version=5.3.1
|
||||||
|
snappy_version=1.1.10.7
|
||||||
|
|
||||||
fabric_minecraft_version=1.20.1
|
# Spigot/Paper build settings
|
||||||
fabric_loader_version=0.15.11
|
bukkit_spigot_api=1.21.4-R0.1-SNAPSHOT
|
||||||
fabric_yarn_mappings=1.20.1+build.10
|
bukkit_paper_api=1.21.4-R0.1-SNAPSHOT
|
||||||
fabric_api_version=0.92.2+1.20.1
|
|
||||||
adventure_platform_fabric_version=5.9.0
|
# Fabric build settings
|
||||||
fabric_permissions_api_version=0.2-SNAPSHOT
|
fabric_loom_version=1.9-SNAPSHOT
|
||||||
sgui_version=1.2.2+1.20
|
fabric_loader_version=0.16.10
|
||||||
|
fabric_yarn_mappings=1.21.4+build.8
|
||||||
|
fabric_api_version=0.115.0+1.21.4
|
||||||
|
fabric_adventure_platform_version=6.2.0
|
||||||
|
fabric_permissions_api_version=0.3.3
|
||||||
|
fabric_sgui_version=1.8.2+1.21.4
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
6
gradlew
vendored
6
gradlew
vendored
@@ -15,6 +15,8 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
@@ -55,7 +57,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -84,7 +86,7 @@ done
|
|||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
|
|||||||
2
gradlew.bat
vendored
2
gradlew.bat
vendored
@@ -13,6 +13,8 @@
|
|||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'xyz.jpenilla.run-paper' version '2.3.0'
|
id 'xyz.jpenilla.run-paper' version '2.3.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':bukkit')
|
implementation project(':bukkit')
|
||||||
compileOnly project(':common')
|
compileOnly project(':common')
|
||||||
|
|
||||||
implementation 'net.william278.uniform:uniform-paper:1.2.1'
|
implementation 'net.william278.uniform:uniform-paper:1.3'
|
||||||
|
|
||||||
compileOnly 'io.papermc.paper:paper-api:1.19.4-R0.1-SNAPSHOT'
|
compileOnly "io.papermc.paper:paper-api:${bukkit_paper_api}"
|
||||||
compileOnly 'org.jetbrains:annotations:24.1.0'
|
compileOnly 'org.jetbrains:annotations:26.0.1'
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.34'
|
compileOnly 'org.projectlombok:lombok:1.18.36'
|
||||||
|
|
||||||
annotationProcessor 'org.projectlombok:lombok:1.18.34'
|
annotationProcessor 'org.projectlombok:lombok:1.18.36'
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
@@ -49,6 +49,6 @@ shadowJar {
|
|||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
runServer {
|
runServer {
|
||||||
minecraftVersion('1.21.1')
|
minecraftVersion('1.21.4')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
package net.william278.husksync;
|
package net.william278.husksync;
|
||||||
|
|
||||||
import net.kyori.adventure.audience.Audience;
|
import net.kyori.adventure.audience.Audience;
|
||||||
|
import net.william278.desertwell.util.Version;
|
||||||
import net.william278.husksync.listener.BukkitEventListener;
|
import net.william278.husksync.listener.BukkitEventListener;
|
||||||
import net.william278.husksync.listener.PaperEventListener;
|
import net.william278.husksync.listener.PaperEventListener;
|
||||||
import net.william278.uniform.Uniform;
|
import net.william278.uniform.Uniform;
|
||||||
@@ -29,7 +30,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings({"unchecked", "unused"})
|
||||||
public class PaperHuskSync extends BukkitHuskSync {
|
public class PaperHuskSync extends BukkitHuskSync {
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -45,6 +46,12 @@ public class PaperHuskSync extends BukkitHuskSync {
|
|||||||
return player == null || !player.isOnline() ? Audience.empty() : player;
|
return player == null || !player.isOnline() ? Audience.empty() : player;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Version getMinecraftVersion() {
|
||||||
|
return Version.fromString(getServer().getMinecraftVersion());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
public Uniform getUniform() {
|
public Uniform getUniform() {
|
||||||
|
|||||||
@@ -43,6 +43,12 @@ public class PaperEventListener extends BukkitEventListener {
|
|||||||
super(plugin);
|
super(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable() {
|
||||||
|
getPlugin().getServer().getPluginManager().registerEvents(this, getPlugin());
|
||||||
|
lockedHandler.onEnable();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handlePlayerDeath(@NotNull PlayerDeathEvent event) {
|
public void handlePlayerDeath(@NotNull PlayerDeathEvent event) {
|
||||||
// If the player is locked or the plugin disabling, clear their drops
|
// If the player is locked or the plugin disabling, clear their drops
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from tqdm import tqdm
|
|||||||
class Parameters:
|
class Parameters:
|
||||||
root_dir = './servers/'
|
root_dir = './servers/'
|
||||||
proxy_version = "1.21"
|
proxy_version = "1.21"
|
||||||
minecraft_version = '1.21.1'
|
minecraft_version = '1.21.4'
|
||||||
eula_agreement = 'true'
|
eula_agreement = 'true'
|
||||||
|
|
||||||
backend_names = ['alpha', 'beta']
|
backend_names = ['alpha', 'beta']
|
||||||
|
|||||||
Reference in New Issue
Block a user