mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-23 16:49:19 +00:00
Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
52ec138273 | ||
|
|
0f7a866652 | ||
|
|
eeb52ac41e | ||
|
|
4c7ec9ec21 | ||
|
|
2f9064c4c6 | ||
|
|
5c234cdb1d | ||
|
|
7d8a74381b | ||
|
|
04a7793585 | ||
|
|
ea068529f6 | ||
|
|
fead3df0d8 | ||
|
|
0c5a42a344 | ||
|
|
75a2378ea8 | ||
|
|
662fc96ad5 | ||
|
|
f456443da0 | ||
|
|
07da1c04ce | ||
|
|
845abf370a | ||
|
|
83b5209a75 | ||
|
|
8e9850dd19 | ||
|
|
1d24209b68 | ||
|
|
da70a54d78 | ||
|
|
fc7330213a | ||
|
|
d8272ba52d | ||
|
|
315f0eeb2f | ||
|
|
8e83617ac4 | ||
|
|
212bb0beb8 | ||
|
|
c16231b12b | ||
|
|
93f7294859 | ||
|
|
32ac57e2a4 | ||
|
|
c949c976d6 | ||
|
|
ab736829f2 | ||
|
|
4433926ce7 | ||
|
|
f819fd4d5e | ||
|
|
e7659255fe | ||
|
|
0dee2e8319 | ||
|
|
7b35c47315 | ||
|
|
5056a794d8 | ||
|
|
5e6068431a | ||
|
|
8d69508689 | ||
|
|
efb6d8a7de | ||
|
|
79d9778378 | ||
|
|
6a6695e447 | ||
|
|
8862e6cd70 | ||
|
|
0b29de9efc | ||
|
|
962cdfce0b | ||
|
|
0c527202e5 | ||
|
|
d4e33aa9d2 | ||
|
|
2fcd58fc18 | ||
|
|
3d10b2324f | ||
|
|
31419f3b97 | ||
|
|
8105ac27fc | ||
|
|
44f251a948 | ||
|
|
463e707d27 | ||
|
|
2d85910744 |
44
.github/workflows/ci.yml
vendored
44
.github/workflows/ci.yml
vendored
@@ -1,44 +0,0 @@
|
|||||||
name: CI Tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ 'master' ]
|
|
||||||
paths-ignore:
|
|
||||||
- 'docs/**'
|
|
||||||
- 'workflows/**'
|
|
||||||
- 'README.md'
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
checks: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: 'Checkout for CI 🛎️'
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: 'Set up JDK 17 📦'
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
java-version: '17'
|
|
||||||
distribution: 'temurin'
|
|
||||||
- name: 'Build with Gradle 🏗️'
|
|
||||||
uses: gradle/gradle-build-action@v3
|
|
||||||
with:
|
|
||||||
arguments: build test publish
|
|
||||||
env:
|
|
||||||
SNAPSHOTS_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
|
||||||
SNAPSHOTS_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
|
||||||
- name: 'Publish Test Report 📊'
|
|
||||||
uses: mikepenz/action-junit-report@v4
|
|
||||||
if: success() || failure() # Continue on failure
|
|
||||||
with:
|
|
||||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
|
||||||
- name: 'Fetch Version Name 📝'
|
|
||||||
run: |
|
|
||||||
echo "::set-output name=VERSION_NAME::$(${{github.workspace}}/gradlew properties --no-daemon --console=plain -q | grep "^version:" | awk '{printf $2}')"
|
|
||||||
id: fetch-version
|
|
||||||
- name: Get Version
|
|
||||||
run: |
|
|
||||||
echo "version_name=${{steps.fetch-version.outputs.VERSION_NAME}}" >> $GITHUB_ENV
|
|
||||||
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'
|
||||||
|
env:
|
||||||
|
SNAPSHOTS_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
||||||
|
SNAPSHOTS_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||||
|
- name: '[Current - 1.20.1] Build 🛎️'
|
||||||
|
run: |
|
||||||
|
./gradlew clean build publish
|
||||||
|
- name: 'Publish Test Report 📊'
|
||||||
|
uses: mikepenz/action-junit-report@v4
|
||||||
|
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
|
||||||
68
.github/workflows/ci_1.21.1.yml
vendored
Normal file
68
.github/workflows/ci_1.21.1.yml
vendored
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
name: CI Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ 'master' ]
|
||||||
|
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
|
||||||
|
env:
|
||||||
|
SNAPSHOTS_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
||||||
|
SNAPSHOTS_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||||
|
- name: '[Current - 1.21.1] Build 🛎️'
|
||||||
|
run: |
|
||||||
|
./gradlew clean build publish
|
||||||
|
- name: 'Publish Test Report 📊'
|
||||||
|
uses: mikepenz/action-junit-report@v4
|
||||||
|
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
|
||||||
4
.github/workflows/pr_tests.yml
vendored
4
.github/workflows/pr_tests.yml
vendored
@@ -14,10 +14,10 @@ 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
|
||||||
|
|||||||
64
.github/workflows/release.yml
vendored
64
.github/workflows/release.yml
vendored
@@ -8,26 +8,76 @@ 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.8 🏗️'
|
||||||
uses: gradle/gradle-build-action@v3
|
uses: gradle/actions/setup-gradle@v4
|
||||||
with:
|
with:
|
||||||
arguments: build test publish
|
gradle-version: '8.8'
|
||||||
|
- name: '[Current - 1.21.1] Checkout for CI 🛎️'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
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'
|
||||||
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: '[Current - 1.21.1] Build 🛎️'
|
||||||
|
run: |
|
||||||
|
mkdir target
|
||||||
|
cd 1_21_1
|
||||||
|
./gradlew clean build publish -Dforce-hide-version-meta=1
|
||||||
|
cp -rf target/* ../target/
|
||||||
|
cd ..
|
||||||
|
- 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 ..
|
||||||
- name: 'Publish Test Report 📊'
|
- name: 'Publish Test Report 📊'
|
||||||
uses: mikepenz/action-junit-report@v4
|
uses: mikepenz/action-junit-report@v4
|
||||||
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: 'Publish to William278.net 🚀'
|
||||||
|
uses: WiIIiam278/bones-publish-action@v1
|
||||||
|
with:
|
||||||
|
api-key: ${{ secrets.BONES_API_KEY }}
|
||||||
|
project: 'husksync'
|
||||||
|
channel: 'release'
|
||||||
|
version: ${{ github.event.release.tag_name }}
|
||||||
|
changelog: ${{ github.event.release.body }}
|
||||||
|
distro-names: |
|
||||||
|
paper-1.21.1
|
||||||
|
fabric-1.21.1
|
||||||
|
paper-1.20.1
|
||||||
|
fabric-1.20.1
|
||||||
|
distro-groups: |
|
||||||
|
paper
|
||||||
|
fabric
|
||||||
|
paper
|
||||||
|
fabric
|
||||||
|
distro-descriptions: |
|
||||||
|
Paper 1.21.1
|
||||||
|
Fabric 1.21.1
|
||||||
|
Paper 1.20.1
|
||||||
|
Fabric 1.20.1
|
||||||
|
files: |
|
||||||
|
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
|
||||||
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 🛎️'
|
||||||
|
|||||||
27
README.md
27
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_1.21.1.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_1.21.1.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,27 @@
|
|||||||
|
|
||||||
**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 Ends |
|
||||||
|
|:---------------:|:---------------:|:------------:|:--------------|:--------------------------|
|
||||||
|
| 1.21.1 | _latest_ | 21 | Paper, Fabric | ✅ **Active Release** |
|
||||||
|
| 1.20.6 | 3.6.8 | 17 | Paper | ❌ _October 2024_ |
|
||||||
|
| 1.20.4 | 3.6.8 | 17 | Paper | ❌ _July 2024_ |
|
||||||
|
| 1.20.1 | _latest_ | 17 | Paper, Fabric | ✅ **November 2025** (LTS) |
|
||||||
|
| 1.17.1 - 1.19.4 | 3.6.8 | 17 | Paper | ❌ _Support ended_ |
|
||||||
|
| 1.16.5 | 3.2.1 | 16 | Paper | ❌ _Support ended_ |
|
||||||
|
|
||||||
|
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/Mongo/PostgreSQL database, a Redis (v5.0+) server and a network of Spigot or Fabric Minecraft servers (see [Compatibility](#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 +71,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
|
||||||
|
|||||||
35
build.gradle
35
build.gradle
@@ -1,7 +1,7 @@
|
|||||||
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.2'
|
||||||
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 '1.7-SNAPSHOT' apply false
|
||||||
id 'org.ajoberstar.grgit' version '5.2.2'
|
id 'org.ajoberstar.grgit' version '5.2.2'
|
||||||
@@ -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,12 @@ 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.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 +74,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 +85,9 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.1'
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.11.1'
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.11.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@@ -99,9 +101,11 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
processResources {
|
processResources {
|
||||||
|
def tokenMap = rootProject.ext.properties
|
||||||
|
tokenMap.merge("grgit",'',(s, s2) -> s)
|
||||||
filesMatching(['**/*.json', '**/*.yml']) {
|
filesMatching(['**/*.json', '**/*.yml']) {
|
||||||
filter ReplaceTokens as Class, beginToken: '${', endToken: '}',
|
filter ReplaceTokens as Class, beginToken: '${', endToken: '}',
|
||||||
tokens: rootProject.ext.properties
|
tokens: tokenMap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,9 +127,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
|
||||||
@@ -161,7 +165,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
|
||||||
@@ -174,7 +178,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
|
||||||
@@ -188,10 +192,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,31 +1,30 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation project(path: ':common')
|
implementation project(path: ':common')
|
||||||
|
|
||||||
implementation 'net.william278.uniform:uniform-bukkit:1.1.4'
|
implementation 'net.william278.uniform:uniform-bukkit:1.2.1'
|
||||||
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:1.0.3'
|
||||||
implementation 'net.william278:andjam:1.0.2'
|
implementation 'org.bstats:bstats-bukkit:3.1.0'
|
||||||
implementation 'org.bstats:bstats-bukkit:3.0.2'
|
implementation 'net.kyori:adventure-platform-bukkit:4.3.4'
|
||||||
implementation 'net.kyori:adventure-platform-bukkit:4.3.3'
|
|
||||||
implementation 'dev.triumphteam:triumph-gui:3.1.10'
|
implementation 'dev.triumphteam:triumph-gui:3.1.10'
|
||||||
implementation 'space.arim.morepaperlib:morepaperlib:0.4.4'
|
implementation 'space.arim.morepaperlib:morepaperlib:0.4.4'
|
||||||
implementation 'de.tr7zw:item-nbt-api:2.13.1-SNAPSHOT'
|
implementation 'de.tr7zw:item-nbt-api:2.13.2'
|
||||||
|
|
||||||
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.3.0'
|
||||||
compileOnly 'com.comphenix.protocol:ProtocolLib:5.1.0'
|
compileOnly 'com.comphenix.protocol:ProtocolLib:5.1.0'
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
compileOnly 'org.projectlombok:lombok:1.18.34'
|
||||||
compileOnly 'commons-io:commons-io:2.16.1'
|
compileOnly 'commons-io:commons-io:2.17.0'
|
||||||
compileOnly 'org.json:json:20240303'
|
compileOnly 'org.json:json:20240303'
|
||||||
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.0.0'
|
||||||
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.32'
|
annotationProcessor 'org.projectlombok:lombok:1.18.34'
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
@@ -46,7 +45,6 @@ shadowJar {
|
|||||||
relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell'
|
relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell'
|
||||||
relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown'
|
relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown'
|
||||||
relocate 'net.william278.mapdataapi', 'net.william278.husksync.libraries.mapdataapi'
|
relocate 'net.william278.mapdataapi', 'net.william278.husksync.libraries.mapdataapi'
|
||||||
relocate 'net.william278.andjam', 'net.william278.husksync.libraries.andjam'
|
|
||||||
relocate 'net.william278.mpdbconverter', 'net.william278.husksync.libraries.mpdbconverter'
|
relocate 'net.william278.mpdbconverter', 'net.william278.husksync.libraries.mpdbconverter'
|
||||||
relocate 'net.william278.hslmigrator', 'net.william278.husksync.libraries.hslconverter'
|
relocate 'net.william278.hslmigrator', 'net.william278.husksync.libraries.hslconverter'
|
||||||
relocate 'org.json', 'net.william278.husksync.libraries.json'
|
relocate 'org.json', 'net.william278.husksync.libraries.json'
|
||||||
|
|||||||
@@ -137,6 +137,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)));
|
||||||
|
|
||||||
@@ -290,7 +293,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
@Override
|
@Override
|
||||||
public boolean isDependencyLoaded(@NotNull String name) {
|
public boolean isDependencyLoaded(@NotNull String name) {
|
||||||
final Plugin plugin = getServer().getPluginManager().getPlugin(name);
|
final Plugin plugin = getServer().getPluginManager().getPlugin(name);
|
||||||
return plugin != null && plugin.isEnabled();
|
return plugin != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register bStats metrics
|
// Register bStats metrics
|
||||||
@@ -333,6 +336,12 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
return PLATFORM_TYPE_ID;
|
return PLATFORM_TYPE_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public String getServerVersion() {
|
||||||
|
return String.format("%s/%s", getServer().getName(), getServer().getVersion());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<LegacyConverter> getLegacyConverter() {
|
public Optional<LegacyConverter> getLegacyConverter() {
|
||||||
return Optional.of(legacyConverter);
|
return Optional.of(legacyConverter);
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public class BukkitHuskSyncAPI extends HuskSyncAPI {
|
|||||||
public static BukkitHuskSyncAPI getInstance() {
|
public static BukkitHuskSyncAPI getInstance() {
|
||||||
if (!JavaPlugin.getProvidingPlugin(BukkitHuskSyncAPI.class).getName().equals("HuskSync")) {
|
if (!JavaPlugin.getProvidingPlugin(BukkitHuskSyncAPI.class).getName().equals("HuskSync")) {
|
||||||
throw new NotRegisteredException("This is likely because you have shaded HuskSync into your plugin JAR " +
|
throw new NotRegisteredException("This is likely because you have shaded HuskSync into your plugin JAR " +
|
||||||
"and need to fix your maven/gradle/build script so that it *compiles against* HuskSync instead.");
|
"and need to fix your maven/gradle/build script so that it *compiles against* HuskSync instead.");
|
||||||
}
|
}
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
throw new NotRegisteredException();
|
throw new NotRegisteredException();
|
||||||
|
|||||||
@@ -26,21 +26,19 @@ import de.tr7zw.changeme.nbtapi.NBTCompound;
|
|||||||
import de.tr7zw.changeme.nbtapi.NBTPersistentDataContainer;
|
import de.tr7zw.changeme.nbtapi.NBTPersistentDataContainer;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
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;
|
||||||
|
import net.william278.husksync.config.Settings.SynchronizationSettings.AttributeSettings;
|
||||||
import net.william278.husksync.user.BukkitUser;
|
import net.william278.husksync.user.BukkitUser;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.*;
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.Registry;
|
|
||||||
import org.bukkit.Statistic;
|
|
||||||
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;
|
||||||
@@ -48,6 +46,7 @@ import org.bukkit.potion.PotionEffectType;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.annotations.Range;
|
import org.jetbrains.annotations.Range;
|
||||||
|
import org.jetbrains.annotations.Unmodifiable;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
@@ -156,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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,8 +232,9 @@ public abstract class BukkitData implements Data {
|
|||||||
private final Collection<PotionEffect> effects;
|
private final Collection<PotionEffect> effects;
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static BukkitData.PotionEffects from(@NotNull Collection<PotionEffect> effects) {
|
public static BukkitData.PotionEffects from(@NotNull Collection<PotionEffect> sei) {
|
||||||
return new BukkitData.PotionEffects(effects);
|
return new BukkitData.PotionEffects(Lists.newArrayList(sei.stream().filter(e -> !e.isAmbient()).toList()));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -261,7 +258,7 @@ public abstract class BukkitData implements Data {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static BukkitData.PotionEffects empty() {
|
public static BukkitData.PotionEffects empty() {
|
||||||
return new BukkitData.PotionEffects(List.of());
|
return new BukkitData.PotionEffects(Lists.newArrayList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -277,10 +274,11 @@ public abstract class BukkitData implements Data {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
|
@Unmodifiable
|
||||||
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(),
|
||||||
@@ -364,7 +362,7 @@ public abstract class BukkitData implements Data {
|
|||||||
|
|
||||||
// Set player experience and level (prevent advancement awards applying twice), reset game rule
|
// Set player experience and level (prevent advancement awards applying twice), reset game rule
|
||||||
if (!toAward.isEmpty()
|
if (!toAward.isEmpty()
|
||||||
&& (player.getLevel() != expLevel || player.getExp() != expProgress)) {
|
&& (player.getLevel() != expLevel || player.getExp() != expProgress)) {
|
||||||
player.setLevel(expLevel);
|
player.setLevel(expLevel);
|
||||||
player.setExp(expProgress);
|
player.setExp(expProgress);
|
||||||
}
|
}
|
||||||
@@ -455,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);
|
||||||
@@ -478,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);
|
||||||
@@ -528,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,6 +551,7 @@ 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 List<Attribute> attributes;
|
private List<Attribute> attributes;
|
||||||
@@ -570,14 +559,13 @@ public abstract class BukkitData implements Data {
|
|||||||
@NotNull
|
@NotNull
|
||||||
public static BukkitData.Attributes adapt(@NotNull Player player, @NotNull HuskSync plugin) {
|
public static BukkitData.Attributes adapt(@NotNull Player player, @NotNull HuskSync plugin) {
|
||||||
final List<Attribute> attributes = Lists.newArrayList();
|
final List<Attribute> attributes = Lists.newArrayList();
|
||||||
|
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 || instance.getValue() == instance.getDefaultValue() || plugin
|
if (settings.isIgnoredAttribute(id.getKey().toString()) || instance == null) {
|
||||||
.getSettings().getSynchronization().isIgnoredAttribute(id.getKey().toString())) {
|
return; // We don't sync attributes not marked as to be synced
|
||||||
// We don't sync unmodified or disabled attributes
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
attributes.add(adapt(instance, plugin.getMinecraftVersion()));
|
attributes.add(adapt(instance, settings));
|
||||||
});
|
});
|
||||||
return new BukkitData.Attributes(attributes);
|
return new BukkitData.Attributes(attributes);
|
||||||
}
|
}
|
||||||
@@ -596,47 +584,63 @@ public abstract class BukkitData implements Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private static Attribute adapt(@NotNull AttributeInstance instance, @NotNull Version version) {
|
private static Attribute adapt(@NotNull AttributeInstance instance, @NotNull AttributeSettings settings) {
|
||||||
return new Attribute(
|
return new Attribute(
|
||||||
instance.getAttribute().getKey().toString(),
|
instance.getAttribute().getKey().toString(),
|
||||||
instance.getBaseValue(),
|
instance.getBaseValue(),
|
||||||
instance.getModifiers().stream().map(m -> adapt(m, version)).collect(Collectors.toSet())
|
instance.getModifiers().stream()
|
||||||
|
.filter(modifier -> !settings.isIgnoredModifier(modifier.getName()))
|
||||||
|
.filter(modifier -> modifier.getSlotGroup() != EquipmentSlotGroup.ANY)
|
||||||
|
.map(BukkitData.Attributes::adapt).collect(Collectors.toSet())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private static Modifier adapt(@NotNull AttributeModifier modifier, @NotNull Version version) {
|
private static Modifier adapt(@NotNull AttributeModifier modifier) {
|
||||||
return new Modifier(
|
return new Modifier(
|
||||||
version.compareTo(Version.fromString("1.21")) >= 0 ? null : modifier.getUniqueId(),
|
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()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(@NotNull BukkitUser user, @NotNull BukkitHuskSync plugin) throws IllegalStateException {
|
|
||||||
Registry.ATTRIBUTE.forEach(id -> applyAttribute(user.getPlayer().getAttribute(id), getAttribute(id).orElse(null)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void applyAttribute(@Nullable AttributeInstance instance, @Nullable Attribute attribute) {
|
private static void applyAttribute(@Nullable AttributeInstance instance, @Nullable Attribute attribute) {
|
||||||
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().forEach(modifier -> instance.addModifier(new AttributeModifier(
|
attribute.modifiers().stream()
|
||||||
modifier.uuid(),
|
.filter(mod -> instance.getModifiers().stream().map(AttributeModifier::getName)
|
||||||
modifier.name(),
|
.noneMatch(n -> n.equals(mod.name())))
|
||||||
modifier.amount(),
|
.distinct().filter(mod -> !mod.hasUuid())
|
||||||
AttributeModifier.Operation.values()[modifier.operationType()],
|
.forEach(mod -> instance.addModifier(adapt(mod)));
|
||||||
modifier.equipmentSlot() != -1 ? EquipmentSlot.values()[modifier.equipmentSlot()] : null
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private static AttributeModifier adapt(@NotNull Modifier modifier) {
|
||||||
|
return new AttributeModifier(
|
||||||
|
Objects.requireNonNull(NamespacedKey.fromString(modifier.name())),
|
||||||
|
modifier.amount(),
|
||||||
|
AttributeModifier.Operation.values()[modifier.operation()],
|
||||||
|
Optional.ofNullable(EquipmentSlotGroup.getByName(modifier.slotGroup())).orElse(EquipmentSlotGroup.ANY)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(@NotNull BukkitUser user, @NotNull BukkitHuskSync plugin) throws IllegalStateException {
|
||||||
|
final AttributeSettings settings = plugin.getSettings().getSynchronization().getAttributes();
|
||||||
|
Registry.ATTRIBUTE.forEach(id -> {
|
||||||
|
if (settings.isIgnoredAttribute(id.getKey().toString())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applyAttribute(user.getPlayer().getAttribute(id), getAttribute(id).orElse(null));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@@ -696,11 +700,12 @@ public abstract class BukkitData implements Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set health scale
|
// Set health scale
|
||||||
|
double scale = healthScale <= 0 ? player.getMaxHealth() : healthScale;
|
||||||
try {
|
try {
|
||||||
player.setHealthScale(healthScale);
|
player.setHealthScale(scale);
|
||||||
player.setHealthScaled(isHealthScaled);
|
player.setHealthScaled(isHealthScaled);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
plugin.log(Level.WARNING, "Error setting %s's health scale to %s".formatted(player.getName(), healthScale), e);
|
plugin.log(Level.WARNING, "Error setting %s's health scale to %s".formatted(player.getName(), scale), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ public class BukkitSerializer {
|
|||||||
case "1.20", "1.20.1", "1.20.2" -> DataFixerUtil.VERSION1_20_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.3", "1.20.4" -> DataFixerUtil.VERSION1_20_4;
|
||||||
case "1.20.5", "1.20.6" -> DataFixerUtil.VERSION1_20_5;
|
case "1.20.5", "1.20.6" -> DataFixerUtil.VERSION1_20_5;
|
||||||
|
case "1.21" -> DataFixerUtil.VERSION1_21;
|
||||||
default -> DataFixerUtil.getCurrentVersion();
|
default -> DataFixerUtil.getCurrentVersion();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.github.retrooper.packetevents.PacketEvents;
|
|||||||
import com.github.retrooper.packetevents.event.PacketListenerAbstract;
|
import com.github.retrooper.packetevents.event.PacketListenerAbstract;
|
||||||
import com.github.retrooper.packetevents.event.PacketListenerPriority;
|
import com.github.retrooper.packetevents.event.PacketListenerPriority;
|
||||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||||
|
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
|
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
|
||||||
@@ -78,7 +79,20 @@ public class BukkitPacketEventsLockedPacketListener extends BukkitLockedEventLis
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPacketReceive(PacketReceiveEvent event) {
|
public void onPacketReceive(PacketReceiveEvent event) {
|
||||||
if(!(event.getPacketType() instanceof PacketType.Play.Client client)) {
|
if (!(event.getPacketType() instanceof PacketType.Play.Client client)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!CANCEL_PACKETS.contains(client)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (listener.cancelPlayerEvent(event.getUser().getUUID())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPacketSend(PacketSendEvent event) {
|
||||||
|
if (!(event.getPacketType() instanceof PacketType.Play.Client client)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!CANCEL_PACKETS.contains(client)) {
|
if (!CANCEL_PACKETS.contains(client)) {
|
||||||
|
|||||||
@@ -204,10 +204,10 @@ public class LegacyMigrator extends Migrator {
|
|||||||
}) {
|
}) {
|
||||||
plugin.log(Level.INFO, getHelpMenu());
|
plugin.log(Level.INFO, getHelpMenu());
|
||||||
plugin.log(Level.INFO, "Successfully set " + args[0] + " to " +
|
plugin.log(Level.INFO, "Successfully set " + args[0] + " to " +
|
||||||
obfuscateDataString(args[1]));
|
obfuscateDataString(args[1]));
|
||||||
} else {
|
} else {
|
||||||
plugin.log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " +
|
plugin.log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " +
|
||||||
obfuscateDataString(args[1]) + " (is it a valid option?)");
|
obfuscateDataString(args[1]) + " (is it a valid option?)");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
plugin.log(Level.INFO, getHelpMenu());
|
plugin.log(Level.INFO, getHelpMenu());
|
||||||
|
|||||||
@@ -201,10 +201,10 @@ public class MpdbMigrator extends Migrator {
|
|||||||
}) {
|
}) {
|
||||||
plugin.log(Level.INFO, getHelpMenu());
|
plugin.log(Level.INFO, getHelpMenu());
|
||||||
plugin.log(Level.INFO, "Successfully set " + args[0] + " to " +
|
plugin.log(Level.INFO, "Successfully set " + args[0] + " to " +
|
||||||
obfuscateDataString(args[1]));
|
obfuscateDataString(args[1]));
|
||||||
} else {
|
} else {
|
||||||
plugin.log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " +
|
plugin.log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " +
|
||||||
obfuscateDataString(args[1]) + " (is it a valid option?)");
|
obfuscateDataString(args[1]) + " (is it a valid option?)");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
plugin.log(Level.INFO, getHelpMenu());
|
plugin.log(Level.INFO, getHelpMenu());
|
||||||
@@ -255,7 +255,7 @@ public class MpdbMigrator extends Migrator {
|
|||||||
If any of these are not correct, please correct them
|
If any of these are not correct, please correct them
|
||||||
using the command:
|
using the command:
|
||||||
"husksync migrate mpdb set <parameter> <value>"
|
"husksync migrate mpdb set <parameter> <value>"
|
||||||
(e.g.: "husksync migrate mpdb set host 1.2.3.4")
|
(e.g.: "husksync migrate set mpdb host 1.2.3.4")
|
||||||
|
|
||||||
STEP 3] HuskSync will migrate data into the database
|
STEP 3] HuskSync will migrate data into the database
|
||||||
tables configures in the config.yml file of this
|
tables configures in the config.yml file of this
|
||||||
@@ -263,7 +263,7 @@ public class MpdbMigrator extends Migrator {
|
|||||||
before proceeding.
|
before proceeding.
|
||||||
|
|
||||||
STEP 4] To start the migration, please run:
|
STEP 4] To start the migration, please run:
|
||||||
"husksync migrate mpdb start"
|
"husksync migrate start mpdb"
|
||||||
|
|
||||||
NOTE: This migrator currently WORKS WITH MPDB version
|
NOTE: This migrator currently WORKS WITH MPDB version
|
||||||
v4.9.2 and below!
|
v4.9.2 and below!
|
||||||
|
|||||||
@@ -23,14 +23,10 @@ import de.themoep.minedown.adventure.MineDown;
|
|||||||
import dev.triumphteam.gui.builder.gui.StorageBuilder;
|
import dev.triumphteam.gui.builder.gui.StorageBuilder;
|
||||||
import dev.triumphteam.gui.guis.Gui;
|
import dev.triumphteam.gui.guis.Gui;
|
||||||
import dev.triumphteam.gui.guis.StorageGui;
|
import dev.triumphteam.gui.guis.StorageGui;
|
||||||
import net.roxeez.advancement.display.FrameType;
|
|
||||||
import net.william278.andjam.Toast;
|
|
||||||
import net.william278.husksync.BukkitHuskSync;
|
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.data.BukkitData;
|
import net.william278.husksync.data.BukkitData;
|
||||||
import net.william278.husksync.data.BukkitUserDataHolder;
|
import net.william278.husksync.data.BukkitUserDataHolder;
|
||||||
import net.william278.husksync.data.Data;
|
import net.william278.husksync.data.Data;
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
@@ -40,8 +36,6 @@ import java.util.Arrays;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import static net.william278.husksync.util.BukkitKeyedAdapter.matchMaterial;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bukkit platform implementation of an {@link OnlineUser}
|
* Bukkit platform implementation of an {@link OnlineUser}
|
||||||
*/
|
*/
|
||||||
@@ -68,20 +62,12 @@ public class BukkitUser extends OnlineUser implements BukkitUserDataHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated(since = "3.6.7")
|
||||||
public void sendToast(@NotNull MineDown title, @NotNull MineDown description,
|
public void sendToast(@NotNull MineDown title, @NotNull MineDown description,
|
||||||
@NotNull String iconMaterial, @NotNull String backgroundType) {
|
@NotNull String iconMaterial, @NotNull String backgroundType) {
|
||||||
try {
|
plugin.log(Level.WARNING, "Toast notifications are deprecated. " +
|
||||||
final Material material = matchMaterial(iconMaterial);
|
"Please change your notification display slot to CHAT, ACTION_BAR or NONE.");
|
||||||
Toast.builder((BukkitHuskSync) plugin)
|
this.sendActionBar(title);
|
||||||
.setTitle(title.toComponent())
|
|
||||||
.setDescription(description.toComponent())
|
|
||||||
.setIcon(material != null ? material : Material.BARRIER)
|
|
||||||
.setFrameType(FrameType.valueOf(backgroundType))
|
|
||||||
.build()
|
|
||||||
.show(player);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
plugin.log(Level.WARNING, "Failed to send toast to player " + player.getName(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -155,8 +155,8 @@ public interface BukkitMapPersister {
|
|||||||
Optional<String> world = Optional.empty();
|
Optional<String> world = Optional.empty();
|
||||||
for (String worldUid : mapIds.getKeys()) {
|
for (String worldUid : mapIds.getKeys()) {
|
||||||
world = getPlugin().getServer().getWorlds().stream()
|
world = getPlugin().getServer().getWorlds().stream()
|
||||||
.map(w -> w.getUID().toString()).filter(u -> u.equals(worldUid))
|
.map(w -> w.getUID().toString()).filter(u -> u.equals(worldUid))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
if (world.isPresent()) {
|
if (world.isPresent()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -179,7 +179,7 @@ public interface BukkitMapPersister {
|
|||||||
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(Objects.requireNonNull(mapData.getByteArray(MAP_PIXEL_DATA_KEY),
|
||||||
"Map pixel data is null"));
|
"Map pixel data is 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;
|
||||||
@@ -195,8 +195,8 @@ public interface BukkitMapPersister {
|
|||||||
// Set the map view ID in NBT
|
// Set the map view ID in NBT
|
||||||
NBT.modify(map, editable -> {
|
NBT.modify(map, editable -> {
|
||||||
Objects.requireNonNull(editable.getCompound(MAP_VIEW_ID_MAPPINGS_KEY),
|
Objects.requireNonNull(editable.getCompound(MAP_VIEW_ID_MAPPINGS_KEY),
|
||||||
"Map view ID mappings compound is null")
|
"Map view ID mappings compound is null")
|
||||||
.setInteger(worldUid, view.getId());
|
.setInteger(worldUid, view.getId());
|
||||||
});
|
});
|
||||||
getPlugin().debug(String.format("Generated view (#%s) and updated map (UID: %s)", view.getId(), worldUid));
|
getPlugin().debug(String.format("Generated view (#%s) and updated map (UID: %s)", view.getId(), worldUid));
|
||||||
});
|
});
|
||||||
@@ -276,7 +276,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!");
|
||||||
}
|
}
|
||||||
@@ -308,7 +308,7 @@ public interface BukkitMapPersister {
|
|||||||
// We set the pixels in this order to avoid the map being rendered upside down
|
// We set the pixels in this order to avoid the map being rendered upside down
|
||||||
for (int i = 0; i < 128; i++) {
|
for (int i = 0; i < 128; i++) {
|
||||||
for (int j = 0; j < 128; j++) {
|
for (int j = 0; j < 128; j++) {
|
||||||
canvas.setPixel(j, i, (byte) canvasData.getColorAt(i, j));
|
canvas.setPixelColor(j, i, canvasData.getMapColorAt(i, j));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,29 +326,29 @@ public interface BukkitMapPersister {
|
|||||||
@NotNull
|
@NotNull
|
||||||
private static MapCursor createBannerCursor(@NotNull MapBanner banner) {
|
private static MapCursor createBannerCursor(@NotNull MapBanner banner) {
|
||||||
return new MapCursor(
|
return new MapCursor(
|
||||||
(byte) banner.getPosition().getX(),
|
(byte) banner.getPosition().getX(),
|
||||||
(byte) banner.getPosition().getZ(),
|
(byte) banner.getPosition().getZ(),
|
||||||
(byte) 8, // Always rotate banners upright
|
(byte) 8, // Always rotate banners upright
|
||||||
switch (banner.getColor().toLowerCase(Locale.ENGLISH)) {
|
switch (banner.getColor().toLowerCase(Locale.ENGLISH)) {
|
||||||
case "white" -> MapCursor.Type.BANNER_WHITE;
|
case "white" -> MapCursor.Type.BANNER_WHITE;
|
||||||
case "orange" -> MapCursor.Type.BANNER_ORANGE;
|
case "orange" -> MapCursor.Type.BANNER_ORANGE;
|
||||||
case "magenta" -> MapCursor.Type.BANNER_MAGENTA;
|
case "magenta" -> MapCursor.Type.BANNER_MAGENTA;
|
||||||
case "light_blue" -> MapCursor.Type.BANNER_LIGHT_BLUE;
|
case "light_blue" -> MapCursor.Type.BANNER_LIGHT_BLUE;
|
||||||
case "yellow" -> MapCursor.Type.BANNER_YELLOW;
|
case "yellow" -> MapCursor.Type.BANNER_YELLOW;
|
||||||
case "lime" -> MapCursor.Type.BANNER_LIME;
|
case "lime" -> MapCursor.Type.BANNER_LIME;
|
||||||
case "pink" -> MapCursor.Type.BANNER_PINK;
|
case "pink" -> MapCursor.Type.BANNER_PINK;
|
||||||
case "gray" -> MapCursor.Type.BANNER_GRAY;
|
case "gray" -> MapCursor.Type.BANNER_GRAY;
|
||||||
case "light_gray" -> MapCursor.Type.BANNER_LIGHT_GRAY;
|
case "light_gray" -> MapCursor.Type.BANNER_LIGHT_GRAY;
|
||||||
case "cyan" -> MapCursor.Type.BANNER_CYAN;
|
case "cyan" -> MapCursor.Type.BANNER_CYAN;
|
||||||
case "purple" -> MapCursor.Type.BANNER_PURPLE;
|
case "purple" -> MapCursor.Type.BANNER_PURPLE;
|
||||||
case "blue" -> MapCursor.Type.BANNER_BLUE;
|
case "blue" -> MapCursor.Type.BANNER_BLUE;
|
||||||
case "brown" -> MapCursor.Type.BANNER_BROWN;
|
case "brown" -> MapCursor.Type.BANNER_BROWN;
|
||||||
case "green" -> MapCursor.Type.BANNER_GREEN;
|
case "green" -> MapCursor.Type.BANNER_GREEN;
|
||||||
case "red" -> MapCursor.Type.BANNER_RED;
|
case "red" -> MapCursor.Type.BANNER_RED;
|
||||||
default -> MapCursor.Type.BANNER_BLACK;
|
default -> MapCursor.Type.BANNER_BLACK;
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
banner.getText().isEmpty() ? null : banner.getText()
|
banner.getText().isEmpty() ? null : banner.getText()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,20 +383,40 @@ 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 getPixel(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPixelColor(int i, int i1, @Nullable Color color) {
|
||||||
|
pixels[i][i1] = color == null ? 0 : color.getRGB();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Color getPixelColor(int x, int y) {
|
||||||
|
return getBasePixelColor(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Color getBasePixelColor(int x, int y) {
|
||||||
|
return new Color(pixels[x][y]);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void drawImage(int x, int y, @NotNull Image image) {
|
public void drawImage(int x, int y, @NotNull Image image) {
|
||||||
// Not implemented
|
// Not implemented
|
||||||
@@ -427,16 +447,17 @@ public interface BukkitMapPersister {
|
|||||||
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().name().toLowerCase(Locale.ENGLISH);
|
final String type = cursor.getType().getKey().getKey();
|
||||||
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()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return MapData.fromPixels(pixels, getDimension(), (byte) 2, banners, List.of());
|
return MapData.fromPixels(pixels, getDimension(), (byte) 2, banners, List.of());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api 'commons-io:commons-io:2.16.1'
|
api 'commons-io:commons-io:2.17.0'
|
||||||
api 'org.apache.commons:commons-text:1.12.0'
|
api 'org.apache.commons:commons-text:1.12.0'
|
||||||
api 'net.william278:minedown:1.8.2'
|
api 'net.william278:minedown:1.8.2'
|
||||||
api 'org.json:json:20240303'
|
api 'org.json:json:20240303'
|
||||||
@@ -12,17 +12,17 @@ dependencies {
|
|||||||
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.0.0') {
|
||||||
exclude module: 'slf4j-api'
|
exclude module: 'slf4j-api'
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOnly 'net.william278.uniform:uniform-common:1.1.4'
|
compileOnly 'net.william278.uniform:uniform-common:1.2.1'
|
||||||
compileOnly 'com.mojang:brigadier:1.1.8'
|
compileOnly 'com.mojang:brigadier:1.1.8'
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
compileOnly 'org.projectlombok:lombok:1.18.34'
|
||||||
compileOnly 'org.jetbrains:annotations:24.1.0'
|
compileOnly 'org.jetbrains:annotations:24.1.0'
|
||||||
compileOnly 'net.kyori:adventure-api:4.17.0'
|
compileOnly 'net.kyori:adventure-api:4.17.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.3.1-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.3.1-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:24.1.0'
|
||||||
|
|
||||||
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
annotationProcessor 'org.projectlombok:lombok:1.18.34'
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
@@ -255,6 +257,14 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider
|
|||||||
@NotNull
|
@NotNull
|
||||||
String getPlatformType();
|
String getPlatformType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the server software version
|
||||||
|
*
|
||||||
|
* @return the server software version string
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
String getServerVersion();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the legacy data converter if it exists
|
* Returns the legacy data converter if it exists
|
||||||
*
|
*
|
||||||
@@ -265,10 +275,10 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider
|
|||||||
@NotNull
|
@NotNull
|
||||||
default UpdateChecker getUpdateChecker() {
|
default UpdateChecker getUpdateChecker() {
|
||||||
return UpdateChecker.builder()
|
return UpdateChecker.builder()
|
||||||
.currentVersion(getPluginVersion())
|
.currentVersion(getPluginVersion())
|
||||||
.endpoint(UpdateChecker.Endpoint.SPIGOT)
|
.endpoint(UpdateChecker.Endpoint.SPIGOT)
|
||||||
.resource(Integer.toString(SPIGOT_RESOURCE_ID))
|
.resource(Integer.toString(SPIGOT_RESOURCE_ID))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
default void checkForUpdates() {
|
default void checkForUpdates() {
|
||||||
@@ -276,8 +286,8 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider
|
|||||||
getUpdateChecker().check().thenAccept(checked -> {
|
getUpdateChecker().check().thenAccept(checked -> {
|
||||||
if (!checked.isUpToDate()) {
|
if (!checked.isUpToDate()) {
|
||||||
log(Level.WARNING, String.format(
|
log(Level.WARNING, String.format(
|
||||||
"A new version of HuskSync is available: v%s (running v%s)",
|
"A new version of HuskSync is available: v%s (running v%s)",
|
||||||
checked.getLatestVersion(), getPluginVersion())
|
checked.getLatestVersion(), getPluginVersion())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -320,17 +330,21 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider
|
|||||||
final class FailedToLoadException extends IllegalStateException {
|
final class FailedToLoadException extends IllegalStateException {
|
||||||
|
|
||||||
private static final String FORMAT = """
|
private static final String FORMAT = """
|
||||||
HuskSync has failed to load! The plugin will not be enabled and no data will be synchronized.
|
HuskSync has failed to load! The plugin will not be enabled and no data will be synchronized.
|
||||||
Please make sure the plugin has been setup correctly (https://william278.net/docs/husksync/setup):
|
Please make sure the plugin has been setup correctly (https://william278.net/docs/husksync/setup):
|
||||||
|
|
||||||
1) Make sure you've entered your MySQL, MariaDB or MongoDB database details correctly in config.yml
|
1) Make sure you've entered your MySQL, MariaDB or MongoDB database details correctly in config.yml
|
||||||
2) Make sure your Redis server details are also correct in config.yml
|
2) Make sure your Redis server details are also correct in config.yml
|
||||||
3) Make sure your config is up-to-date (https://william278.net/docs/husksync/config-file)
|
3) Make sure your config is up-to-date (https://william278.net/docs/husksync/config-file)
|
||||||
4) Check the error below for more details
|
4) Check the error below for more details
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,29 +46,29 @@ public class EnderChestCommand extends ItemsCommand {
|
|||||||
final Optional<Data.Items.EnderChest> optionalEnderChest = snapshot.getEnderChest();
|
final Optional<Data.Items.EnderChest> optionalEnderChest = snapshot.getEnderChest();
|
||||||
if (optionalEnderChest.isEmpty()) {
|
if (optionalEnderChest.isEmpty()) {
|
||||||
plugin.getLocales().getLocale("error_no_data_to_display")
|
plugin.getLocales().getLocale("error_no_data_to_display")
|
||||||
.ifPresent(viewer::sendMessage);
|
.ifPresent(viewer::sendMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display opening message
|
// Display opening message
|
||||||
plugin.getLocales().getLocale("ender_chest_viewer_opened", user.getUsername(),
|
plugin.getLocales().getLocale("ender_chest_viewer_opened", user.getUsername(),
|
||||||
snapshot.getTimestamp().format(DateTimeFormatter
|
snapshot.getTimestamp().format(DateTimeFormatter
|
||||||
.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT)))
|
.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT)))
|
||||||
.ifPresent(viewer::sendMessage);
|
.ifPresent(viewer::sendMessage);
|
||||||
|
|
||||||
// Show GUI
|
// Show GUI
|
||||||
final Data.Items.EnderChest enderChest = optionalEnderChest.get();
|
final Data.Items.EnderChest enderChest = optionalEnderChest.get();
|
||||||
viewer.showGui(
|
viewer.showGui(
|
||||||
enderChest,
|
enderChest,
|
||||||
plugin.getLocales().getLocale("ender_chest_viewer_menu_title", user.getUsername())
|
plugin.getLocales().getLocale("ender_chest_viewer_menu_title", user.getUsername())
|
||||||
.orElse(new MineDown(String.format("%s's Ender Chest", user.getUsername()))),
|
.orElse(new MineDown(String.format("%s's Ender Chest", user.getUsername()))),
|
||||||
allowEdit,
|
allowEdit,
|
||||||
enderChest.getSlotCount(),
|
enderChest.getSlotCount(),
|
||||||
(itemsOnClose) -> {
|
(itemsOnClose) -> {
|
||||||
if (allowEdit && !enderChest.equals(itemsOnClose)) {
|
if (allowEdit && !enderChest.equals(itemsOnClose)) {
|
||||||
plugin.runAsync(() -> this.updateItems(viewer, itemsOnClose, user));
|
plugin.runAsync(() -> this.updateItems(viewer, itemsOnClose, user));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ public class EnderChestCommand extends ItemsCommand {
|
|||||||
final Optional<DataSnapshot.Packed> latestData = plugin.getDatabase().getLatestSnapshot(holder);
|
final Optional<DataSnapshot.Packed> latestData = plugin.getDatabase().getLatestSnapshot(holder);
|
||||||
if (latestData.isEmpty()) {
|
if (latestData.isEmpty()) {
|
||||||
plugin.getLocales().getLocale("error_no_data_to_display")
|
plugin.getLocales().getLocale("error_no_data_to_display")
|
||||||
.ifPresent(viewer::sendMessage);
|
.ifPresent(viewer::sendMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ public class EnderChestCommand extends ItemsCommand {
|
|||||||
data.getEnderChest().ifPresent(enderChest -> enderChest.setContents(items));
|
data.getEnderChest().ifPresent(enderChest -> enderChest.setContents(items));
|
||||||
data.setSaveCause(DataSnapshot.SaveCause.ENDERCHEST_COMMAND);
|
data.setSaveCause(DataSnapshot.SaveCause.ENDERCHEST_COMMAND);
|
||||||
data.setPinned(
|
data.setPinned(
|
||||||
plugin.getSettings().getSynchronization().doAutoPin(DataSnapshot.SaveCause.ENDERCHEST_COMMAND)
|
plugin.getSettings().getSynchronization().doAutoPin(DataSnapshot.SaveCause.ENDERCHEST_COMMAND)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -52,41 +56,41 @@ public class HuskSyncCommand extends PluginCommand {
|
|||||||
private final AboutMenu aboutMenu;
|
private final AboutMenu aboutMenu;
|
||||||
|
|
||||||
public HuskSyncCommand(@NotNull HuskSync plugin) {
|
public HuskSyncCommand(@NotNull HuskSync plugin) {
|
||||||
super("husksync", List.of(), Permission.Default.TRUE, plugin);
|
super("husksync", List.of(), Permission.Default.TRUE, ExecutionScope.ALL, plugin);
|
||||||
this.updateChecker = plugin.getUpdateChecker();
|
this.updateChecker = plugin.getUpdateChecker();
|
||||||
this.aboutMenu = AboutMenu.builder()
|
this.aboutMenu = AboutMenu.builder()
|
||||||
.title(Component.text("HuskSync"))
|
.title(Component.text("HuskSync"))
|
||||||
.description(Component.text("A modern, cross-server player data synchronization system"))
|
.description(Component.text("A modern, cross-server player data synchronization system"))
|
||||||
.version(plugin.getPluginVersion())
|
.version(plugin.getPluginVersion())
|
||||||
.credits("Author",
|
.credits("Author",
|
||||||
AboutMenu.Credit.of("William278").description("Click to visit website").url("https://william278.net"))
|
AboutMenu.Credit.of("William278").description("Click to visit website").url("https://william278.net"))
|
||||||
.credits("Contributors",
|
.credits("Contributors",
|
||||||
AboutMenu.Credit.of("HarvelsX").description("Code"),
|
AboutMenu.Credit.of("HarvelsX").description("Code"),
|
||||||
AboutMenu.Credit.of("HookWoods").description("Code"),
|
AboutMenu.Credit.of("HookWoods").description("Code"),
|
||||||
AboutMenu.Credit.of("Preva1l").description("Code"),
|
AboutMenu.Credit.of("Preva1l").description("Code"),
|
||||||
AboutMenu.Credit.of("hanbings").description("Code (Fabric porting)"),
|
AboutMenu.Credit.of("hanbings").description("Code (Fabric porting)"),
|
||||||
AboutMenu.Credit.of("Stampede2011").description("Code (Fabric mixins)"))
|
AboutMenu.Credit.of("Stampede2011").description("Code (Fabric mixins)"))
|
||||||
.credits("Translators",
|
.credits("Translators",
|
||||||
AboutMenu.Credit.of("Namiu").description("Japanese (ja-jp)"),
|
AboutMenu.Credit.of("Namiu").description("Japanese (ja-jp)"),
|
||||||
AboutMenu.Credit.of("anchelthe").description("Spanish (es-es)"),
|
AboutMenu.Credit.of("anchelthe").description("Spanish (es-es)"),
|
||||||
AboutMenu.Credit.of("Melonzio").description("Spanish (es-es)"),
|
AboutMenu.Credit.of("Melonzio").description("Spanish (es-es)"),
|
||||||
AboutMenu.Credit.of("Ceddix").description("German (de-de)"),
|
AboutMenu.Credit.of("Ceddix").description("German (de-de)"),
|
||||||
AboutMenu.Credit.of("Pukejoy_1").description("Bulgarian (bg-bg)"),
|
AboutMenu.Credit.of("Pukejoy_1").description("Bulgarian (bg-bg)"),
|
||||||
AboutMenu.Credit.of("mateusneresrb").description("Brazilian Portuguese (pt-br)"),
|
AboutMenu.Credit.of("mateusneresrb").description("Brazilian Portuguese (pt-br)"),
|
||||||
AboutMenu.Credit.of("小蔡").description("Traditional Chinese (zh-tw)"),
|
AboutMenu.Credit.of("小蔡").description("Traditional Chinese (zh-tw)"),
|
||||||
AboutMenu.Credit.of("Ghost-chu").description("Simplified Chinese (zh-cn)"),
|
AboutMenu.Credit.of("Ghost-chu").description("Simplified Chinese (zh-cn)"),
|
||||||
AboutMenu.Credit.of("DJelly4K").description("Simplified Chinese (zh-cn)"),
|
AboutMenu.Credit.of("DJelly4K").description("Simplified Chinese (zh-cn)"),
|
||||||
AboutMenu.Credit.of("Thourgard").description("Ukrainian (uk-ua)"),
|
AboutMenu.Credit.of("Thourgard").description("Ukrainian (uk-ua)"),
|
||||||
AboutMenu.Credit.of("xF3d3").description("Italian (it-it)"),
|
AboutMenu.Credit.of("xF3d3").description("Italian (it-it)"),
|
||||||
AboutMenu.Credit.of("cada3141").description("Korean (ko-kr)"),
|
AboutMenu.Credit.of("cada3141").description("Korean (ko-kr)"),
|
||||||
AboutMenu.Credit.of("Wirayuda5620").description("Indonesian (id-id)"),
|
AboutMenu.Credit.of("Wirayuda5620").description("Indonesian (id-id)"),
|
||||||
AboutMenu.Credit.of("WinTone01").description("Turkish (tr-tr)"),
|
AboutMenu.Credit.of("WinTone01").description("Turkish (tr-tr)"),
|
||||||
AboutMenu.Credit.of("IbanEtchep").description("French (fr-fr)"))
|
AboutMenu.Credit.of("IbanEtchep").description("French (fr-fr)"))
|
||||||
.buttons(
|
.buttons(
|
||||||
AboutMenu.Link.of("https://william278.net/docs/husksync").text("Documentation").icon("⛏"),
|
AboutMenu.Link.of("https://william278.net/docs/husksync").text("Documentation").icon("⛏"),
|
||||||
AboutMenu.Link.of("https://github.com/WiIIiam278/HuskSync/issues").text("Issues").icon("❌").color(TextColor.color(0xff9f0f)),
|
AboutMenu.Link.of("https://github.com/WiIIiam278/HuskSync/issues").text("Issues").icon("❌").color(TextColor.color(0xff9f0f)),
|
||||||
AboutMenu.Link.of("https://discord.gg/tVYhJfyDWG").text("Discord").icon("⭐").color(TextColor.color(0x6773f5)))
|
AboutMenu.Link.of("https://discord.gg/tVYhJfyDWG").text("Discord").icon("⭐").color(TextColor.color(0x6773f5)))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,8 +114,8 @@ public class HuskSyncCommand extends PluginCommand {
|
|||||||
final CommandUser user = user(sub, ctx);
|
final CommandUser user = user(sub, ctx);
|
||||||
plugin.getLocales().getLocale("system_status_header").ifPresent(user::sendMessage);
|
plugin.getLocales().getLocale("system_status_header").ifPresent(user::sendMessage);
|
||||||
user.sendMessage(Component.join(
|
user.sendMessage(Component.join(
|
||||||
JoinConfiguration.newlines(),
|
JoinConfiguration.newlines(),
|
||||||
Arrays.stream(StatusLine.values()).map(s -> s.get(plugin)).toList()
|
Arrays.stream(StatusLine.values()).map(s -> s.get(plugin)).toList()
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -126,7 +131,7 @@ public class HuskSyncCommand extends PluginCommand {
|
|||||||
plugin.getLocales().getLocale("reload_complete").ifPresent(user::sendMessage);
|
plugin.getLocales().getLocale("reload_complete").ifPresent(user::sendMessage);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
user.sendMessage(new MineDown(
|
user.sendMessage(new MineDown(
|
||||||
"[Error:](#ff3300) [Failed to reload the plugin. Check console for errors.](#ff7e5e)"
|
"[Error:](#ff3300) [Failed to reload the plugin. Check console for errors.](#ff7e5e)"
|
||||||
));
|
));
|
||||||
plugin.log(Level.SEVERE, "Failed to reload the plugin", e);
|
plugin.log(Level.SEVERE, "Failed to reload the plugin", e);
|
||||||
}
|
}
|
||||||
@@ -139,11 +144,11 @@ public class HuskSyncCommand extends PluginCommand {
|
|||||||
final CommandUser user = user(sub, ctx);
|
final CommandUser user = user(sub, ctx);
|
||||||
if (checked.isUpToDate()) {
|
if (checked.isUpToDate()) {
|
||||||
plugin.getLocales().getLocale("up_to_date", plugin.getPluginVersion().toString())
|
plugin.getLocales().getLocale("up_to_date", plugin.getPluginVersion().toString())
|
||||||
.ifPresent(user::sendMessage);
|
.ifPresent(user::sendMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
plugin.getLocales().getLocale("update_available", checked.getLatestVersion().toString(),
|
plugin.getLocales().getLocale("update_available", checked.getLatestVersion().toString(),
|
||||||
plugin.getPluginVersion().toString()).ifPresent(user::sendMessage);
|
plugin.getPluginVersion().toString()).ifPresent(user::sendMessage);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,14 +157,18 @@ public class HuskSyncCommand extends PluginCommand {
|
|||||||
return (sub) -> {
|
return (sub) -> {
|
||||||
sub.setCondition((ctx) -> sub.getUser(ctx).isConsole());
|
sub.setCondition((ctx) -> sub.getUser(ctx).isConsole());
|
||||||
sub.setDefaultExecutor((ctx) -> {
|
sub.setDefaultExecutor((ctx) -> {
|
||||||
plugin.log(Level.INFO, "Please choose a migrator, then run \"husksync migrate <migrator>\"");
|
plugin.log(Level.INFO, "Please choose a migrator, then run \"husksync migrate start <migrator>\"");
|
||||||
plugin.log(Level.INFO, String.format(
|
plugin.log(Level.INFO, String.format(
|
||||||
"List of available migrators:\nMigrator ID / Migrator Name:\n%s",
|
"List of available migrators:\nMigrator ID / Migrator Name:\n%s",
|
||||||
plugin.getAvailableMigrators().stream()
|
plugin.getAvailableMigrators().stream()
|
||||||
.map(migrator -> String.format("%s - %s", migrator.getIdentifier(), migrator.getName()))
|
.map(migrator -> String.format("%s - %s", migrator.getIdentifier(), migrator.getName()))
|
||||||
.collect(Collectors.joining("\n"))
|
.collect(Collectors.joining("\n"))
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
sub.addSubCommand("help", (help) -> help.addSyntax((cmd) -> {
|
||||||
|
final Migrator migrator = cmd.getArgument("migrator", Migrator.class);
|
||||||
|
plugin.log(Level.INFO, migrator.getHelpMenu());
|
||||||
|
}, migrator()));
|
||||||
sub.addSubCommand("start", (start) -> start.addSyntax((cmd) -> {
|
sub.addSubCommand("start", (start) -> start.addSyntax((cmd) -> {
|
||||||
final Migrator migrator = cmd.getArgument("migrator", Migrator.class);
|
final Migrator migrator = cmd.getArgument("migrator", Migrator.class);
|
||||||
migrator.start().thenAccept(succeeded -> {
|
migrator.start().thenAccept(succeeded -> {
|
||||||
@@ -173,17 +182,46 @@ public class HuskSyncCommand extends PluginCommand {
|
|||||||
sub.addSubCommand("set", (set) -> set.addSyntax((cmd) -> {
|
sub.addSubCommand("set", (set) -> set.addSyntax((cmd) -> {
|
||||||
final Migrator migrator = cmd.getArgument("migrator", Migrator.class);
|
final Migrator migrator = cmd.getArgument("migrator", Migrator.class);
|
||||||
final String[] args = cmd.getArgument("args", String.class).split(" ");
|
final String[] args = cmd.getArgument("args", String.class).split(" ");
|
||||||
migrator.handleConfigurationCommand(Arrays.copyOfRange(args, 3, args.length));
|
migrator.handleConfigurationCommand(args);
|
||||||
}, migrator(), BaseCommand.greedyString("args")));
|
}, migrator(), BaseCommand.greedyString("args")));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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 -> {
|
||||||
final String id = reader.readString();
|
final String id = reader.readString();
|
||||||
final Migrator migrator = plugin.getAvailableMigrators().stream()
|
final Migrator migrator = plugin.getAvailableMigrators().stream()
|
||||||
.filter(m -> m.getIdentifier().equalsIgnoreCase(id)).findFirst().orElse(null);
|
.filter(m -> m.getIdentifier().equalsIgnoreCase(id)).findFirst().orElse(null);
|
||||||
if (migrator == null) {
|
if (migrator == null) {
|
||||||
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().createWithContext(reader);
|
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().createWithContext(reader);
|
||||||
}
|
}
|
||||||
@@ -198,54 +236,54 @@ public class HuskSyncCommand extends PluginCommand {
|
|||||||
|
|
||||||
private enum StatusLine {
|
private enum StatusLine {
|
||||||
PLUGIN_VERSION(plugin -> Component.text("v" + plugin.getPluginVersion().toStringWithoutMetadata())
|
PLUGIN_VERSION(plugin -> Component.text("v" + plugin.getPluginVersion().toStringWithoutMetadata())
|
||||||
.appendSpace().append(plugin.getPluginVersion().getMetadata().isBlank() ? Component.empty()
|
.appendSpace().append(plugin.getPluginVersion().getMetadata().isBlank() ? Component.empty()
|
||||||
: Component.text("(build " + plugin.getPluginVersion().getMetadata() + ")"))),
|
: Component.text("(build " + plugin.getPluginVersion().getMetadata() + ")"))),
|
||||||
PLATFORM_TYPE(plugin -> Component.text(WordUtils.capitalizeFully(plugin.getPlatformType()))),
|
SERVER_VERSION(plugin -> Component.text(plugin.getServerVersion())),
|
||||||
LANGUAGE(plugin -> Component.text(plugin.getSettings().getLanguage())),
|
LANGUAGE(plugin -> Component.text(plugin.getSettings().getLanguage())),
|
||||||
MINECRAFT_VERSION(plugin -> Component.text(plugin.getMinecraftVersion().toString())),
|
MINECRAFT_VERSION(plugin -> Component.text(plugin.getMinecraftVersion().toString())),
|
||||||
JAVA_VERSION(plugin -> Component.text(System.getProperty("java.version"))),
|
JAVA_VERSION(plugin -> Component.text(System.getProperty("java.version"))),
|
||||||
JAVA_VENDOR(plugin -> Component.text(System.getProperty("java.vendor"))),
|
JAVA_VENDOR(plugin -> Component.text(System.getProperty("java.vendor"))),
|
||||||
SYNC_MODE(plugin -> Component.text(WordUtils.capitalizeFully(
|
|
||||||
plugin.getSettings().getSynchronization().getMode().toString()
|
|
||||||
))),
|
|
||||||
DELAY_LATENCY(plugin -> Component.text(
|
|
||||||
plugin.getSettings().getSynchronization().getNetworkLatencyMilliseconds() + "ms"
|
|
||||||
)),
|
|
||||||
SERVER_NAME(plugin -> Component.text(plugin.getServerName())),
|
SERVER_NAME(plugin -> Component.text(plugin.getServerName())),
|
||||||
CLUSTER_ID(plugin -> Component.text(plugin.getSettings().getClusterId().isBlank() ? "None" : plugin.getSettings().getClusterId())),
|
CLUSTER_ID(plugin -> Component.text(plugin.getSettings().getClusterId().isBlank() ? "None" : plugin.getSettings().getClusterId())),
|
||||||
|
SYNC_MODE(plugin -> Component.text(WordUtils.capitalizeFully(
|
||||||
|
plugin.getSettings().getSynchronization().getMode().toString()
|
||||||
|
))),
|
||||||
|
DELAY_LATENCY(plugin -> Component.text(
|
||||||
|
plugin.getSettings().getSynchronization().getNetworkLatencyMilliseconds() + "ms"
|
||||||
|
)),
|
||||||
DATABASE_TYPE(plugin ->
|
DATABASE_TYPE(plugin ->
|
||||||
Component.text(plugin.getSettings().getDatabase().getType().getDisplayName() +
|
Component.text(plugin.getSettings().getDatabase().getType().getDisplayName() +
|
||||||
(plugin.getSettings().getDatabase().getType() == Database.Type.MONGO ?
|
(plugin.getSettings().getDatabase().getType() == Database.Type.MONGO ?
|
||||||
(plugin.getSettings().getDatabase().getMongoSettings().isUsingAtlas() ? " Atlas" : "") : ""))
|
(plugin.getSettings().getDatabase().getMongoSettings().isUsingAtlas() ? " Atlas" : "") : ""))
|
||||||
),
|
),
|
||||||
IS_DATABASE_LOCAL(plugin -> getLocalhostBoolean(plugin.getSettings().getDatabase().getCredentials().getHost())),
|
IS_DATABASE_LOCAL(plugin -> getLocalhostBoolean(plugin.getSettings().getDatabase().getCredentials().getHost())),
|
||||||
USING_REDIS_SENTINEL(plugin -> getBoolean(
|
USING_REDIS_SENTINEL(plugin -> getBoolean(
|
||||||
!plugin.getSettings().getRedis().getSentinel().getMaster().isBlank()
|
!plugin.getSettings().getRedis().getSentinel().getMaster().isBlank()
|
||||||
)),
|
)),
|
||||||
USING_REDIS_PASSWORD(plugin -> getBoolean(
|
USING_REDIS_PASSWORD(plugin -> getBoolean(
|
||||||
!plugin.getSettings().getRedis().getCredentials().getPassword().isBlank()
|
!plugin.getSettings().getRedis().getCredentials().getPassword().isBlank()
|
||||||
)),
|
)),
|
||||||
REDIS_USING_SSL(plugin -> getBoolean(
|
REDIS_USING_SSL(plugin -> getBoolean(
|
||||||
plugin.getSettings().getRedis().getCredentials().isUseSsl()
|
plugin.getSettings().getRedis().getCredentials().isUseSsl()
|
||||||
)),
|
)),
|
||||||
IS_REDIS_LOCAL(plugin -> getLocalhostBoolean(
|
IS_REDIS_LOCAL(plugin -> getLocalhostBoolean(
|
||||||
plugin.getSettings().getRedis().getCredentials().getHost()
|
plugin.getSettings().getRedis().getCredentials().getHost()
|
||||||
)),
|
)),
|
||||||
DATA_TYPES(plugin -> Component.join(
|
DATA_TYPES(plugin -> Component.join(
|
||||||
JoinConfiguration.commas(true),
|
JoinConfiguration.commas(true),
|
||||||
plugin.getRegisteredDataTypes().stream().map(i -> Component.textOfChildren(Component.text(i.toString())
|
plugin.getRegisteredDataTypes().stream().map(i -> Component.textOfChildren(Component.text(i.toString())
|
||||||
.appendSpace().append(Component.text(i.isEnabled() ? '✔' : '❌')))
|
.appendSpace().append(Component.text(i.isEnabled() ? '✔' : '❌')))
|
||||||
.color(i.isEnabled() ? NamedTextColor.GREEN : NamedTextColor.RED)
|
.color(i.isEnabled() ? NamedTextColor.GREEN : NamedTextColor.RED)
|
||||||
.hoverEvent(HoverEvent.showText(
|
.hoverEvent(HoverEvent.showText(
|
||||||
Component.text(i.isEnabled() ? "Enabled" : "Disabled")
|
Component.text(i.isEnabled() ? "Enabled" : "Disabled")
|
||||||
.append(Component.newline())
|
.append(Component.newline())
|
||||||
.append(Component.text("Dependencies: %s".formatted(i.getDependencies()
|
.append(Component.text("Dependencies: %s".formatted(i.getDependencies()
|
||||||
.isEmpty() ? "(None)" : i.getDependencies().stream()
|
.isEmpty() ? "(None)" : i.getDependencies().stream()
|
||||||
.map(d -> "%s (%s)".formatted(
|
.map(d -> "%s (%s)".formatted(
|
||||||
d.getKey().value(), d.isRequired() ? "Required" : "Optional"
|
d.getKey().value(), d.isRequired() ? "Required" : "Optional"
|
||||||
)).collect(Collectors.joining(", ")))
|
)).collect(Collectors.joining(", ")))
|
||||||
).color(NamedTextColor.GRAY))
|
).color(NamedTextColor.GRAY))
|
||||||
))).toList()
|
))).toList()
|
||||||
));
|
));
|
||||||
|
|
||||||
private final Function<HuskSync, Component> supplier;
|
private final Function<HuskSync, Component> supplier;
|
||||||
@@ -257,13 +295,13 @@ public class HuskSyncCommand extends PluginCommand {
|
|||||||
@NotNull
|
@NotNull
|
||||||
private Component get(@NotNull HuskSync plugin) {
|
private Component get(@NotNull HuskSync plugin) {
|
||||||
return Component
|
return Component
|
||||||
.text("•").appendSpace()
|
.text("•").appendSpace()
|
||||||
.append(Component.text(
|
.append(Component.text(
|
||||||
WordUtils.capitalizeFully(name().replaceAll("_", " ")),
|
WordUtils.capitalizeFully(name().replaceAll("_", " ")),
|
||||||
TextColor.color(0x848484)
|
TextColor.color(0x848484)
|
||||||
))
|
))
|
||||||
.append(Component.text(':')).append(Component.space().color(NamedTextColor.WHITE))
|
.append(Component.text(':')).append(Component.space().color(NamedTextColor.WHITE))
|
||||||
.append(supplier.apply(plugin));
|
.append(supplier.apply(plugin));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -274,7 +312,7 @@ public class HuskSyncCommand extends PluginCommand {
|
|||||||
@NotNull
|
@NotNull
|
||||||
private static Component getLocalhostBoolean(@NotNull String value) {
|
private static Component getLocalhostBoolean(@NotNull String value) {
|
||||||
return getBoolean(value.equals("127.0.0.1") || value.equals("0.0.0.0")
|
return getBoolean(value.equals("127.0.0.1") || value.equals("0.0.0.0")
|
||||||
|| value.equals("localhost") || value.equals("::1"));
|
|| value.equals("localhost") || value.equals("::1"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,29 +47,29 @@ public class InventoryCommand extends ItemsCommand {
|
|||||||
if (optionalInventory.isEmpty()) {
|
if (optionalInventory.isEmpty()) {
|
||||||
viewer.sendMessage(new MineDown("what the FUCK is happening"));
|
viewer.sendMessage(new MineDown("what the FUCK is happening"));
|
||||||
plugin.getLocales().getLocale("error_no_data_to_display")
|
plugin.getLocales().getLocale("error_no_data_to_display")
|
||||||
.ifPresent(viewer::sendMessage);
|
.ifPresent(viewer::sendMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display opening message
|
// Display opening message
|
||||||
plugin.getLocales().getLocale("inventory_viewer_opened", user.getUsername(),
|
plugin.getLocales().getLocale("inventory_viewer_opened", user.getUsername(),
|
||||||
snapshot.getTimestamp().format(DateTimeFormatter
|
snapshot.getTimestamp().format(DateTimeFormatter
|
||||||
.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT)))
|
.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT)))
|
||||||
.ifPresent(viewer::sendMessage);
|
.ifPresent(viewer::sendMessage);
|
||||||
|
|
||||||
// Show GUI
|
// Show GUI
|
||||||
final Data.Items.Inventory inventory = optionalInventory.get();
|
final Data.Items.Inventory inventory = optionalInventory.get();
|
||||||
viewer.showGui(
|
viewer.showGui(
|
||||||
inventory,
|
inventory,
|
||||||
plugin.getLocales().getLocale("inventory_viewer_menu_title", user.getUsername())
|
plugin.getLocales().getLocale("inventory_viewer_menu_title", user.getUsername())
|
||||||
.orElse(new MineDown(String.format("%s's Inventory", user.getUsername()))),
|
.orElse(new MineDown(String.format("%s's Inventory", user.getUsername()))),
|
||||||
allowEdit,
|
allowEdit,
|
||||||
inventory.getSlotCount(),
|
inventory.getSlotCount(),
|
||||||
(itemsOnClose) -> {
|
(itemsOnClose) -> {
|
||||||
if (allowEdit && !inventory.equals(itemsOnClose)) {
|
if (allowEdit && !inventory.equals(itemsOnClose)) {
|
||||||
plugin.runAsync(() -> this.updateItems(viewer, itemsOnClose, user));
|
plugin.runAsync(() -> this.updateItems(viewer, itemsOnClose, user));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ public class InventoryCommand extends ItemsCommand {
|
|||||||
final Optional<DataSnapshot.Packed> latestData = plugin.getDatabase().getLatestSnapshot(holder);
|
final Optional<DataSnapshot.Packed> latestData = plugin.getDatabase().getLatestSnapshot(holder);
|
||||||
if (latestData.isEmpty()) {
|
if (latestData.isEmpty()) {
|
||||||
plugin.getLocales().getLocale("error_no_data_to_display")
|
plugin.getLocales().getLocale("error_no_data_to_display")
|
||||||
.ifPresent(viewer::sendMessage);
|
.ifPresent(viewer::sendMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ public class InventoryCommand extends ItemsCommand {
|
|||||||
data.getInventory().ifPresent(inventory -> inventory.setContents(items));
|
data.getInventory().ifPresent(inventory -> inventory.setContents(items));
|
||||||
data.setSaveCause(DataSnapshot.SaveCause.INVENTORY_COMMAND);
|
data.setSaveCause(DataSnapshot.SaveCause.INVENTORY_COMMAND);
|
||||||
data.setPinned(
|
data.setPinned(
|
||||||
plugin.getSettings().getSynchronization().doAutoPin(DataSnapshot.SaveCause.INVENTORY_COMMAND)
|
plugin.getSettings().getSynchronization().doAutoPin(DataSnapshot.SaveCause.INVENTORY_COMMAND)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import java.util.UUID;
|
|||||||
public abstract class ItemsCommand extends PluginCommand {
|
public abstract class ItemsCommand extends PluginCommand {
|
||||||
|
|
||||||
protected ItemsCommand(@NotNull String name, @NotNull List<String> aliases, @NotNull HuskSync plugin) {
|
protected ItemsCommand(@NotNull String name, @NotNull List<String> aliases, @NotNull HuskSync plugin) {
|
||||||
super(name, aliases, Permission.Default.IF_OP, plugin);
|
super(name, aliases, Permission.Default.IF_OP, ExecutionScope.IN_GAME, plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -46,7 +46,7 @@ public abstract class ItemsCommand extends PluginCommand {
|
|||||||
final CommandUser executor = user(command, ctx);
|
final CommandUser executor = user(command, ctx);
|
||||||
if (!(executor instanceof OnlineUser online)) {
|
if (!(executor instanceof OnlineUser online)) {
|
||||||
plugin.getLocales().getLocale("error_in_game_command_only")
|
plugin.getLocales().getLocale("error_in_game_command_only")
|
||||||
.ifPresent(executor::sendMessage);
|
.ifPresent(executor::sendMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.showSnapshotItems(online, user, version);
|
this.showSnapshotItems(online, user, version);
|
||||||
@@ -56,7 +56,7 @@ public abstract class ItemsCommand extends PluginCommand {
|
|||||||
final CommandUser executor = user(command, ctx);
|
final CommandUser executor = user(command, ctx);
|
||||||
if (!(executor instanceof OnlineUser online)) {
|
if (!(executor instanceof OnlineUser online)) {
|
||||||
plugin.getLocales().getLocale("error_in_game_command_only")
|
plugin.getLocales().getLocale("error_in_game_command_only")
|
||||||
.ifPresent(executor::sendMessage);
|
.ifPresent(executor::sendMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.showLatestItems(online, user);
|
this.showLatestItems(online, user);
|
||||||
@@ -66,44 +66,44 @@ public abstract class ItemsCommand extends PluginCommand {
|
|||||||
// View (and edit) the latest user data
|
// View (and edit) the latest user data
|
||||||
private void showLatestItems(@NotNull OnlineUser viewer, @NotNull User user) {
|
private void showLatestItems(@NotNull OnlineUser viewer, @NotNull User user) {
|
||||||
plugin.getRedisManager().getUserData(user.getUuid(), user).thenAccept(data -> data
|
plugin.getRedisManager().getUserData(user.getUuid(), user).thenAccept(data -> data
|
||||||
.or(() -> plugin.getDatabase().getLatestSnapshot(user))
|
.or(() -> plugin.getDatabase().getLatestSnapshot(user))
|
||||||
.or(() -> {
|
.or(() -> {
|
||||||
plugin.getLocales().getLocale("error_no_data_to_display")
|
plugin.getLocales().getLocale("error_no_data_to_display")
|
||||||
.ifPresent(viewer::sendMessage);
|
.ifPresent(viewer::sendMessage);
|
||||||
return Optional.empty();
|
|
||||||
})
|
|
||||||
.flatMap(packed -> {
|
|
||||||
if (packed.isInvalid()) {
|
|
||||||
plugin.getLocales().getLocale("error_invalid_data", packed.getInvalidReason(plugin))
|
|
||||||
.ifPresent(viewer::sendMessage);
|
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
})
|
||||||
return Optional.of(packed.unpack(plugin));
|
.flatMap(packed -> {
|
||||||
})
|
if (packed.isInvalid()) {
|
||||||
.ifPresent(snapshot -> this.showItems(
|
plugin.getLocales().getLocale("error_invalid_data", packed.getInvalidReason(plugin))
|
||||||
viewer, snapshot, user, viewer.hasPermission(getPermission("edit"))
|
.ifPresent(viewer::sendMessage);
|
||||||
)));
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.of(packed.unpack(plugin));
|
||||||
|
})
|
||||||
|
.ifPresent(snapshot -> this.showItems(
|
||||||
|
viewer, snapshot, user, viewer.hasPermission(getPermission("edit"))
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// View a specific version of the user data
|
// View a specific version of the user data
|
||||||
private void showSnapshotItems(@NotNull OnlineUser viewer, @NotNull User user, @NotNull UUID version) {
|
private void showSnapshotItems(@NotNull OnlineUser viewer, @NotNull User user, @NotNull UUID version) {
|
||||||
plugin.getDatabase().getSnapshot(user, version)
|
plugin.getDatabase().getSnapshot(user, version)
|
||||||
.or(() -> {
|
.or(() -> {
|
||||||
plugin.getLocales().getLocale("error_invalid_version_uuid")
|
plugin.getLocales().getLocale("error_invalid_version_uuid")
|
||||||
.ifPresent(viewer::sendMessage);
|
.ifPresent(viewer::sendMessage);
|
||||||
return Optional.empty();
|
|
||||||
})
|
|
||||||
.flatMap(packed -> {
|
|
||||||
if (packed.isInvalid()) {
|
|
||||||
plugin.getLocales().getLocale("error_invalid_data", packed.getInvalidReason(plugin))
|
|
||||||
.ifPresent(viewer::sendMessage);
|
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
})
|
||||||
return Optional.of(packed.unpack(plugin));
|
.flatMap(packed -> {
|
||||||
})
|
if (packed.isInvalid()) {
|
||||||
.ifPresent(snapshot -> this.showItems(
|
plugin.getLocales().getLocale("error_invalid_data", packed.getInvalidReason(plugin))
|
||||||
viewer, snapshot, user, false
|
.ifPresent(viewer::sendMessage);
|
||||||
));
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.of(packed.unpack(plugin));
|
||||||
|
})
|
||||||
|
.ifPresent(snapshot -> this.showItems(
|
||||||
|
viewer, snapshot, user, false
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show a GUI menu with the correct item data from the snapshot
|
// Show a GUI menu with the correct item data from the snapshot
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ public abstract class PluginCommand extends Command {
|
|||||||
|
|
||||||
protected final HuskSync plugin;
|
protected final HuskSync plugin;
|
||||||
|
|
||||||
protected PluginCommand(@NotNull String name, @NotNull List<String> aliases,
|
protected PluginCommand(@NotNull String name, @NotNull List<String> aliases, @NotNull Permission.Default defPerm,
|
||||||
@NotNull Permission.Default permissionDefault, @NotNull HuskSync plugin) {
|
@NotNull ExecutionScope scope, @NotNull HuskSync plugin) {
|
||||||
super(name, aliases, getDescription(plugin, name), new Permission(createPermission(name), permissionDefault));
|
super(name, aliases, getDescription(plugin, name), new Permission(createPermission(name), defPerm), scope);
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import java.util.logging.Level;
|
|||||||
public class UserDataCommand extends PluginCommand {
|
public class UserDataCommand extends PluginCommand {
|
||||||
|
|
||||||
public UserDataCommand(@NotNull HuskSync plugin) {
|
public UserDataCommand(@NotNull HuskSync plugin) {
|
||||||
super("userdata", List.of("playerdata"), Permission.Default.IF_OP, plugin);
|
super("userdata", List.of("playerdata"), Permission.Default.IF_OP, ExecutionScope.ALL, plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -193,14 +193,20 @@ public class Locales {
|
|||||||
* Displays the notification in the action bar
|
* Displays the notification in the action bar
|
||||||
*/
|
*/
|
||||||
ACTION_BAR,
|
ACTION_BAR,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the notification in the chat
|
* Displays the notification in the chat
|
||||||
*/
|
*/
|
||||||
CHAT,
|
CHAT,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the notification in an Advancement Toast
|
* Displays the notification in an Advancement Toast
|
||||||
|
*
|
||||||
|
* @deprecated No longer supported
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "3.6.7")
|
||||||
TOAST,
|
TOAST,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does not display the notification
|
* Does not display the notification
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public class Settings {
|
|||||||
private boolean checkForUpdates = true;
|
private boolean checkForUpdates = true;
|
||||||
|
|
||||||
@Comment("Specify a common ID for grouping servers running HuskSync. "
|
@Comment("Specify a common ID for grouping servers running HuskSync. "
|
||||||
+ "Don't modify this unless you know what you're doing!")
|
+ "Don't modify this unless you know what you're doing!")
|
||||||
private String clusterId = "";
|
private String clusterId = "";
|
||||||
|
|
||||||
@Comment("Enable development debug logging")
|
@Comment("Enable development debug logging")
|
||||||
@@ -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());
|
||||||
@@ -229,7 +232,7 @@ public class Settings {
|
|||||||
private boolean enabled = false;
|
private boolean enabled = false;
|
||||||
|
|
||||||
@Comment("What items to save in death snapshots? (DROPS or ITEMS_TO_KEEP). "
|
@Comment("What items to save in death snapshots? (DROPS or ITEMS_TO_KEEP). "
|
||||||
+ "Note that ITEMS_TO_KEEP (suggested for keepInventory servers) requires a Paper 1.19.4+ server.")
|
+ "Note that ITEMS_TO_KEEP (suggested for keepInventory servers) requires a Paper 1.19.4+ server.")
|
||||||
private DeathItemsMode itemsToSave = DeathItemsMode.DROPS;
|
private DeathItemsMode itemsToSave = DeathItemsMode.DROPS;
|
||||||
|
|
||||||
@Comment("Should a death snapshot still be created even if the items to save on the player's death are empty?")
|
@Comment("Should a death snapshot still be created even if the items to save on the player's death are empty?")
|
||||||
@@ -250,14 +253,14 @@ public class Settings {
|
|||||||
@Comment("Whether to use the snappy data compression algorithm. Keep on unless you know what you're doing")
|
@Comment("Whether to use the snappy data compression algorithm. Keep on unless you know what you're doing")
|
||||||
private boolean compressData = true;
|
private boolean compressData = true;
|
||||||
|
|
||||||
@Comment("Where to display sync notifications (ACTION_BAR, CHAT, TOAST or NONE)")
|
@Comment("Where to display sync notifications (ACTION_BAR, CHAT or NONE)")
|
||||||
private Locales.NotificationSlot notificationDisplaySlot = Locales.NotificationSlot.ACTION_BAR;
|
private Locales.NotificationSlot notificationDisplaySlot = Locales.NotificationSlot.ACTION_BAR;
|
||||||
|
|
||||||
@Comment("Persist maps locked in a Cartography Table to let them be viewed on any server")
|
@Comment("Persist maps locked in a Cartography Table to let them be viewed on any server")
|
||||||
private boolean persistLockedMaps = true;
|
private boolean persistLockedMaps = true;
|
||||||
|
|
||||||
@Comment("If using the DELAY sync method, how long should this server listen for Redis key data updates before "
|
@Comment("If using the DELAY sync method, how long should this server listen for Redis key data updates before "
|
||||||
+ "pulling data from the database instead (i.e., if the user did not change servers).")
|
+ "pulling data from the database instead (i.e., if the user did not change servers).")
|
||||||
private int networkLatencyMilliseconds = 500;
|
private int networkLatencyMilliseconds = 500;
|
||||||
|
|
||||||
@Comment({"Which data types to synchronize.", "Docs: https://william278.net/docs/husksync/sync-features"})
|
@Comment({"Which data types to synchronize.", "Docs: https://william278.net/docs/husksync/sync-features"})
|
||||||
@@ -267,10 +270,52 @@ public class Settings {
|
|||||||
@Comment("Commands which should be blocked before a player has finished syncing (Use * to block all commands)")
|
@Comment("Commands which should be blocked before a player has finished syncing (Use * to block all commands)")
|
||||||
private List<String> blacklistedCommandsWhileLocked = new ArrayList<>(List.of("*"));
|
private List<String> blacklistedCommandsWhileLocked = new ArrayList<>(List.of("*"));
|
||||||
|
|
||||||
@Comment({"For attribute syncing, which attributes should be ignored/skipped when syncing",
|
@Comment("Configuration for how to sync attributes")
|
||||||
"(e.g. ['minecraft:generic.max_health', 'minecraft:generic.attack_damage'])"})
|
private AttributeSettings attributes = new AttributeSettings();
|
||||||
@Getter(AccessLevel.NONE)
|
|
||||||
private List<String> ignoredAttributes = new ArrayList<>(List.of(""));
|
@Getter
|
||||||
|
@Configuration
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public static class AttributeSettings {
|
||||||
|
|
||||||
|
@Comment({"Which attribute types should be saved as part of attribute syncing. Supports wildcard matching.",
|
||||||
|
"(e.g. ['minecraft:generic.max_health', 'minecraft:generic.*'])"})
|
||||||
|
@Getter(AccessLevel.NONE)
|
||||||
|
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 attribute modifiers should be saved. Supports wildcard matching.",
|
||||||
|
"(e.g. ['minecraft:effect.speed', 'minecraft:effect.*'])"})
|
||||||
|
@Getter(AccessLevel.NONE)
|
||||||
|
private List<String> ignoredModifiers = new ArrayList<>(List.of(
|
||||||
|
"minecraft:effect.*", "minecraft:creative_mode_*"
|
||||||
|
));
|
||||||
|
|
||||||
|
private boolean matchesWildcard(@NotNull String pat, @NotNull String value) {
|
||||||
|
if (!pat.contains(":")) {
|
||||||
|
pat = "minecraft:%s".formatted(pat);
|
||||||
|
}
|
||||||
|
if (!value.contains(":")) {
|
||||||
|
value = "minecraft:%s".formatted(value);
|
||||||
|
}
|
||||||
|
return pat.contains("*") ? value.matches(pat.replace("*", ".*")) : pat.equals(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIgnoredAttribute(@NotNull String attribute) {
|
||||||
|
return syncedAttributes.stream().noneMatch(wildcard -> matchesWildcard(wildcard, attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIgnoredModifier(@NotNull String modifier) {
|
||||||
|
return ignoredModifiers.stream().anyMatch(wildcard -> matchesWildcard(wildcard, modifier));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Comment("Event priorities for listeners (HIGHEST, NORMAL, LOWEST). Change if you encounter plugin conflicts")
|
@Comment("Event priorities for listeners (HIGHEST, NORMAL, LOWEST). Change if you encounter plugin conflicts")
|
||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
@@ -284,10 +329,6 @@ public class Settings {
|
|||||||
return id.isCustom() || features.getOrDefault(id.getKeyValue(), id.isEnabledByDefault());
|
return id.isCustom() || features.getOrDefault(id.getKeyValue(), id.isEnabledByDefault());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isIgnoredAttribute(@NotNull String attribute) {
|
|
||||||
return ignoredAttributes.contains(attribute);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public EventListener.Priority getEventPriority(@NotNull EventListener.ListenerType type) {
|
public EventListener.Priority getEventPriority(@NotNull EventListener.ListenerType type) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -155,14 +154,14 @@ public interface Data {
|
|||||||
*/
|
*/
|
||||||
interface Advancements extends Data {
|
interface Advancements extends Data {
|
||||||
|
|
||||||
|
String RECIPE_ADVANCEMENT = "minecraft:recipe";
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
List<Advancement> getCompleted();
|
List<Advancement> getCompleted();
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
default List<Advancement> getCompletedExcludingRecipes() {
|
default List<Advancement> getCompletedExcludingRecipes() {
|
||||||
return getCompleted().stream()
|
return getCompleted().stream().filter(adv -> !adv.getKey().startsWith(RECIPE_ADVANCEMENT)).toList();
|
||||||
.filter(advancement -> !advancement.getKey().startsWith("minecraft:recipe"))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setCompleted(@NotNull List<Advancement> completed);
|
void setCompleted(@NotNull List<Advancement> completed);
|
||||||
@@ -191,13 +190,13 @@ public interface Data {
|
|||||||
@NotNull
|
@NotNull
|
||||||
private static Map<String, Long> adaptDateMap(@NotNull Map<String, Date> dateMap) {
|
private static Map<String, Long> adaptDateMap(@NotNull Map<String, Date> dateMap) {
|
||||||
return dateMap.entrySet().stream()
|
return dateMap.entrySet().stream()
|
||||||
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getTime()));
|
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getTime()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private static Map<String, Date> adaptLongMap(@NotNull Map<String, Long> dateMap) {
|
private static Map<String, Date> adaptLongMap(@NotNull Map<String, Long> dateMap) {
|
||||||
return dateMap.entrySet().stream()
|
return dateMap.entrySet().stream()
|
||||||
.collect(Collectors.toMap(Map.Entry::getKey, e -> new Date(e.getValue())));
|
.collect(Collectors.toMap(Map.Entry::getKey, e -> new Date(e.getValue())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -250,9 +249,9 @@ public interface Data {
|
|||||||
void setWorld(@NotNull World world);
|
void setWorld(@NotNull World world);
|
||||||
|
|
||||||
record World(
|
record World(
|
||||||
@SerializedName("name") @NotNull String name,
|
@SerializedName("name") @NotNull String name,
|
||||||
@SerializedName("uuid") @NotNull UUID uuid,
|
@SerializedName("uuid") @NotNull UUID uuid,
|
||||||
@SerializedName("environment") @NotNull String environment
|
@SerializedName("environment") @NotNull String environment
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -324,9 +323,9 @@ public interface Data {
|
|||||||
List<Attribute> getAttributes();
|
List<Attribute> getAttributes();
|
||||||
|
|
||||||
record Attribute(
|
record Attribute(
|
||||||
@NotNull String name,
|
@NotNull String name,
|
||||||
double baseValue,
|
double baseValue,
|
||||||
@NotNull Set<Modifier> modifiers
|
@NotNull Set<Modifier> modifiers
|
||||||
) {
|
) {
|
||||||
|
|
||||||
public double getValue() {
|
public double getValue() {
|
||||||
@@ -341,36 +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) {
|
||||||
return obj instanceof Modifier modifier && modifier.uuid().equals(uuid());
|
if (obj instanceof Modifier other) {
|
||||||
|
if (uuid != null && other.uuid != null) {
|
||||||
|
return uuid.equals(other.uuid);
|
||||||
|
}
|
||||||
|
return name.equals(other.name);
|
||||||
|
}
|
||||||
|
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);
|
||||||
@@ -378,6 +401,10 @@ public interface Data {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasUuid() {
|
||||||
|
return uuid != null;
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public UUID uuid() {
|
public UUID uuid() {
|
||||||
return uuid != null ? uuid : UUID.nameUUIDFromBytes(name.getBytes());
|
return uuid != null ? uuid : UUID.nameUUIDFromBytes(name.getBytes());
|
||||||
@@ -387,8 +414,8 @@ public interface Data {
|
|||||||
|
|
||||||
default Optional<Attribute> getAttribute(@NotNull Key key) {
|
default Optional<Attribute> getAttribute(@NotNull Key key) {
|
||||||
return getAttributes().stream()
|
return getAttributes().stream()
|
||||||
.filter(attribute -> attribute.name().equals(key.asString()))
|
.filter(attribute -> attribute.name().equals(key.asString()))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
default void removeAttribute(@NotNull Key key) {
|
default void removeAttribute(@NotNull Key key) {
|
||||||
@@ -397,8 +424,8 @@ public interface Data {
|
|||||||
|
|
||||||
default double getMaxHealth() {
|
default double getMaxHealth() {
|
||||||
return getAttribute(MAX_HEALTH_KEY)
|
return getAttribute(MAX_HEALTH_KEY)
|
||||||
.map(Attribute::getValue)
|
.map(Attribute::getValue)
|
||||||
.orElse(20.0);
|
.orElse(20.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
default void setMaxHealth(double maxHealth) {
|
default void setMaxHealth(double maxHealth) {
|
||||||
|
|||||||
@@ -45,13 +45,13 @@ public class DataException extends IllegalStateException {
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public enum Reason {
|
public enum Reason {
|
||||||
INVALID_MINECRAFT_VERSION((plugin, snapshot) -> String.format("The Minecraft version of the snapshot (%s) is " +
|
INVALID_MINECRAFT_VERSION((plugin, snapshot) -> String.format("The Minecraft version of the snapshot (%s) is " +
|
||||||
"newer than the server's version (%s). Ensure each server is on the same version of Minecraft.",
|
"newer than the server's version (%s). Ensure each server is on the same version of Minecraft.",
|
||||||
snapshot.getMinecraftVersion(), plugin.getMinecraftVersion())),
|
snapshot.getMinecraftVersion(), plugin.getMinecraftVersion())),
|
||||||
INVALID_FORMAT_VERSION((plugin, snapshot) -> String.format("The format version of the snapshot (%s) is newer " +
|
INVALID_FORMAT_VERSION((plugin, snapshot) -> String.format("The format version of the snapshot (%s) is newer " +
|
||||||
"than the server's version (%s). Ensure each server is running the same version of HuskSync.",
|
"than the server's version (%s). Ensure each server is running the same version of HuskSync.",
|
||||||
snapshot.getFormatVersion(), DataSnapshot.CURRENT_FORMAT_VERSION)),
|
snapshot.getFormatVersion(), DataSnapshot.CURRENT_FORMAT_VERSION)),
|
||||||
INVALID_PLATFORM_TYPE((plugin, snapshot) -> String.format("The platform type of the snapshot (%s) does " +
|
INVALID_PLATFORM_TYPE((plugin, snapshot) -> String.format("The platform type of the snapshot (%s) does " +
|
||||||
"not match the server's platform type (%s). Ensure each server has the same platform type.",
|
"not match the server's platform type (%s). Ensure each server has the same platform type.",
|
||||||
snapshot.getPlatformType(), plugin.getPlatformType())),
|
snapshot.getPlatformType(), plugin.getPlatformType())),
|
||||||
NO_LEGACY_CONVERTER((plugin, snapshot) -> String.format("No legacy converter to convert format version: %s",
|
NO_LEGACY_CONVERTER((plugin, snapshot) -> String.format("No legacy converter to convert format version: %s",
|
||||||
snapshot.getFormatVersion()));
|
snapshot.getFormatVersion()));
|
||||||
|
|||||||
@@ -395,7 +395,7 @@ public class DataSnapshot {
|
|||||||
.map(entry -> Map.entry(plugin.getIdentifier(entry.getKey()).orElseThrow(), entry.getValue()))
|
.map(entry -> Map.entry(plugin.getIdentifier(entry.getKey()).orElseThrow(), entry.getValue()))
|
||||||
.collect(Collectors.toMap(
|
.collect(Collectors.toMap(
|
||||||
Map.Entry::getKey,
|
Map.Entry::getKey,
|
||||||
entry -> plugin.deserializeData(entry.getKey(), entry.getValue()),
|
entry -> plugin.deserializeData(entry.getKey(), entry.getValue(), getMinecraftVersion()),
|
||||||
(a, b) -> b, () -> Maps.newTreeMap(SerializerRegistry.DEPENDENCY_ORDER_COMPARATOR)
|
(a, b) -> b, () -> Maps.newTreeMap(SerializerRegistry.DEPENDENCY_ORDER_COMPARATOR)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -535,9 +535,9 @@ public class DataSnapshot {
|
|||||||
public Builder timestamp(@NotNull OffsetDateTime timestamp) {
|
public Builder timestamp(@NotNull OffsetDateTime timestamp) {
|
||||||
if (timestamp.isAfter(OffsetDateTime.now())) {
|
if (timestamp.isAfter(OffsetDateTime.now())) {
|
||||||
throw new IllegalArgumentException("Data snapshots cannot have a timestamp set in the future! "
|
throw new IllegalArgumentException("Data snapshots cannot have a timestamp set in the future! "
|
||||||
+ "Make sure your database server time matches the server time.\n"
|
+ "Make sure your database server time matches the server time.\n"
|
||||||
+ "Current game server timestamp: " + OffsetDateTime.now() + " / "
|
+ "Current game server timestamp: " + OffsetDateTime.now() + " / "
|
||||||
+ "Snapshot timestamp: " + timestamp);
|
+ "Snapshot timestamp: " + timestamp);
|
||||||
}
|
}
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ public class Identifier {
|
|||||||
Dependency.optional("game_mode")
|
Dependency.optional("game_mode")
|
||||||
);
|
);
|
||||||
public static final Identifier ATTRIBUTES = huskSync("attributes", true,
|
public static final Identifier ATTRIBUTES = huskSync("attributes", true,
|
||||||
Dependency.required("potion_effects")
|
Dependency.optional("inventory"),
|
||||||
|
Dependency.optional("potion_effects")
|
||||||
);
|
);
|
||||||
public static final Identifier HEALTH = huskSync("health", true,
|
public static final Identifier HEALTH = huskSync("health", true,
|
||||||
Dependency.optional("attributes")
|
Dependency.optional("attributes")
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
|
import net.william278.desertwell.util.Version;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -119,19 +120,36 @@ public interface SerializerRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deserialize data for the given {@link Identifier}
|
* Deserialize data of a given {@link Version Minecraft version} for the given {@link Identifier data identifier}
|
||||||
|
*
|
||||||
|
* @param identifier the {@link Identifier} to deserialize data for
|
||||||
|
* @param data the data to deserialize
|
||||||
|
* @param dataMcVersion the Minecraft version of the data
|
||||||
|
* @return the deserialized data
|
||||||
|
* @throws IllegalStateException if no serializer is found for the given {@link Identifier}
|
||||||
|
* @since 3.6.4
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
default Data deserializeData(@NotNull Identifier identifier, @NotNull String data,
|
||||||
|
@NotNull Version dataMcVersion) throws IllegalStateException {
|
||||||
|
return getSerializer(identifier).map(serializer -> serializer.deserialize(data, dataMcVersion)).orElseThrow(
|
||||||
|
() -> new IllegalStateException("No serializer found for %s".formatted(identifier))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize data for the given {@link Identifier data identifier}
|
||||||
*
|
*
|
||||||
* @param identifier the {@link Identifier} to deserialize data for
|
* @param identifier the {@link Identifier} to deserialize data for
|
||||||
* @param data the data to deserialize
|
* @param data the data to deserialize
|
||||||
* @return the deserialized data
|
* @return the deserialized data
|
||||||
* @throws IllegalStateException if no serializer is found for the given {@link Identifier}
|
|
||||||
* @since 3.5.4
|
* @since 3.5.4
|
||||||
|
* @deprecated Use {@link #deserializeData(Identifier, String, Version)} instead
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
default Data deserializeData(@NotNull Identifier identifier, @NotNull String data) throws IllegalStateException {
|
@Deprecated(since = "3.6.5")
|
||||||
return getSerializer(identifier).map(serializer -> serializer.deserialize(data)).orElseThrow(
|
default Data deserializeData(@NotNull Identifier identifier, @NotNull String data) {
|
||||||
() -> new IllegalStateException("No serializer found for %s".formatted(identifier))
|
return deserializeData(identifier, data, getPlugin().getMinecraftVersion());
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -50,17 +50,13 @@ public class MongoDbDatabase extends Database {
|
|||||||
|
|
||||||
private final String usersTable;
|
private final String usersTable;
|
||||||
private final String userDataTable;
|
private final String userDataTable;
|
||||||
|
|
||||||
public MongoDbDatabase(@NotNull HuskSync plugin) {
|
public MongoDbDatabase(@NotNull HuskSync plugin) {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
this.usersTable = plugin.getSettings().getDatabase().getTableName(TableName.USERS);
|
this.usersTable = plugin.getSettings().getDatabase().getTableName(TableName.USERS);
|
||||||
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();
|
||||||
@@ -68,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);
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ public class MongoDbDatabase extends Database {
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalStateException("Failed to establish a connection to the MongoDB database. " +
|
throw new IllegalStateException("Failed to establish a connection to the MongoDB database. " +
|
||||||
"Please check the supplied database credentials in the config file", e);
|
"Please check the supplied database credentials in the config file", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,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) {
|
||||||
@@ -135,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) {
|
||||||
@@ -157,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) {
|
||||||
@@ -180,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) {
|
||||||
@@ -208,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
|
||||||
@@ -237,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) {
|
||||||
@@ -265,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) {
|
||||||
@@ -296,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) {
|
||||||
@@ -319,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) {
|
||||||
@@ -351,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) {
|
||||||
@@ -373,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) {
|
||||||
@@ -395,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() {
|
||||||
@@ -409,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));
|
||||||
@@ -124,11 +127,11 @@ public class MySqlDatabase extends Database {
|
|||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new IllegalStateException("Failed to create database tables. Please ensure you are running MySQL v8.0+ " +
|
throw new IllegalStateException("Failed to create database tables. Please ensure you are running MySQL v8.0+ " +
|
||||||
"and that your connecting user account has privileges to create tables.", e);
|
"and that your connecting user account has privileges to create tables.", e);
|
||||||
}
|
}
|
||||||
} catch (SQLException | IOException e) {
|
} catch (SQLException | IOException e) {
|
||||||
throw new IllegalStateException("Failed to establish a connection to the MySQL database. " +
|
throw new IllegalStateException("Failed to establish a connection to the MySQL database. " +
|
||||||
"Please check the supplied database credentials in the config file", e);
|
"Please check the supplied database credentials in the config file", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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));
|
||||||
@@ -123,11 +120,11 @@ public class PostgresDatabase extends Database {
|
|||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new IllegalStateException("Failed to create database tables. Please ensure you are running PostgreSQL " +
|
throw new IllegalStateException("Failed to create database tables. Please ensure you are running PostgreSQL " +
|
||||||
"and that your connecting user account has privileges to create tables.", e);
|
"and that your connecting user account has privileges to create tables.", e);
|
||||||
}
|
}
|
||||||
} catch (SQLException | IOException e) {
|
} catch (SQLException | IOException e) {
|
||||||
throw new IllegalStateException("Failed to establish a connection to the PostgreSQL database. " +
|
throw new IllegalStateException("Failed to establish a connection to the PostgreSQL database. " +
|
||||||
"Please check the supplied database credentials in the config file", e);
|
"Please check the supplied database credentials in the config file", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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,9 +414,9 @@ 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;"""))) {
|
LIMIT 1;"""))) {
|
||||||
statement.setString(1, data.getSaveCause().name());
|
statement.setString(1, data.getSaveCause().name());
|
||||||
statement.setBoolean(2, data.isPinned());
|
statement.setBoolean(2, data.isPinned());
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ public class MongoCollectionHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the collection helper
|
* Initialize the collection helper
|
||||||
|
*
|
||||||
* @param database Instance of {@link MongoConnectionHandler}
|
* @param database Instance of {@link MongoConnectionHandler}
|
||||||
*/
|
*/
|
||||||
public MongoCollectionHelper(@NotNull MongoConnectionHandler database) {
|
public MongoCollectionHelper(@NotNull MongoConnectionHandler database) {
|
||||||
@@ -37,6 +38,7 @@ public class MongoCollectionHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a collection
|
* Create a collection
|
||||||
|
*
|
||||||
* @param collectionName the collection name
|
* @param collectionName the collection name
|
||||||
*/
|
*/
|
||||||
public void createCollection(@NotNull String collectionName) {
|
public void createCollection(@NotNull String collectionName) {
|
||||||
@@ -45,6 +47,7 @@ public class MongoCollectionHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a collection
|
* Delete a collection
|
||||||
|
*
|
||||||
* @param collectionName the collection name
|
* @param collectionName the collection name
|
||||||
*/
|
*/
|
||||||
public void deleteCollection(@NotNull String collectionName) {
|
public void deleteCollection(@NotNull String collectionName) {
|
||||||
@@ -53,6 +56,7 @@ public class MongoCollectionHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a collection
|
* Get a collection
|
||||||
|
*
|
||||||
* @param collectionName the collection name
|
* @param collectionName the collection name
|
||||||
* @return MongoCollection<Document>
|
* @return MongoCollection<Document>
|
||||||
*/
|
*/
|
||||||
@@ -62,8 +66,9 @@ public class MongoCollectionHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a document to a collection
|
* Add a document to a collection
|
||||||
|
*
|
||||||
* @param collectionName collection to add to
|
* @param collectionName collection to add to
|
||||||
* @param document Document to add
|
* @param document Document to add
|
||||||
*/
|
*/
|
||||||
public void insertDocument(@NotNull String collectionName, @NotNull Document document) {
|
public void insertDocument(@NotNull String collectionName, @NotNull Document document) {
|
||||||
MongoCollection<Document> collection = database.getDatabase().getCollection(collectionName);
|
MongoCollection<Document> collection = database.getDatabase().getCollection(collectionName);
|
||||||
@@ -72,9 +77,10 @@ public class MongoCollectionHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Update a document
|
* Update a document
|
||||||
|
*
|
||||||
* @param collectionName collection the document is in
|
* @param collectionName collection the document is in
|
||||||
* @param document filter of document
|
* @param document filter of document
|
||||||
* @param updates Bson of updates
|
* @param updates Bson of updates
|
||||||
*/
|
*/
|
||||||
public void updateDocument(@NotNull String collectionName, @NotNull Document document, @NotNull Bson updates) {
|
public void updateDocument(@NotNull String collectionName, @NotNull Document document, @NotNull Bson updates) {
|
||||||
MongoCollection<Document> collection = database.getDatabase().getCollection(collectionName);
|
MongoCollection<Document> collection = database.getDatabase().getCollection(collectionName);
|
||||||
@@ -83,8 +89,9 @@ public class MongoCollectionHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a document
|
* Delete a document
|
||||||
|
*
|
||||||
* @param collectionName collection the document is in
|
* @param collectionName collection the document is in
|
||||||
* @param document filter to remove
|
* @param document filter to remove
|
||||||
*/
|
*/
|
||||||
public void deleteDocument(@NotNull String collectionName, @NotNull Document document) {
|
public void deleteDocument(@NotNull String collectionName, @NotNull Document document) {
|
||||||
MongoCollection<Document> collection = database.getDatabase().getCollection(collectionName);
|
MongoCollection<Document> collection = database.getDatabase().getCollection(collectionName);
|
||||||
|
|||||||
@@ -35,9 +35,10 @@ public class MongoConnectionHandler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiate a connection to a Mongo Server
|
* Initiate a connection to a Mongo Server
|
||||||
|
*
|
||||||
* @param uri The connection string
|
* @param uri The connection string
|
||||||
*/
|
*/
|
||||||
public MongoConnectionHandler(@NotNull ConnectionString uri, @NotNull String databaseName) {
|
public MongoConnectionHandler(@NotNull ConnectionString uri, @NotNull String databaseName) {
|
||||||
try {
|
try {
|
||||||
final MongoClientSettings settings = MongoClientSettings.builder()
|
final MongoClientSettings settings = MongoClientSettings.builder()
|
||||||
.applyConnectionString(uri)
|
.applyConnectionString(uri)
|
||||||
@@ -48,7 +49,7 @@ public class MongoConnectionHandler {
|
|||||||
this.database = mongoClient.getDatabase(databaseName);
|
this.database = mongoClient.getDatabase(databaseName);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalStateException("Failed to establish a connection to the MongoDB database. " +
|
throw new IllegalStateException("Failed to establish a connection to the MongoDB database. " +
|
||||||
"Please check the supplied database credentials in the config file", e);
|
"Please check the supplied database credentials in the config file", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public abstract class EventListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
plugin.lockPlayer(user.getUuid());
|
plugin.lockPlayer(user.getUuid());
|
||||||
plugin.getDataSyncer().setUserData(user);
|
plugin.getDataSyncer().syncApplyUserData(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,7 +66,7 @@ public abstract class EventListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
plugin.lockPlayer(user.getUuid());
|
plugin.lockPlayer(user.getUuid());
|
||||||
plugin.getDataSyncer().saveUserData(user);
|
plugin.getDataSyncer().syncSaveUserData(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,7 +94,7 @@ public abstract class EventListener {
|
|||||||
protected void saveOnPlayerDeath(@NotNull OnlineUser user, @NotNull Data.Items items) {
|
protected void saveOnPlayerDeath(@NotNull OnlineUser user, @NotNull Data.Items items) {
|
||||||
final SaveOnDeathSettings settings = plugin.getSettings().getSynchronization().getSaveOnDeath();
|
final SaveOnDeathSettings settings = plugin.getSettings().getSynchronization().getSaveOnDeath();
|
||||||
if (plugin.isDisabling() || !settings.isEnabled() || plugin.isLocked(user.getUuid())
|
if (plugin.isDisabling() || !settings.isEnabled() || plugin.isLocked(user.getUuid())
|
||||||
|| user.isNpc() || (!settings.isSaveEmptyItems() && items.isEmpty())) {
|
|| user.isNpc() || (!settings.isSaveEmptyItems() && items.isEmpty())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ public class RedisManager extends JedisPubSub {
|
|||||||
jedisPool.getResource().ping();
|
jedisPool.getResource().ping();
|
||||||
} catch (JedisException e) {
|
} catch (JedisException e) {
|
||||||
throw new IllegalStateException("Failed to establish connection with Redis. "
|
throw new IllegalStateException("Failed to establish connection with Redis. "
|
||||||
+ "Please check the supplied credentials in the config file", e);
|
+ "Please check the supplied credentials in the config file", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscribe using a thread (rather than a task)
|
// Subscribe using a thread (rather than a task)
|
||||||
@@ -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);
|
||||||
@@ -281,16 +282,21 @@ public class RedisManager extends JedisPubSub {
|
|||||||
@Blocking
|
@Blocking
|
||||||
public void setUserCheckedOut(@NotNull User user, boolean checkedOut) {
|
public void setUserCheckedOut(@NotNull User user, boolean checkedOut) {
|
||||||
try (Jedis jedis = jedisPool.getResource()) {
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
|
final String key = getKeyString(RedisKeyType.DATA_CHECKOUT, user.getUuid(), clusterId);
|
||||||
if (checkedOut) {
|
if (checkedOut) {
|
||||||
jedis.set(
|
jedis.set(
|
||||||
getKey(RedisKeyType.DATA_CHECKOUT, user.getUuid(), clusterId),
|
key.getBytes(StandardCharsets.UTF_8),
|
||||||
plugin.getServerName().getBytes(StandardCharsets.UTF_8)
|
plugin.getServerName().getBytes(StandardCharsets.UTF_8)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
jedis.del(getKey(RedisKeyType.DATA_CHECKOUT, user.getUuid(), clusterId));
|
if (jedis.del(key.getBytes(StandardCharsets.UTF_8)) == 0) {
|
||||||
|
plugin.debug(String.format("[%s] %s key not set on Redis when attempting removal (%s)",
|
||||||
|
user.getUsername(), RedisKeyType.DATA_CHECKOUT, key));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
plugin.debug(String.format("[%s] %s %s key to/from Redis", user.getUsername(),
|
plugin.debug(String.format("[%s] %s %s key %s Redis (%s)", user.getUsername(),
|
||||||
checkedOut ? "Set" : "Removed", RedisKeyType.DATA_CHECKOUT));
|
checkedOut ? "Set" : "Removed", RedisKeyType.DATA_CHECKOUT, checkedOut ? "to" : "from", key));
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
plugin.log(Level.SEVERE, "An exception occurred setting checkout to", e);
|
plugin.log(Level.SEVERE, "An exception occurred setting checkout to", e);
|
||||||
}
|
}
|
||||||
@@ -418,7 +424,12 @@ public class RedisManager extends JedisPubSub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] getKey(@NotNull RedisKeyType keyType, @NotNull UUID uuid, @NotNull String clusterId) {
|
private static byte[] getKey(@NotNull RedisKeyType keyType, @NotNull UUID uuid, @NotNull String clusterId) {
|
||||||
return String.format("%s:%s", keyType.getKeyPrefix(clusterId), uuid).getBytes(StandardCharsets.UTF_8);
|
return getKeyString(keyType, uuid, clusterId).getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private static String getKeyString(@NotNull RedisKeyType keyType, @NotNull UUID uuid, @NotNull String clusterId) {
|
||||||
|
return String.format("%s:%s", keyType.getKeyPrefix(clusterId), uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -81,18 +81,18 @@ public abstract class DataSyncer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a user's data should be fetched and applied to them
|
* Called when a user's data should be fetched and applied to them as part of a synchronization process
|
||||||
*
|
*
|
||||||
* @param user the user to fetch data for
|
* @param user the user to fetch data for
|
||||||
*/
|
*/
|
||||||
public abstract void setUserData(@NotNull OnlineUser user);
|
public abstract void syncApplyUserData(@NotNull OnlineUser user);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a user's data should be serialized and saved
|
* Called when a user's data should be serialized and saved as part of a synchronization process
|
||||||
*
|
*
|
||||||
* @param user the user to save
|
* @param user the user to save
|
||||||
*/
|
*/
|
||||||
public abstract void saveUserData(@NotNull OnlineUser user);
|
public abstract void syncSaveUserData(@NotNull OnlineUser user);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save a {@link DataSnapshot.Packed user's data snapshot} to the database,
|
* Save a {@link DataSnapshot.Packed user's data snapshot} to the database,
|
||||||
@@ -150,7 +150,7 @@ public abstract class DataSyncer {
|
|||||||
private long getMaxListenAttempts() {
|
private long getMaxListenAttempts() {
|
||||||
return BASE_LISTEN_ATTEMPTS + (
|
return BASE_LISTEN_ATTEMPTS + (
|
||||||
(Math.max(100, plugin.getSettings().getSynchronization().getNetworkLatencyMilliseconds()) / 1000)
|
(Math.max(100, plugin.getSettings().getSynchronization().getNetworkLatencyMilliseconds()) / 1000)
|
||||||
* 20 / LISTEN_DELAY
|
* 20 / LISTEN_DELAY
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class DelayDataSyncer extends DataSyncer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUserData(@NotNull OnlineUser user) {
|
public void syncApplyUserData(@NotNull OnlineUser user) {
|
||||||
plugin.runAsyncDelayed(
|
plugin.runAsyncDelayed(
|
||||||
() -> {
|
() -> {
|
||||||
// Fetch from the database if the user isn't changing servers
|
// Fetch from the database if the user isn't changing servers
|
||||||
@@ -58,7 +58,7 @@ public class DelayDataSyncer extends DataSyncer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void saveUserData(@NotNull OnlineUser onlineUser) {
|
public void syncSaveUserData(@NotNull OnlineUser onlineUser) {
|
||||||
plugin.runAsync(() -> {
|
plugin.runAsync(() -> {
|
||||||
getRedis().setUserServerSwitch(onlineUser);
|
getRedis().setUserServerSwitch(onlineUser);
|
||||||
saveData(
|
saveData(
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class LockstepDataSyncer extends DataSyncer {
|
|||||||
|
|
||||||
// Consume their data when they are checked in
|
// Consume their data when they are checked in
|
||||||
@Override
|
@Override
|
||||||
public void setUserData(@NotNull OnlineUser user) {
|
public void syncApplyUserData(@NotNull OnlineUser user) {
|
||||||
this.listenForRedisData(user, () -> {
|
this.listenForRedisData(user, () -> {
|
||||||
if (getRedis().getUserCheckedOut(user).isPresent()) {
|
if (getRedis().getUserCheckedOut(user).isPresent()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -58,7 +58,7 @@ public class LockstepDataSyncer extends DataSyncer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void saveUserData(@NotNull OnlineUser onlineUser) {
|
public void syncSaveUserData(@NotNull OnlineUser onlineUser) {
|
||||||
plugin.runAsync(() -> saveData(
|
plugin.runAsync(() -> saveData(
|
||||||
onlineUser, onlineUser.createSnapshot(DataSnapshot.SaveCause.DISCONNECT),
|
onlineUser, onlineUser.createSnapshot(DataSnapshot.SaveCause.DISCONNECT),
|
||||||
(user, data) -> {
|
(user, data) -> {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import net.kyori.adventure.audience.Audience;
|
|||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public interface CommandUser {
|
public interface CommandUser {
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
Audience getAudience();
|
Audience getAudience();
|
||||||
|
|||||||
@@ -89,7 +89,9 @@ public abstract class OnlineUser extends User implements CommandUser, UserDataHo
|
|||||||
* @param description the description of the toast
|
* @param description the description of the toast
|
||||||
* @param iconMaterial the namespace-keyed material to use as an hasIcon of the toast
|
* @param iconMaterial the namespace-keyed material to use as an hasIcon of the toast
|
||||||
* @param backgroundType the background ("ToastType") of the toast
|
* @param backgroundType the background ("ToastType") of the toast
|
||||||
|
* @deprecated No longer supported
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "3.6.7")
|
||||||
public abstract void sendToast(@NotNull MineDown title, @NotNull MineDown description,
|
public abstract void sendToast(@NotNull MineDown title, @NotNull MineDown description,
|
||||||
@NotNull String iconMaterial, @NotNull String backgroundType);
|
@NotNull String iconMaterial, @NotNull String backgroundType);
|
||||||
|
|
||||||
@@ -145,12 +147,6 @@ public abstract class OnlineUser extends User implements CommandUser, UserDataHo
|
|||||||
switch (plugin.getSettings().getSynchronization().getNotificationDisplaySlot()) {
|
switch (plugin.getSettings().getSynchronization().getNotificationDisplaySlot()) {
|
||||||
case CHAT -> cause.getCompletedLocale(plugin).ifPresent(this::sendMessage);
|
case CHAT -> cause.getCompletedLocale(plugin).ifPresent(this::sendMessage);
|
||||||
case ACTION_BAR -> cause.getCompletedLocale(plugin).ifPresent(this::sendActionBar);
|
case ACTION_BAR -> cause.getCompletedLocale(plugin).ifPresent(this::sendActionBar);
|
||||||
case TOAST -> cause.getCompletedLocale(plugin)
|
|
||||||
.ifPresent(locale -> this.sendToast(
|
|
||||||
locale, new MineDown(""),
|
|
||||||
"minecraft:bell",
|
|
||||||
"TASK"
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
plugin.fireEvent(
|
plugin.fireEvent(
|
||||||
plugin.getSyncCompleteEvent(this),
|
plugin.getSyncCompleteEvent(this),
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -178,11 +179,11 @@ public class DataDumper {
|
|||||||
@NotNull
|
@NotNull
|
||||||
private String getFileName() {
|
private String getFileName() {
|
||||||
return new StringJoiner("_")
|
return new StringJoiner("_")
|
||||||
.add(user.getUsername())
|
.add(user.getUsername())
|
||||||
.add(snapshot.getTimestamp().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")))
|
.add(snapshot.getTimestamp().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")))
|
||||||
.add(snapshot.getSaveCause().name().toLowerCase(Locale.ENGLISH))
|
.add(snapshot.getSaveCause().name().toLowerCase(Locale.ENGLISH))
|
||||||
.add(snapshot.getShortId())
|
.add(snapshot.getShortId())
|
||||||
+ ".json";
|
+ ".json";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ public class DataSnapshotOverview {
|
|||||||
.ifPresent(user::sendMessage);
|
.ifPresent(user::sendMessage);
|
||||||
|
|
||||||
if (user.hasPermission("husksync.command.inventory.edit")
|
if (user.hasPermission("husksync.command.inventory.edit")
|
||||||
&& user.hasPermission("husksync.command.enderchest.edit")) {
|
&& user.hasPermission("husksync.command.enderchest.edit")) {
|
||||||
locales.getLocale("data_manager_item_buttons", dataOwner.getUsername(), snapshot.getId().toString())
|
locales.getLocale("data_manager_item_buttons", dataOwner.getUsername(), snapshot.getId().toString())
|
||||||
.ifPresent(user::sendMessage);
|
.ifPresent(user::sendMessage);
|
||||||
}
|
}
|
||||||
|
|||||||
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}'
|
||||||
@@ -53,12 +53,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 +76,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>
|
||||||
|
|||||||
27
docs/Compatibility.md
Normal file
27
docs/Compatibility.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
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 Ends |
|
||||||
|
|:---------------:|:---------------:|:------------:|:--------------|:--------------------------|
|
||||||
|
| 1.21.1 | _latest_ | 21 | Paper, Fabric | ✅ **Active Release** |
|
||||||
|
| 1.20.6 | 3.6.8 | 17 | Paper | ❌ _October 2024_ |
|
||||||
|
| 1.20.4 | 3.6.8 | 17 | Paper | ❌ _July 2024_ |
|
||||||
|
| 1.20.1 | _latest_ | 17 | Paper, Fabric | ✅ **November 2025** (LTS) |
|
||||||
|
| 1.17.1 - 1.19.4 | 3.6.8 | 17 | Paper | ❌ _Support ended_ |
|
||||||
|
| 1.16.5 | 3.2.1 | 16 | Paper | ❌ _Support ended_ |
|
||||||
|
|
||||||
|
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
|
||||||
|
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.
|
||||||
@@ -109,7 +109,7 @@ synchronization:
|
|||||||
sync_dead_players_changing_server: true
|
sync_dead_players_changing_server: true
|
||||||
# Whether to use the snappy data compression algorithm. Keep on unless you know what you're doing
|
# Whether to use the snappy data compression algorithm. Keep on unless you know what you're doing
|
||||||
compress_data: true
|
compress_data: true
|
||||||
# Where to display sync notifications (ACTION_BAR, CHAT, TOAST or NONE)
|
# Where to display sync notifications (ACTION_BAR, CHAT or NONE)
|
||||||
notification_display_slot: ACTION_BAR
|
notification_display_slot: ACTION_BAR
|
||||||
# Persist maps locked in a Cartography Table to let them be viewed on any server
|
# Persist maps locked in a Cartography Table to let them be viewed on any server
|
||||||
persist_locked_maps: true
|
persist_locked_maps: true
|
||||||
@@ -134,9 +134,26 @@ synchronization:
|
|||||||
# Commands which should be blocked before a player has finished syncing (Use * to block all commands)
|
# Commands which should be blocked before a player has finished syncing (Use * to block all commands)
|
||||||
blacklisted_commands_while_locked:
|
blacklisted_commands_while_locked:
|
||||||
- '*'
|
- '*'
|
||||||
# For attribute syncing, which attributes should be ignored/skipped when syncing
|
# Configuration for how to sync attributes
|
||||||
# (e.g. ['minecraft:generic.max_health', 'minecraft:generic.attack_damage'])
|
attributes:
|
||||||
ignored_attributes: []
|
# Which attribute types should be saved as part of attribute syncing. Supports wildcard matching.
|
||||||
|
# (e.g. ['minecraft:generic.max_health', 'minecraft:generic.*'])
|
||||||
|
synced_attributes:
|
||||||
|
- "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.*'])
|
||||||
|
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
|
||||||
event_priorities:
|
event_priorities:
|
||||||
quit_listener: LOWEST
|
quit_listener: LOWEST
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ This guide will walk you through how to upgrade from HuskSync v1.4.x to HuskSync
|
|||||||
|
|
||||||
### 3. Configure the migrator
|
### 3. Configure the migrator
|
||||||
- With your servers back on and correctly configured to run HuskSync v3.x, ensure nobody is online.
|
- With your servers back on and correctly configured to run HuskSync v3.x, ensure nobody is online.
|
||||||
- Use the console on one of your Spigot servers to enter: `husksync migrate legacy`
|
- Use the console on one of your Spigot servers to enter: `husksync migrate help legacy`
|
||||||
- Carefully read the migration configuration instructions. In most cases, you won't have to change the settings, but if you do need to adjust them, use `husksync migrate legacy set <setting> <value>`.
|
- Carefully read the migration configuration instructions. In most cases, you won't have to change the settings, but if you do need to adjust them, use `husksync migrate set legacy <setting> <value>`.
|
||||||
- Migration will be carried out *from* the database you specify with the settings in console *to* the database configured in `config.yml`. If you're migrating from multiple clusters, ensure you run the migrator on the correct servers corresponding to the migrator.
|
- Migration will be carried out *from* the database you specify with the settings in console *to* the database configured in `config.yml`. If you're migrating from multiple clusters, ensure you run the migrator on the correct servers corresponding to the migrator.
|
||||||
|
|
||||||
### 4. Start the migrator
|
### 4. Start the migrator
|
||||||
- Run `husksync migrate legacy start` to begin the migration process. This may take some time, depending on the amount of data you're migrating.
|
- Run `husksync migrate start legacy` to begin the migration process. This may take some time, depending on the amount of data you're migrating.
|
||||||
|
|
||||||
### 5. Ensure the migration was successful
|
### 5. Ensure the migration was successful
|
||||||
- HuskSync will notify in console when migration is complete. Verify that the migration went OK by logging in and using the `/userdata list <username>` command to see if the data was imported with the `legacy migration` saveCause.
|
- HuskSync will notify in console when migration is complete. Verify that the migration went OK by logging in and using the `/userdata list <username>` command to see if the data was imported with the `legacy migration` saveCause.
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ This guide will walk you through how to migrate from MySQLPlayerDataBridge (MPDB
|
|||||||
|
|
||||||
### 2. Configure the migrator
|
### 2. Configure the migrator
|
||||||
- With your servers back on and correctly configured to run HuskSync v3.x, ensure nobody is online.
|
- With your servers back on and correctly configured to run HuskSync v3.x, ensure nobody is online.
|
||||||
- Use the console on one of your Spigot servers to enter: `husksync migrate mpdb`. If the MPDB migrator is not available, ensure MySQLPlayerDataBridge is still installed.
|
- Use the console on one of your Spigot servers to enter: `husksync migrate help mpdb`. If the MPDB migrator is not available, ensure MySQLPlayerDataBridge is still installed.
|
||||||
- Adjust the migration setting as needed using the following command: `husksync migrate mpdb set <setting> <value>`.
|
- Adjust the migration setting as needed using the following command: `husksync migrate set mpdb <setting> <value>`.
|
||||||
- Note that migration will be carried out *from* the database you specify with the settings in console *to* the database configured in `config.yml`.
|
- Note that migration will be carried out *from* the database you specify with the settings in console *to* the database configured in `config.yml`.
|
||||||
|
|
||||||
### 3. Start the migrator
|
### 3. Start the migrator
|
||||||
- Run `husksync migrate mpdb start` to begin the migration process. This may take some time, depending on the amount of data you're migrating.
|
- Run `husksync migrate start mpdb` to begin the migration process. This may take some time, depending on the amount of data you're migrating.
|
||||||
|
|
||||||
### 4. Uninstall MySQLPlayerDataBridge
|
### 4. Uninstall MySQLPlayerDataBridge
|
||||||
- HuskSync will display a message in console when data migration is complete.
|
- HuskSync will display a message in console when data migration is complete.
|
||||||
|
|||||||
@@ -1,17 +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.
|
> **Warning:** Fabric support is currently in beta and is not production ready yet.
|
||||||
|
|
||||||
|
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.
|
> **Warning:** Mixing and matching Fabric/Spigot servers is not supported, and all servers must be running the same Minecraft version.
|
||||||
|
|
||||||
> **Note:** Please also note some specific legacy Paper/Purpur versions are [not compatible](Unsupported-Versions) with HuskSync.
|
> **Note:** Please also note some specific legacy Paper/Purpur versions are [not compatible](Compatibility) with HuskSync.
|
||||||
|
|
||||||
* A MySQL Database (v8.0+)
|
* A MySQL Database (v8.0+)
|
||||||
* **OR** a MariaDB, PostrgreSQL or MongoDB database, which are also supported
|
* **OR** a MariaDB, PostrgreSQL or MongoDB database, which are also supported
|
||||||
* A Redis Database (v5.0+) — see [[FAQs]] for more details.
|
* A Redis Database (v5.0+) — see [[FAQs]] for more details.
|
||||||
* Any number of Spigot servers, connected by a BungeeCord or Velocity-based proxy (Minecraft v1.17.1+, running Java 17+)
|
* Any number of Spigot servers, connected by a BungeeCord or Velocity-based proxy (see [[Compatibility]])
|
||||||
* **OR** a network of Fabric servers, connected by a Fabric proxy (Minecraft v1.20.1, running Java 17+)
|
* **OR** a network of Fabric servers, connected by a Velocity-based proxy
|
||||||
|
|
||||||
## Setup Instructions
|
## Setup Instructions
|
||||||
### 1. Install the jar
|
### 1. Install the jar
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -11,27 +11,28 @@ 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.1.4+1.20.1')
|
modImplementation include('net.william278.uniform:uniform-fabric:1.2.1+1.20.1')
|
||||||
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')
|
||||||
implementation include("redis.clients:jedis:$jedis_version")
|
implementation include("redis.clients:jedis:$jedis_version")
|
||||||
implementation include("com.mysql:mysql-connector-j:$mysql_driver_version")
|
implementation include("com.mysql:mysql-connector-j:$mysql_driver_version")
|
||||||
implementation include("org.mariadb.jdbc:mariadb-java-client:$mariadb_driver_version")
|
implementation include("org.mariadb.jdbc:mariadb-java-client:$mariadb_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:24.1.0'
|
||||||
compileOnly 'net.william278:DesertWell:2.0.4'
|
compileOnly 'net.william278:DesertWell:2.0.4'
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
compileOnly 'org.projectlombok:lombok:1.18.34'
|
||||||
|
|
||||||
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
annotationProcessor 'org.projectlombok:lombok:1.18.34'
|
||||||
|
|
||||||
shadow project(path: ":common")
|
shadow project(path: ":common")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import net.william278.husksync.database.MongoDbDatabase;
|
|||||||
import net.william278.husksync.database.MySqlDatabase;
|
import net.william278.husksync.database.MySqlDatabase;
|
||||||
import net.william278.husksync.database.PostgresDatabase;
|
import net.william278.husksync.database.PostgresDatabase;
|
||||||
import net.william278.husksync.event.FabricEventDispatcher;
|
import net.william278.husksync.event.FabricEventDispatcher;
|
||||||
|
import net.william278.husksync.event.ModLoadedCallback;
|
||||||
import net.william278.husksync.hook.PlanHook;
|
import net.william278.husksync.hook.PlanHook;
|
||||||
import net.william278.husksync.listener.EventListener;
|
import net.william278.husksync.listener.EventListener;
|
||||||
import net.william278.husksync.listener.FabricEventListener;
|
import net.william278.husksync.listener.FabricEventListener;
|
||||||
@@ -78,12 +79,12 @@ import java.util.logging.Level;
|
|||||||
@Getter
|
@Getter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
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 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
|
||||||
);
|
);
|
||||||
private final Map<UUID, Map<Identifier, Data>> playerCustomDataStore = Maps.newConcurrentMap();
|
private final Map<UUID, Map<Identifier, Data>> playerCustomDataStore = Maps.newConcurrentMap();
|
||||||
private final Map<String, Boolean> permissions = Maps.newHashMap();
|
private final Map<String, Boolean> permissions = Maps.newHashMap();
|
||||||
@@ -143,6 +144,9 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
// Initial plugin setup
|
// Initial plugin setup
|
||||||
this.audiences = FabricServerAudiences.of(minecraftServer);
|
this.audiences = FabricServerAudiences.of(minecraftServer);
|
||||||
|
|
||||||
|
// Check compatibility
|
||||||
|
checkCompatibility();
|
||||||
|
|
||||||
// Prepare data adapter
|
// Prepare data adapter
|
||||||
initialize("data adapter", (plugin) -> {
|
initialize("data adapter", (plugin) -> {
|
||||||
if (getSettings().getSynchronization().isCompressData()) {
|
if (getSettings().getSynchronization().isCompressData()) {
|
||||||
@@ -206,6 +210,16 @@ 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());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDisable() {
|
private void onDisable() {
|
||||||
@@ -264,15 +278,15 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
@Nullable
|
@Nullable
|
||||||
public InputStream getResource(@NotNull String name) {
|
public InputStream getResource(@NotNull String name) {
|
||||||
return this.mod.findPath(name)
|
return this.mod.findPath(name)
|
||||||
.map(path -> {
|
.map(path -> {
|
||||||
try {
|
try {
|
||||||
return Files.newInputStream(path);
|
return Files.newInputStream(path);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log(Level.WARNING, "Failed to load resource: " + name, e);
|
log(Level.WARNING, "Failed to load resource: " + name, e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
.orElse(this.getClass().getClassLoader().getResourceAsStream(name));
|
.orElse(this.getClass().getClassLoader().getResourceAsStream(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -292,11 +306,11 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
@Override
|
@Override
|
||||||
public void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... throwable) {
|
public void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... throwable) {
|
||||||
LoggingEventBuilder logEvent = logger.makeLoggingEventBuilder(
|
LoggingEventBuilder logEvent = logger.makeLoggingEventBuilder(
|
||||||
switch (level.getName()) {
|
switch (level.getName()) {
|
||||||
case "WARNING" -> org.slf4j.event.Level.WARN;
|
case "WARNING" -> org.slf4j.event.Level.WARN;
|
||||||
case "SEVERE" -> org.slf4j.event.Level.ERROR;
|
case "SEVERE" -> org.slf4j.event.Level.ERROR;
|
||||||
default -> org.slf4j.event.Level.INFO;
|
default -> org.slf4j.event.Level.INFO;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (throwable.length >= 1) {
|
if (throwable.length >= 1) {
|
||||||
logEvent = logEvent.setCause(throwable[0]);
|
logEvent = logEvent.setCause(throwable[0]);
|
||||||
@@ -328,6 +342,14 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
return PLATFORM_TYPE_ID;
|
return PLATFORM_TYPE_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public String getServerVersion() {
|
||||||
|
return String.format("%s %s/%s", getPlatformType(), FabricLoader.getInstance()
|
||||||
|
.getModContainer("fabricloader").map(l -> l.getMetadata().getVersion().getFriendlyString())
|
||||||
|
.orElse("unknown"), minecraftServer.getVersion());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<LegacyConverter> getLegacyConverter() {
|
public Optional<LegacyConverter> getLegacyConverter() {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
|
|||||||
@@ -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,24 +34,26 @@ 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.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;
|
||||||
import org.jetbrains.annotations.Range;
|
import org.jetbrains.annotations.Range;
|
||||||
|
import org.jetbrains.annotations.Unmodifiable;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -86,13 +87,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);
|
||||||
@@ -237,8 +235,8 @@ public abstract class FabricData implements Data {
|
|||||||
private final Collection<StatusEffectInstance> effects;
|
private final Collection<StatusEffectInstance> effects;
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static FabricData.PotionEffects from(@NotNull Collection<StatusEffectInstance> effects) {
|
public static FabricData.PotionEffects from(@NotNull Collection<StatusEffectInstance> sei) {
|
||||||
return new FabricData.PotionEffects(effects);
|
return new FabricData.PotionEffects(Lists.newArrayList(sei.stream().filter(e -> !e.isAmbient()).toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -247,7 +245,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(),
|
||||||
@@ -263,22 +261,26 @@ public abstract class FabricData implements Data {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static FabricData.PotionEffects empty() {
|
public static FabricData.PotionEffects empty() {
|
||||||
return new FabricData.PotionEffects(List.of());
|
return new FabricData.PotionEffects(Lists.newArrayList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@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.getActiveStatusEffects().forEach((effect, instance) -> player.removeStatusEffect(effect));
|
//todo ambient check
|
||||||
|
List<StatusEffect> effectsToRemove = new ArrayList<>(player.getActiveStatusEffects().keySet().stream()
|
||||||
|
.map(RegistryEntry::value).toList());
|
||||||
|
effectsToRemove.forEach(effect -> player.removeStatusEffect(RegistryEntry.of(effect)));
|
||||||
getEffects().forEach(player::addStatusEffect);
|
getEffects().forEach(player::addStatusEffect);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
|
@Unmodifiable
|
||||||
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(),
|
||||||
@@ -306,16 +308,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);
|
||||||
@@ -330,10 +332,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;
|
||||||
@@ -342,7 +344,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()
|
||||||
);
|
);
|
||||||
@@ -350,7 +352,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,
|
||||||
@@ -362,21 +364,21 @@ 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()
|
||||||
&& (player.experienceLevel != expLevel || player.experienceProgress != expProgress)) {
|
&& (player.experienceLevel != expLevel || player.experienceProgress != expProgress)) {
|
||||||
player.setExperienceLevel(expLevel);
|
player.setExperienceLevel(expLevel);
|
||||||
player.setExperiencePoints((int) (player.getNextLevelExperience() * expProgress));
|
player.setExperiencePoints((int) (player.getNextLevelExperience() * expProgress));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,9 +421,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()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -432,18 +434,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) {
|
||||||
@@ -572,19 +571,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(),
|
||||||
@@ -611,10 +610,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)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,14 +629,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())
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -58,7 +59,7 @@ public abstract class FabricSerializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class Inventory extends FabricSerializer implements Serializer<FabricData.Items.Inventory>,
|
public static class Inventory extends FabricSerializer implements Serializer<FabricData.Items.Inventory>,
|
||||||
ItemDeserializer {
|
ItemDeserializer {
|
||||||
|
|
||||||
public Inventory(@NotNull HuskSync plugin) {
|
public Inventory(@NotNull HuskSync plugin) {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
@@ -66,7 +67,7 @@ public abstract class FabricSerializer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FabricData.Items.Inventory deserialize(@NotNull String serialized, @NotNull Version dataMcVersion)
|
public FabricData.Items.Inventory deserialize(@NotNull String serialized, @NotNull Version dataMcVersion)
|
||||||
throws DeserializationException {
|
throws DeserializationException {
|
||||||
// Read item NBT from string
|
// Read item NBT from string
|
||||||
final FabricHuskSync plugin = (FabricHuskSync) getPlugin();
|
final FabricHuskSync plugin = (FabricHuskSync) getPlugin();
|
||||||
final NbtCompound root;
|
final NbtCompound root;
|
||||||
@@ -79,8 +80,8 @@ public abstract class FabricSerializer {
|
|||||||
// Deserialize the inventory data
|
// Deserialize the inventory data
|
||||||
final NbtCompound items = root.contains(ITEMS_TAG) ? root.getCompound(ITEMS_TAG) : null;
|
final NbtCompound items = root.contains(ITEMS_TAG) ? root.getCompound(ITEMS_TAG) : null;
|
||||||
return FabricData.Items.Inventory.from(
|
return FabricData.Items.Inventory.from(
|
||||||
items != null ? getItems(items, dataMcVersion, plugin) : new ItemStack[INVENTORY_SLOT_COUNT],
|
items != null ? getItems(items, dataMcVersion, plugin) : new ItemStack[INVENTORY_SLOT_COUNT],
|
||||||
root.contains(HELD_ITEM_SLOT_TAG) ? root.getInt(HELD_ITEM_SLOT_TAG) : 0
|
root.contains(HELD_ITEM_SLOT_TAG) ? root.getInt(HELD_ITEM_SLOT_TAG) : 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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) {
|
||||||
@@ -105,7 +106,7 @@ public abstract class FabricSerializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class EnderChest extends FabricSerializer implements Serializer<FabricData.Items.EnderChest>,
|
public static class EnderChest extends FabricSerializer implements Serializer<FabricData.Items.EnderChest>,
|
||||||
ItemDeserializer {
|
ItemDeserializer {
|
||||||
|
|
||||||
public EnderChest(@NotNull HuskSync plugin) {
|
public EnderChest(@NotNull HuskSync plugin) {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
@@ -113,7 +114,7 @@ public abstract class FabricSerializer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FabricData.Items.EnderChest deserialize(@NotNull String serialized, @NotNull Version dataMcVersion)
|
public FabricData.Items.EnderChest deserialize(@NotNull String serialized, @NotNull Version dataMcVersion)
|
||||||
throws DeserializationException {
|
throws DeserializationException {
|
||||||
final FabricHuskSync plugin = (FabricHuskSync) getPlugin();
|
final FabricHuskSync plugin = (FabricHuskSync) getPlugin();
|
||||||
try {
|
try {
|
||||||
final NbtCompound items = StringNbtReader.parse(serialized);
|
final NbtCompound items = StringNbtReader.parse(serialized);
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -161,9 +162,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 +176,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.encode(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 +200,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,7 +208,7 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -216,8 +219,8 @@ public abstract class FabricSerializer {
|
|||||||
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())
|
getDataVersion(mcVersion), getDataVersion(plugin.getMinecraftVersion())
|
||||||
).getValue();
|
).getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,7 +254,7 @@ public abstract class FabricSerializer {
|
|||||||
@Override
|
@Override
|
||||||
public FabricData.PotionEffects deserialize(@NotNull String serialized) throws DeserializationException {
|
public FabricData.PotionEffects deserialize(@NotNull String serialized) throws DeserializationException {
|
||||||
return FabricData.PotionEffects.adapt(
|
return FabricData.PotionEffects.adapt(
|
||||||
plugin.getGson().fromJson(serialized, TYPE.getType())
|
plugin.getGson().fromJson(serialized, TYPE.getType())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,7 +278,7 @@ public abstract class FabricSerializer {
|
|||||||
@Override
|
@Override
|
||||||
public FabricData.Advancements deserialize(@NotNull String serialized) throws DeserializationException {
|
public FabricData.Advancements deserialize(@NotNull String serialized) throws DeserializationException {
|
||||||
return FabricData.Advancements.from(
|
return FabricData.Advancements.from(
|
||||||
plugin.getGson().fromJson(serialized, TYPE.getType())
|
plugin.getGson().fromJson(serialized, TYPE.getType())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* 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.event;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.event.Event;
|
||||||
|
import net.fabricmc.fabric.api.event.EventFactory;
|
||||||
|
import net.william278.husksync.api.HuskSyncAPI;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public interface ModLoadedCallback {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
Event<ModLoadedCallback> EVENT = EventFactory.createArrayBacked(
|
||||||
|
ModLoadedCallback.class,
|
||||||
|
(listeners) -> (api) -> Arrays.stream(listeners).forEach(listener -> listener.post(api))
|
||||||
|
);
|
||||||
|
|
||||||
|
void post(@NotNull HuskSyncAPI api);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -54,8 +54,6 @@ import net.william278.husksync.user.OnlineUser;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class FabricEventListener extends EventListener implements LockedHandler {
|
public class FabricEventListener extends EventListener implements LockedHandler {
|
||||||
|
|
||||||
public FabricEventListener(@NotNull HuskSync plugin) {
|
public FabricEventListener(@NotNull HuskSync plugin) {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,13 +44,12 @@ public abstract class ServerPlayNetworkHandlerMixin {
|
|||||||
@Shadow
|
@Shadow
|
||||||
public ServerPlayerEntity player;
|
public ServerPlayerEntity player;
|
||||||
|
|
||||||
@Shadow
|
|
||||||
public abstract void sendPacket(Packet<?> packet);
|
public abstract void sendPacket(Packet<?> 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);
|
||||||
|
|
||||||
@@ -83,7 +82,7 @@ public abstract class ServerPlayNetworkHandlerMixin {
|
|||||||
|
|
||||||
@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);
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ package net.william278.husksync.mixins;
|
|||||||
|
|
||||||
import net.minecraft.entity.ItemEntity;
|
import net.minecraft.entity.ItemEntity;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.item.ItemStack;
|
||||||
import net.minecraft.server.MinecraftServer;
|
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
import net.minecraft.util.ActionResult;
|
import net.minecraft.util.ActionResult;
|
||||||
import net.william278.husksync.event.ItemDropCallback;
|
import net.william278.husksync.event.ItemDropCallback;
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import org.jetbrains.annotations.ApiStatus;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
public class FabricUser extends OnlineUser implements FabricUserDataHolder {
|
public class FabricUser extends OnlineUser implements FabricUserDataHolder {
|
||||||
|
|
||||||
@@ -70,9 +71,12 @@ public class FabricUser extends OnlineUser implements FabricUserDataHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated(since = "3.6.7")
|
||||||
public void sendToast(@NotNull MineDown title, @NotNull MineDown description, @NotNull String iconMaterial,
|
public void sendToast(@NotNull MineDown title, @NotNull MineDown description, @NotNull String iconMaterial,
|
||||||
@NotNull String backgroundType) {
|
@NotNull String backgroundType) {
|
||||||
getAudience().sendActionBar(title.toComponent()); // Toasts unimplemented for now
|
plugin.log(Level.WARNING, "Toast notifications are deprecated. " +
|
||||||
|
"Please change your notification display slot to CHAT, ACTION_BAR or NONE.");
|
||||||
|
this.sendActionBar(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,18 +19,21 @@
|
|||||||
|
|
||||||
package net.william278.husksync.util;
|
package net.william278.husksync.util;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
import net.william278.husksync.FabricHuskSync;
|
import net.william278.husksync.FabricHuskSync;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.data.UserDataHolder;
|
import net.william278.husksync.data.UserDataHolder;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public interface FabricTask extends Task {
|
public interface FabricTask extends Task {
|
||||||
|
ScheduledExecutorService ASYNC_EXEC = Executors.newScheduledThreadPool(4,
|
||||||
|
new ThreadFactoryBuilder()
|
||||||
|
.setDaemon(true)
|
||||||
|
.setNameFormat("HuskSync-ThreadPool")
|
||||||
|
.build());
|
||||||
|
|
||||||
class Sync extends Task.Sync implements FabricTask {
|
class Sync extends Task.Sync implements FabricTask {
|
||||||
|
|
||||||
@@ -46,7 +49,7 @@ public interface FabricTask extends Task {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
Executors.newSingleThreadScheduledExecutor().schedule(
|
ASYNC_EXEC.schedule(
|
||||||
() -> ((FabricHuskSync) getPlugin()).getMinecraftServer().executeSync(runnable),
|
() -> ((FabricHuskSync) getPlugin()).getMinecraftServer().executeSync(runnable),
|
||||||
delayTicks * 50,
|
delayTicks * 50,
|
||||||
TimeUnit.MILLISECONDS
|
TimeUnit.MILLISECONDS
|
||||||
@@ -73,7 +76,7 @@ public interface FabricTask extends Task {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
this.task = CompletableFuture.runAsync(runnable, ((FabricHuskSync) getPlugin()).getMinecraftServer());
|
this.task = CompletableFuture.runAsync(runnable, ASYNC_EXEC);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +100,7 @@ public interface FabricTask extends Task {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
this.task = Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(
|
this.task = ASYNC_EXEC.scheduleAtFixedRate(
|
||||||
runnable,
|
runnable,
|
||||||
0,
|
0,
|
||||||
repeatingTicks * 50,
|
repeatingTicks * 50,
|
||||||
@@ -129,7 +132,7 @@ public interface FabricTask extends Task {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void cancelTasks() {
|
default void cancelTasks() {
|
||||||
// Do nothing
|
ASYNC_EXEC.shutdownNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -1,23 +1,30 @@
|
|||||||
|
# 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.1
|
# Plugin settings
|
||||||
|
plugin_version=3.7
|
||||||
|
minecraft_version=1.21.1
|
||||||
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.3
|
# Drivers
|
||||||
mysql_driver_version=8.4.0
|
jedis_version=5.1.4
|
||||||
mariadb_driver_version=3.4.0
|
mysql_driver_version=9.0.0
|
||||||
|
mariadb_driver_version=3.4.1
|
||||||
postgres_driver_version=42.7.3
|
postgres_driver_version=42.7.3
|
||||||
mongodb_driver_version=5.1.0
|
mongodb_driver_version=5.1.2
|
||||||
snappy_version=1.1.10.5
|
snappy_version=1.1.10.6
|
||||||
|
|
||||||
fabric_minecraft_version=1.20.1
|
# Spigot/Paper build settings
|
||||||
fabric_loader_version=0.15.11
|
bukkit_spigot_api=1.21.1-R0.1-SNAPSHOT
|
||||||
fabric_yarn_mappings=1.20.1+build.10
|
bukkit_paper_api=1.21.1-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_loader_version=0.16.2
|
||||||
sgui_version=1.2.2+1.20
|
fabric_yarn_mappings=1.21.1+build.3
|
||||||
|
fabric_api_version=0.102.1+1.21.1
|
||||||
|
fabric_adventure_platform_version=5.14.1
|
||||||
|
fabric_permissions_api_version=0.3.1
|
||||||
|
fabric_sgui_version=1.6.0+1.21
|
||||||
@@ -1,14 +1,18 @@
|
|||||||
|
plugins {
|
||||||
|
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.1.4'
|
implementation 'net.william278.uniform:uniform-paper:1.2.1'
|
||||||
|
|
||||||
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:24.1.0'
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
compileOnly 'org.projectlombok:lombok:1.18.34'
|
||||||
|
|
||||||
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
annotationProcessor 'org.projectlombok:lombok:1.18.34'
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
@@ -30,7 +34,6 @@ shadowJar {
|
|||||||
relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell'
|
relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell'
|
||||||
relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown'
|
relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown'
|
||||||
relocate 'net.william278.mapdataapi', 'net.william278.husksync.libraries.mapdataapi'
|
relocate 'net.william278.mapdataapi', 'net.william278.husksync.libraries.mapdataapi'
|
||||||
relocate 'net.william278.andjam', 'net.william278.husksync.libraries.andjam'
|
|
||||||
relocate 'net.william278.mpdbconverter', 'net.william278.husksync.libraries.mpdbconverter'
|
relocate 'net.william278.mpdbconverter', 'net.william278.husksync.libraries.mpdbconverter'
|
||||||
relocate 'net.william278.hslmigrator', 'net.william278.husksync.libraries.hslconverter'
|
relocate 'net.william278.hslmigrator', 'net.william278.husksync.libraries.hslconverter'
|
||||||
relocate 'org.json', 'net.william278.husksync.libraries.json'
|
relocate 'org.json', 'net.william278.husksync.libraries.json'
|
||||||
@@ -43,3 +46,9 @@ shadowJar {
|
|||||||
|
|
||||||
minimize()
|
minimize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks {
|
||||||
|
runServer {
|
||||||
|
minecraftVersion('1.21.1')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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() {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
certifi==2023.7.22
|
certifi==2024.7.4
|
||||||
charset-normalizer==3.2.0
|
charset-normalizer==3.2.0
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
idna==3.7
|
idna==3.7
|
||||||
|
|||||||
@@ -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'
|
minecraft_version = '1.21.1'
|
||||||
eula_agreement = 'true'
|
eula_agreement = 'true'
|
||||||
|
|
||||||
backend_names = ['alpha', 'beta']
|
backend_names = ['alpha', 'beta']
|
||||||
|
|||||||
Reference in New Issue
Block a user